aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBart Van Assche <bvanassche@google.com>2022-11-23 20:04:46 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-11-23 20:04:46 +0000
commit62b99c09a4bbb75fea6c309c77ebf856323d908c (patch)
tree207cdb23f8257e270a30143fde23d2033901581f
parent8718296061e35ba33bf88847e7498f3d7e38f8f1 (diff)
parent3c61bd9e26bf930f865e28e7eef48856653d1c98 (diff)
downloadsg3_utils-62b99c09a4bbb75fea6c309c77ebf856323d908c.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into HEAD am: 448b67b18a am: d39ef90544 am: 3c61bd9e26
Original change: https://android-review.googlesource.com/c/platform/external/sg3_utils/+/2312210 Change-Id: I0362cffa263ba632a3e2058f8f50f32e024b2cc3 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.github/workflows/ci.yml69
-rw-r--r--.gitignore109
-rw-r--r--AUTHORS3
-rw-r--r--BSD_LICENSE27
-rw-r--r--COPYING32
-rw-r--r--COVERAGE188
-rw-r--r--CREDITS163
-rw-r--r--ChangeLog1919
-rw-r--r--INSTALL240
-rw-r--r--Makefile.am184
-rw-r--r--Makefile.in903
-rw-r--r--NEWS1
-rw-r--r--README113
-rw-r--r--README.details560
-rw-r--r--README.freebsd164
-rw-r--r--README.iscsi37
-rw-r--r--README.sg_start38
-rw-r--r--README.solaris168
-rw-r--r--README.tru6497
-rw-r--r--README.win32247
-rw-r--r--aclocal.m410363
-rwxr-xr-xar-lib271
-rwxr-xr-xautogen.sh1578
-rwxr-xr-xbuild_debian.sh15
-rwxr-xr-xcompile348
-rwxr-xr-xconfig.guess1754
-rw-r--r--config.h.in177
-rwxr-xr-xconfig.sub1890
-rwxr-xr-xconfigure16049
-rw-r--r--configure.ac220
-rw-r--r--debian/README.debian414
-rw-r--r--debian/changelog278
-rw-r--r--debian/compat1
-rw-r--r--debian/control43
-rw-r--r--debian/copyright24
-rw-r--r--debian/docs7
-rw-r--r--debian/libsgutils2-2.install1
-rw-r--r--debian/libsgutils2-dev.install4
-rwxr-xr-xdebian/rules83
-rw-r--r--debian/sg3-utils.examples1
-rw-r--r--debian/sg3-utils.install2
-rwxr-xr-xdepcomp791
-rw-r--r--doc/Makefile.am47
-rw-r--r--doc/Makefile.in565
-rw-r--r--doc/README36
-rw-r--r--doc/rescan-scsi-bus.sh.8135
-rw-r--r--doc/scsi_logging_level.8128
-rw-r--r--doc/scsi_mandat.844
-rw-r--r--doc/scsi_readcap.851
-rw-r--r--doc/scsi_ready.840
-rw-r--r--doc/scsi_satl.844
-rw-r--r--doc/scsi_start.840
-rw-r--r--doc/scsi_stop.841
-rw-r--r--doc/scsi_temperature.835
-rw-r--r--doc/sg3_utils.8883
-rw-r--r--doc/sg3_utils_json.8296
-rw-r--r--doc/sg_bg_ctl.872
-rw-r--r--doc/sg_compare_and_write.8244
-rw-r--r--doc/sg_copy_results.8126
-rw-r--r--doc/sg_dd.8614
-rw-r--r--doc/sg_decode_sense.8219
-rw-r--r--doc/sg_emc_trespass.852
-rw-r--r--doc/sg_format.8725
-rw-r--r--doc/sg_get_config.8143
-rw-r--r--doc/sg_get_elem_status.8137
-rw-r--r--doc/sg_get_lba_status.8161
-rw-r--r--doc/sg_ident.8119
-rw-r--r--doc/sg_inq.8562
-rw-r--r--doc/sg_logs.8571
-rw-r--r--doc/sg_luns.8319
-rw-r--r--doc/sg_map.8182
-rw-r--r--doc/sg_map26.8161
-rw-r--r--doc/sg_modes.8314
-rw-r--r--doc/sg_opcodes.8339
-rw-r--r--doc/sg_persist.8435
-rw-r--r--doc/sg_prevent.859
-rw-r--r--doc/sg_raw.8270
-rw-r--r--doc/sg_rbuf.8189
-rw-r--r--doc/sg_rdac.846
-rw-r--r--doc/sg_read.8192
-rw-r--r--doc/sg_read_attr.8214
-rw-r--r--doc/sg_read_block_limits.868
-rw-r--r--doc/sg_read_buffer.8175
-rw-r--r--doc/sg_read_long.8102
-rw-r--r--doc/sg_readcap.8196
-rw-r--r--doc/sg_reassign.8151
-rw-r--r--doc/sg_referrals.871
-rw-r--r--doc/sg_rem_rest_elem.895
-rw-r--r--doc/sg_rep_density.897
-rw-r--r--doc/sg_rep_pip.858
-rw-r--r--doc/sg_rep_zones.8214
-rw-r--r--doc/sg_requests.8138
-rw-r--r--doc/sg_reset.8135
-rw-r--r--doc/sg_reset_wp.865
-rw-r--r--doc/sg_rmsn.864
-rw-r--r--doc/sg_rtpg.864
-rw-r--r--doc/sg_safte.8132
-rw-r--r--doc/sg_sanitize.8267
-rw-r--r--doc/sg_sat_identify.8167
-rw-r--r--doc/sg_sat_phy_event.8109
-rw-r--r--doc/sg_sat_read_gplog.8114
-rw-r--r--doc/sg_sat_set_features.8112
-rw-r--r--doc/sg_scan.8.linux78
-rw-r--r--doc/sg_scan.8.win32170
-rw-r--r--doc/sg_seek.8146
-rw-r--r--doc/sg_senddiag.8300
-rw-r--r--doc/sg_ses.8801
-rw-r--r--doc/sg_ses_microcode.8279
-rw-r--r--doc/sg_start.8283
-rw-r--r--doc/sg_stpg.8122
-rw-r--r--doc/sg_stream_ctl.8117
-rw-r--r--doc/sg_sync.897
-rw-r--r--doc/sg_test_rwbuf.886
-rw-r--r--doc/sg_timestamp.8155
-rw-r--r--doc/sg_turs.8152
-rw-r--r--doc/sg_unmap.8166
-rw-r--r--doc/sg_verify.8219
-rw-r--r--doc/sg_vpd.8365
-rw-r--r--doc/sg_wr_mode.8225
-rw-r--r--doc/sg_write_buffer.8227
-rw-r--r--doc/sg_write_long.8176
-rw-r--r--doc/sg_write_same.8355
-rw-r--r--doc/sg_write_verify.8191
-rw-r--r--doc/sg_write_x.8600
-rw-r--r--doc/sg_xcopy.8381
-rw-r--r--doc/sg_z_act_query.8115
-rw-r--r--doc/sg_zone.897
-rw-r--r--doc/sginfo.8325
-rw-r--r--doc/sgm_dd.8284
-rw-r--r--doc/sgp_dd.8345
-rw-r--r--examples/Makefile127
-rw-r--r--examples/Makefile.freebsd82
-rw-r--r--examples/README17
-rw-r--r--examples/reassign_addr.txt11
-rw-r--r--examples/scsi_inquiry.c130
-rw-r--r--examples/sdiag_sas_p0_cjtpat.txt12
-rw-r--r--examples/sdiag_sas_p0_prbs9.txt12
-rw-r--r--examples/sdiag_sas_p1_cjtpat.txt13
-rw-r--r--examples/sdiag_sas_p1_idle.txt14
-rw-r--r--examples/sdiag_sas_p1_prbs15.txt12
-rw-r--r--examples/sdiag_sas_p1_stop.txt11
-rw-r--r--examples/sg__sat_identify.c216
-rw-r--r--examples/sg__sat_phy_event.c350
-rw-r--r--examples/sg__sat_set_features.c280
-rw-r--r--examples/sg_compare_and_write.txt67
-rw-r--r--examples/sg_excl.c202
-rwxr-xr-xexamples/sg_persist_tst.sh130
-rw-r--r--examples/sg_sat_chk_power.c256
-rw-r--r--examples/sg_sat_smart_rd_data.c188
-rw-r--r--examples/sg_simple1.c193
-rw-r--r--examples/sg_simple16.c122
-rw-r--r--examples/sg_simple2.c197
-rw-r--r--examples/sg_simple3.c205
-rw-r--r--examples/sg_simple4.c238
-rw-r--r--examples/sg_simple5.c236
-rw-r--r--examples/sg_unmap_example.txt40
-rw-r--r--examples/sgq_dd.c1230
-rw-r--r--examples/transport_ids.txt31
-rw-r--r--getopt_long/getopt.h86
-rw-r--r--getopt_long/getopt_long.c434
-rw-r--r--include/Makefile.am63
-rw-r--r--include/Makefile.in606
-rw-r--r--include/freebsd_nvme_ioctl.h175
-rw-r--r--include/sg_cmds.h21
-rw-r--r--include/sg_cmds_basic.h364
-rw-r--r--include/sg_cmds_extra.h391
-rw-r--r--include/sg_cmds_mmc.h54
-rw-r--r--include/sg_io_linux.h202
-rw-r--r--include/sg_lib.h813
-rw-r--r--include/sg_lib_data.h145
-rw-r--r--include/sg_lib_names.h31
-rw-r--r--include/sg_linux_inc.h58
-rw-r--r--include/sg_pr2serr.h376
-rw-r--r--include/sg_pt.h292
-rw-r--r--include/sg_pt_linux.h202
-rw-r--r--include/sg_pt_nvme.h224
-rw-r--r--include/sg_pt_win32.h473
-rw-r--r--include/sg_unaligned.h491
-rw-r--r--inhex/README94
-rw-r--r--inhex/descriptor_sense.hex30
-rw-r--r--inhex/fixed_sense.hex5
-rw-r--r--inhex/forwarded_sense.hex16
-rw-r--r--inhex/get_elem_status.hex51
-rw-r--r--inhex/get_lba_status.hex14
-rw-r--r--inhex/inq_standard.hex23
-rw-r--r--inhex/logs_last_n.hex41
-rw-r--r--inhex/nvme_dev_self_test.hex20
-rw-r--r--inhex/nvme_identify_ctl.hex27
-rw-r--r--inhex/nvme_read_ctl.hex39
-rw-r--r--inhex/nvme_read_oob_ctl.hex47
-rw-r--r--inhex/nvme_write_ctl.hex38
-rw-r--r--inhex/opcodes.hex27
-rw-r--r--inhex/ref_sense.hex7
-rw-r--r--inhex/rep_density.hex18
-rw-r--r--inhex/rep_density_media.hex13
-rw-r--r--inhex/rep_density_media_typem.hex13
-rw-r--r--inhex/rep_density_typem.hex31
-rw-r--r--inhex/rep_realms.hex35
-rw-r--r--inhex/rep_zdomains.hex29
-rw-r--r--inhex/rep_zones.hex39
-rw-r--r--inhex/ses_areca_all.hex195
-rw-r--r--inhex/vpd_bdce.hex18
-rw-r--r--inhex/vpd_consistuents.hex47
-rw-r--r--inhex/vpd_cpr.hex18
-rw-r--r--inhex/vpd_dev_id.hex9
-rw-r--r--inhex/vpd_di_all.hex51
-rw-r--r--inhex/vpd_fp.hex31
-rw-r--r--inhex/vpd_lbpro.hex7
-rw-r--r--inhex/vpd_lbpv.hex9
-rw-r--r--inhex/vpd_ref.hex9
-rw-r--r--inhex/vpd_sbl.hex10
-rw-r--r--inhex/vpd_sdeb.hex99
-rw-r--r--inhex/vpd_sfs.hex7
-rw-r--r--inhex/vpd_tpc.hex43
-rw-r--r--inhex/vpd_zbdc.hex29
-rw-r--r--inhex/vpd_zbdc.rawbin0 -> 64 bytes
-rw-r--r--inhex/z_act_query.hex28
-rwxr-xr-xinstall-sh541
-rw-r--r--lib/BSD_LICENSE26
-rw-r--r--lib/Makefile.am109
-rw-r--r--lib/Makefile.in800
-rw-r--r--lib/sg_cmds_basic.c927
-rw-r--r--lib/sg_cmds_basic2.c1117
-rw-r--r--lib/sg_cmds_extra.c2620
-rw-r--r--lib/sg_cmds_mmc.c370
-rw-r--r--lib/sg_io_linux.c235
-rw-r--r--lib/sg_json_builder.c999
-rw-r--r--lib/sg_json_builder.h333
-rw-r--r--lib/sg_lib.c4088
-rw-r--r--lib/sg_lib_data.c2014
-rw-r--r--lib/sg_lib_names.c132
-rw-r--r--lib/sg_pr2serr.c2026
-rw-r--r--lib/sg_pt_common.c651
-rw-r--r--lib/sg_pt_dummy.c442
-rw-r--r--lib/sg_pt_freebsd.c3168
-rw-r--r--lib/sg_pt_haiku.c572
-rw-r--r--lib/sg_pt_linux.c1181
-rw-r--r--lib/sg_pt_linux_nvme.c1935
-rw-r--r--lib/sg_pt_osf1.c722
-rw-r--r--lib/sg_pt_solaris.c578
-rw-r--r--lib/sg_pt_win32.c3155
-rwxr-xr-xltmain.sh11448
-rwxr-xr-xmissing215
-rw-r--r--scripts/40-usb-blacklist.rules14
-rw-r--r--scripts/54-before-scsi-sg3_id.rules55
-rw-r--r--scripts/55-scsi-sg3_id.rules109
-rw-r--r--scripts/58-scsi-sg3_symlink.rules38
-rw-r--r--scripts/59-fc-wwpn-id.rules17
-rw-r--r--scripts/59-scsi-cciss_id.rules18
-rw-r--r--scripts/Makefile.am3
-rw-r--r--scripts/Makefile.in518
-rw-r--r--scripts/README55
-rwxr-xr-xscripts/cciss_id66
-rw-r--r--scripts/fc_wwpn_id51
-rw-r--r--scripts/lunmask.service11
-rwxr-xr-xscripts/rescan-scsi-bus.sh1436
-rwxr-xr-xscripts/scsi-enable-target-scan.sh15
-rwxr-xr-xscripts/scsi_logging_level268
-rwxr-xr-xscripts/scsi_mandat133
-rwxr-xr-xscripts/scsi_readcap57
-rwxr-xr-xscripts/scsi_ready56
-rwxr-xr-xscripts/scsi_satl134
-rwxr-xr-xscripts/scsi_start55
-rwxr-xr-xscripts/scsi_stop58
-rwxr-xr-xscripts/scsi_temperature46
-rw-r--r--sg3_utils.man8.html989
-rw-r--r--sg3_utils.spec280
-rw-r--r--src/BSD_LICENSE26
-rw-r--r--src/Makefile.am219
-rw-r--r--src/Makefile.in1608
-rw-r--r--src/sg_bg_ctl.c271
-rw-r--r--src/sg_compare_and_write.c623
-rw-r--r--src/sg_copy_results.c502
-rw-r--r--src/sg_dd.c2750
-rw-r--r--src/sg_decode_sense.c547
-rw-r--r--src/sg_emc_trespass.c176
-rw-r--r--src/sg_format.c1729
-rw-r--r--src/sg_get_config.c1145
-rw-r--r--src/sg_get_elem_status.c659
-rw-r--r--src/sg_get_lba_status.c693
-rw-r--r--src/sg_ident.c318
-rw-r--r--src/sg_inq.c4881
-rw-r--r--src/sg_inq_data.c555
-rw-r--r--src/sg_logs.c9156
-rw-r--r--src/sg_luns.c722
-rw-r--r--src/sg_map.c508
-rw-r--r--src/sg_map26.c1285
-rw-r--r--src/sg_modes.c1579
-rw-r--r--src/sg_opcodes.c1500
-rw-r--r--src/sg_persist.c1324
-rw-r--r--src/sg_prevent.c193
-rw-r--r--src/sg_raw.c849
-rw-r--r--src/sg_rbuf.c688
-rw-r--r--src/sg_rdac.c516
-rw-r--r--src/sg_read.c931
-rw-r--r--src/sg_read_attr.c1004
-rw-r--r--src/sg_read_block_limits.c282
-rw-r--r--src/sg_read_buffer.c844
-rw-r--r--src/sg_read_long.c325
-rw-r--r--src/sg_readcap.c682
-rw-r--r--src/sg_reassign.c508
-rw-r--r--src/sg_referrals.c387
-rw-r--r--src/sg_rem_rest_elem.c331
-rw-r--r--src/sg_rep_density.c478
-rw-r--r--src/sg_rep_pip.c335
-rw-r--r--src/sg_rep_zones.c1525
-rw-r--r--src/sg_requests.c543
-rw-r--r--src/sg_reset.c314
-rw-r--r--src/sg_reset_wp.c287
-rw-r--r--src/sg_rmsn.c231
-rw-r--r--src/sg_rtpg.c371
-rw-r--r--src/sg_safte.c776
-rw-r--r--src/sg_sanitize.c792
-rw-r--r--src/sg_sat_identify.c540
-rw-r--r--src/sg_sat_phy_event.c534
-rw-r--r--src/sg_sat_read_gplog.c495
-rw-r--r--src/sg_sat_set_features.c463
-rw-r--r--src/sg_scan_linux.c629
-rw-r--r--src/sg_scan_win32.c733
-rw-r--r--src/sg_seek.c429
-rw-r--r--src/sg_senddiag.c971
-rw-r--r--src/sg_ses.c5986
-rw-r--r--src/sg_ses_microcode.c941
-rw-r--r--src/sg_start.c618
-rw-r--r--src/sg_stpg.c726
-rw-r--r--src/sg_stream_ctl.c517
-rw-r--r--src/sg_sync.c314
-rw-r--r--src/sg_test_rwbuf.c584
-rw-r--r--src/sg_timestamp.c550
-rw-r--r--src/sg_turs.c657
-rw-r--r--src/sg_unmap.c794
-rw-r--r--src/sg_verify.c475
-rw-r--r--src/sg_vpd.c2770
-rw-r--r--src/sg_vpd_common.c3501
-rw-r--r--src/sg_vpd_common.h294
-rw-r--r--src/sg_vpd_vendor.c1296
-rw-r--r--src/sg_wr_mode.c648
-rw-r--r--src/sg_write_buffer.c597
-rw-r--r--src/sg_write_long.c331
-rw-r--r--src/sg_write_same.c678
-rw-r--r--src/sg_write_verify.c634
-rw-r--r--src/sg_write_x.c2678
-rw-r--r--src/sg_xcopy.c1934
-rw-r--r--src/sg_z_act_query.c639
-rw-r--r--src/sg_zone.c397
-rw-r--r--src/sginfo.c3999
-rw-r--r--src/sgm_dd.c1474
-rw-r--r--src/sgp_dd.c2019
-rw-r--r--suse/sg3_utils.changes393
-rw-r--r--suse/sg3_utils.spec125
-rw-r--r--testing/Makefile158
-rw-r--r--testing/Makefile.cyg110
-rw-r--r--testing/Makefile.freebsd96
-rw-r--r--testing/README47
-rw-r--r--testing/bsg_queue_tst.c171
-rw-r--r--testing/sg_chk_asc.c218
-rw-r--r--testing/sg_iovec_tst.cpp599
-rw-r--r--testing/sg_json_builder_test.c73
-rw-r--r--testing/sg_mrq_dd.cpp4664
-rw-r--r--testing/sg_queue_tst.c234
-rw-r--r--testing/sg_scat_gath.cpp1049
-rw-r--r--testing/sg_scat_gath.h143
-rw-r--r--testing/sg_sense_test.c204
-rw-r--r--testing/sg_take_snap.c225
-rw-r--r--testing/sg_tst_async.cpp2227
-rw-r--r--testing/sg_tst_bidi.c602
-rw-r--r--testing/sg_tst_context.cpp502
-rw-r--r--testing/sg_tst_excl.cpp984
-rw-r--r--testing/sg_tst_excl2.cpp556
-rw-r--r--testing/sg_tst_excl3.cpp561
-rw-r--r--testing/sg_tst_ioctl.c1351
-rw-r--r--testing/sg_tst_json_builder.c160
-rw-r--r--testing/sg_tst_nvme.c957
-rw-r--r--testing/sgh_dd.cpp5090
-rw-r--r--testing/sgs_dd.c1667
-rw-r--r--testing/tst_sg_lib.c734
-rw-r--r--testing/uapi_sg.h493
-rw-r--r--utils/Makefile56
-rw-r--r--utils/Makefile.cygwin33
-rw-r--r--utils/Makefile.freebsd52
-rw-r--r--utils/Makefile.mingw33
-rw-r--r--utils/Makefile.solaris51
-rw-r--r--utils/README20
-rw-r--r--utils/hxascdmp.1111
-rw-r--r--utils/hxascdmp.c521
385 files changed, 225836 insertions, 0 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..782848cd
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,69 @@
+# See also https://docs.github.com/en/actions/learn-github-actions/expressions
+# See also https://github.com/marketplace/actions/setup-android-ndk
+
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ build:
+ - android
+ - linux-gcc
+ - linux-clang
+ - linux-x86-gcc
+ - linux-powerpc64-gcc
+ - linux-mingw64-gcc
+ - macos
+ include:
+ - build: android
+ cc: clang
+ host: aarch64-linux-android32
+ - build: linux-gcc
+ cc: gcc
+ - build: linux-clang
+ cc: clang
+ - build: linux-x86-gcc
+ cc: gcc
+ arch: x86
+ - build: linux-powerpc64-gcc
+ cc: gcc
+ host: powerpc64-linux-gnu
+ - build: linux-mingw64-gcc
+ cc: gcc
+ host: x86_64-w64-mingw32
+ - build: macos
+ cc: clang
+ os: macos-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Android NDK
+ run: |
+ if [ ${{matrix.build}} = android ]; then \
+ wget --quiet https://dl.google.com/android/repository/android-ndk-r24-linux.zip; \
+ unzip -q android-ndk-r24-linux.zip; \
+ fi
+ - name: Install Ubuntu packages
+ run: |
+ sudo apt-get -q update
+ case "${{matrix.host}}" in \
+ x86_64-w64-mingw32) \
+ sudo apt-get -q install -y binutils-mingw-w64 gcc-mingw-w64;; \
+ powerpc64-linux-gnu) \
+ sudo apt-get -q install -y binutils-powerpc64-linux-gnu \
+ gcc-powerpc64-linux-gnu;; \
+ esac
+ - name: Build
+ run: |
+ echo "HOST=${{matrix.host}}"
+ NDK=$PWD/android-ndk-r24/toolchains/llvm/prebuilt/linux-x86_64/bin
+ export PATH="$NDK:$PATH"
+ ./autogen.sh
+ ./configure --host=${{matrix.host}} \
+ CC=${{ matrix.host && format('{0}-{1}', matrix.host, matrix.cc) || matrix.cc }} \
+ CFLAGS="-Wall -Wextra -Werror -Wno-sign-compare -Wno-unused-function -Wno-unused-parameter ${{matrix.cflags}}"
+ make -j$(nproc)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..8ed670de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,109 @@
+# Please keep the entries in this file sorted with the following vi command:
+# :3,$!LC_ALL=C sort -fu
+
+*.exe
+*.la
+*.lo
+*.o
+*~
+.deps/
+.libs/
+/aclocal.m4
+/ar-lib
+/autom4te.cache/
+/compile
+/config.guess
+/config.h
+/config.h.in
+/config.log
+/config.status
+/config.sub
+/configure
+/depcomp
+/doc/Makefile
+/doc/sg_scan.8
+/include/Makefile
+/install-sh
+/lib/Makefile
+/libtool
+/ltmain.sh
+/Makefile
+/missing
+/scripts/Makefile
+/sg3_utils-*.tar.gz
+/src/Makefile
+/src/sginfo
+/src/sgm_dd
+/src/sgp_dd
+/src/sg_bg_ctl
+/src/sg_compare_and_write
+/src/sg_copy_results
+/src/sg_dd
+/src/sg_decode_sense
+/src/sg_emc_trespass
+/src/sg_format
+/src/sg_get_config
+/src/sg_get_elem_status
+/src/sg_get_lba_status
+/src/sg_ident
+/src/sg_inq
+/src/sg_logs
+/src/sg_luns
+/src/sg_map
+/src/sg_map26
+/src/sg_modes
+/src/sg_opcodes
+/src/sg_persist
+/src/sg_prevent
+/src/sg_raw
+/src/sg_rbuf
+/src/sg_rdac
+/src/sg_read
+/src/sg_readcap
+/src/sg_read_attr
+/src/sg_read_block_limits
+/src/sg_read_buffer
+/src/sg_read_long
+/src/sg_reassign
+/src/sg_referrals
+/src/sg_rem_rest_elem
+/src/sg_rep_density
+/src/sg_rep_pip
+/src/sg_rep_zones
+/src/sg_requests
+/src/sg_reset
+/src/sg_reset_wp
+/src/sg_rmsn
+/src/sg_rtpg
+/src/sg_safte
+/src/sg_sanitize
+/src/sg_sat_identify
+/src/sg_sat_phy_event
+/src/sg_sat_read_gplog
+/src/sg_sat_set_features
+/src/sg_scan
+/src/sg_seek
+/src/sg_senddiag
+/src/sg_ses
+/src/sg_ses_microcode
+/src/sg_start
+/src/sg_stpg
+/src/sg_stream_ctl
+/src/sg_sync
+/src/sg_test_rwbuf
+/src/sg_timestamp
+/src/sg_turs
+/src/sg_unmap
+/src/sg_verify
+/src/sg_vpd
+/src/sg_write_buffer
+/src/sg_write_long
+/src/sg_write_same
+/src/sg_write_verify
+/src/sg_write_x
+/src/sg_wr_mode
+/src/sg_xcopy
+/src/sg_zone
+/src/sg_z_act_query
+/stamp-h1
+Makefile.in
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 00000000..f72f8630
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,3 @@
+Douglas Gilbert <dgilbert at interlog dot com>
+
+See the CREDITS file for the names of those who have contributed.
diff --git a/BSD_LICENSE b/BSD_LICENSE
new file mode 100644
index 00000000..426cf50c
--- /dev/null
+++ b/BSD_LICENSE
@@ -0,0 +1,27 @@
+
+Copyright (c) 1999-2022, Douglas Gilbert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Above is the:
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..c584dc50
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,32 @@
+
+Upstream Authors: Douglas Gilbert <dgilbert at interlog dot com>,
+ Bruce Allen <ballen at gravity dot phys dot uwm dot edu>,
+ Peter Allworth <linsol at zeta dot org dot au>,
+ James Bottomley <jejb at parisc-linux dot org>,
+ Lars Marowsky-Bree <lmb at suse dot de>,
+ Kurt Garloff,
+ Grant Grundler <grundler at parisc-linux dot org>,
+ Christophe Varoqui <christophe dot varoqui at free dot fr>,
+ Michael Weller <eowmob at exp-math dot uni-essen dot de>,
+ Eric Youngdale <eric at andante dot org>
+
+Copyright:
+
+This software is copyright(c) 1994-2021 by the authors
+
+Most of the code in this package is covered by a BSD license.
+On Debian systems, the complete text of the BSD License
+can be found in `/usr/share/common-licenses/BSD'. All the code
+in the library (usually called libsgutils) is covered by a
+BSD license.
+
+Some of the older utilities are covered by the GPL. More precisely:
+You are free to distribute this software under the terms of the
+GNU General Public License either version 2, or (at your option)
+any later version. On Debian systems, the complete text of the GNU
+General Public License can be found in /usr/share/common-licenses/GPL-2
+file. The later GPL-3 is found in /usr/share/common-licenses/GPL-3
+file but no code in this package refers to that license.
+
+Douglas Gilbert
+4th October 2021
diff --git a/COVERAGE b/COVERAGE
new file mode 100644
index 00000000..09c90105
--- /dev/null
+++ b/COVERAGE
@@ -0,0 +1,188 @@
+ Command coverage
+ ================
+The following table lists SCSI commands in alphabetical order on the
+left and the sg3_utils (or related) utilities that implement invocations
+of them on the right. The second table lists supported ATA commands. The
+third table list supported NVMe commands.
+
+SCSI command sg3_utils utilities that use this SCSI command
+------------ -------------------------------------------------
+ATA COMMAND PASS-THROUGH(12) sg_sat_identify, ++
+ATA COMMAND PASS-THROUGH(16) sg_sat_identify, sg_sat_set_features,
+ sg_sat_phy_event, sg_sat_read_gplog ++
+ [sg_sat_chk_power, sg__sat_identify,
+ sg__sat_set_features, sg_sat_smart_rd_data
+ (previous four in the examples directory)]
+ATA COMMAND PASS-THROUGH(32) sg_sat_identify, ++
+BACKGROUND CONTROL sg_bg_ctl
+CLOSE ZONE sg_zone
+COMPARE AND WRITE sg_compare_and_write
+COPY OPERATION ABORT ddptctl, ++
+EXTENDED COPY(LID1) sg_xcopy, ddpt, ++
+GET CONFIGURATION sg_get_config, ++
+GET LBA STATUS sg_get_lba_status, ++
+GET PHYSICAL ELEMENT STATUS sg_get_elem_status, ++
+GET STREAM STATUS sg_stream_ctl
+INQUIRY sg_dd, sg_format, sg_inq, sginfo,
+ sg_logs, sg_map('-i'), sg_modes, sg_opcodes,
+ sg_persist, sg_scan, sg_ses, sg_vpd ++
+FINISH ZONE sg_zone
+FORMAT MEDIUM sg_format, ++ [SSC]
+FORMAT UNIT sg_format, ++ [SBC]
+FORMAT WITH PRESET sg_format, ++ [SBC]
+LOG SELECT sg_logs('-r' or '-select'), ++
+LOG SENSE sg_logs, ++
+MODE SELECT(6) sdparm, sg_wr_mode, sginfo, sg_format,
+ sg_emc_trespass, sg_rdac, ++
+MODE SELECT(10) sdparm, sg_wr_mode, sginfo, sg_format,
+ sg_emc_trespass, sg_rdac, ++
+MODE SENSE(6) sdparm, sg_modes, sg_wr_mode, sginfo, sg_format,
+ sg_senddiag('-e'), sg_rdac, ++
+MODE SENSE(10) sdparm, sg_modes, sg_wr_mode, sginfo, sg_format,
+ sg_senddiag('-e'), sg_rdac, ++
+OPEN ZONE sg_zone
+ORWRITE(16) sg_write_x
+ORWRITE(32) sg_write_x
+PERSISTENT RESERVE IN sg_persist, ++
+PERSISTENT RESERVE OUT sg_persist, ++
+POPULATE TOKEN ddpt, ddptctl, ++
+PRE-FETCH(10) sg_seek
+PRE-FETCH(16) sg_seek
+PREVENT ALLOW MEDIUM REMOVAL sg_prevent, ++
+READ(6) sg_dd, sgm_dd, sgp_dd, sg_read
+READ(10) sg_dd, sgm_dd, sgp_dd, sg_read
+READ(12) sg_dd, sgm_dd, sgp_dd, sg_read
+READ(16) sg_dd, sgm_dd, sgp_dd, sg_read
+READ ATTRIBUTE sg_read_attr
+READ BLOCK LIMITS sg_read_block_limits, ++
+READ BUFFER(10) sg_rbuf, sg_test_rwbuf, sg_read_buffer, sg_safte, ++
+READ BUFFER(16) sg_read_buffer
+READ CAPACITY(10) sg_readcap, sg_dd, sgm_dd, sgp_dd, sg_format, ++
+READ CAPACITY(16) sg_readcap, sg_dd, sgm_dd, sgp_dd, sg_format, ++
+READ DEFECT(10) sginfo('-d' or '-G'), sg_reassign('-g'), smartmontools, ++
+READ DEFECT(12) sginfo('-d' or '-G'), smartmontools
+READ LONG(10) sg_read_long, sg_dd, ++
+READ LONG(16) sg_read_long, ++
+READ MEDIA SERIAL NUMBER sg_rmsn, ++
+REASSIGN BLOCKS sg_reassign, ++
+RECEIVE COPY DATA(LID1) sg_copy_results, ++
+RECEIVE COPY FAILURE DETAILS(LID1) sg_copy_results, ++
+RECEIVE COPY OPERATING PARAMETERS ddpt, sg_copy_results, sg_xcopy, ++
+RECEIVE COPY STATUS(LID1) sg_copy_results, ++
+RECEIVE DIAGNOSTIC RESULTS sg_senddiag, sg_ses, sg_ses_microcode ++
+RECEIVE ROD TOKEN INFORMATION ddpt, ddptctl ++
+REMOVE ELEMENT AND MODIFY ZONES sg_zone
+REMOVE ELEMENT AND TRUNCATE sg_rem_rest_elem
+REPORT ALL ROD TOKENS ddptctl ++
+REPORT DENSITY SUPPORT sg_rep_density
+REPORT IDENTIFYING INFORMATION sg_ident, ++ (2)
+REPORT LUNS sg_luns, ++
+REPORT PROVISIONING INITIALIZATION PATTERN sg_rep_pip, ++
+REPORT REALMS sg_rep_zones
+REPORT REFERRALS sg_referrals, ++
+REPORT SUPPORTED OPERATION CODES sg_opcodes
+REPORT SUPPORTED TASK MANAGEMENT FUNCTIONS sg_opcodes
+REPORT TARGET PORT GROUPS sg_rtpg, sg_stpg ++
+REPORT TIMESTAMP sg_timestamp
+REPORT ZONES sg_rep_zones
+REPORT ZONE DOMAINS sg_rep_zones
+REQUEST SENSE sg_requests, ++
+RESET WRITE POINTER sg_reset_wp
+RESTORE ELEMENTS AND REBUILD sg_rem_rest_elem
+SANITIZE sg_sanitize
+SEEK(10) sg_seek ++
+SEND DIAGNOSTIC sg_senddiag, sg_ses, sg_ses_microcode ++
+SEQUENTIALIZE ZONE sg_zone
+SET IDENTIFYING INFORMATION sg_ident, ++ (3)
+SET TARGET PORT GROUPS sg_stpg, ++
+SET TIMESTAMP sg_timestamp
+START STOP sg_start, ++
+STREAM CONTROL sg_stream_ctl
+SYNCHRONIZE CACHE(10) sg_sync, sg_dd, sgm_dd, sgp_dd, ++
+SYNCHRONIZE CACHE(16) sg_sync++
+TEST UNIT READY sg_turs, sg_format, ++
+UNMAP sg_unmap, ++
+VERIFY(10) sg_verify, ++
+VERIFY(16) sg_verify, ++
+WRITE(6) sg_dd, sgm_dd, sgp_dd
+WRITE(10) sg_dd, sgm_dd, sgp_dd
+WRITE(12) sg_dd, sgm_dd, sgp_dd
+WRITE(16) sg_dd, sgm_dd, sgp_dd, sg_write_x
+WRITE(32) sg_write_x
+WRITE AND VERIFY(10) sg_write_verify
+WRITE AND VERIFY(16) sg_write_verify
+WRITE ATOMIC(16) ddpt, sg_write_x
+WRITE ATOMIC(32) sg_write_x
+WRITE BUFFER sg_test_rwbuf, sg_write_buffer, ++
+WRITE LONG(10) sg_write_long, ++
+WRITE LONG(16) sg_write_long, ++
+WRITE SAME(10) sg_write_same
+WRITE SAME(16) sg_write_same, sg_write_x
+WRITE SAME(32) sg_write_same, sg_write_x
+WRITE SCATTERED(16) sg_write_x
+WRITE SCATTERED(32) sg_write_x
+WRITE STREAM(16) sg_write_x
+WRITE STREAM(32) sg_write_x
+WRITE USING TOKEN ddpt, ddptctl, ++
+ZONE ACTIVATE sg_z_act_query
+ZONE QUERY sg_z_act_query
+<most commands> sg_raw
+
+
+ATA command sg3_utils utilities that use this (S)ATA command
+----------- ------------------------------------------------
+CHECK POWER MODE examples/sg_sat_chk_power
+IDENTIFY DEVICE sg_inq, sg_scan, sg_sat_identify,
+ examples/sg__sat_identify
+IDENTIFY PACKET DEVICE sg_inq, sg_sat_identify,
+ examples/sg__sat_identify
+READ LOG EXT sg_sat_phy_event, examples/sg__sat_phy_event
+ sg_sat_read_gplog
+READ LOG DMA EXT sg_sat_read_gplog
+SET FEATURES sg_sat_set_features
+ examples/sg__sat_set_features
+SMART READ DATA examples/sg_sat_smart_rd_data
+
+
+NVMe command sg3_utils utilities that use this NVMe command
+------------ ------------------------------------------------
+Identify sg_inq
+SES Read sg_senddiag, sg_ses (NVME-MI command)
+SES Write sg_senddiag, sg_ses (NVME-MI command)
+Device self-test [SNTL of SEND DIAGNOSTIC] sg_senddiag
+Get features(power management) [SNTL of REQUEST SENSE] sg_requests
+Read [SCSI READ(10) -->SNTL--> Read]
+ [SCSI READ(16) -->SNTL--> Read]
+Write [SCSI WRITE(10) -->SNTL--> Write]
+ [SCSI WRITE(16) -->SNTL--> Write]
+Compare [SCSI VERIFY(10,BYTCHK=1) -->SNTL--> Compare]
+ [SCSI VERIFY(16,BYTCHK=1) -->SNTL--> Compare]
+Write zeroes [SCSI WRITE SAME(10,zeros) -->SNTL--> Write zeroes]
+ [SCSI WRITE SAME(16,zeros) -->SNTL--> Write zeroes]
+Flush [SCSI SYNCHRONIZE CACHE -->SNTL--> Flush]
+Set Features [SCSI MODE SELECT(10) -->SNTL--> Set Features]
+ only for WCE in Caching page
+
+The following SCSI commands do nothing (currently) in the SNTL but
+do return GOOD status: TEST UNIT READY, START STOP UNIT, REPORT LUNS
+and REQUEST SENSE. READ CAPACITY(10 and 16) yield appropriate data
+by examining the response to the NVMe Identify command.
+
+
+++ command wrapper found in sg_cmds_basic.c, sg_cmds_mmc.c or
+ sg_cmds_extra.c for this command
+(2) this command was known as REPORT DEVICE IDENTIFIER prior to spc4r07
+(3) this command was known as SET DEVICE IDENTIFIER prior to spc4r07
+
+Note that any SCSI command, including bi-directional and variable length
+commands (whose cdb size is > 16 bytes) can be issued by the sg_raw utility.
+
+The RECEIVE COPY * commands in SPC-4 were grouped as one command name
+with 4 service actions in SPC-3 and earlier. The single SPC-3 command
+name is RECEIVE COPY RESULTS. The two opcodes associated with all
+EXTENDED COPY commands are now known as THIRD PARTY COPY IN (0x84) and
+THIRD PARTY COPY IN (0x83).
+
+
+Douglas Gilbert
+10 June 2022
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 00000000..1b1de838
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,163 @@
+The author of sg3_utils would like to thank the following people who
+have made contributions:
+
+
+Andries Brouwer <aebr at win dot tue dot nl> rewrite of isosize (original
+ written by Joerg Schilling). isosize is now found in the util-linux
+ package and in the archive directory of this package.
+
+Bart Van Assche <bart dot vanassche at sandisk dot com>
+ harden (improve) code in rescan-scsi-bus.sh [20160224]
+ configure.ac and Makefile.am cleanup plus sgp_dd code
+ to replace pthread_cancel with pthread_kill [20180102]
+ sg_xcopy: fix identification CSCD descriptor's designator
+ length, fix CSCD descriptor mask [20210902]
+
+Bean Huo <beanhuo dot micron dot com>
+ sg_write_buffer: patch to allow comma or period separated bytes
+ (in decimal or hex) to be decoded when given as standard input.
+
+Brian Bunker <Brian dot Bunker at netapp dot com> contributed
+ sg_read_block_limits and the target reset addition to sg_reset
+ [20090615]
+
+Christophe Varoqui <christophe dot varoqui at free dot fr> original sg_rtpg
+ [20041229]
+
+Clayton Weaver <cgweav at email dot com> contributed safe_strerror().
+
+Dan Horak <dhorak at redhat dot com> website support for this package and
+ others. Lot of fixes, recently man pages [20140128]
+
+Dave Johnson <ddj at ccv dot brown dot edu> improved disk defect list
+ handling [20051218]
+
+Dave Williams <dave at opensourcesolutions dot co dot uk> help with
+ sgp_dd especially and "> 0x7fffffff" with sg*_dd [20060303]
+
+Eric Schwartz <emschwar at debian dot org> who wrote these man pages:
+ sg_readcap, sg_reset, sg_scan, sg_start, sg_test_rwbuf,
+ sg_turs and sginfo
+
+Eric Seppanen <eric @ purestorage dot com> borrowed ideas from alternate
+ implementation of sg_compare_and_write [20130823]
+
+Eric Youngdale <eric at andante dot org> author of scsi_info on which sginfo
+ is based.
+
+Fabrice Fontaine <fontaine dot fabrice at gmail dot com>
+ various build fixes [20211116]
+
+Frank Jansen <fjansen at egenera dot com>: additions to sg_scan; contributed
+ code for '--alloc-length=' option in sg_persist [20090402]
+
+Grant Grundler <grundler at parisc-linux dot org> co-author of blk512-linux
+ that has become sg_format [20050201]
+
+Greg Inozemtsev <greg at purestorage dot com>
+ extensions to sg_xcopy [20130207+20130816]
+
+Hannes Reinecke <hare at suse dot de>
+ contributed sg_rdac, (and some corresponding VPD entries to
+ sg_vpd_vendor), sg_stpg and sg_safte [20071013+20130110]
+ sg_referrals [20100906]
+ sg_inq --export option [20120220+20130109]
+ sg_xcopy+sg_copy_results [20120322]
+ rescan-scsi-bus.sh patches to Kurt Garloff's v1.57 [20130715]
+ 55-scsi-sg3_id.rules + 58-scsi-sg3_symlink.rules [20140527]
+ sg_sat_read_gplog [20141107]
+ sg_inq --only option plus --inhex fixes [20180102]
+
+Hayashi Naoyuki <titan at culzean dot org>
+ port to Tru64 [20060127]
+
+Heiko Eissfeldt <heiko at colossus dot escape dot de> sg based example
+ programs for the original sg driver
+
+Ilan Steinberg <ilan dot steinberg at kaminario dot com>
+ sg_xcopy: contributed --on_src and --on_dst options [20130505]
+
+Ingo van Lil <inguin at gmx dot de>
+ contributed sg_raw [20070331]
+
+James Bottomley <jejb at parisc-linux dot org> co-author of blk512-linux
+ that has become sg_format [20050201]
+
+Jan Engelhardt <jengelh at inai dot de>
+ autotools clean-up [20150216]
+
+Joe Krahn <krahn at niehs dot nih dot gov> help with int64_t cleanup
+ [20071219]
+
+Kai Makisara <Kai dot Makisara at kolumbus dot fi> help with tape
+ minor numbers in lk 2.6 plus earlier advice [20081008]
+
+Kurt Garloff: original sg_start and sg_test_rwbuf.
+ Additions to sginfo and sg_map. Author of rescan-scsi-bus.sh with
+ latest update to v1.57 [20130331]
+
+Lars Marowsky-Brée <lmb at suse dot de> contributed Unit Path Report VPD
+ page decoding in sg_inq (vendor specific: EMC) and sg_emc_trespass
+ utility
+
+Luben Tuikov <ltuikov at yahoo dot com>
+ help with documentation and other suggestions [20061014]
+ contribution sg_read_buffer and sg_write_buffer [20061103]
+
+Marius Konitzer <marius dot konitzer at ruhr-uni-bochum dot de
+ log pages on IBM LTO Ultrium drives [20100225]
+
+Mark Knibbs <markk at clara dot co dot uk>
+ suggested and tested oflag=sparse for sg_dd
+
+Martin Schwenke <martin at meltin dot net> added the raw switch "-r" to sg_inq
+
+Martin Wilck <mwilck at suse dot com> contributed script files [20190425 and
+ 20220218]]
+
+Nate Dailey < Nate dot Dailey at stratus dot com > extended sg_map for sparse
+ disk node names (e.g. /dev/sdaaa) [20050511]
+
+Nitin U. Yewale < nyewale at redhat dot com> sent patch via github:
+ https://github.com/doug-gilbert/sg3_utils/pull/10/ to fix crash with
+ rescan-scsi-bus.sh -r due to rev 867 change to sg_inq [20220103]
+
+Pat LaVarre <p.lavarre at ieee dot org> pointed out danger of negative bpt
+ values in sg_dd (and friends); also problems when reading /dev/null
+
+Peter Allworth <linsol at zeta dot org dot au> original dd clone design used
+ by sg3_utils's dd variants (e.g. sg_dd).
+
+Roland Dreier <roland at purestorage dot com>
+ extension and correction to sg_xcopy [20120205]
+
+Ronnie Sahlberg <ronniesahlberg at gmail dot com> has written libiscsi and a
+ set of external patches to add direct iSCSI support to this package.
+ See README.iscsi [20110518]
+
+Saeed Bishara contributed sg_write_long
+
+Sean Stewart <Sean dot Stewart at netapp dot com> various improvements
+ to rescan-scsi-bush.sh script [20130827]
+
+Shahar Salzman <shahar dot salzman at kaminario dot com> contributed
+ sg_compare_and_write [20121205]
+
+Thomas Kolbe <tkolbe at partnersdata dot com>
+ Solaris port help and testing [20070503]
+
+Tim Hunt <tim at timhunt dot net> increased number of (sd and sg) devices
+ that sginfo could detect.
+
+Tom Steudten <steudten at gmx dot ch> sginfo addition: add '-Fhead' option
+ to sort defect list by head.
+
+Trent Piepho <xyzzy at speakeasy dot org> print out some "sense key specific"
+ data and "-6" switch for sg_modes
+
+Xose Vazquez Perez <xose dot vazquez at gmail dot com>
+ documentation corrections [20200117]
+
+
+Douglas Gilbert
+18th February 2022
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..1adf83f8
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1919 @@
+Each utility has its own version number, date of last change and
+some description at the top of its ".c" file. All utilities in the main
+directory have their own "man" pages. There is also a sg3_utils man page.
+
+Changelog for pre-release sg3_utils-1.48 [20221112] [svn: r983]
+ - some utilities: add experimental --json[=JO] option
+ - sg_z_act_query: new utility for sending either a
+ Zone activate or Zone query command
+ - sg_rep_density: new utility for decoding the response of
+ Report density support command [ssc (tape)]
+ - sg_rem_rest_elem: new utility for removing or restoring
+ elements
+ - sg_rtpg: https://github.com/hreinecke/sg3_utils/pull/79
+ applied
+ - rescan-scsi-bus.sh: with '-r' it crashed due to change in
+ rev 867 in sg_inq.c from Device_type= to PDT= .
+ Change script to use either
+ - undo regression in rev 815 that added newline after
+ each LUN in the default (no option) output
+ - rev 815 changed the order of listing hosts from
+ numeric to alphabetical, change it back to numeric
+ - https://github.com/doug-gilbert/sg3_utils/pull/17
+ applied with tweaks: add timeout parameter
+ - clean $norm handling
+ - fix handling of '-I <secs>' option
+ - sgdevice26: do not traverse sg class if scsi_device
+ is not added
+ - add -no-lip-scan option
+ - https://github.com/doug-gilbert/sg3_utils/pull/21
+ - speed testonline() when peripheral_qualifier != 0
+ see https://github.com/doug-gilbert/sg3_utils/issues/24
+ - speed multipath scans with many LUNs, cache multipath
+ LUN info in temporary file, see
+ https://github.com/doug-gilbert/sg3_utils/issues/22
+ - sg_rep_zones: add Report zone starting LBA granularity
+ field in REPORT ZONES response [zbc2r12]
+ - add --brief option, show part of header and last
+ descriptor fetched
+ - add --find ZT option to find the first occurrence of
+ ZT; if ZT prefixed by - or ! find first not equal to ZT
+ - add --statistics option
+ - sg_get_elem_status: change '--maxlen=' option default to
+ 1056 (header plus 32 physical element status descriptors)
+ - sg_decode_sense: add --nodecode option
+ - sg_logs: tweak the meaning of --list option to more closely
+ reflect the contents of log pages 0x0 and 0x0,0xff
+ - make '-lll' set union of log pages 0x0 and 0x0,0xff
+ - add --exclude option to exclude vendor specific pages
+ and parameters
+ - add --undefined option for hex format of undefined/
+ unrecognized fields
+ - for short binary fields, remove address (index) from
+ the left hand side of each line of hex
+ - improve 'last_n' log pages; supply VPD and mode pages
+ with their name (if T10 defined)
+ - update names for TapeAlert lpage
+ - allow --page selection with --inhex=FN
+ - sg_modes: improve handling of zbc disks with pdt=0x14
+ - sg_inq, sg_vpd: merge VPD page processing for T10
+ defined pages, VS pages still differ
+ - Device Identication VPD page, change
+ "IEEE Company_id" to "AOI" as per spc6r06.pdf
+ - add support for Hitachi/HP open-v ldev names
+ - sg_vpd: apply github pull 18 (missing LF)
+ - add --sinq_inraw=RFN option
+ - sg_opcodes: cleanup error reporting
+ - add --inhex=FN to process earlier -HHH
+ - sg_format: allow disk formats on ZBC (zoned) disks
+ - sg_read_buffer: add --eh_code= and --no_output options
+ - sg_ses: add exp_sas_addr acronym for getting expander's
+ SAS address
+ - sg_turs: change nanosleep() to Sleep() in MinGW
+ - see sg_lib below for two new exit status values
+ - sg_stream_ctl: fix --get indexing
+ - sg_read_block_limits: fix granularity value
+ - add --mloi option
+ - inhex/logs_last_n.hex: new, tests for the 4 "Last n ..."
+ log (sub)pages
+ - sg_lib: add sg_pdt_s_eq() to cope with ZBC disks which may
+ be either PDT_ZBC (if host managed) or PDT_DISK
+ - add hex2fp(), similar to hex2str() but outputs to FILE
+ - cleanup masks for PDT [0x1f] and group_number [0x3f]
+ - new sg_json_builder.[hc] files local to lib folder
+ - document internal json interface in include/sg_pr2serr.h
+ - add SG_C_CPP_ZERO_INIT to better handle aggregate stack
+ instance zeroing (C23 adding 'struct T t {};' will help)
+ - correct fixed format sense data command-specific field
+ for ata-passthrough (lba field), see:
+ https://github.com/doug-gilbert/sg3_utils/pull/25
+ - add sg_ll_read_block_limits_v2() for MLOI bit
+ - https://github.com/doug-gilbert/sg3_utils/pull/32 added
+ with tweaks; adds SG_LIB_CAT_STANDBY and
+ SG_LIB_CAT_UNAVAILABLE which refine the 'not ready'
+ exit status. Useful for sg_turs
+ - initialize all sense buffers to 0
+ - linux: replace references to /proc/scsi/sg with
+ /sys/module/sg/parameters/
+ - rework main README file
+ - rev 921+922 are bugfix revs on release 1.47 [r919,920]
+ - configure.ac: map msys to mingw
+ - repeat tweak to accept uclinux as linux
+ - sg_dd: change uint type to uint32_t
+ - remove unused out2_off in main()
+ - sg_pt_dummy.c: remove problematic include
+ - sg3_utils.spec: change tarball extension from .tgz to
+ .tar.gz ; fix build issue with Fedora 36
+ - build cleanups for 'make distcheck', see
+ https://github.com/doug-gilbert/sg3_utils/pull/26, 27 and
+ 28; need svn:ignore property or maybe global-ignores
+ - round of coverity identified issue fixes (and non-issues)
+ - autoconf: upgrade version 2.70 to 2.71; automake upgrade
+ to version 1.16.5 (from Fedora 36)
+ - remove archive directory (and its contents)
+ - codespell fixes
+
+Changelog for released sg3_utils-1.47 [20211110] [svn: r919]
+ - sg_rep_zones: add support for REPORT ZONE DOMAINS and
+ REPORT REALMS in this utility
+ - sg_raw: fix prints of NVMe NVM command names
+ - sg_ses: fix Windows problem "No command (cdb) given"
+ - fix crash when '-m LEN' < 252
+ - guard against smaller '--maxlen=' values
+ - sg_logs: additions to Volume statistics lpage [ssc5r05c]
+ - additions to Command duration limits statistics log
+ page [spc6r06]
+ - sg_vpd: fix do_hex type on some recent pages
+ - zoned block dev char vpd: add zone alignment mode and
+ zone starting LBA granularity [zbc2r11]
+ - sg_read_buffer: fix --length= problem
+ - sg_dd, sgm_dd, sgp_dd: don't close negative file descriptors
+ - sg_dd: srand48_r() and mrand48_r() are GNU libc specific,
+ put conditional in so non-reentrant version used otherwise
+ - 'iflag=00,ff' places the 32 bit block address (big endian)
+ into each block
+ - sgp_dd: major rework, fix issue with error being ignored
+ - new: --chkaddr which checks for block address in each block
+ - add check for stdatomic.h presence in configure.ac
+ - sg_xcopy: tweak CSCD identification descriptor
+ - sg_get_elem_status: fix issue with '--maxlen=' option
+ - add 2 depopulation revocation health attributes [sbc5r01]
+ - transport error handling improved. To fix report of a
+ BAD_TARGET transport error but the utility still continued.
+ - introduce SG_LIB_TRANSPORT_ERROR [35] exit status
+ - several utilities: override '--maxlen=LEN' when LEN
+ is < 16 (or 4), take default (or 4) instead
+ - scripts: 55-scsi-sg3_id.rules remove outdated rule
+ - sg_lib: add sg_scsi_status_is_good(),
+ sg_scsi_status_is_bad() and sg_get_zone_type_str()
+ - pt_linux: fix verify(BytChk=0) which Linux SNTL translated
+ to write, other SNTL cleanups
+ - pt_linux_nvme: fix fua setting
+ - pt: check_pt_file_handle() add return value of 5 for
+ FreeBSD for nvme(cam)
+ - pt: new configure option --enable-pt_dummy builds the
+ library with sg_pt_dummy.c instead of OS specific code;
+ for experimenting with --inhex= decoding on netbsd
+ - pt: add Haiku OS support
+ - gcc -fanalyzer fixes: in sg_pt_linux.c + sg_write_x.c
+ - sg_pt_dummy.c: add list of functions that a new pt
+ needs to define
+ - configure.ac: tweak to accept uclinux as linux
+ - move some hex files from examples to inhex directory
+ - major rework of lib/sg_pt_freebsd.c; make SNTL as similar
+ as practical to the Linux implementation
+ - add testing/sg_take_snap
+ - change links to http://sg.danny/cz/sg/* to https
+
+Changelog for released sg3_utils-1.46 [20210329] [svn: r891]
+ - sg_rep_pip: new utility: report provisioning initialization
+ pattern command
+ - sg_turs: estimated time-to-ready [spc6r03]
+ - add --delay=MS option
+ - sg_requests: substantial cleanup
+ - sg_vpd: add Format presets and Concurrent positioning ranges
+ - add hot-pluggable field in standard Inquiry [spc6r05]
+ - fix vendor struct opts_t alignment
+ - sg_inq: add hot-pluggable field in standard Inquiry
+ - sg_dd: --verify : separate category for miscompare errors
+ - --verify : oflag=coe continue on miscompares, counts them
+ - add cdl= operand for command duration limit indexes
+ - add oflag=nocreat and conv=nocreat : OFILE must exist
+ - add iflag=00, ff, random flags
+ - setup conditional auto rule for getrandom()
+ - add command timeout after comma in time= operand
+ - sg_get_elem_status: add ralwd bit sbc4r20a
+ - sg_write_x: add dld bits to write(32) [sbc4r19a]
+ - sg_rep_zones: print invalid write pointer LBA as -1 rather
+ than 16 "f"s
+ - sg_opcodes: improve handling of RWCDLP field
+ - sg_ses: use fan speed factor field for calculation [ses4r04]
+ - add --all (-a) option, same action as --join
+ - sg_compare_and_write: add examples section to its manpage
+ - sg_modes: document '-s' option (same as '-6')
+ - sg_sanitize + sg_format: when --verbose given once report
+ probable success; without --verbose 'no news is good news'
+ - sg_zone: add Remove element and modify zones command
+ - sg_raw: increase maximum data-in and data-out buffer size
+ from 64 KB to 1 MB
+ - fix --cmdfile= handling
+ - add --nvm option to send commands from the NVM command set
+ - add --cmdset option to bypass cdb heuristic
+ - add --scan= first_opcode,last_opcode
+ - sg_pt_freebsd: allow device names without leading /dev/
+ thus fix for regression introduced in rev 731 (ver: 1.43)
+ - sg_pt_solaris+sg_pt_osf1: fix problem with clear_scsi_pt_obj()
+ which needs to remember is_nvme and dev_fd values
+ - sg_lib: add ZBC (2020) feature set entries
+ - sg_lib: restore elements and rebuild command added
+ - sg_lib,sg_pt: add partial_clear_scsi_pt_obj(),
+ get_scsi_pt_cdb_len() and get_scsi_pt_cdb_buf()
+ - add do_nvm_pt() for the NVM (sub-)command set
+ - tweak transport error handling in Linux
+ - sg_lib: Linux NVMe SNTL: add read, write and verify;
+ synchronize cache and write same translations
+ - add dummy start stop unit and test unit ready commands
+ - wire cache mpage's WCE to nvme 'volatile write cache'
+ - fix crash in sg_f2hex_arr() when fname not found
+ - sg_lib: reprint cdb with illegal request sense key
+ - asc/ascq match asc-num.txt @t10 20200708 [spc6r02]
+ - gcc-10: suppress warnings
+ - autoconf: upgrade version 2.69 to 2.70
+ - remove space from end of source lines for git-svn
+ - testing/sg_mrq_testing: new, for blocking mrq usage
+ - testing/sgs_dd: add evfd flags and eventfd processing
+ - testing: remove master-slave terminology for sgv4
+ - examples: add nvme_read_ctl.hex and nvme_write_ctl.hex
+
+Changelog for released sg3_utils-1.45 [20200229] [svn: r843]
+ - sg_get_elem_status: new utility [sbc4r16]
+ - sg_ses: bug: --page= being overridden when --control and --data= also
+ given; fix
+ - document explicit Element type codes and example
+ - rename 'SAS SlimLine' to SlimSAS [ses4r02]
+ - add --inhex=FN, equivalent to --data=@FN, for compatibility with
+ other utilities
+ - 'fan speed factor' field added in 20-013r1
+ - sg_opcodes: expand MLU (now 2 bits, spc5r20)
+ - include RWCDLP field as extension of CDLP field (spc5r01)
+ - sg_write_buffer: allow comma and period separated lists when input
+ from stdin
+ - sg_inq: update version descriptors to spc5r21
+ - add some NVMe 1.4 snippets to ctl identify
+ - sg_format: add --dcrt used twice (FOV=1 DCRT=0)
+ - add support for FORMAT WITH PRESET (sbc4r18)
+ - sg_raw: fix --send bug when using stdin
+ - sg_vpd: 3pc VPD page add copy group descriptor
+ - add --examine option
+ - new zoned block device char. page (zbc2r04)
+ - sg_read_buffer: decode read microcode status page
+ - add --inhex=FN option
+ - sg_request: add --error option, replaces opcode with 0xff (or skips call
+ to pass-through)
+ - sg_get_lba_status: add --inhex=FN option
+ - sg_xcopy: add --fco (fast copy only) (spc5r20)
+ - implement --app=1 (append) on regular OFILE type
+ - sg_scan (win32): expand limits for big arrays
+ - sg_modes: placeholders for Command duration limit T2A and T2B mpages
+ (sbc4r17)
+ - improve zbc support (e.g. caching mpage)
+ - sg_logs: add Command duration limits statistics lpage (spc6r01)
+ - zoned block device statistics log page: shorten counter fields from
+ 8 to 4 bytes (zbc2r02)
+ - new field in this log page (zbc2r04)
+ - change '-ll' option to suppress subpages=0xff apart from page
+ 0x0,0xff. Used three times: list all pages and subpages names
+ reported
+ - sg_reassign: for defect list format 6 (vendor specific) don't try to decode
+ - sg_rep_zones: expand some fields per zbc2r04
+ - add --num= and --wp options
+ - sg_verify: correct so issues VERIFY(16)
+ - add --0 and --ff options and implement bytchk=3 properly
+ - sg_write_same: add --ff for 0xff fill
+ - sg_luns: report new "target commands" w-lun (19-117)
+ - sg_dd: add --verify support
+ - sgp_dd: support memory-mapped IO via mmap flag
+ - inhex directory: new, contains ASCII hex files that can be used with
+ the '--inhex=' option
+ - sg_lib: add sg_t10_uuid_desig2str()
+ - add sg_get_command_str and sg_print_command_len()
+ - speed up sg_print_command()
+ - sg_scsi_normalize_sense(): populate byte4,5,6
+ - tweak sg_pt interface to better handle bidi
+ - sg_cmds_process_resp(): two arguments removed
+ - add ${PACKAGE_VERSION} to '.so' name
+ - add sg_f2hex_arr()
+ - update some tables for NVMe 1.4
+ - sg_get_num()+sg_get_llnum(): add 'e' decoding, exabytes; allow
+ addition (e.g. --count=3+1k)
+ - asc/ascq match asc-num.txt @t10 20191014
+ - new zbc2r04 service actions
+ - sg_pt_freebsd: fixes for FreeBSD 12.0 release
+ - scripts: update 54-before-scsi-sg3_id.rules, scsi-enable-target-scan.sh
+ and 59-fc-wwpn-id.rules
+ - linux: add nanosecond durations when SG3_UTILS_LINUX_NANO environment
+ variable givenand Linux sg driver >= 4.0.30
+ - rescan-scsi-bus: widen LUN 0 only scanning
+ - multiple patches to sync with Suse
+ - testing/sg_tst_async: fix free_list issue
+ - testing/sg_tst_ioctl: for sg 4.0 driver
+ - testing/sg_tst_bidi: for sg 4.0 driver
+ - testing/sgh_dd: test request sharing, mreqs...
+ - add --verify support
+ - testing/sgs_dd: back from archive, for testing SIGPOLL (SIGIO) and
+ realtime (RT) signals
+ - testing/sg_chk_asc: allow LF and CR/LF in asc-num.txt
+ - testing: 'make' now builds both C and C++ programs
+ - sg_pt: add sg_get_opcode_translation() to replace global pointer to
+ array: sg_opcode_info_arr[]
+ - extend small SNTL to support read capacity
+ - utils/hxascdmp: add -o=<offset> option
+ - add -1, -2 and -q options
+ - sg_io_linux (sg_lib): add sg_linux_sense_print()
+ - sg_pt_linux: uses sg v4 interface if sg driver >= 4.0.0 . Force sg v3
+ always by building with './configure --disable-linux-sgv4'
+ - add sg_linux_get_sg_version() function
+ - add: 'SPDX-License-Identifier: BSD-2-Clause' or a small number of
+ 'GPL-2.0-or-later'
+ - gcc-9: suppress (pointless) warnings
+ - automake: upgrade to version 1.16.1
+ - autoconf: upgrade to version 2.69
+ - sync with fixes from Redhat, via github
+
+Changelog for sg3_utils-1.44 [20180912] [svn: r791]
+ - same code as release 1.43 20180911 svn rev 789;
+ new release due to sync problem with git mirror at:
+ https://github.com/hreinecke/sg3_utils
+ that has a v1.43 tag dated 20160217 [svn: r663]
+
+Changelog for sg3_utils-1.43 [20180911] [svn: r789]
+ - release 1.43 20180911 svn rev 789
+ - sg_write_x: where x can be normal, atomic, or(write),
+ same, scattered, or stream writes with 16 or 32 byte
+ cdbs (sbc4r04 for atomic, sbc4r11 for scattered)
+ - sg_bg_ctl: new Background control command (sbc4r08)
+ - sg_seek: new SEEK(10) or PRE-FETCH(10 or 16)
+ - sg_stream_ctl: new, STREAM CONTROL or GET STREAM STATUS
+ - sg_senddiag: add --timeout=SECS option
+ - sg_sanitize: add --timeout=SECS option
+ - add --dry-run option
+ - sg_format: add --timeout=SECS option
+ - add --dry-run option to bypass modifying behaviour
+ - add --quick option to skip reconsideration time
+ - extend --wait timeout to 40 hours for disk sizes
+ > 4 TB and 80 hours if > 8 TB
+ - when changing block size allow for Mode Select
+ rejecting SP=1 (Save Page): repeat with SP=0
+ - FFMT tweaks: default CMPLST to false, shorten poll
+ - make all data-in and data-out buffers page aligned
+ - sg_decode sense: add --cdb and --err=ES options
+ - sg_ses: handle 2 bit EIIOE field in aes dpage
+ - increase join array size from 260 to 520 elements
+ - add --quiet option to suppress messages
+ - expand join handling of SAS connectors and others
+ - expand join debug code
+ - allow multiple --clear=, --get= and --set= options
+ - allow individual index ranges (e.g. --index=3-5)
+ - allow --index=IIA with -ee to enumerate only fields
+ belonging to element type IIA
+ - --data=@FN with --status now decodes dpage(s) in FN
+ - add 'offset_temp' and 'rqst_override' to temperature
+ sensor element type
+ - add 'hw_reset' and 'sw_reset' to enclosure services
+ controller electronics element type (18-047r1)
+ - interpret '--join --page=aes' to only display join
+ rows that have a corresponding AES dpage element
+ - support NVMe attached enclosure via NVME-MI Send and
+ Receive SES commands
+ - decode array status diagnostic page (obsolete)
+ - sync to ses4r01
+ - sg_ses_microcode: add --dry-run and -ealsd options
+ - sg_ses, sg_ses_microcode, sg_senddiag: make all access
+ buffer page size aligned (typically page_size=4096)
+ - sg_write_buffer: add --dry-run option
+ - sg_luns: resync with drafts (sam6r02+spc5r10)
+ - remove undocumented test "W" format
+ - accept and output on request "quad dashed" format
+ - sg_logs: fix volume statistics lpage when subpage
+ is zero (ssc5r02a); decode mount history log parameter
+ - add --vendor=VP and '--pdt=DT' options
+ - decode Requested recovery, TapeAlert response, and
+ Service buffer information lpages for tape
+ - add min+max 'mounted' temperature and rel. humidity
+ fields to Environmental reporting lpage (spc5r10)
+ - add last n Inquiry/Mode_page data changed log
+ pages (spc5r17)
+ - add zoned block device statistics lpage (zbc2r?,
+ 16-264r4)
+ - fixup enumeration in power condition transition
+ log page (from H. Reinecke, Suse)
+ - sg_inq: fix potential unbounded loop in --export
+ - add --only to stop standard inquiry decoding also
+ doing a serial number vpd page (0x80) fetch
+ - update version descriptor list to 20170114
+ - add further checks so CDROM standard inquiry response
+ doesn't trick --inhex into thinking it's VPD pg 0x80
+ - decode NVMe Identify controller/nsid commands
+ - with NVMe --only restricts to a single Identify
+ controller command
+ - add --long which decodes more of the NVMe Identify
+ command responses
+ - sg_inq+sg_vpd: update Extended inquiry data vpd
+ page (spc5r09 and 17-142r5)
+ - block limits and block limit extension VPD pages:
+ add extra info about corner cases
+ - add maximum inquiry|mode_page change logs fields
+ to extended inquiry vpd page (spc5r17)
+ - both now return EDOM (adjusted sg error code) when
+ requested page not in Supported VPD Pages page
+ - add --force option to bypass checking Supported
+ Vpd Pages page and fetch requested page directly
+ - sg_vpd: 3 party copy VPD page improvements
+ - fully implement Device constituents VPD page
+ - decode some WDC/Hitachi vendor VPD pages
+ - improve handling of unknown pages
+ - sg_reassign+sg_write_same: fix ULONG_MAX problem
+ - sg_rdac: add sanity checks for -f=lun value
+ - sg_turs+sg_requests: make both accept '--num=NUM'
+ and '--number=NUM' for mutual compatibility
+ - sg_turs: add --low option
+ - fix exit status when not ready in single case
+ - sg_zone: fix debug cdb naming
+ - add --sequentialize, --count=ZC options, zbc2r01b
+ - sg_reset_wp add --count=ZC option, zbc2r01b
+ - sg_persist: add --maxlen-LEN option, LEN defaults to
+ decimal, similar to --alloc-length= which takes hex
+ - add Replace lost reservation capable (RLR_C) bit
+ in Report Capabilities (spc4r36)
+ - sg_dd: add --dry-run and --verbose options
+ - allow multiple short options (e.g. -dvv )
+ - sgp_dd: pthread_cancel() has issues in C++ (and
+ the Android multi-threaded library doesn't supply it)
+ so use pthread_kill() in its place [Linux only]
+ - add --dry-run and --verbose options
+ - sgm_dd: add --dry-run and --verbose options
+ - sg_opcode: add '--enumerate' and '--pdt=' options
+ - support CDLP (command duration limit page)
+ - support MLU, Multiple Logical Units (18-045r1)
+ - check resid and trim response if necessary
+ - report when --no-inquiry is ignored
+ - sg_raw: add --enumerate option
+ - add --cmdfile=CF option, permit 64 byte NVMe
+ admin commands to be sent
+ - add --raw option (for CF in binary)
+ - page align input and output buffers
+ - sg_get_lba_status: add --report-type= option (sbc4r12)
+ - add support for 32 byte cdb variant (sbc4r14)
+ - add support for --element-id= and --scan-len=
+ options (sbc4r14)
+ - decode response's RTP and two more provisioning
+ statuses and the additional status (sbc4r12)
+ - decode completion condition (sbc4r14)
+ - sg_modes: add Out of band management control mpage
+ - accept acronym for page/subpage codes
+ - sg_rep_zones: expand --help option information
+ - sg_unmap: add --all=ST,RN[,LA] option to unmap
+ large contiguous segments of a disk/ssd
+ - add --dry-run and --force options
+ - sg_wr_mode: add --rtd option for RTD bit
+ - sg_timestamp: add '--no-timestamp' option
+ - add --elapsed and --hex options
+ - sginfo: don't open /dev/snapshot
+ - introduce SG3_UTILS_DSENSE environment variable
+ - manpages and usage messages: corrections from
+ Gris Ge via github
+ - group_number: is 6 bit field allowing 0 to 63,
+ code in several utilities limited it to 31, fix
+ - convert many two valued 'int's to bool
+ - sg_lib: add SSC maintenance in/out sa names
+ - enhance exit status values and associated
+ strings, add SG_LIB_OS_BASE_ERR (50)
+ - add sg_ll_inquiry_v2(), sg_ll_inquiry_pt() and
+ sg_simple_inquiry_pt()
+ - add sg_ll_report_luns_pt()
+ - add sg_ll_log_sense_v2()
+ - add sg_ll_mode_sense10_v2()
+ - add sg_ll_mode_select6_v2() and
+ sg_ll_mode_select10_v2() for RTD bit
+ - add sg_ll_receive_diag_v2()
+ - add sg_ll_write_buffer_v2()
+ - add sg_get_llnum_nomult()
+ - add sg_ll_get_lba_status16()
+ - add sg_ll_get_lba_status32()
+ - add sg_ll_format_unit_v2()
+ - add sg_ll_test_unit_ready_progress_pt()
+ - add sg_ll_start_stop_unit_pt()
+ - add sg_ll_request_sense_pt()
+ - add sg_ll_send_diag_pt(), sg_ll_receive_diag_pt()
+ - add sg_get_sfs_name() for spc5r11 (Feature sets)
+ - add sg_decode_transportid_str()
+ - add sg_msense_calc_length()
+ - add sg_all_zeros(), sg_all_ffs()
+ - add sg_get_sense_cmd_spec_fld()
+ - add sg_is_scsi_cdb()
+ - add sg_get_nvme_cmd_status_str()
+ - add sg_nvme_status2scsi()
+ - add sg_nvme_desc2sense()
+ - add sg_build_sense_buffer()
+ - add sg_get_nvme_opcode_name()
+ - add sg_memalign() and sg_get_page_size()
+ - add sg_is_aligned() and pr2ws()
+ - add sg_get_big_endian(), sg_set_big_endian()
+ - add hex2stdout(), hex2stderr() and hex2str()
+ - add sg_convert_errno()
+ - add sg_if_can2stdout(), sg_if_can2stderr() and
+ sg_exit2str()
+ - implement 'format' argument in dStrHexStr()
+ - add read buffer(16) command mode names
+ - add Microcode activation sense descriptor spc5r10
+ - add SG_LIB_OK_TRUE(0) and SG_LIB_OK_FALSE(36)
+ non "error" code defines for exit status
+ - add SG_LIB_LBA_OUT_OF_RANGE error code
+ - add SG_LIB_UNBOUNDED_32BIT (_16BIT and _64BIT)
+ defines to help with decoding corner cases
+ - identify vendor specific sense data (response
+ code 0x7f), print contents in hex
+ - sg_pr2serr.h: add sg_scnpr() [like lk scnprintf()]
+ - sg_pt: add construct_scsi_pt_obj_with_fd()
+ - add pt_device_is_nvme(), get_pt_nvme_nsid()
+ - add check_pt_file_handle()
+ - add get_pt_file_handle(), set_pt_file_handle()
+ - add small SNTL to support sg_ses on NVMe
+ - sg_lib_data: sync asc/ascq codes with T10 20170114
+ - add write scattered (16+32) cdb names sbc4r11
+ - sg_cmds_extra: expand sg_ll_ata_pt() to send new
+ Ata pass-through(32) command (sat4r05)
+ - sg_sat_identify: expand to take --len=32
+ - sg_pt: add dummy pt_device_is_nvme()
+ - rescan-scsi-bus.sh: harden code
+ - fixes from Suse; bump version
+ - bump version to 20180615
+ - add to install list in Makefile, hope it does
+ not clash with other package providing it
+ - add --ignore-rev to ignore revision change
+ - 55-scsi-sg3_id.rules: fixes from Suse
+ - https://github.com/hreinecke/sg3_utils branch
+ sles15 synced 20170914
+ - move some testing utilities out of the
+ 'examples' and 'utils' directories into the new
+ 'testing' directory
+ - add testing/sg_tst_nvme utility
+ - clean Makefile.freebsd in examples/ and testing/
+ - gcc 7.2 cleanups (sysmacros.h etc)
+ - clang --analyze static checker clean ups
+ - shellcheck cleanup on scripts
+ - ./configure automake utility:
+ - option --enable-debug added for testing
+ - option --disable-linuxbsg retired, still accepted
+ but now ignored, Linux sg v3 or v4 interface
+ decision made at runtime
+ - Info section now printed at end of ./configure
+ - automake: add AM_PROG_AR to configure.ac
+ - upgrade to version 1.15
+ - various configure.ac and Makefile.am cleanups
+ - add SG_LIB_ANDROID build 'define'. If defined then
+ SG_LIB_LINUX is also defined, so test for Android
+ before Linux if need to differentiate
+ - update BSD license from 3 to 2 clause aka FreeBSD
+ license (without reference to FreeBSD project)
+ - debian: bump compat file contents from 7 to 10
+
+Changelog for sg3_utils-1.42 [20160217] [svn: r663]
+ - sg_timestamp: new, to report or set timestamp
+ - sg_read_attr: new, supported by tape drives
+ - sg_stpg: fix truncation of target port field
+ - sg_inq: cope with unicode strings, udev fixes
+ - update version descriptor list to 20160125
+ - '--export': new entries for UUID descriptor
+ - sg_ses: add more field acronyms (ses3r11)
+ - sg_logs: add Utilization lpage (sbc4r07)
+ - add Background operation lpage
+ - add Pending defects lpage
+ - add LPS misalignment lpage (sbc4r10)
+ - document '--All' ('-A') option
+ - rework lto tape vendor lpages
+ - sg_vpd: add Block limits extension VPD page
+ - add Device constituents VPD page
+ - add LB Protection VPD page (ssc ssc5r02a)
+ - LB provisioning VPD page: expand LBPRZ, add
+ Minimum and Threshold percentage fields
+ - rework lto tape vendor VPD pages
+ - sg_inq+sg_vpd+sg_xcopy: add support for locally
+ assigned UUIDs in VPD page 0x83 (spc5r08)
+ - sg_sanitize: add --znr option (sbc4r07)
+ - sg_rep_zones: add --partial option (zbc-r04)
+ - sg_format: add ffmt option (sbc4r10)
+ - add support for FORMAT MEDIUM (for tape)
+ - sg_raw: document length relationships
+ - rescan-scsi-bus.sh: updates from Suse
+ - sg_lib_data: sync asc/ascq codes with T10 20151126
+ - sg_lib: add 'sense' categories for SCSI statuses:
+ condition met, busy, task set full, ACA active and
+ task aborted
+ - add pr2serr() extern
+ - change sg_get_sense_str() and dStrHexStr(), return
+ chars written (returned void previously)
+ - add sg_get_sense_descriptors_str() function
+ - add sg_get_designation_descriptor_str() function
+ - sg_get_desig_type_str()+sg_get_desig_assoc_str()
+ and sg_get_desig_code_set_str() added
+ - sg_get_opcode_sa_name() break out zoning in/out,
+ read attribute and read position service actions
+ - sg_cmds_extra: add sg_ll_format_unit2() for FFMT
+ - sg_pr2serr.h: new, to shorten fprintf(stderr, ...)
+ - sg_io_linux, sg_pt_linux: drop SUGGEST_* decoding
+ - sg_unaligned.h: add 48 bit support and gets for
+ variable length unsigned integers
+ - add specializations for little and big endian
+ - change sg_ll_*() function's 'int noisy' to bool
+
+Changelog for sg3_utils-1.41 [20150511] [svn: r644]
+ - sg_zone: new utility for open, close and finish
+ zone commands introduced in zbc-r02
+ - sg_rep_zones and sg_reset_wp: change opcodes as
+ indicated in zbc-r02
+ - sg_read_buffer: add READ BUFFER(16) support (spc5r02)
+ - sg_logs: add --enumerate and acronyms
+ - allow decode from hex or binary in file
+ - decode environmental reporting + limits lpages
+ - sg_write_buffer: add --timeout=TO option
+ - sg_lib interface: add sg_lib_pdt_decay(), TPROTO_PCIE
+ plus support for zoning service actions
+ - sg_lib: in Linux blocked devices yield ENXIO from
+ ioctl(SG_IO), map to SG_LIB_CAT_NOT_READY
+ - clean up sg_warnings_stream handling
+ - sg_inq+sg_vpd: fix SCSI name string decoding in
+ device identification VPD page (0x83)
+ - increase sanity on Unit Serial number VPD page
+ - improve rdac vpd page reporting (vendor)
+ - sg_inq: improve NAA handling in dev_id VPD page
+ - update version descriptor list to 20150126
+ - sg_vpd: add atomic boundary values (sbc4r04)
+ - block limits VPD page: fix unmap granularity
+ alignment value; spc5r02 additions
+ - sg_readcap: add support for ZBC's rc_basis field
+ - sg_senddiag: fix bug with --raw option
+ - add support for -HHH for output suitable for --raw
+ - sg_ses: enclosure element: add failure and warning
+ acronyms, fix warning indication output
+ - additional element status dpage: add PCIe/NVMe
+ - handle element descriptor names that count
+ multiple trailing NULLs
+ - rescan-scsi-bus.sh: add --issue-lip-wait option and
+ improve error handling
+ - improve dm-multipath handling
+ - sg_modes: make '-HHH' output suitable as input to
+ 'sdparm --inhex='
+ - sg_rdac: add '-6' option for mode sense/select(6)
+ - add support for reporting more SCSI transports
+ and accessing rdac extended mode page 0x2c
+ - sg_write_same: cleanup, mainly man page
+ - scsi_logging_level: replace use of tr command
+ - examples/sg_tst_async: cleanup
+ - examples/sg-simple_aio.c: remove
+ - sg_lib_data: sync asc/ascq codes with T10 20150423
+ - Makefile cleanup
+ - autogen.sh: upgrade to buildconf 20091223 version
+
+Changelog for sg3_utils-1.40 [20141110] [svn: r620]
+ - sg_write_verify: new utility for WRITE AND VERIFY
+ - sg_ses_microcode: new utility
+ - sg_sat_read_gplog: new utility
+ - sg_senddiag: add --maxlen= option
+ - sg_copy_results: correct response length calculations
+ - sg_format: make '-FFF' bypass mode sense/select
+ - add --mode=MP to supply alternate mode page,
+ default remains read-write error recovery mpage
+ - output unit serial number and LU name prior to
+ - sg_inq: expand Block limits VPD page output
+ - fix --cmddt output if not supported by device
+ - more sanity checks on vendor supplied fields
+ - sg_vpd: add --all option
+ - more TPC VPD page decoding
+ - add zoned block device characteristics page
+ - more sanity checks on vendor supplied fields
+ - sg_ses: fix problem with --index=sse (and ssc)
+ - mask status element before using as control
+ - defeat previous item with --mask (ignore) option
+ - SAS connector status element: add overcurrent bit
+ - handle element descriptor names that count a
+ trailing NULL
+ - add --warn option mainly for broken joins
+ - add optional descriptions to -ee output
+ - sync with ses3r07
+ - sg_sanitize: add --desc and --zero options
+ - output unit serial number and LU name prior to
+ - sg_rep_zones: corrections, sync with zbc-r01c
+ - sg_persist: split help into two pages, '-hh' for 2nd
+ - sg_logs: refine tape drive output
+ - sg_raw: with -vvv decode T10 CDB name
+ - do not output/print data-in if error
+ - sg_opcodes: add --compact field
+ - sg_senddiag: add --page=PG option
+ - sg_reset: add words for EAGAIN from reset ioctl
+ - sg_sat_*: mention t_type and multiple_count fields
+ - win32: sg_scan: handle larger configurations
+ - sg_lib: trim trailing spaces in dStrHex() and friends
+ - sg_lib_data: sync asc/ascq codes with T10 20140924
+ - clean up service action string functions
+ - sg_ll_unmap_v2(): fix group number
+ - sg_ll_inquiry(), sg_ll_mode_sense*(),
+ sg_ll_log_sense(): use resid to clear unfilled
+ data-in buffer
+ - sg_unaligned.h: add header for building parameters
+ - examples/sg_tst_async: new Linux sg test utility
+
+Changelog for sg3_utils-1.39 [20140612] [svn: r588]
+ - sg_rep_zones: new utility for ZBC REPORT ZONES
+ - sg_reset_wp: new utility, ZBC RESET WRITE POINTER
+ - sg_ses: add --eiioe=auto|force option
+ - fix AES dpage element indexing problems
+ - add --readonly option
+ - sg_write_buffer: add --bpw=CS option to call
+ write buffer multiple times for big blobs
+ - sg_format: add --ip_def option to fully provision
+ - sg_opcodes: add --mask option
+ - sg_logs: add --in=FN option for log select params
+ - add --filter=PARC (parameter code)
+ - add --no_inq for suppress initial INQUIRY call
+ - add --readonly option
+ - sg_persist: add --readonly option, environment
+ variable SG_PERSIST_IN_RDONLY sets ro on prin cmds
+ - sg_inq: sync version descriptors dated 20105176
+ - suppress dev-id VPD messages so they only appear
+ when --verbose is given
+ - add new SCSI_IDENT_*_ATA pair to --export output
+ - sg_luns: add decoding for conglomerate LUNS
+ - add --lu_cong option to simulate the LU_CONG bit
+ - sg_vpd: add --vendor=VP option, re-order vendor
+ specific pages, split lto into lto5 and lto6
+ - add Supported block lengths and protection types
+ page (sbc4r01)
+ - add Block device characteristics extension
+ page (sbc4r02)
+ - sg_copy_results, sg_get_lba_status, sg_luns,
+ sg_read_buffer, sg_readcap, sg_referrals, sg_rtpg,
+ sg_sat_set_features, sg_sat_identify:
+ add --readonly option
+ - sginfo: strip trailing spaces from INQUIRY text
+ - sg_rbuf: add --echo option (to use echo buffer)
+ - sg_lib: add sanitize command service action names
+ - add 'sense' categories for reservation conflict,
+ data protect and protection information violations
+ - add sg_get_category_sense_str() to API
+ - change struct sg_simple_inquiry_resp::rmb to byte_1
+ - add initial zbc service actions
+ - dStrHex(Err): fix output truncation error
+ - linux, sg: support SCSI_PT_FLAGS_QUEUE_AT_TAIL and
+ SCSI_PT_FLAGS_QUEUE_AT_HEAD (block layer queueing)
+ - sg_lib_data: sync asc/ascq codes with T10 20140516
+ - sync operation code with T10 20140515
+ - add id string for SPC-5
+ - scripts/59-scsi-sg3_utils.rules: removed
+ - functionality split into two scripts:
+ 55-scsi-sg3_id.rules + 58-scsi-sg3_symlink.rules
+ - examples/sg_persist_tst.sh: add --exclusive option
+ - win32: sg_scan, sg_ses and sg_log fixes
+ - examples/sgq_dd: re-add old utility as example
+
+Changelog for sg3_utils-1.38 [20140401] [svn: r563]
+ - sg_ses: add --dev-slot-num= and --sas-addr=
+ - fix --data=- problem with large buffers
+ - new --data=@FN to read hex data from file FN
+ - error and warning message cleanup
+ - add --maxlen= option
+ - sg_inq: add --block=0|1 option to control opens
+ - add --inhex=FN to read response in ASCII hex from
+ a file; --inhex=FN --raw reads response in binary
+ - make -HHH (-HHHH for '-p ai') output suitable for
+ another sg_inq invocation to use --inhex to decode
+ - add LU_CONG to standard inquiry response output
+ - decode ASCII information VPD pages
+ - add HAW_ZBC in block dev char. VPD page (sbc4r01)
+ - sync version descriptors dated 20131126
+ - allow --page=-1 to force std INQUIRY decoding
+ - fix overflow in encode_whitespaces
+ - improve unit serial number display (VPD page 0x80)
+ - sg_vpd: add LU_CONG to standard inquiry response output
+ - add --inhex=FN to read response in ASCII hex from
+ a file; --inhex=FN --raw reads response in binary
+ - decode Third Party Copy (tpc) page
+ - add HAW_ZBC in block dev char. VPD page (sbc4r01)
+ - add LTO and DDS vendor pages
+ - allow --page=num to restrict --enumerate output
+ - sg_persist: add PROUT: Replace Lost Reservation (spc4r36)
+ - add --transport-id= for SOP: 'sop,<routing_id_in_hex>'
+ - sg_readcap: for --16 show physical block size if
+ different from logical block size
+ - sg_xcopy: environment variables: XCOPY_TO_SRC and
+ XCOPY_TO_DST indicate where xcopy command is sent
+ - change default to send xcopy to dst (was src)
+ - improve CL handling of short options (e.g. '-vv')
+ - sg_luns: guard against garbage response
+ - sg_decode_sense: with --nospace ignore spaces on
+ command line, so multiple arguments are concatenated
+ - sg_write_same: repeat if unit attention
+ - sg_rtpg: fix indexing bug with --extended option
+ - sg_logs: placeholder for pending defects lpage
+ - sg_unmap: fix another problem with --grpnum= option
+ - sg_lib.h: add PDT_ZBC define (spc4r36p)
+ - sg_lib_data: sync asc/ascq codes with T10 dated 20140320
+ - add pdt string for ZBC (spc4r36p)
+ - sg_lib: extensions to sg_get_num() and sg_get_llnum()
+ - sg_cmds_extra: fix sa bug in sg_ll_3party_copy_out()
+ - scripts/rescan-scsi-bus.sh: check if FC driver exports
+ issue_lip before using it
+ - man page added (Linux only)
+ - scripts/59-scsi-sg3_utils.rules: linux specific udev rules
+ - examples: add sg_tst_excl3 for testing O_EXCL
+ - improve sg_tst_excl and sg_tst_excl2
+ - add sg_tst_context for testing file handle contexts
+ - upgrade automake to version 1.13.3
+ - add suse directory and 'spec' file to facilitate builds
+
+Changelog for sg3_utils-1.37 [20131014] [svn: r522]
+ - sg_compare_and_write: fix wrprotect setting
+ - add --quiet option to suppress miscompare report
+ - merge features from another implementation
+ - sg_inq: fix referrals VPD page
+ - dev_id VPD: T10 vendor id designator clean up
+ - sg_logs: improve for tape drives, general cleanup
+ - sg_persist: fix core dump on -Q option
+ - sg_unmap: fix core dump on -g option
+ - sg_vpd: dev_id VPD: T10 vendor id designator clean up
+ - cleanup up dev_id NAA-3: locally assigned
+ - sg_ses: add --nickname and --nickid options
+ - eiioe added to additional element status page (ses3r6)
+ - multiple --filter options to prune output
+ - sg_verify: improve miscompare handling
+ - rename --btychk=ndo option to --ndo=ndo (hide former)
+ - add --quiet option
+ - sg_xcopy: allow sg and bsg devices
+ - fix for bpt going negative
+ - limit each XCOPY(LID1) command to 65535 blocks
+ - fix for seek in multi-segment copies
+ - sg_sanitize: skip 15 second safety delay with --fail
+ - sg_libs: extended copy opcode renamed (spc4r34)
+ - sg_ll_receive_copy_results(): expand for all sa_s
+ - add sg_get_sense_key()
+ - add sg_ll_3party_copy_out()
+ - add dStrHexErr(): ascii hex to stderr
+ - add dStrHexStr(): ascii hex to string
+ - add SG_LIB_CAT_MISCOMPARE to categories
+ - clean header files
+ - sg_pt_freebsd: sanity check on sense_resid; fix leaks
+ - scripts/rescan-scsi-bus.sh KG's v1.57 + HR patch
+ - improve wlun handling, detect updated and resized
+ devices, better multipath support
+ - Makefile.am cleanup
+ - examples: add sg_tst_excl and sg_tst_excl2
+
+Changelog for sg3_utils-1.36 [20130531] [svn: r497]
+ - sg_vpd: Protocol-specific port information VPD page
+ for SAS SSP, persistent connection (spl3r2), power
+ disable (spl3r3)
+ - block device characteristics: add FUAB bit
+ - sg_xcopy: handle more descriptor types; handle zero
+ maximum segment length; allow list IDs to be disabled;
+ improve skip/seek handling; allow xcopy on destination
+ - sg_reset: and --no-esc option to stop reset escalation
+ - clean up cli, add long option names
+ - sg_luns: add --test=ALUN option for decoding LUNs
+ - decoded luns output in decimal or hex (if -HH given)
+ - add '--linux' option to show Linux LUN after T10
+ representation, can map one to the other
+ - sg_inq: add --vendor option to show standard inquiry's
+ vendor specific fields in ASCII
+ - take resid into account with response output
+ - sg_sync: add --16 (for 16 byte command) and --timeout=
+ - sg_logs: add data compression page (ssc4)
+ - sg_sat_set_features: increase --lba from 1 to 4 bytes
+ - sg_write_same: add --ndob option (sbc3r35d)
+ - sg_map: mark as deprecated
+ - sginfo: mark as deprecated, especially -l (list)
+ - sg_lib: improve snprintf handling
+ - sg_lib_data: sync asc/ascq codes with T10 20130117
+ - sg_cmds (lib): if noisy given, give more UA info
+ - make code more C++ friendly
+
+Changelog for sg3_utils-1.35 [20130117] [svn: r476]
+ - sg_compare_and_write: new utility
+ - sg_inq+sg_vpd: block device characteristics VPD page:
+ add product_type, WABEREQ, WACEREQ and VBULS fields
+ - sg_inq: more --export option changes for udev
+ - sg_vpd: add more rdac vendor specific vpd pages
+ - sg_verify: add --ebytchk option for sbc3r34 changes
+ - sg_stpg: --offline option: fix 'Invalid state 0xe'
+ - sg_ses: Door Lock element changed to Door element and
+ abbreviation changed from 'dl' to 'do' (ses3r05)
+ - archive/rescan-scsi-bus.sh: upgrade to version 1.53hr
+ - move rescan-scsi-bus.sh to scripts directory
+ - sync to sbc3r34
+ - sg_lib: sg_ll_verify10+16 expand BYTCHK to 2 bit field
+ - sg_pt_win32, sg_scan(win32): changes for cygwin 1.7.17
+ - clean up man page summary lines
+
+Changelog for sg3_utils-1.34 [20121013] [svn: r461]
+ - sg_xcopy: new dd like 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 spc4r36 and sbc3r32
+ - sg_inq: add --export so sg_inq can replace udev's scsi_id
+ - decode old EMC Symmetrix abuse of VPD page 0x83
+ - sg_vpd: decode old EMC Symmetrix abuse of VPD page 0x83
+ - sg_ses: increase max dpage response size to 64 KB
+ - allow ident,locate on enclosure controller
+ - more sanity for additional element status descriptor
+ - sg_sanitize: add --ause, --fail and --test=
+ - sg_luns: add long extended flat space addressing format
+ - sg_logs: add ATA pass-through results lpage (SAT-2)
+ - sg_rtpg: add --extended option
+ - sg_senddiag: list rebuild assist diag page name
+ - sg_pt_linux: expand DID_ (host_byte) codes
+ - cope with a transport error plus sense data
+ - prefer major() over MAJOR() macro
+ - sg_lib: fix sg_get_command_name() service actions
+ - report sdat_ovfl bit (if set) in sense data
+ - decode extended_copy and receive_copy service actions
+ - decode read_buffer and write_buffer modes
+ - decode ATA PT fixed format sense (SAT-2)
+ - sg_cmds_extra: add sg_ll_report_tgt_prt_grp2()
+ - ./configure options:
+ - change --enable-no-linux-bsg to --disable-linuxbsg
+ - add --disable-scsistrings to reduce utility sizes
+
+Changelog for sg3_utils-1.33 [20120118] [svn: r435]
+ - sg_ses: major rework of indexes (again), now two level
+ - sg_write_buffer: new --specific option for mode specific
+ field; new mode 13 (spc4r32)
+ - sg_vpd: add hp3par volume info vendor VPD page
+ - fix 'scsi ports' [0x88] page problem
+ - add 'sinq' pseudo page for standard inquiry response
+ - add power consumption page
+ - sg_format: add --poll= option for request sense polling
+ - improve handling of disks > 2 TB and DIF (protection)
+ - sg_logs: LB provision lpage extra (sbc3r28)
+ - sg_modes: application tag mpage subcode 0xf0->0x2
+ - sg_write_same: no prot fields when wrprotect=0
+ - sg_get_lba_status: reflect change in sbc3r25 to Parameter
+ Data Length response field (offset reduced from 8 to 4)
+ - sg_inq, sg_vpd: sync with spc4r33
+ - win32: change DataBufferOffset type per MSDN; caused
+ problem with 64 bit machines (with buffered interface)
+ - sg_luns: tweak documentation for vendor specific reports
+ - add man pages for scsi_loging_level, scsi_mandat,
+ scsi_satl and scsi_temperature
+
+Changelog for sg3_utils-1.32 [20110730] [svn: r410]
+ - sg_sanitize: new utility for command added in sb3r27
+ - sg_sat_identify: add '--ident' to output WWN
+ - sg_ses: major rework of descriptor output
+ - add --index, --descriptor, --join, --clear, --get,
+ and --set options
+ - sg_raw: exit status corrections
+ - sg_decode_sense: add --nospace and --hex options
+ - sg_logs: fix bug with large --maxlen
+ - zero response length when resid implies it is invalid
+ - add scope field to lb provisioning lpage (sb3r27)
+ - sg_inq: sync version descriptors with spc4r31
+ - sg_lib_data: sync asc/ascq codes with spc4r31
+ - sg_vpd: add LBPRZ field in LB provisioning VPD page
+ - sg_format: allow format of pdt 7 (some MO drives)
+ - sg_cmds_basic: sg_cmds_process_resp() handle status good
+ with a sense key other than no_sense (e.g. completed)
+ - add README.iscsi
+
+Changelog for sg3_utils-1.31 [20110216] [svn: r386]
+ - sg_decode_sense: new utility to decode sense data
+ - sg_vpd: LB provisioning + Block limits pages (sbc3r26)
+ - sync asc/ascq and version descriptors with spc4r28
+ - sg_get_config, sg_rmsn, sg_verify: add --readonly option
+ - sg_lib: implement forwarded sense data descriptor
+ - decode user data segment referral sense data descriptor
+ - sg_lib, sg_turs, sg_format: more precision for progress
+ indication (two places after decimal point)
+ - sg_lib(win32): add runtime selection of SPT direct or
+ indirect interface
+ - sg_read_buffer+sg_write_buffer: set SPT direct
+ - add examples/forwarded_sense.txt + examples/ref_sense.txt
+
+Changelog for sg3_utils-1.30 [20101111] [svn: r363]
+ - sg_referrals: new utility for REPORT REFERRALS
+ - sbc3r25 renames 'thin' provisioning' to 'logical block
+ provisioning': changes in sg_format, sg_inq, sg_logs,
+ sg_modes, sg_readcap, sg_vpd
+ - sg_inq: update version descriptor list to spc4r27
+ - extended inquiry vpd page add extended self test
+ completion minutes field
+ - sg_lib: sync asc/ascq list to spc4r27
+ - dStrHex(): trim excess trailing spaces
+ - sg_read_long: add --readonly option (open() is rw)
+ - sg_raw: add --readonly option (open() is rw)
+ - allow bidirectional commands
+ - sg_vpd: rdac vendor page [0xc8] parse corrections
+ - extended inquiry vpd page add extended self test
+ completion minutes field
+ - sg_ses: expand --data (in) buffer to 2048 bytes
+ - sg_opcodes: add extended parameter data for TMFs (spc4r26)
+ - sg_dd: clean count calculation, document nocache flag
+ - treat bsg devices as implicit sg_io
+ - add more conversions
+ - sg_write_same: if READ CAPACITY(16) fails try 10 byte variant
+ - anticipate approval of proposal to allow UNMAP and ANCHOR
+ bits to be set on WRITE SAME(10) with '--10' option
+ - sg3_utils man page: sections added for OS device names
+
+Changelog for sg3_utils-1.29 [20100406] [svn: r334]
+ - sg_rtpg: new logical block dependent state and bit (spc4r23)
+ - sg_start: add '--readonly' option for ATA disks
+ - sg_lib: update asc/ascq list to spc4r23
+ - sg_inq: update version descriptor list to spc4r23
+ - sg_vpd: block device characteristics page: fix form factor
+ - update Extended Inquiry VPD page to spc4r23
+ - update Block Limits VPD page to sbc3r22
+ - update Thin Provisioning VPD page to sbc3r22
+ - Automation device serial number and Data transfer device
+ element VPD pages (ssc4r01)
+ - add Referrals VPD page (sbc3r22)
+ - sg_logs: add thin provisioning and solid state media log pages
+ - addition of IBM LTO specific log pages
+ - sg_modes: new page names from ssc4r01
+ - sg_ses: sync with ses3r02 (SAS-2.1 connector types)
+ - sg_unmap: add '--anchor' option (sbc3r22)
+ - sg_write_same: add '--anchor' option (sbc3r22)
+ - sg_pt interface: add set_scsi_pt_flags() to permit passing
+ through SCSI_PT_FLAGS_QUEUE_AT_TAIL and AT_HEAD flags
+ - add examples/sg_queue_tst+bsg_queue_tst for SG_FLAG_Q_AT_TAIL
+ - add AM_MAINTAINER_MODE to configure.ac to lessen build issues
+ - add BSD_LICENSE file to this and lib directories, refer to
+ it from source and header files. Some source has GPL license
+
+Changelog for sg3_utils-1.28 [20091002] [svn: r315]
+ - sg_unmap: new utility for thin provisioning
+ - add examples/sg_unmap_example.txt
+ - sg_get_lba_status: new utility for thin provisioning
+ - sg_read_block_limits: new utility for tape drives
+ - sg_logs: add cache memory statistics log (sub)page
+ - sg_vpd, sg_inq: extend Block limits VPD page (sbc3r19)
+ - sg_vpd: add Thin provisioning VPD page (sbc3r20) and
+ TapeAlert supported flags VPD page
+ - sg_inq: note VPD page support better in sg_vpd
+ - sg_persist: add transport specific transportID format
+ - allow transportIDs to be read from named file
+ - sg_opcodes: allow --opcode= option to take OP and SA
+ values (comma separated)
+ - tweak print format, remove test code
+ - sg_requests: remove test code in progress calculation
+ - sg_reset: add target reset option
+ - sg_luns: reduce default maxlen to 8192 (for FreeBSD)
+ - sg_raw: extend max cdb length from 16 to 256 bytes
+ - align heap allocs to page boundaries
+ - sg_lib: sg_set_binary_mode() needs config.h included
+ - add progress indication sense data descriptor (0xa)
+ - change SG3_UTILS_* constants to SG_LIB_*
+ - decode service actions within persistent reserve in/out
+ - sync with spc4r21
+ - sg_cmds_extra: add sg_ll_unmap() and sg_ll_get_lba_status()
+ - sg_pt_linux: fix check condition but empty sense buffer; occurred
+ when sg v3 node used and /usr/include/linux/bsg.h visible
+ - major() macro grief, if present include <linux/kdev_t.h> and
+ use MAJOR() instead
+ - scripts/sas_disk_blink: moved from this package to sdparm
+ - utils/hxascdmp: in Windows set binary mode on read files
+ - examples/sg_persist_tst.sh: add PRIN read full status command
+ - sg_raw,sg_write_buffer,sg_write_long,sg_write_same: in Windows
+ set binary mode on read files
+ - sg_pt_win32: default to non-direct variant of SPT interface
+ - use './configure --enable-win32-spt-direct' to override
+ - non-direct data length set to 16 KB, extended if required
+ - debian: incorporate patch from debian sid
+
+Changelog for sg3_utils-1.27 [20090411] [svn: r250]
+ - sg_write_same: new utility: 10, 16 and 32 byte cdb variants
+ - sg_inq: sync version descriptors with spc4r18
+ - add power condition VPD page
+ - expand block limits VPD page (sbc3r18)
+ - sg_vpd: add power condition VPD page
+ - expand block limits VPD page (sbc3r18)
+ - sg_map26: fix for lk 2.6.26 when CONFIG_SYSFS_DEPRECATED_V2
+ is not defined
+ - output cdb when verbose option given
+ - correct tape minors >= 32
+ - sg_dd: flock flag (does LOCK_EX|LOCK_NB)
+ - switch open on input for sg device nodes: first open
+ read-write and if that fails try opening read-only
+ - experiment with of2=OFILE2; add conv=sparse
+ - use posix_fadvise() to defeat caching of normal+block files
+ when new 'nocache' flag given
+ - sg_dd copied to own package called ddpt
+ - sg_dd, sgm_dd, sgp_dd: accept 'count=-1' for calculate count,
+ accept '-V' for version string
+ - sg_get_config: add OSSC feature [mmc6r02]
+ - sg_modes: add ATA power condition mode page
+ - sg_logs: protocol specific (SAS) lpage sync to sas2r15
+ - power condition transitions lpage (added in spc4r18)
+ - extra parameters for start-stop cycle counter lpage
+ - sg_format: add '--fmtpinfo=' and '--pie=' options (sbc3r18)
+ - sg_readcap: more protection + thin provisioning (sbc3r18)
+ - add a '--16' option for 16 byte cdb version
+ - sg_persist: code clean up
+ - allow '--transport-id=' argument to use space as separator
+ - add '--alloc-length=' argument
+ - sg_scan: (win32) new format, scsi adapter scan optional
+ - sginfo: fix crash when 1024 sg device nodes (or more)
+ - sg_ses: allow '--data=' argument to use space as separator
+ - sg_senddiag: allow '--raw=' argument to use space as separator
+ - sg_reassign: allow '--address=' argument to use space as
+ separator
+ - sg_wr_mode: allow '--contents=' and '--mask=' arguments to
+ use space as separator
+ - sg3_utils.spec: correction to configure call
+ - sg_pt: add scsi_pt_open_device_flags() call
+ - add scsi_pt_version() and clear_scsi_pt_obj() calls
+ - clear os_err at start of do_scsi_pt()
+ - add linux bsg support via runtime detection
+ - sg_cmds: add sg_cmds_open_device_flags()
+ - sg_cmds_extra: sg_ll_format_unit: remove rto_req argument,
+ the expanded fmtpinfo argument subsumes it.
+ - clearer split between Linux and Windows only code and doc
+ - automake tools: change to what Ubuntu 8.10 provides
+ - Ubuntu 8.10 libtool problems -> Debian 4.0
+
+Changelog for sg3_utils-1.26 [20080625] [svn: r183]
+ - sg_sat_phy_event: new utility; copied from examples
+ directory and enhanced, rename original to sg__sat_phy_event
+ - sg_ses: sync with ses2r19b, many nomenclature changes
+ - sg_get_config: sync with mmc6r01
+ - allow Microcode upgrade and DVD read feature descriptors
+ to be 4 bytes long
+ - add '--raw' option
+ - sg_verify: add --vrprotect= option
+ - sg_vpd: add nominal form factor to block dev. char. VPD page
+ - add --maxlen= option to set allocation length in cdb
+ - sg_inq: add --maxlen= option that does same as --len=
+ - move version descriptors (spc4r15) to sg_inq_data.c file
+ - sg_inq+sg_vpd: logic for "NAA-3 Locally assigned" identifier
+ - update extended inquiry VPD page
+ - sg_modes: add --maxlen= option to specify allocation length
+ - sg_start: add '--noflush' and '--mod=PC_MOD' options (sbc3r14)
+ - sg_request: add a '--progress' option (similar to sg_turs)
+ - add --maxlen= option to set allocation length in cdb
+ - sg_luns: add --maxlen= option to specify allocation length
+ - sg_dd: improve MMC handling of 'illegal mode for this track'
+ read errors (with ILI and info field)
+ - sg_dd, sgm_dd, sgp_dd, sginfo, sg_rbuf, sg_read: replace
+ "%lld" and friends with PRI macros
+ - sg_opcodes: tmf name change in spc4r15 (async event)
+ - sg_turs: add more to man page about '--progress' indication
+ - sg_write_long: add examples section to man page
+ - '--raw' option: modify utilities that can send binary output
+ to call sg_set_binary_mode(). For MingGW port CR problem.
+ - sg_lib: update asc/ascq and command name strings to spc4r15
+ - split sg_lib into sg_lib_data.[hc] and sg_lib.[hc]
+ - split sg_cmds_extra into sg_cmds_extra and sg_cmds_mmc
+ - add osd2r03 service actions (all different from osd-r10)
+ - add sg_get_trans_proto_str()
+ - add sg_get_sense_filemark_eom_ili() function (MMC uses ILI)
+ - add sense key specific unit attention condition queue
+ overflow decoding (added in spc4r13)
+ - add sg_set_text_mode() and sg_set_binary_mode() functions
+ for non-Unix OSes
+ - sg_cmds_mmc: add sg_ll_set_streaming() function
+ - sg_cmds_extra: add vrprotect argument to sg_ll_verify10()
+ - add sg_ll_get_performance() and sg_ll_set_cd_speed()
+ - change 'long long' to int64_t and 'unsigned long long' to
+ uint64_t to stress that 64 bit integer wanted, not larger
+ - audit of dangerous 'u64 = uch[24] << 24' code, replace most
+ 'unsigned long's
+ - multiple documentation corrections provided by Dan Horak
+ - win32/MinGW: define SG3_UTILS_MINGW when detected
+ - remove archive/pre_configure subdirectory
+ - move sg_io_linux.c into the lib subdirectory
+ - utils/hxascdmp: add hxascdmp(1) man page
+ - switch primary build to ubuntu environment, rename
+ library to libsgutils2 to avoid clash
+
+Changelog for sg3_utils-1.25 [20071016] [svn: r115]
+ - sg_stpg: new utility to Set Target Port Groups
+ - sg_safte: new utility to query SAF-TE processor (SES like)
+ - sg_sat_set_features: new utility (actually copied from examples
+ directory); renamed examples version to: sg__sat_set_features
+ - sg_read_buffer: restore (had fallen out of build scripts)
+ - sg_dd: add oflag=sparse to step over bs*bpt number of zeros;
+ - with oflag=sparse, write last bs*bpt segment at end or after
+ error so file length of OFILE is appropriate
+ - when coe>1 then SCSI READ LONG logic remembers extended block
+ length of first encountered error
+ - sg_dd, sgm_dd, sgp_dd: allow iflag=null and oflag=null both of
+ which do nothing (placeholders)
+ - sg_ses: sync with ses2r17 then r18
+ - sg_vpd, sg_inq: add block device characteristics VPD page
+ - sg_inq: add '--vpd' option (or '-e') for backward compatibility
+ - sg_vpd: decode protocol specific lu information page for SAS
+ - add more RDAC vendor VPD pages
+ - sg_logs: update background scan results log page, sbc3r11
+ - add generation code to protocol specific page for SAS SSP
+ - add media changer diagnostic data log page
+ - sg_raw: fix error message when do_scsi_pt() fails
+ - sg_lib: sync asc/ascq codes with spc4r11
+ - add sg_get_num_nomult()
+ - add TPROTO_* protocol identifier constants to sg_lib.h
+ - sg_cmds_extra: add sg_ll_set_tgt_prt_grp()
+ - place source in subversion repository
+ - split code into src/ lib/ and include/ directories
+ - sync debian directory with their 1.24 version (sid unstable)
+ - convert build logic to use autotools (i.e. './configure ; make')
+ - rename this file from CHANGELOG to ChangeLog
+ - note: only code in lib/ and src/ directories built by
+ autotools; some other subdirectories still use hand-crafted
+ Makefiles
+
+Changelog for sg3_utils-1.24 [20070507] [svn: r77]
+ - sg_raw: new utility to send arbitrary SCSI commands
+ - sg_luns: increase number of luns that can be fetched
+ - fix length of raw and hex output
+ - add '--quiet' option to output only ASCII hex
+ representation of each lun
+ - sg_rtpg: update for changes in spc4r09
+ - sg_persist: update documentation, spc-4 references
+ - fix exit status values
+ - sg_inq: update version descriptors per spc4r09
+ - fix '--id' and '--extended'
+ - extend block limits VPD page (sbc3r09)
+ - sg_vpd: extend block limits VPD page (sbc3r09)
+ - append relative target port identifier to SAS target
+ port address with '-iq' option
+ - sg_logs: add decoding for stats+performance log pages
+ - fix showing of page names for pdt > 0
+ - implement '-HH' for single and all pages, fix '-r'
+ - when '--maxlen=' given, only do single fetch
+ - add Tape Alert (ssc), Media and Element statistics (smc) pages
+ - add '--brief' option
+ - sg_ses: sync with ses2r16
+ - fix bay number for SAS
+ - sg_format: add '--dcrt' and '--security' options
+ - sgm_dd: add 'smmap' oflag for shared_mmap_io testing
+ - add 'dio' oflag
+ - sg_dd, sgp_dd: add 'dio' iflag and oflag
+ - sg_modes: change SAS mode page names per sas2r09
+ - check validity of block descriptors length
+ - sg_pt: change opaque context object from 'void *'
+ to 'struct sg_pt_base *'
+ - sg_opcodes: anticipate extra tmfs from 07-159r0
+ - sg_sat_set_features: add more usage information
+ - add man page
+ - sg_sat_phy_event: add to examples directory
+ - sg_lib: sync asc/ascq codes with spc4r10
+ - Solaris port: using uscsi interface
+ - various .html files removed from doc directory
+
+Changelog for sg3_utils-1.23 [20070131] [svn: 75]
+ - sg_read_buffer: new utility
+ - sg_write_buffer: new utility
+ - sg_opcodes, sg_senddiag, sg_logs, sg_modes, sg_start, sg_inq,
+ sg_turs, sg_readcap, sg_rbuf: add getopt_long() based cli;
+ old and new cli selectable, new getopt_long cli is default
+ - scripts: new subdirectory containing some bash scripts
+ - add scripts/README file
+ - sg_reassign: add '--hex' option for grown and primary lists
+ - sg_rtpg: add '--raw' option
+ - sg_lib.h, sg_cmds_basic.h + sg_cmds_extra.h: add C++
+ 'extern "C" ' wrappers
+ - cleanup C code so it will compile as C++
+ - sg_lib: sync with spc4r08
+ - include <inttypes.h>, use PRId64 instead of %lld form
+ - fix sg_get_sense_str() when empty sense buffer
+ - win32 port: add Makefile.mingw + related support for MinGW
+ - sg_cmds_extra: add sg_ll_read_buffer() and sg_ll_write_buffer()
+ - sg_dd, sgp_dd, sgm_dd, sg_read: use lseek64() instead of llseek.c
+ - sgm_dd: accept coe=<n> for interworking with sg_dd
+ - sg_rdac: fix on non-linux ports
+ - sg_ses: fix spurious warning in additional element status page
+ - '-rr' option outputs a diagnostic page in binary to stdout
+ - sg_opcodes: add command timeout descriptor support (spc4r08)
+ - change linux specific pass through to generic pass through
+ - sg_logs: add 'name=value' decoding for SAS specific lpage
+ - examples+utils subdirectories: remove symlinks
+ - synchronize man pages with usage messages
+ - sg3_utils.spec: rework
+
+Changelog for sg3_utils-1.22 [20061016] [svn: 72]
+ - sgp_dd: accept verbose=<n> as well as deb=<n> to ease
+ interworking with sg_dd and sgm_dd
+ - sg_sat_set_features: added to examples directory
+ - sg_lib: sync asc/ascq text with spc4r06
+ - move SG_LIB_CAT_NO_SENSE and SG_LIB_CAT_RECOVERED to
+ 20 and 21 respectively; add SG_LIB_CAT_ABORTED_COMMAND
+ at 11 (its sense key value)
+ - sg_vpd: tweak '--page=sp --quiet' output
+ - change '-HHH' so same as '-rr' (prepares ATA Information
+ (ai) response for hdparm)
+ - sg_requests: add '-s' option to set exit status from
+ parameter data
+ - sg_modes: exit quickly from '-e' if device not ready
+ - sg_logs: sync sas log pages with sas2r05a
+ - expand background scan results log page
+ - add '-m=<max_len>' to limit response length
+ - drop '-scum' and '-sthr' options and add '-select'
+ - sg_write_long: add '--16' option to send 16 byte cdb
+ - add '--wr_uncor' and '--pblock' options
+ - sg_senddiag: cleanup and add sdiag_sas_p1_stop.txt
+ to examples directory
+ - sg_format: add '--cmplst=<n>' option (default: 1)
+ - add '--pfu=<n>' option
+ - expand man page to discuss P/D/C/GLISTs
+ - sg_reassign: add '--primary' option to fetch primary
+ defect list (PLIST) length
+ - sg_readcap: add '-H' option to output response in hex
+ and '-r' to output response in binary to stdout
+ - add logical blocks per physical block (sbc3r07)
+ - sginfo: add PLIST and GLIST designation to defect lists
+ - sg_cmds: split this support file into sg_cmds_basic.[hc]
+ and sg_cmds_extra.[hc]
+ - add sg_ll_ata_pt() (SATL ATA pass) to sg_cmds_extra.[hc]
+ - sg_rdac: fix includes for FreeBSD
+ - sg_dd: add 'coe_limit=' option to exit after <n>
+ consecutive 'coe' type read errors
+ - sgm_dd: print out throughput information when signal arrives
+ if time=1 (like sg_dd does already)
+ - sg_inq: change '-HHH' so same as '-rr'. Now sg_inq, sg_vpd
+ and sdparm output for hdparm with '-HHH'
+ -add '-l=<resp_len>' option
+ - sg_read_long: add '--pblock' option for physical blocks
+ - sg_luns: add '--hex' and '--raw' options
+ - sg_requests: add '--hex' and '--raw' options
+ - sg_scan: windows version added (was previously linux only)
+ - 2 man pages: sg_scan.8l and sg_scan.8w that are installed
+ as sg_scan.8
+ - archive directory: removed all but rescan-scsi-bus.sh
+ - README points to previous version in that directory
+ - sg_sat_identify: add to main directory
+ - rename earlier version to examples/sg__sat_identify.c
+ - sg_ident: rework as spc4r07 changed command names and
+ expanded functionality
+
+Changelog for sg3_utils-1.21 [20060706] [svn: 70]
+ - sg_vpd: new utility for decoding VPD pages. sg_inq's cli is
+ cluttered; also borrows from sdparm's VPD handling
+ - sg_rdac: new utility for vendor specific work
+ - sg_lib: add sg_vpd_dev_id_iter() to iterate over di VPD page
+ - add sg_ata_get_chars() to fetch chars from ATA words
+ - sync additional sense code strings with spc4r05a
+ - add SG_LIB_CAT_NOT_READY category when sense_key is NOT READY
+ - add SG_LIB_FILE_ERROR category for open problems
+ - add SG_LIB_SYNTAX_ERROR category for command line problems
+ - broaden SG_LIB_CAT_MEDIA_CHANGED to SG_LIB_CAT_UNIT_ATTENTION
+ - add SG_LIB_CAT_MALFORMED for bad responses
+ - BEWARE: these changes cause confusion if an executable from this
+ version is run with a libsgutils library from 1.20 or earlier
+ - sg_cmds: add SG_LIB_CAT_NOT_READY return to most "ll" functions
+ - alter many utilities to report SG_LIB_CAT_NOT_READY
+ - sg_dd: add retries=<n> option for sg_io
+ - sg_logs: add '-T' option to output protocol specific port log page
+ - add support for log subpages (new in spc4r05)
+ - more sanity checks in Start Stop Cycle Counter page
+ - sg_cmds: add sg_ll_read_long16()
+ - add page_code and subpage_code to sg_ll_log_select()
+ - add subpage_code to sg_ll_log_sense()
+ - sg_read_long: do READ LONG(16) when '--16' given
+ - sg_read: accept and ignore 'of=' arguments
+ - sg_dd: expand medium/hardware error "coe' processing to include
+ the "blank check" sense key (for optical devices)
+ - sg_ses: expand display element (per 05-011r2)
+ - sg_format: clear 'cmplst' bit (for MO disks)
+ - add '--six' ('-6') option for mode sense/select(6)
+ - sg_format + sg_test_rwbuf: fix for when char is unsigned
+ - sg_inq: VPD page 0x89: output ATA IDENTIFY DEVICE strings
+ - for IDENTIFY (PACKET) DEVICE response use sg_ata_get_chars()
+ - sg3_utils.html : new name, was previously u_index.html. Copy
+ placed in doc subdirectory
+ - tools.html : SCSI and storage tools reference, copy placed in
+ doc subdirectory
+ - sg3_utils.8 : add a new man page containing general information
+ especially common exit status values
+ - sg_sat_identify: added to examples directory (SAT passthrough test)
+ - extend to pass through IDENTIFY PACKET DEVICE with '-p' option
+ - sg_sat_chk_power: added to examples directory
+ - sg_sat_smart_rd_data: added to examples directory
+ - sg_chk_asc: added to utils directory to check asc_ascq codes
+ - debian: stop placing archive directory under examples
+ - add build_debian.sh script
+
+Changelog for sg3_utils-1.20 [20060418] [svn: 68]
+ - sg_logs: decode phy event descriptors in SAS port specific
+ log page (sas2r03)
+ - new parameter control byte format (spc4r03), subpages to come
+ - update Makefile (linux) to install sg_io_linux.h + sg_linux_inc.h
+ - sg_map26: fix for block device mapping in lk 2.6.16-rc1 and beyond
+ - cope with sysfs removal of 'generic' symlink post lk 2.6.16,
+ anticipate removal of 'tape' symlink
+ - sg_dd, sgm_dd, sgp_dd: fix problem around 0x7fffffff blocks
+ - sg_dd: fix read_long processing error (when 'coe=2' or 3)
+ - expand 'coe=' to take 0...3 (invokes read long with 2 or 3)
+ - allow for SG_GET_RESERVED_SIZE yielding 0, lk 2.6.16 feature
+ - sgp_dd: add 'iflag=' and 'oflag=' arguments; signals (like sg_dd)
+ - sgm_dd: add 'iflag=' and 'oflag=' arguments; signals (like sg_dd)
+ - sg_get_config: double->dual renaming (mmc5r03)
+ - sg_read: add 'dpo=' and 'fua=' options
+ - allow 'count' < 0 (or 'bpt=0') for issuing zero block READs
+ - allow for SG_GET_RESERVED_SIZE yielding 0, lk 2.6.16 feature
+ - add 'no_dxfer=0|1' option
+ - sg_modes: fix exit value when MODE SENSE fails
+ - add '-e' to examine presence of page codes from 0x0 to 0x3e
+ - sg_requests: add '--num=' and '--time' options for timing multiple
+ invocations
+ - sg_inq: fix vpd 0x83 designator code 8 name: "scsi name string"
+ - sg_scan: if lk 2.6, use sysfs to find active sg device nodes
+ - sg_map: if lk 2.6, use sysfs to find active sg device nodes
+ - sg_ses: expand display element (per 05-011r1)
+ - sg_start: add an '-i' option which is equivalent to '--imm=1'
+ - sg_senddiag: update man page showing use of two scripts in
+ examples directory (sdiag_sas_p0_cjtpat.txt and _p1_)
+ - sg_lib: fix sg_get_sense_descriptors_str() case 9 (ATA Return)
+
+Changelog for sg3_utils-1.19 [20060127] [svn: 66]
+ - sg_start: accept '--' options (e.g. 'sg_start --stop')
+ - add '--fl=<n>' option for jump to format layer (mmc5)
+ - sg_logs: background scan log page, resync with sbc3r03
+ - add '-scum' and '-sthr' for setting defaults
+ - add device statistics log page (ssc + adc)
+ - fix "last n" deferred errors/error events incrementing
+ - partial addition of log subpages (spc4r03)
+ - sg_get_config: sync features with mmc5 rev 02b
+ - sg_wr_mode: mask out dpofua bit in mode select header
+ - sg_inq: try harder with '-A' to identify ATA device
+ - broaden meaning of '-d' option to decode ...
+ - decode software interface id VPD page ('-p=84 -d')
+ - decode device capabilities (ssc) VPD page ('-p=b0 -d')
+ - sginfo: correct defect list handling ('-d' and '-G')
+ - sg_verify: improve error processing (e.g. medium errors)
+ - sg_ses: scsi target_initiator port additional element
+ status (ses2r14)
+ - many: arguments that currently accept '0x' or '0X' to
+ indicate a hex number may alternatively take a trailing
+ 'h' or 'H' to indicate hex
+ - sg_lib: update asc/ascq strings (spc4r03)
+ - sg_lib+sg_cmds: make independent of linux via
+ sg_pt.h function based interface.
+ - linux pass through code placed in sg_pt_linux.c
+ - rename sg_include.h to sg_linux_inc.h
+ - linux specific code in sg_lib.[hc] moved to
+ sg_io_linux.[hc]
+ - port to FreeBSD: using sg_pt_freebsd.c
+ - port to Tru64: using sg_pt_osf1.c
+ - sg_cmds: add sg_ll_get_config(), sg_ll_format_unit(),
+ sg_ll_reassign_blocks(), sg_ll_persistent_reserve_in+out(),
+ sg_ll_read_long10(), sg_ll_verify10(), sg_ll_write_long10()
+ - sg_persist: add "allow commands" to report capabilities
+ - sg_persist_tst: (examples) takes device node as argument
+ - sg_luns: add "security protocol" wlun
+
+Changelog for sg3_utils-1.18 [20051118] [svn:63]
+ - sg_map26: new utility to map sg devices in lk 2.6
+ - sg_luns: luns > 16,384 (sam-4 rev 4)
+ - sg_ses: bump fan speed field to 11 bits
+ - SAS connector names (ses2r13)
+ - sg_inq: add '-rr' option for "hdparm --Istdin"
+ - sg_get_config: tracking mmc-5
+ - sg_write_long: add support for COR_DIS bit
+ - sg_cmds: add sg_ll_test_unit_ready_progress()
+ - sg_turs: '-p' option shows progress
+ - sg_dd: add 'iflag=' and 'oflag=' options
+ - remove output of mode page info when verbose > 0
+ - add control of DPO bit via iflag/oflag
+ - sg_lib: add sg_get_pdt_str()
+ - update asc/ascq strings
+ - sg_modes + sginfo: add SAS(2) SSP shared mode subpage
+ - doc: rename "html" directory to "doc"
+ - Makefile: add 'libtool --finish' to install
+
+Changelog for sg3_utils-1.17 [20050922] [svn: 60]
+ - sg_inq: add '-a' option for ATA information VPD page
+ - add '-b' option for Block limits VPD page (SBC)
+ - add '-A' option for probing ATA or ATAPI device
+ - increase raw ('-r') and verbose ('-v') output for
+ ATA(PI) devices to 512 bytes (was 256 bytes)
+ - output hex ('-H') and verbose response for ATA(PI)
+ devices in 16 bit words (corrected for endianness)
+ - output bytes if '-HH' option given
+ - sync with spc4 rev 02
+ - sg_lib: add dWordHex() and sg_is_big_endian()
+ - sync asc/ascq with spc4 rev 02
+ - sg_cmds: defensive prefill for inquiry commands
+ - sg_opcodes: sync with spc4 rev 02 (add tmf I_T nexus reset)
+ - sginfo: add EBACKERR in Informational exception mode page
+ - add Background control mode page (SBC-3)
+ - sgm_dd: add 'verbose=<n>' option
+
+Changelog for sg3_utils-1.16 [20050810] [svn: 58]
+ - sg_ident: new utility to report+set device identifier
+ - sg_map: increase MAX_SG_DEVS from 256 to 2048
+ - debian: new directory to support deb package builds
+ - sg_get_config: add '--current' option, same as '--rt=1'
+ - update for DVD+RW Dual Layer
+ - sg_inq: add notes in source about use of SCSI INQUIRY
+ - decode Management network addresses VPD page ('-m')
+ - decode Mode page policy VPD page ('-M')
+ - sginfo: increase device mapping capability (> 78 disks)
+ - add '-r' option to scan /dev/raw* device nodes [Tim Hunt]
+ - sg_dd: change bpt default value to 32 when bs >= 2048 bytes
+ - sg_ses: mention SAF-TE in man page
+ - sg_readcap: add '-b' option for brief output (2 hex numbers)
+ - sg_cmds: add sg_ll_start_stop_unit(), sg_ll_prevent_allow(),
+ sg_ll_report_dev_id() and sg_ll_set_dev_id()
+ - sg_lib: add extra argument to sense print functions to enable
+ the suppression of the raw output of the sense buffer
+ - resid > 0 warnings now includes number actually fetched
+ - sg_start: add '-load' and '-eject' options
+ - default to start action when no other indication given
+ - change -imm=0|1 option default to 0 (was 1)
+ - gcc 4.0: cleanup warnings (apart from sgp_dd: revisit later)
+
+Changelog for sg3_utils-1.15 [20050605] [svn: 56]
+ - sg_cmds: sg_get_mode_page_controls(): improve error processing,
+ add double fetch
+ - sg_turs, sg_rbuf, sg_requests, sg_test_rwbuf, sg_format,
+ sg_dd and sgm_dd: add O_NONBLOCK to open()
+ - sgm_dd: switch to use SG_IO ioctl (that leaves only
+ sgp_dd using the asynchronous sg write()/read() sequence)
+ - sg_ses: sync with rev 12 changes
+ - sg_map: extend to cope with sparse disk device names with
+ up to 3 letters (e.g. /dev/sdaaa) [Nate Dailey]
+ - sg_modes: add '-f' option to flexibly decode broken mode
+ sense responses.
+ - zero prefill response; stop decoding response after 3
+ unit attention mode pages seen (i.e. malformed)
+ - add '-L' option for LLBAA bit in msense 10 cdb
+ - sg_reset: update man page
+ - sg_inq: VPD page 0x83: output eui addresses in hex as well
+ - Makefile: fix bug in rules for sgp_dd (when 'make dep' used)
+ - sg_format: expand explanations in its man page
+ - sg_inq, sg_logs, sg_modes, sg_opcodes, sg_rbuf, sg_readcap,
+ sg_scan, sg_senddiag, sg_start and sg_turs: allow command
+ line to take concaternated options
+ - sg_start: add -start and -stop to parallel "1" and "0"
+ - sg_senddiag: set pf bit with '-l' option
+
+Changelog for sg3_utils-1.14 [20050506] [svn: 54]
+ - sg_rmsn: new utility to read media serial number
+ - sg_rtpg: add T_SUP bit report
+ - sg_ses: ses-2 rev 11 changes (mainly to additional element status)
+ - add 'bay number' to SAS additional element status
+ - sg_modes: recognise attached enclosure and medium changer
+ - sg_inq: spell out non-zero peripheral qualifiers
+ - note VS bit preceding MultiP(ort) when latter set
+ - VPD page 0x83: output naa addresses in hex as well
+ - sginfo: recognise attached enclosure and medium changer
+ - increase device mapping capability (to 78 disks)
+ [Tim Hunt]
+ - sg_senddiag: add option to send raw diagnostic page
+ - sg_get_config: update some BD information
+ - sg_reasign: add '-g' option to give grown defect list length
+ - sg_dd: note default bpt value (128) may be too high for cd/dvds
+ - sg_lib: sync with SPC-3 rev 22a [opcodes + asc/q]
+ - add DID_IMM_RETRY and DID_REQUEUE [linux specific "host" bytes]
+ - sg_cmds: add send+receive diagnostic, read defect data commands
+ - add duration output on some commands when verbose > 2
+ - spec: change to produce libsgutils (and -devel variant) as well as
+ sg3_utils binary rpms
+ - sdparm: new utility like hdparm but for SCSI disks (or other devices)
+ - moved to its own package called: sdparm
+
+Changelog for sg3_utils-1.13 [20050313] [svn: 52]
+ - sg_format: new utility to format disks (perhaps change block size)
+ - sg_ses: rename "device element" to "additional element" [SES-2 rev 10]
+ - add SAS expander and connector elements; add download
+ microcode and subenclosure nickname diagnostic pages
+ - fix additional element descriptor for SAS
+ - off by 1 error when no type descriptor text in config page
+ <David dot Baldwin at anu dot edu dot au>
+ - sg_logs: log page for background media scan results
+ - sginfo: add "-flba64" option for outputting 64 bit lba defect lists
+ - sg_get_config: additions for BD from MMC-5 rev 1b
+ - sg_lib: add SG_LIB_CAT_ILLEGAL_REQ sense category
+ - add sg_get_sense_progress_fld()
+ - SPC-3 rev21d updates: report + set timestamp
+ - sg_get_num() + sg_get_llnum(): switch to multipliers that
+ are compatible with SI and with IEC 60027-2. Used modern
+ GNU's dd command as guide.
+ - report field replaceable unit code in fixed format
+ - sg_dd: add logic to use read_long on unrecovered read errors when
+ 'coe' set, read just prior to error if 'coe' clear
+ - both 'odir' and 'blk_sgio" are honoured on block devices
+ - add 'verbose' switch, output some mode page info when verbose
+ - print out elapsed time/throughput when signal received
+ - add new web page discussing sg_dd, copy in html subdirectory
+ - sg_read: add 'blk_sgio' and 'odir' options
+ - sg_wr_mode: clear mode data length in mode select(10)
+ - sg_test_rwbuf: add long options, allow test to run multiple times
+ - sg_cmds: add sg_get_mode_page_types() [get current, changeable, etc]
+ - llseek.c: add Makefile rule without "-std=c99", breaks on some archs
+
+Changelog for sg3_utils-1.12 [20050121] [svn: 50]
+ - sg_wr_mode: new utility to modify (i.e. write to) mode pages
+ - sg_reassign: new utility: issues Reassign Blocks command
+ - sg_rtpg: new utility: issues Report Target Port Groups command
+ [Christophe Varoqui]
+ - ATA IDENTIFY command misspelt as "IDENTITY" in several places
+ - sginfo: tweak SAS mode pages to match sas 1.1 rev 07
+ - add NV_DIS bit to disk caching mode page
+ - sg_map: open /dev/nst* rather than /dev/st* (to stop spurious rewinds)
+ - sg_lib: ATA return sense descriptor
+ - add sg_get_sense_info_fld() to fetch info field from sense data
+ - fix bug in sg_scsi_sense_desc_find()
+ - add sense key specific decoding for fixed format sense data
+ - sg_modes: extend '-p' option to allow '-p=<page_code>,<subpage_code>'
+ - add '-A' option to output all mode pages and subpages
+ - extend '-l' option to show subpages, selected command set pages
+ - sg_inq: fix LUN WWN output in unit path report VPD page (emc)
+ [Hergen Lange]
+ - sg_get_config: some additions for DVD-R dual layer
+ - sg_modes: show write protect (WP) and DpoFua flags for disks
+ - sg_cmds: add llbaa argument to sg_ll_mode_sense10()
+
+Changelog for sg3_utils-1.11 [20041126] [svn: 48]
+ - sg_sync: new utility: invokes the synchronize cache command
+ - sg_prevent: new utility: invokes the prevent allow medium removal command
+ - sg_get_config: new utility: get configuration command for dvds and cds
+ - sg_request: fix, allocation length wasn't set
+ - sg_start: remove '-s', as start_stop_unit implicitly syncs caches
+ - sg_ses: add SAS expander element type
+ - sg_inq: add sanity check to unit serial number (VPD page 0x80)
+ - output ANSI version string (e.g. "SPC-2", previously was number)
+ - add '-s' option to decode SCSI Ports VPD page
+ - sg_logs: decoding of format status and non-volatile cache log
+ pages (0x8 and 0x17 in sbc-2)
+ - sg_dd: handle compile error when O_DIRECT not defined
+ - sginfo: tighten sanity checks around Unit serial number VPD page
+
+Changelog for sg3_utils-1.10 [20041030] [svn: 46]
+ - sg_readcap, sg_dd, sgm_dd, sgp_dd: fix sg_ll_readcap_10+16 (sg_cmds.c)
+ - sg_luns: new utility to report luns
+ - sg_logs: with '-t' (show temperature) ignore extra parameters in
+ temperature log page (still show them with '-p=d')
+ - sg_ses: clean argument sanity checks
+ - sg_cmds: add more common command wrappers
+
+Changelog for sg3_utils-1.09 [20041022] [svn: 44]
+ - sg_ses: new utility to get status and set control on SES devices
+ - sg_verify: new utility to verify block devices
+ - sg_emc_trespass: new utility for EMC specific trespass mode page
+ - sg_request: new utility that sends a REQUEST SENSE command
+ - sg_logs: '-r' to reset to manufacturer's defaults
+ - decode last n error events and last n deferred errors pages
+ - add names of ADC log pages
+ - sg_inq: update to SPC-3 rev 21
+ - decode Extended INQUIRY data VPD page [0x86] {'-x'}
+ - decode Unit Path Report VPD page [0xc0] (EMC) {'-P'}
+ - sginfo: decode SAS protocol specific lu mode page
+ - sg_err: convert to sg_lib + update to SPC-3 rev 21
+ - change GPL to FreeBSD license
+ - flag vendor specific asc/ascq ranges
+ - libsgutils: library made from sg_lib.c and sg_cmds.c
+ - rpm "spec" file additionally builds a "-devel" rpm with
+ static libsgutils.a and sg_lib.h and sg_cmds.h
+ - utils/hxascdmp.c: add FreeBSD license
+ - sg_persist: additions to man page
+ - add sg_persist_tst.sh example script to examples directory
+ - sg_turs: add '-v' and '-V' options
+ - sg_senddiag: add '-v' option
+
+Changelog for sg3_utils-1.08 [20040831] [svn: 42]
+ - sg_inq: fix noisy message when EVPD and raw modes set
+ - for VPD page 0, list supported page names if known {'-e'}
+ - add '-d' option to list version descriptors
+ - sg_opcodes: numerically sort list of opcodes unless '-u' given
+ - add '-a' to sort alphabetically list of opcode names
+ - add '-t' to report supported task management functions
+ - sg_persist: add 'register and move" PROUT service action
+ - add transportId support, document in sg_persist.8 and example
+ - sg_modes: handle subpage code for known pages (e.g. control extension)
+ - clean up sense buffer handling (allow for descriptor format)
+ - SPC-3 draft revision 20a updates
+ - sg_write_long: new utility to exercise WRITE LONG command
+ - sg_read_long: new utility to exercise READ LONG command
+ - sg_err.c: fix compile errors when SG_KERNEL_INCLUDES defined in lk 2.6
+ - sg_includes.h typedef of u64 for BLKGETSIZE64 ioctl in lk 2.4
+ - add safe_strerror(), sg_scsi_normalize_sense(), sg_normalize_sense()
+ and sg_scsi_sense_desc_find() functions
+ - add more sense data descriptor format decoding
+ - move multiple implementations of dStrHex() into sg_err.c
+ - sg_logs: exit if SCSI INQUIRY fails (e.g. when applied to ATA disk)
+ - sginfo: bug fixes and SPC-3 revision 20a updates
+ - add '-E' option to access Control Extension mode (sub)page
+ - sg_start: change '-d' switch to '-v' (verbose) switch for consistency
+ - document extra power condition states in man page
+ - sg_readcap: output rto_en and prot_en bits from long read capacity data
+ - add COVERAGE file to list SCSI command coverage
+
+Changelog for sg3_utils-1.07 [20040708] [svn: 40]
+ - sginfo: clean up inquiry vendor,product,revision strings
+ - '-Fhead': sort defect list by head
+ Tom Steudten <steudten at mx dot ch>
+ - rework sg_err for better command name coverage: service actions,
+ variable length and peripheral device type
+ - update asc,ascq codes to SPC-3 revision 19
+ - move scsi_devfs_scan to archive directory
+ - add sg_opcodes utility to list supported operation codes
+ - add sg_persist utility to support persistent reservations
+ - add '-i' option to sg_inq to decode device identification VPD page (0x83)
+ - sg_inq tries an ATA IDENTIFY if SCSI INQUIRY fails
+ - sg_dd, sgm_dd and sgp_dd calculate block device sizes (if count not given)
+ - drop SG_GET_VERSION_NUM ioctl guard in most utilities
+
+Changelog for sg3_utils-1.06 [20040426] [svn: 37]
+ - sg_logs: some HBAs don't like odd transfer lengths so increment
+ - do INQUIRY and output product strings
+ - add ASCII rendering of the Protocol specific SAS page
+ - add '-v' verbose option to output cdb
+ - sg_scan: optionally take device file names (e.g. /dev/hdc and /dev/sda)
+ - only request 36 byte INQUIRY responses
+ - sg_err: add sg_decode_sense() function
+ - sg_inq: update output (ref: SPC-3 t10/1416-d rev 17, 28 January 2004)
+ - remove '-p' option to print out PCI address of host
+ - add '-v' verbose option to output cdb
+ - sginfo: allow '-u' to take hex arguments (prefixed by '0x'),
+ when subpage value is 255 show multiple subpages
+ - accept /dev/hd? ATAPI devices directly in lk 2.6
+ - add '-t <pn>[,<spn>]' argument; like '-u' but decodes page
+ if it recognizes it
+ - drop '-L' argument
+ - add cd/dvd, tape, SES, more disk and more SPC-3 decoded mode pages
+ - add transport protocol decoded mode pages for SPI-4, FCP and SAS
+ - sg_modes: print all subpages when '-subp=ff' is selected
+ - do INQUIRY and output product strings
+ - add '-v' verbose option to output cdb
+ - Makefile: add -W compile flag and fix exposed warnings
+ - .spec file: change to build on Mandrake without errors
+
+Changelog for sg3_utils-1.05 [20031112] [svn: 35]
+ - sginfo: major rework; add IE page, clean up control, cache +
+ disconnect pages (as per SPC-3 and SBC-2).
+ - when storing, update saved page (change from previous version)
+ - use 10 byte mode sense and select by default (override with '-6')
+ - mode subpage support
+ - sg_dd, sgm_dd + sgp_dd:
+ - 64 bit capable (read capacity; count, skip and seek values).
+ - numerical arguments accept hex (prefixed by '0x' or '0X')
+ - require bpt > 0
+ - fix problem when reading /dev/null
+ - sg_dd: Treat SIGUSR1 properly: print stats and continue;
+ - sgp_dd: reduce READ CAPACITY response size to 8 bytes
+ - sg_read: require bpt > 0
+ - sg_test_rwbuf: switch from sg_header to sg_io_hdr interface
+ N.B. After these changes no sg3_utils utilities (in the main directory)
+ use the sg_header interface
+ - sg_scan: switch from sg_header to sg_io_hdr interface
+ - sg_senddiag: increase extended foreground timeout to 60 minutes
+ - sg_inq: add names of peripheral device types
+ - sg_readcap: show total size in bytes, MB, GB
+ - sg_logs: read log pages twice (first time to get response length), for
+ fragile HBAs; decode Seagate 0x37 + 0x3e pages; display pcbs
+ - sg_modes: fix core dump when corrupted response, don't print extra pages
+ - sg_map: increase sg device scanning from 128 to 256
+ - change man page references from lk 2.5 to lk 2.6
+ - examples/sg_iovec_tst: added testing sg_iovec (sg_io_hdr iovec's)
+ [retrospective addition to this log: "#define __user" added into
+ sg_include.h so user space programs aren't broken if they choose
+ to include kernel header.]
+ - utils/hxascdmp: add utility for displaying ASCII hex
+
+Changelog for sg3_utils-1.04 [20030513] [svn: 33]
+ - all remaining utilities in the main directory have man pages [thanks
+ to Eric Schwartz <emschwar at debian dot org> for 7 man pages]
+ - add CREDITS file
+ - sg_simple1, sg_simple2, sg_simple3, sg_simple4, sg_simple16 and
+ scsi_inquiry: moved to the examples directory
+ - sg_debug: moved to the archive directory
+ - sg_modes: add '-subp=<n>' for sub page code, suggests 6/10 byte
+ alternative if bad opcode sense received, flip -cpf flag to -pf,
+ add page names for most peripheral types
+ - sg_turs: default '-n=' argument to 1, only when '-n=1' print error
+ message in full
+ - sg_logs: print temperature "<not available>" for 255, '-t' switch
+ for temperature (from either temperature or IE log page)
+ - sg_dd: add '-odir=0|1' switch for O_DIRECT on block devices
+ - sg_start: add '-imm', '-loej' and 'pc=<n>' switches plus man page
+ - sg_readcap: add '-pmi' and 'lba=<n>' switches
+ - open files O_NONBLOCK in sg_inq, sg_modes and sg_logs so they
+ can be used on cd/dvd drivers when there is no disk present
+
+Changelog for sg3_utils-1.03 [20030402] [svn: 30]
+ - sg_senddiag: added, allows self tests and listing of diag pages
+ - sg_start: changed to use SG_IO so works on block devices
+ - sg_err: print out some "sense key specific" data [Trent Piepho]
+ - sg_modes: add "-6" switch for force 6 byte MODE SENSE [Trent Piepho]
+ - sg_modes: more information on page codes and controls
+ - sg_inq, sg_modes, sg_logs, sg_senddiag: add man pages
+ - sg_dd: add "append=0|1" switch for glueing together large files
+ - note in README about utilities offered by scsirastools package
+
+Changelog for sg3_utils-1.02 [20030101] [svn: 28]
+ - sg_inq: check if cmddt or evpd bits ignored
+ - sg_inq: warn -o=<n> not used for standard INQUIRY
+ - sg_turs: add -t option to time Test Unit Ready commands
+ - sg_errs: (used by most utilities) warn if sense buffer empty
+ - sg_modes: make safe with block SG_IO (bypass SG_GET_SCSI_ID ioctl)
+ - sg_logs: make safe with block SG_IO, self-test page work
+ - sg_dd: add "blksg_io=" switch to experiment with block SG_IO
+ - sg_read: now use SG_IO ioctl rather than sg write/read
+ - sginfo: fix writing parameters, check for block devices that answer
+ sg's ioctls, recognize "scd<n>" device names
+ - sg_map: stop close error report on tape devices
+ - sg_readcap: make safe with block SG_IO
+ - sg_start: make safe with block SG_IO
+ - sg_test_rwbuf: make safe with block SG_IO
+
+Changelog for sg3_utils-1.01 [20020814] [svn: 27]
+----------------------------
+ - add raw switch ("-r") to sg_inq [Martin Schwenke]
+
+Changelog for sg3_utils-1.00 [20020728] [svn: 26]
+----------------------------
+ - update sg_err [to SPC-3 T10/1416-D Rev 07 3 May 2002]
+ - "sg_inq -cl" now outputs opcode names
+ - add "continue on error" option to sg_dd
+ - add _LARGEFILE64_SOURCE _FILE_OFFSET_BITS=64 defines to Makefile
+ - drop 'gen' argument from sg_dd and friends, allow any file types
+ except scsi tape device file names
+ - treat of=/dev/null as special (skip write). Accept of=. as alias
+ for of=/dev/null
+ - decode various log pages in sg_logs
+ - add 'dio' argument to sgm_dd for testing "zero copy" copies
+
+Changelog for sg3_utils-0.99 [20020317]
+----------------------------
+ - add 'fua' and 'sync' arguments to sg_dd, sgp_dd and sgm_dd
+ - improve sg_inq, add "-cl" and "-36" arguments
+ - add sg_modes + sg_logs for MODE SENSE and LOG SENSE queries
+ - add rescan-scsi-bus.sh [Kurt Garloff] to archive directory
+
+Changelog for sg3_utils-0.98 [20020216]
+----------------------------
+ - move sg_reset back from archive to main directory + build
+ - sprintf() to snprintf() changes
+ - add "time=<n>" argument to sg_dd, sgp_dd and sgm_dd to time transfer
+ - add man pages for sgm_dd and sg_read, update sg_dd and sgp_dd man pages
+ - add "cdbsz=" argument to sg_dd, sgp_dd, sgm_dd + sg_read
+ - add "gen=0|1" argument to sg_dd
+
+Changelog for sg3_utils-0.97 [20011223]
+----------------------------
+ - move isosize to archive since introduced into util-linux-2.10s
+
+Changelog for sg3_utils-0.96 [20011221]
+----------------------------
+ - add '-p' switch to sg_inq to provide PCI slot_name
+ - add '-n' switch to scsi_inquiry for non-blocking open
+ - new sgm_dd (dd variant) using mmap-ed IO
+ - sg_rbuf now has a '-m' argument to select mmap-ed IO
+ - sg_rbuf now has a '-t' switch to do timing + throughput calculation
+ - add sg_simple4 to demonstrate mmap-ed IO on an INQUIRY response
+ - add sg_simple16 to do a READ_16 (16 byte SCSI READ command)
+ - mmap-ed IO requires sg version 3.1.22 or later
+ - add sg_read to read multiple times starting at same offset
+
+Changelog for sg3_utils-0.95 [20010915]
+----------------------------
+ - make sg_dd, sgp_dd and archive/sgq_dd warn if dio selected but
+ /sys/module/sg/parameters/allow_dio is '0'
+ - sg_map can now do any INQUIRY (when '-i' argument given)
+ - expand example in scsi_inquiry
+
+Changelog for sg3_utils-0.94 [20010419]
+----------------------------
+ - add sg_start, documented in README.sg_start [Kurt Garloff]
+ - add osst support in sg_map [Kurt Garloff]
+ - improvements to sginfo [Kurt Garloff]
+
+Changelog for sg3_utils-0.93 [20010415]
+----------------------------
+ - more include file fine tuning
+ - some "dio" work sg_rbuf
+ - extend sgp_dd so "continue on error" works on normal files
+ - introduce sg_include.h to encapsulate sg include problems
+ - add scsi_devfs_scan
+ - add sg_bus_xfer to archive directory
+ - more error info in sginfo
+
+Changelog for sg3_utils-0.92 [20010116]
+----------------------------
+ - change sg_err.c output from stdout to stderr
+ - change sg_debug to call system("cat /proc/scsi/sg/debug");
+ - fix in+out totals in sg_dd and sgp_dd when partial transfers
+ - lower include dependencies in sg_err.h
+ - add sgq_dd + Makefile to archive directory
+
+Changelog for sg3_utils-0.91 [20001221]
+----------------------------
+ - signalling handling added to sg_dd (and documented in sg_dd.8)
+ - add man page for sg_rbuf (and a small change to its code)
+ - add "-d" switch to isosize and cope with > 2 GB (and man page)
+
+Changelog for sg3_utils-0.90
+----------------------------
+ - switch from dated versioning. Previous version was sg3_utils001012.
+ Arbitrarily start at package version 0.90 . Start Changelog.
+ - incorporate Kurt Garloff's patches from Suse scsi.spm source rpm
+ compilation:
+ - add Kurt Garloff's sg_test_rwbuf utility to read and write to disk
+ buffer
+ - clean up Makefile to include a "make install" (and also add a
+ "make uninstall").
+ - add "-uno" switch to sginfo
+ - make raw and sg devices equally acceptable to sg_dd and sgp_dd.
+ [Raw devices still not as fast as sg devices doing disk to disk
+ copies in sgp_dd but this may be improved soon. Still faster than
+ using dd!]
+ - change lseek() in sg_dd and sgp_dd to _llseek() [using code borrowed
+ from fdisk] so big disks can be properly offset with 'skip' and
+ 'seek' arguments. [This change is significant for raw devices and
+ normal files since sg devices already use 31 bit block addressing.]
+ - rename sg_s3_inq to sg_inq. This utility allows the INQUIRY response
+ to be decoded as per SCSI 3 and 4. Also can probe VPD and CmdDt pages.
+ - change multiplier suffixes on sg_dd, sgp_dd and sg_turs so lower case
+ "k, m, g" are powers of 2 while "K, M, G" are powers of 10. This idea
+ borrowed from lmdd (lmbench suite)
+ - retire a few more less used utilities into the archive directory.
+ - add man pages for sg_dd, sgp_dd and sg_map
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 00000000..dc471d1d
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,240 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006, 2007 Free Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+Briefly, the shell commands './configure; make; make install' should
+configure, build, and install this package. If that fails try
+doing './autogen.sh' first and then repeat the above sequence. The
+autogen.sh script may require some autotools packages to be loaded.
+
+The following more detailed instructions are generic; see the `README'
+file for instructions specific to this package.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system.
+
+ Running `configure' might take a while. While running, it prints
+ some messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+ 6. Often, you can also type `make uninstall' to remove the installed
+ files again.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about. Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you can use GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory. After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc. You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug. Until the bug is fixed you can use this workaround:
+
+ CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+ Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 00000000..240acbe2
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,184 @@
+
+SUBDIRS = \
+ include \
+ lib \
+ src \
+ doc \
+ scripts
+
+EXTRA_DIST = \
+ autogen.sh \
+ COVERAGE \
+ CREDITS \
+ BSD_LICENSE \
+ build_debian.sh \
+ README.details \
+ README.freebsd \
+ README.iscsi \
+ README.sg_start \
+ README.solaris \
+ README.tru64 \
+ README.win32 \
+ sg3_utils.man8.html \
+ sg3_utils.spec
+
+EXTRA_DIST += \
+ archive/align_b4_memalign.c \
+ archive/llseek.c \
+ archive/llseek.h \
+ archive/o_scsi_logging_level \
+ archive/README \
+ archive/sg_json_writer.c \
+ archive/sg_json_writer.h
+
+EXTRA_DIST += \
+ debian/changelog \
+ debian/compat \
+ debian/control \
+ debian/copyright \
+ debian/docs \
+ debian/libsgutils2-2.install \
+ debian/libsgutils2-dev.install \
+ debian/README.debian4 \
+ debian/rules \
+ debian/sg3-utils.examples \
+ debian/sg3-utils.install
+
+EXTRA_DIST += \
+ examples/Makefile.freebsd \
+ examples/README \
+ examples/reassign_addr.txt \
+ examples/scsi_inquiry.c \
+ examples/sdiag_sas_p0_cjtpat.txt \
+ examples/sdiag_sas_p0_prbs9.txt \
+ examples/sdiag_sas_p1_cjtpat.txt \
+ examples/sdiag_sas_p1_idle.txt \
+ examples/sdiag_sas_p1_prbs15.txt \
+ examples/sdiag_sas_p1_stop.txt \
+ examples/sg_compare_and_write.txt \
+ examples/sg_excl.c \
+ examples/sg_persist_tst.sh \
+ examples/sgq_dd.c \
+ examples/sg_sat_chk_power.c \
+ examples/sg__sat_identify.c \
+ examples/sg__sat_phy_event.c \
+ examples/sg__sat_set_features.c \
+ examples/sg_sat_smart_rd_data.c \
+ examples/sg_simple16.c \
+ examples/sg_simple1.c \
+ examples/sg_simple2.c \
+ examples/sg_simple3.c \
+ examples/sg_simple4.c \
+ examples/sg_simple5.c \
+ examples/sg_unmap_example.txt \
+ examples/transport_ids.txt \
+ examples/Makefile
+
+EXTRA_DIST += \
+ getopt_long/getopt.h \
+ getopt_long/getopt_long.c
+
+EXTRA_DIST += \
+ include/freebsd_nvme_ioctl.h
+
+EXTRA_DIST += \
+ inhex/descriptor_sense.hex \
+ inhex/fixed_sense.hex \
+ inhex/forwarded_sense.hex \
+ inhex/get_elem_status.hex \
+ inhex/get_lba_status.hex \
+ inhex/inq_standard.hex \
+ inhex/logs_last_n.hex \
+ inhex/nvme_dev_self_test.hex \
+ inhex/nvme_identify_ctl.hex \
+ inhex/nvme_read_ctl.hex \
+ inhex/nvme_read_oob_ctl.hex \
+ inhex/nvme_write_ctl.hex \
+ inhex/opcodes.hex \
+ inhex/README \
+ inhex/ref_sense.hex \
+ inhex/rep_density.hex \
+ inhex/rep_density_media.hex \
+ inhex/rep_density_media_typem.hex \
+ inhex/rep_density_typem.hex \
+ inhex/rep_realms.hex \
+ inhex/rep_zdomains.hex \
+ inhex/rep_zones.hex \
+ inhex/ses_areca_all.hex \
+ inhex/vpd_bdce.hex \
+ inhex/vpd_consistuents.hex \
+ inhex/vpd_cpr.hex \
+ inhex/vpd_dev_id.hex \
+ inhex/vpd_di_all.hex \
+ inhex/vpd_fp.hex \
+ inhex/vpd_lbpro.hex \
+ inhex/vpd_lbpv.hex \
+ inhex/vpd_ref.hex \
+ inhex/vpd_sbl.hex \
+ inhex/vpd_sdeb.hex \
+ inhex/vpd_sfs.hex \
+ inhex/vpd_tpc.hex \
+ inhex/vpd_zbdc.hex \
+ inhex/vpd_zbdc.raw \
+ inhex/z_act_query.hex
+
+EXTRA_DIST += \
+ scripts/40-usb-blacklist.rules \
+ scripts/54-before-scsi-sg3_id.rules \
+ scripts/55-scsi-sg3_id.rules \
+ scripts/58-scsi-sg3_symlink.rules \
+ scripts/59-fc-wwpn-id.rules \
+ scripts/59-scsi-cciss_id.rules \
+ scripts/cciss_id \
+ scripts/fc_wwpn_id \
+ scripts/lunmask.service \
+ scripts/scsi-enable-target-scan.sh
+
+EXTRA_DIST += \
+ suse/sg3_utils.changes \
+ suse/sg3_utils.spec
+
+EXTRA_DIST += \
+ testing/bsg_queue_tst.c \
+ testing/Makefile \
+ testing/Makefile.cyg \
+ testing/Makefile.freebsd \
+ testing/README \
+ testing/sg_chk_asc.c \
+ testing/sgh_dd.cpp \
+ testing/sg_iovec_tst.cpp \
+ testing/sg_json_builder_test.c \
+ testing/sg_mrq_dd.cpp \
+ testing/sg_queue_tst.c \
+ testing/sg_scat_gath.cpp \
+ testing/sg_scat_gath.h \
+ testing/sgs_dd.c \
+ testing/sg_sense_test.c \
+ testing/sg_take_snap.c \
+ testing/sg_tst_async.cpp \
+ testing/sg_tst_bidi.c \
+ testing/sg_tst_context.cpp \
+ testing/sg_tst_excl2.cpp \
+ testing/sg_tst_excl3.cpp \
+ testing/sg_tst_excl.cpp \
+ testing/sg_tst_ioctl.c \
+ testing/sg_tst_json_builder.c \
+ testing/sg_tst_nvme.c \
+ testing/tst_sg_lib.c \
+ testing/uapi_sg.h
+
+EXTRA_DIST += \
+ utils/hxascdmp.1 \
+ utils/hxascdmp.c \
+ utils/Makefile \
+ utils/Makefile.cygwin \
+ utils/Makefile.freebsd \
+ utils/Makefile.mingw \
+ utils/Makefile.solaris \
+ utils/README
+
+distclean-local:
+ rm -rf autom4te.cache
+ rm -f build-stamp configure-stamp
+ rm -rf lib/.deps
+ rm -rf src/.deps
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 00000000..b3722f3b
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,903 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = .
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
+ $(am__configure_deps) $(am__DIST_COMMON)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ cscope distdir distdir-am dist dist-all distcheck
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \
+ config.h.in
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in AUTHORS \
+ COPYING ChangeLog INSTALL NEWS README ar-lib compile \
+ config.guess config.sub install-sh ltmain.sh missing
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+ if test -d "$(distdir)"; then \
+ find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+ && rm -rf "$(distdir)" \
+ || { sleep 5 && rm -rf "$(distdir)"; }; \
+ else :; fi
+am__post_remove_distdir = $(am__remove_distdir)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+DIST_TARGETS = dist-gzip
+# Exists only to be overridden by the user if desired.
+AM_DISTCHECK_DVI_TARGET = dvi
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = \
+ include \
+ lib \
+ src \
+ doc \
+ scripts
+
+EXTRA_DIST = autogen.sh COVERAGE CREDITS BSD_LICENSE build_debian.sh \
+ README.details README.freebsd README.iscsi README.sg_start \
+ README.solaris README.tru64 README.win32 sg3_utils.man8.html \
+ sg3_utils.spec archive/align_b4_memalign.c archive/llseek.c \
+ archive/llseek.h archive/o_scsi_logging_level archive/README \
+ archive/sg_json_writer.c archive/sg_json_writer.h \
+ debian/changelog debian/compat debian/control debian/copyright \
+ debian/docs debian/libsgutils2-2.install \
+ debian/libsgutils2-dev.install debian/README.debian4 \
+ debian/rules debian/sg3-utils.examples \
+ debian/sg3-utils.install examples/Makefile.freebsd \
+ examples/README examples/reassign_addr.txt \
+ examples/scsi_inquiry.c examples/sdiag_sas_p0_cjtpat.txt \
+ examples/sdiag_sas_p0_prbs9.txt \
+ examples/sdiag_sas_p1_cjtpat.txt \
+ examples/sdiag_sas_p1_idle.txt \
+ examples/sdiag_sas_p1_prbs15.txt \
+ examples/sdiag_sas_p1_stop.txt \
+ examples/sg_compare_and_write.txt examples/sg_excl.c \
+ examples/sg_persist_tst.sh examples/sgq_dd.c \
+ examples/sg_sat_chk_power.c examples/sg__sat_identify.c \
+ examples/sg__sat_phy_event.c examples/sg__sat_set_features.c \
+ examples/sg_sat_smart_rd_data.c examples/sg_simple16.c \
+ examples/sg_simple1.c examples/sg_simple2.c \
+ examples/sg_simple3.c examples/sg_simple4.c \
+ examples/sg_simple5.c examples/sg_unmap_example.txt \
+ examples/transport_ids.txt examples/Makefile \
+ getopt_long/getopt.h getopt_long/getopt_long.c \
+ include/freebsd_nvme_ioctl.h inhex/descriptor_sense.hex \
+ inhex/fixed_sense.hex inhex/forwarded_sense.hex \
+ inhex/get_elem_status.hex inhex/get_lba_status.hex \
+ inhex/inq_standard.hex inhex/logs_last_n.hex \
+ inhex/nvme_dev_self_test.hex inhex/nvme_identify_ctl.hex \
+ inhex/nvme_read_ctl.hex inhex/nvme_read_oob_ctl.hex \
+ inhex/nvme_write_ctl.hex inhex/opcodes.hex inhex/README \
+ inhex/ref_sense.hex inhex/rep_density.hex \
+ inhex/rep_density_media.hex inhex/rep_density_media_typem.hex \
+ inhex/rep_density_typem.hex inhex/rep_realms.hex \
+ inhex/rep_zdomains.hex inhex/rep_zones.hex \
+ inhex/ses_areca_all.hex inhex/vpd_bdce.hex \
+ inhex/vpd_consistuents.hex inhex/vpd_cpr.hex \
+ inhex/vpd_dev_id.hex inhex/vpd_di_all.hex inhex/vpd_fp.hex \
+ inhex/vpd_lbpro.hex inhex/vpd_lbpv.hex inhex/vpd_ref.hex \
+ inhex/vpd_sbl.hex inhex/vpd_sdeb.hex inhex/vpd_sfs.hex \
+ inhex/vpd_tpc.hex inhex/vpd_zbdc.hex inhex/vpd_zbdc.raw \
+ inhex/z_act_query.hex scripts/40-usb-blacklist.rules \
+ scripts/54-before-scsi-sg3_id.rules \
+ scripts/55-scsi-sg3_id.rules scripts/58-scsi-sg3_symlink.rules \
+ scripts/59-fc-wwpn-id.rules scripts/59-scsi-cciss_id.rules \
+ scripts/cciss_id scripts/fc_wwpn_id scripts/lunmask.service \
+ scripts/scsi-enable-target-scan.sh suse/sg3_utils.changes \
+ suse/sg3_utils.spec testing/bsg_queue_tst.c testing/Makefile \
+ testing/Makefile.cyg testing/Makefile.freebsd testing/README \
+ testing/sg_chk_asc.c testing/sgh_dd.cpp \
+ testing/sg_iovec_tst.cpp testing/sg_json_builder_test.c \
+ testing/sg_mrq_dd.cpp testing/sg_queue_tst.c \
+ testing/sg_scat_gath.cpp testing/sg_scat_gath.h \
+ testing/sgs_dd.c testing/sg_sense_test.c \
+ testing/sg_take_snap.c testing/sg_tst_async.cpp \
+ testing/sg_tst_bidi.c testing/sg_tst_context.cpp \
+ testing/sg_tst_excl2.cpp testing/sg_tst_excl3.cpp \
+ testing/sg_tst_excl.cpp testing/sg_tst_ioctl.c \
+ testing/sg_tst_json_builder.c testing/sg_tst_nvme.c \
+ testing/tst_sg_lib.c testing/uapi_sg.h utils/hxascdmp.1 \
+ utils/hxascdmp.c utils/Makefile utils/Makefile.cygwin \
+ utils/Makefile.freebsd utils/Makefile.mingw \
+ utils/Makefile.solaris utils/README
+all: config.h
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+am--refresh: Makefile
+ @:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+ $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ echo ' $(SHELL) ./config.status'; \
+ $(SHELL) ./config.status;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ $(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ $(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+
+config.h: stamp-h1
+ @test -f $@ || rm -f stamp-h1
+ @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
+
+stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
+ @rm -f stamp-h1
+ cd $(top_builddir) && $(SHELL) ./config.status config.h
+$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ ($(am__cd) $(top_srcdir) && $(AUTOHEADER))
+ rm -f stamp-h1
+ touch $@
+
+distclean-hdr:
+ -rm -f config.h stamp-h1
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+distclean-libtool:
+ -rm -f libtool config.lt
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscope: cscope.files
+ test ! -s cscope.files \
+ || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
+clean-cscope:
+ -rm -f cscope.files
+cscope.files: clean-cscope cscopelist
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+ -rm -f cscope.out cscope.in.out cscope.po.out cscope.files
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ $(am__remove_distdir)
+ test -d "$(distdir)" || mkdir "$(distdir)"
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+ -test -n "$(am__skip_mode_fix)" \
+ || find "$(distdir)" -type d ! -perm -755 \
+ -exec chmod u+rwx,go+rx {} \; -o \
+ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+ || chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+ tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
+ $(am__post_remove_distdir)
+
+dist-bzip2: distdir
+ tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+ $(am__post_remove_distdir)
+
+dist-lzip: distdir
+ tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+ $(am__post_remove_distdir)
+
+dist-xz: distdir
+ tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+ $(am__post_remove_distdir)
+
+dist-zstd: distdir
+ tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst
+ $(am__post_remove_distdir)
+
+dist-tarZ: distdir
+ @echo WARNING: "Support for distribution archives compressed with" \
+ "legacy program 'compress' is deprecated." >&2
+ @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+ tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+ $(am__post_remove_distdir)
+
+dist-shar: distdir
+ @echo WARNING: "Support for shar distribution archives is" \
+ "deprecated." >&2
+ @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+ shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
+ $(am__post_remove_distdir)
+
+dist-zip: distdir
+ -rm -f $(distdir).zip
+ zip -rq $(distdir).zip $(distdir)
+ $(am__post_remove_distdir)
+
+dist dist-all:
+ $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
+ $(am__post_remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration. Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+ case '$(DIST_ARCHIVES)' in \
+ *.tar.gz*) \
+ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
+ *.tar.bz2*) \
+ bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+ *.tar.lz*) \
+ lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+ *.tar.xz*) \
+ xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+ *.tar.Z*) \
+ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+ *.shar.gz*) \
+ eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
+ *.zip*) \
+ unzip $(distdir).zip ;;\
+ *.tar.zst*) \
+ zstd -dc $(distdir).tar.zst | $(am__untar) ;;\
+ esac
+ chmod -R a-w $(distdir)
+ chmod u+w $(distdir)
+ mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
+ chmod a-w $(distdir)
+ test -d $(distdir)/_build || exit 0; \
+ dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+ && am__cwd=`pwd` \
+ && $(am__cd) $(distdir)/_build/sub \
+ && ../../configure \
+ $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+ $(DISTCHECK_CONFIGURE_FLAGS) \
+ --srcdir=../.. --prefix="$$dc_install_base" \
+ && $(MAKE) $(AM_MAKEFLAGS) \
+ && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \
+ && $(MAKE) $(AM_MAKEFLAGS) check \
+ && $(MAKE) $(AM_MAKEFLAGS) install \
+ && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+ && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+ distuninstallcheck \
+ && chmod -R a-w "$$dc_install_base" \
+ && ({ \
+ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+ } || { rm -rf "$$dc_destdir"; exit 1; }) \
+ && rm -rf "$$dc_destdir" \
+ && $(MAKE) $(AM_MAKEFLAGS) dist \
+ && rm -rf $(DIST_ARCHIVES) \
+ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+ && cd "$$am__cwd" \
+ || exit 1
+ $(am__post_remove_distdir)
+ @(echo "$(distdir) archives ready for distribution: "; \
+ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+ @test -n '$(distuninstallcheck_dir)' || { \
+ echo 'ERROR: trying to run $@ with an empty' \
+ '$$(distuninstallcheck_dir)' >&2; \
+ exit 1; \
+ }; \
+ $(am__cd) '$(distuninstallcheck_dir)' || { \
+ echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+ exit 1; \
+ }; \
+ test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+ || { echo "ERROR: files left after uninstall:" ; \
+ if test -n "$(DESTDIR)"; then \
+ echo " (check DESTDIR support)"; \
+ fi ; \
+ $(distuninstallcheck_listfiles) ; \
+ exit 1; } >&2
+distcleancheck: distclean
+ @if test '$(srcdir)' = . ; then \
+ echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+ exit 1 ; \
+ fi
+ @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+ || { echo "ERROR: files left in build directory after distclean:" ; \
+ $(distcleancheck_listfiles) ; \
+ exit 1; } >&2
+check-am: all-am
+check: check-recursive
+all-am: Makefile config.h
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-hdr \
+ distclean-libtool distclean-local distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+ -rm -rf $(top_srcdir)/autom4te.cache
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) all install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--refresh check check-am clean clean-cscope clean-generic \
+ clean-libtool cscope cscopelist-am ctags ctags-am dist \
+ dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \
+ dist-xz dist-zip dist-zstd distcheck distclean \
+ distclean-generic distclean-hdr distclean-libtool \
+ distclean-local distclean-tags distcleancheck distdir \
+ distuninstallcheck dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+distclean-local:
+ rm -rf autom4te.cache
+ rm -f build-stamp configure-stamp
+ rm -rf lib/.deps
+ rm -rf src/.deps
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/NEWS b/NEWS
new file mode 100644
index 00000000..6a959142
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+See the ChangeLog file.
diff --git a/README b/README
new file mode 100644
index 00000000..731788b8
--- /dev/null
+++ b/README
@@ -0,0 +1,113 @@
+ README for sg3_utils
+ ====================
+Introduction
+------------
+sg3_utils is a package of utilities originally written to send individual
+SCSI commands to storage devices that used one of the SCSI command sets.
+These utilities can be divided into three groups:
+ - sg_raw: the user supplies the cdb (command descriptor block) and
+ optionally the size of the data-in and data-out buffers
+ - one command utilities: the majority of the utilities in this package
+ send one SCSI command. Their names start with "sg_" while the
+ remaining part of their name alludes to the command which is sent. For
+ example, "sg_inq" sends the SCSI INQUIRY command. Some utilities in
+ this group send one of a selection of commands, typically those
+ commands have a lot it common (e.g. sg_write_x).
+ - copy type utilities: sg_dd, sgp_dd and sgm_dd use the Unix dd command
+ as a template. sg_xcopy sends the SCSI EXTENDED COPY command which in
+ some cases can do offloaded copies. As well as copying some of these
+ utilities can compare if two data segments held on disks are the same.
+
+Platforms
+---------
+These utilities were written on Linux and should work from Linux kernel
+(lk) 2.4 through to the current series 5. The third group ("copy type")
+are only implemented on Linux, but a separate portable package/utility
+called ddpt implements similar functionality. The first two groups are
+implemented (i.e. ported) to Android, FreeBSD, Solaris and Windows. The
+Windows port uses either a Cygwin or MinGW (plus Msys) build environment
+(rather than Visual Studio).
+
+Library
+-------
+Many of these utilities share a lot of code (e.g. SCSI error messages)
+so a lot of repetition (potentially error prone) is saved by having a
+library called libsgutils or some variation on that name. Distributions
+(especially of Linux) have differing policies on how a library (and a
+package) should be named. For that reason this package is sometimes
+known as "sg3-utils" (i.e. the underscore is turned into a hyphen).
+Various other packages use libsgutils. The library interface is not
+altered from one package release, to the next, but the library interface
+may be expanded. If a utility from one release is used with a libsgutils
+from an earlier release, then the runtime linking may fail. Typically
+package managers take care of these details so that runtime linking
+errors should be rare.
+
+Command Sets
+------------
+SCSI command sets are not the only storage command sets in wide use, there
+are also ATA and NVMe command sets. There is a SCSI command set to
+translate SCSI commands to ATA commands (called SAT: SCSI to ATA
+Translation). SAT includes an ATA PASS-THROUGH SCSI command and sg_sat_*
+utilities (there are four) are examples of using SAT. The SAS transport
+(Serial Attached SCSI) can convey ATA commands through a SCSI/SAS domain
+via its Serial ATA Tunnelled Protocol (STP).
+
+NVMe command sets (e.g. Admin, NVM and MI) are relatively new. There was an
+early paper on a SCSI to NVMe Translation Layer (SNTL) but it hasn't been
+standardized. The sg_inq utility will send (and decode the response of) a
+SCSI INQUIRY command if the underlying device is a SCSI device. If the
+underlying device is a NVMe controller or namespace, then sg_inq will send
+a NVMe Admin Identify command and decode the response. The sg_ses utility
+(for SCSI Enclosure Services) also checks whether its underlying device is
+SCSI or NVME. In the NVMe case, sg_ses translates the SCSI SEND DIAGNOSTIC
+and READ DIAGNOSTIC RESULTS commands to the NVMe Management Interface (MI)
+SES Send and SES Receive commands respectively. The output of the sg_ses
+utility should be similar, irrespective of whether the "SES" device is
+SCSI or NVMe.
+
+The sg_raw utility may send NVMe Admin or NVM commands (as well as SCSI
+commands). One difficulty with a command-line utility invoking NVME
+commands is that those commands contain memory addresses for data-in (from
+the storage device) or data-out (toward the storage device) transfers. See
+the sg_raw manpage for how this difficulty is addressed.
+
+Documentation
+-------------
+Manual pages ("manpages") are the primary method of utility documentation.
+All utilities and scripts that are installed by this package have a
+manpage. There are utilities in the examples, testing and utils
+directories that are not installed and do not have manpages. Nearly
+all utilities have runtime help, usually invoked with either the '-h'
+short option or the '--help' long option. There is also an overarching
+manpage called "sg3_utils". All manpages are placed in chapter 8 which
+is for system administration commands/utilities.
+
+The sg3_utils package and some more complex utilities have html pages:
+ sg3_utils: https://sg.danny.cz/sg/sg3_utils.html
+ sg_ses: https://sg.danny.cz/sg/sg_ses.html
+ sg_dd: https://sg.danny.cz/sg/sg_dd.html
+
+A tarball (and zip) of all the manpages from the previous release are
+here:
+ https://sg.danny.cz/sg/p/sg3_utils_man_html.tgz
+ https://sg.danny.cz/sg/p/sg3_utils_man_html.zip
+
+There is a html rendering of the sg3_utils manpage in the same directory
+as this README file called sg3_utils.man8.html .
+
+The previous README file is now called README.details plus there are
+these OS specific files: README.freebsd , README.solaris , README.tru64
+and README.win32 . To know the current state of the package the ChangeLog
+file is the good reference.
+
+The author's primary source code repository uses subversion and is on
+the author's equipment (a RPi). One advantage of subversion is its
+revision numbers which are simply integers starting at 1 and ascending.
+For this package the current revision is 928 . The subversion repository
+is mirrored in git (using "git svn" tools) here:
+ https://github.com/doug-gilbert/sg3_utils
+
+
+Douglas Gilbert
+31st December 2021
diff --git a/README.details b/README.details
new file mode 100644
index 00000000..560ea55e
--- /dev/null
+++ b/README.details
@@ -0,0 +1,560 @@
+ README.details for sg3_utils
+ ============================
+Introduction
+============
+This package contains low level command line utilities for devices that use
+the SCSI command set. Originally the SCSI command set was associated
+exclusively with the SCSI Parallel Interface (SPI) transport. SPI has now
+almost been completely replaced by the Serial Attached SCSI (SAS) transport
+which also accepts the SCSI command set. Additionally many other storage
+related transports use the SCSI command set (amongst others); examples are
+ATAPI devices (CD/DVDs and tapes), USB mass storage devices (including those
+using the newer UAS[P]), Fibre Channel disks, IEEE 1394 storage devices (SBP
+protocol), iSCSI, FCoE and SOP devices. Even NVMe which has its own command
+set accepts SCSI commands in some contexts; one example is for enclosure
+management where NVME-MI has SES Send and SES Receive commands. SES refers
+to the SCSI Enclosure Services command set.
+
+This package originally targeted the Linux SCSI subsystem. Since most
+operating systems contain a SCSI command pass-through mechanism, many
+utilities within this package have been ported. This README mainly
+concentrates on Linux: see the README.freebsd file for the FreeBSD port,
+README.solaris for the Solaris port, the README.tru64 file for the Tru64
+(OSF) port and README.win32 for the Windows ports (of which there are two
+variants).
+
+Most utilities within the sg3_utils package work at the SCSI command level.
+For example the sg_inq utility issues a SCSI INQUIRY command and decodes the
+response. The COVERAGE file has a table containing a row for each SCSI
+command issued by this package; to the right of each row is the utility
+(sometimes more than one) that issue that SCSI command. The COVERAGE file
+has a second table for ATA commands usage.
+
+Some utilities interface at a slightly higher level, for example: sg_dd,
+sgm_dd and sgp_dd. These are closely related to the Unix dd command and
+typically issue a sequence of SCSI READ and WRITE commands to copy data.
+These utilities are relatively tightly bound to Linux and are not ported to
+other Operating Systems. A new utility called ddpt (in a package of the same
+name) is more generic while still allowing a copy to be done in terms of
+SCSI READ and WRITE commands. ddpt has been ported to other OSes.
+
+License
+=======
+All utilities and libraries have either a "2 clause" BSD license or are
+"GPL-2ed". The "2 clause" BSD license is taken from the FreeBSD project but
+drops the last paragraph that directly refers to the "FreeBSD project".
+That BSD license was updated from the "3 clause" to the newer "2 clause"
+version on 20180119. To save space various source code files refer to a
+file called "BSD_LICENSE" in the main, src and lib directories. The author's
+intention is that users may incorporate all or part of the code in their work
+as they please. Attribution is encouraged. Please check the code as other
+contributors (apart from the author) may also have copyright notices. For a
+list of contributors see the CREDITS file.
+
+
+Description
+===========
+A web site supporting the sg3_utils package can be found at
+https://sg.danny.cz/sg/sg3_utils.html . That page has a table of released
+versions for download. The most recent release or beta of sg3_utils may
+be found on this page: https://sg.danny.cz/sg in the News section.
+
+The predecessor to this package was called sg_utils. It is described in
+https://sg.danny.cz/sg/uu_index.html and old versions can be downloaded
+from the Downloads section of https://sg.danny.cz/sg .
+
+In the Linux 2.4 kernel series these utilities need to use the SCSI generic
+(sg) driver to access SCSI devices. The name of this package (i.e. sg3_utils)
+refers to version 3 of the SCSI generic (sg) driver which was introduced at
+the beginning of the 2.4 Linux kernel series. Significantly this added a new
+SCSI command interface structure (i.e. struct sg_io_hdr) that is more
+flexible than the older "sg_header" structure found in the sg driver in the
+2.2 and earlier Linux kernel series. The sg_io_hdr structure is also more
+flexible than the awkward (and limiting) interface to the
+SCSI_IOCTL_SEND_COMMAND ioctl supported by the Linux SCSI mid level. The
+version 3 sg driver also added the SG_IO ioctl that is synchronous (i.e. it
+issues the requested SCSI command and waits for the response (or a timeout)
+before the ioctl returns to the user space program that invoked it). The
+SG_IO ioctl is now supported in other parts of the Linux kernel in the 2.6
+series.
+
+In sg3_utils version 1.27 support has been added for the Linux bsg driver
+which use the sg version 4 interface. There seems no point in renaming
+this package sg4_utils. The existing utilities just silently support either.
+Currently the source build must be able to see the /usr/include/linux/bsg.h
+file. Then at run time the /proc/devices pseudo file needs to have an entry
+for the bsg driver (appeared around lk 2.6.28). With this in place each
+utility at run time checks the device it has been given and if it is a char
+device whose major number matches the bsg entry in /proc/devices then the
+sg v4 interface is used. Otherwise the sg v3 interface is used.
+
+Utilities that wish to use the asynchronous SCSI command interface (i.e. via
+a write() read() sequence) or issue special "commands" (e.g. bus and device
+resets) still need to use the Linux sg driver. Note that various
+drivers (e.g. cdrom/sr) have different open() flag and permissions policies
+that the user may need to take into account.
+
+If users have problems or questions about them please contact the author.
+Documentation for the Linux sg device driver can be found at:
+https://sg.danny.cz/sg/p/sg_v3_ho.html . This is written in DocBook and the
+original xml can be found in the same directory with the ".xml" extension.
+Postscript and pdf renderings are also in that directory. Older documentation
+for the sg version 3 driver can be found at:
+https://sg.danny.cz/sg/p/scsi_generic_v3.txt .
+
+To save the repetition of common code (e.g. SCSI error processing) and
+reduce the size of the executable files, a shared library called
+libsgutils<num>.so (its Linux name) is created during the build process.
+That library is built from the contents of the include and lib
+subdirectories. The header files in the include subdirectory can be seen
+as the API of libsgutils and are commented with that in mind. The SCSI
+pass-through code for the supported operating systems is found in the lib
+subdirectory with names like sg_pt_linux.c and sg_pt_win32.c .
+
+Various distributions (of Linux mainly) distribute sg3_utils as 3
+installable packages. One is a package containing the shared library
+discussed above (e.g. libsgutils2-2_1.33-0.1_i386.deb). A second package
+contains the utilities (e.g. sg3-utils_1.33-0.1_i386.deb) and depends on the
+first package). Finally there is an optional package that contains header
+files and a static library (e.g. libsgutils2-dev_1.33-0.1_i386.deb). This
+final package is only needed to build other packages (e.g. sdparm) that
+wish to use the sg3_utils shared library.
+
+All the utilities in the src subdirectory have "man" pages that are
+placed in the doc subdirectory. There is also a sg3_utils (8) man page that
+summarizes common facilities including exit statuses. Additional
+information (including each utility's version number) can be found towards
+the top of each ".c" file corresponding to the utility name.
+
+The sg driver in Linux can be seen as having 3 distinct versions:
+
+ v1 lk < 2.2.6 sg_header based relatively unchanged since 1992
+ v2 lk >= 2.2.6 enhanced sg_header interface structure [1999/4/16]
+ v3 lk >= 2.4 additional sg_io_hdr interface structure [2001/1/4]
+ v3 lk >= 2.6 same interface as found in lk 2.4 [2.6.0: 2003/12/18]
+
+and the bsg driver supports the sg v4 interface and was added around
+lk 2.6.28 . This package is targeted at "v3" and "v4". Another package called
+"sg_utils" is targeted at "v2" and to a lesser extent "v1". The "sg_utils"
+package has a subset of the utilities found in this package.
+
+In Linux some sg driver ioctls (notably SG_IO) are defined for many block
+devices in lk 2.6 series. In practice this means all SCSI block devices,
+ATAPI block devices (mainly CD, DVD and BD optical devices) but _not_ ATA
+disks, depending on which kernel configuration options, can be accessed by
+the utilities in this package. SATA disks that use the libata kernel library
+(or some other SCSI to ATA Translation (SAT) Layer (SATL)) accept SCSI
+commands and thus are supported. Support for the SG_IO as been added to the
+scsi tape driver (st) in lk 2.6.6 .
+
+In the src directory the bulk of the utilities are written in relatively
+clean POSIX compliant C code with Linux specific system calls and structures
+removed and placed in Linux specific files in the lib directory. A small
+number of utilities in the src directory do contain Linux specific logic
+and are not ported to other OSes (e.g. sg_dd). One utility, sg_scan, has
+two separate implementations, one for Linux (sg_scan_linux.c) and one for
+Windows (sg_scan_win32.c). The src-lib directory split approach allows
+FreeBSD, Solaris, Tru64 and Windows specific code to be isolated to a few
+files in the lib directory whose interfaces match those of the Linux
+specific code.
+
+Darwin is not supported because the Apple folks do not want to give their
+users a pass-through SCSI interface. The author has read about creative
+hackers using a VM containing a real OS to circumvent the Apple restriction.
+
+C standard is C11
+==================
+The C code in this package is written for portability rather than speed.
+It assumes a level of C99 compliance (the C standard prior to C11) and
+favours POSIX system and library calls over OS specific calls.
+
+The C code is written in a C++ friendly way and is checked from time to
+time that it compiles clean with C++. To accommodate C++ certain C99
+constructs such as designated initializers cannot be used. To build
+with C++, C++11 (i.e. the C++ standard from 2011) or later is required.
+Finding a common C and C++ syntax for zeroing stack variables (including
+aggregates) may need to wait until C23 allows this syntax:
+ struct example_t ex1 {};
+which C++ introduced in C++11. In the meantime the SG_C_CPP_ZERO_INIT
+define (hack) does this.
+
+The author has not seriously attempted to build this code on MSVC (aka
+Visual Studio). There are a few roadblocks (that may be overcome in the
+future) that include MSVC being basically a C++ compiler, not a C/C++
+compiler. For some reason MSVC only claims C89 compliance (i.e. the first
+C standard from 1989). MSVC 2013 and 2015 are moving closer to C99
+compliance and may be sufficient to compile this package. Another problem
+is the assumption of the availability of basic Unix system calls such as
+open(). Nearly 20 years ago Microsoft indicated (promised ?) that it
+would move in the direction of POSIX compliance, but very little ever
+happened. "Talk is cheap, there should be a tax on it."
+
+Building
+========
+This package is designed to be built with the usual:
+ "./configure ; make ; make install"
+sequence. In some situations that may need to be prefixed by a call to
+the "./autogen.sh" script which invokes autoconf and automake. That in turn
+may require packages containing those utilities to be installed. The
+libtool utility is also required. Naturally a C compiler is required
+and due to the vagaries of libtool a C++ compiler also.
+
+The "./configure" takes many command line options with the defaults
+being usually sufficient to start with. One quirk is that the location
+of the installation is under the /usr/local directory. So the sg_inq
+utility will be installed at /usr/local/bin/sg_inq . This is controlled
+by the "--prefix=<directory>" option which defaults to
+"--prefix=/usr/local". As an example to install the executables in /usr/bin
+and disable the creation of the shared library (libsgutils<num>.so) this
+invocation could be used: "./configure --prefix=/usr --disable-shared".
+To reduce the size of an executable as well try this:
+"./configure --prefix=/usr --disable-shared --disable-scsistrings".
+Also --disable-shared will produce (relatively) "static" executables in
+the src directory that are easier to debug. And
+"./configure --enable-debug" will compile with more debug type options,
+including more compiler checks and defining "DEBUG" within the src and
+lib source files. Most utilities in the src directory set '-vv' (i.e.
+equivalent to calling "--verbose" twice) when "DEBUG" is set.
+
+In Linux there are package build files for "rpm" based and for "deb" based
+systems. The 'sg3_utils.spec' file in the main directory can be used like
+this: 'rpmbuild -ba sg3_utils.spec' in a rpmbuild tree SPECS directory.
+To cross build or make a more widely distributable package then the --target
+option may be useful: 'rpmbuild --target=i386 -ba sg3_utils.spec' or
+'rpmbuild --target=x86_64 -ba sg3_utils.spec' . The sg3_utils.spec file
+in the main directory targets Red Hat systems, an alternative "spec" file
+for Suse systems has been placed under the 'suse' directory.
+
+The 'build_debian.sh' script should build several "deb" packages and place
+them in the parent directory. In debian based systems doing
+a 'apt-get install build-essential' is one way to get most of build
+environment needed if it has not already been loaded. There are now some
+problems with this script and the superseded Debian 4.0 ("etch"). See
+debian/README.debian4 for a workaround. Amongst other things debian
+builds are sensitive to the value in the debian/compat file. If it
+contains "7" then it works on lenny and gives warning on squeeze (but
+fails on the earlier etch).
+
+Warning
+=======
+Many devices use SCSI command sets over transport protocols not normally
+associated with SCSI (as defined at https://www.t10.org ). Some of these
+devices react poorly (e.g. lock up) when sent SCSI commands that they don't
+support. Even sending a supported SCSI command with a field set to an
+unexpected value can cause problems. [The author is talking about billions
+of USB devices with horrible SCSI implementations.]
+
+For example, all "SCSI" devices must support the INQUIRY command which the
+SCSI-2 standard says should request a 36 byte response. However later SCSI
+standards (e.g. SPC-2) have increased that length but some SCSI devices lock
+up when they receive a request for anything other than a 36 byte response.
+
+Any well implemented "SCSI" device should react sensibly when a utility in
+sg3_utils sends a SCSI command that it doesn't support. Unfortunately this
+cannot be guaranteed.
+
+Prior to lk 2.6.29 USB mass storage limited sense data to 18 bytes which
+caused problems for certain types of descriptor based sense data. An
+example of this is the SCSI ATA PASS-THROUGH command with the CK_COND bit
+set.
+
+
+Utilities
+=========
+Here is list in alphabetical order of utilities found in the 'src'
+subdirectory of the sg3_utils package:
+ sginfo, sg_bt_ctl, sg_compare_and_write, sg_copy_results, sgm_dd, sgp_dd,
+ sg_dd, sg_decode_sense, sg_emc_trespass, sg_format, sg_get_config,
+ sg_get_elem_status, sg_get_lba_status, sg_ident, sg_inq, sg_logs,
+ sg_luns, sg_map, sg_map26, sg_modes, sg_opcodes, sg_persist, sg_prevent,
+ sg_raw, sg_rbuf, sg_rdac, sg_read, sg_read_attr, sg_readcap,
+ sg_read_block_limits, sg_read_buffer, sg_read_long, sg_reassign,
+ sg_referrals, sg_rem_rest_elem, sg_rep_density, sg_rep_pip, sg_rep_zones,
+ sg_request, sg_reset, sg_rmsn, sg_rtpg, sg_safte, sg_sanitize,
+ sg_sat_identify, sg_sat_phy_event, sg_sat_read_gplog, sg_sat_set_features,
+ sg_scan, sg_seek, sg_senddiag, sg_ses, sg_ses_microcode, sg_start,
+ sg_stpg, sg_stream_ctl, sg_sync, sg_test_rwbuff, sg_timestamp, sg_turs,
+ sg_unmap, sg_verify, sg_vpd, sg_write_buffer, sg_write_long,
+ sg_write_same, sg_write_verify, sg_write_x, sg_wr_mode, sg_xcopy, sg_zone,
+ sg_z_act_query
+
+Each of the above utilities depends on header files found in the 'include'
+subdirectory and library code found in the 'lib' subdirectory. Associated
+man pages are found in the 'doc' subdirectory. Additional programs found
+in the 'archive', 'examples' and 'utils' subdirectories in not build by the
+top level build infrastructure. Linux binary distributions of the sg3_utils
+package (e.g. "rpm" and debian packages) typically contain the shared
+library, the utilities found in the 'src' subdirectory, their associated man
+pages and some documentation files (e.g. README, INSTALL, CREDITS, COPYING
+and COVERAGE). See the INSTALL file for generic instructions about building
+with autotools (e.g. ./configure ).
+
+Man pages can be read (without building and installing the package) by
+going to the 'doc' subdirectory and executing something like this:
+ $ man ./sg_dd.8
+
+To see which SCSI commands (and ATA commands) are used by these utilities
+refer to the COVERAGE file.
+
+Here is a list in alphabetical order of utilities found in the 'examples'
+subdirectory:
+ - sg_excl, scsi_inquiry, sg_sat_chk_power, sg__sat_identify,
+ sg__sat_phy_event, sg__sat_set_features, sg_sat_smart_rd_data,
+ sg_simple1, sg_simple2, sg_simple3, sg_simple4, sg_simple5,
+ sg_simple16
+
+Also in that subdirectory is a script to test sg_persist, an example data
+file for sg_persist (called "transport_ids.txt") and an example data file for
+sg_reassign (called "reassign_addr.txt"). There are several scripts
+for 'sg_senddiag -pf -raw=-' that will put some SAS disk phys into
+a "compliant jitter tolerance pattern" (CJTPAT).
+
+The 'testing' subdirectory contains source and a Makefiles to test
+kernel pass-through and associated drivers, mainly for Linux. There is
+both C code (with the extension ".c") and C++ code (with the extension
+".cpp"). There is a "Makefile" to build the C + C++ code. The Makefile
+depends on some object files from the "lib" subdirectory. So a sequence
+like this may be required prior to invoking make: "cd <top_of_package> ;
+./configure ; cd lib ; make ; cd ../testing".
+
+Here is a list in alphabetical order of utilities found in the 'testing'
+subdirectory:
+ - bsg_queue_tst, sgh_dd (C++), sg_iovec_tst, sg_queue_tst, sg_sense_tst,
+ sg_tst_async (C++), sg_tst_context (C++), sg_tst_excl (C++),
+ sg_tst_excl2 (C++), sg_tst_excl3 (C++)
+
+The 'utils' subdirectory contains source and a Makefile to build "hxascdmp"
+which accepts binary data from stdin (or a file on the command line) and
+outputs an ASCII-HEX and ASCII representation of it. It is similar to the
+Unix od command. There is also code to sg_chk_asc.c which checks a given
+text file (typically a copy of https://www.t10.org/lists/asc-num.txt ) and
+checks it against the asc/ascq text strings held in sg_lib_data.c .
+
+The 'doc' subdirectory contains a README file containing the urls of
+various related documents.
+
+The 'scripts' subdirectory contains some Bourne (bash) shell scripts that
+rely on utilities in the main directory. One script uses the sdparm utility.
+These scripts are described in the scripts/README file and have usage
+messages.
+
+
+Notes for utilities without man pages
+=====================================
+These utils are found in the 'examples' subdirectory.
+
+The "scsi_inquiry" program shows the use of the SCSI_IOCTL_SEND_COMMAND
+ioctl to send a SCSI INQUIRY command. That ioctl() is supported by the
+SCSI sub system mid level and so is common to all sd, sr, st and sg devices.
+That ioctl is deprecated in the lk 2.6 series. This program has been placed
+in the "examples" subdirectory.
+
+"sg_simple1" and "sg_simple2" are example programs demonstrating calls
+to the SCSI INQUIRY and TEST UNIT READY commands. They only differ in their
+error processing: sg_simple1 uses sg_lib.[hc] for error processing while
+sg_simple2 does its own more primitive checks.
+
+"sg_simple3" tests out user space scatter gather added to the version 3
+sg driver.
+
+"sg_simple4" shows the INQUIRY command using mmap-ed IO to obtain its
+response buffer.
+
+"sg_simple5" also sends and INQUIRY and TEST UNIT READY commands. It
+uses the generic pass through mechanism based on sg_pt.h . It will
+currently build in Linux and FreeBSD (with "make -f Makefile.freebsd").
+It has extensive error checking code.
+
+"sg_simple16" attempts to send a 16 byte SCSI command, READ_16, to the
+scsi device. This is only supported for lk >= 2.4.15 and for adapter
+drivers that indicate that they have 16 byte CDB capability (otherwise
+DID_ABORT will appear in the host_status).
+
+"sg_sat_chk_power" attempts to push an ATA CHECK POWER MODE command
+through the SAT-defined ATA PASS_THROUGH (16) SCSI command. That
+ATA command needs to read the "FIS" registers after the command is
+completed which involves using the ATA Status Return (sense data)
+descriptor (as defined in SAT).
+
+"sg_sat_smart_rd_data" attempts to push an ATA SMART/READ DATA command
+through the SAT-defined ATA PASS_THROUGH (16) SCSI command. If
+successful, the 256 word (512 byte) response is output.
+
+"sg_tst_excl" and "sg_tst_excl2" use multiple threads to bombard the
+given device with O_EXCL open flags, so only one should succeed at a
+time. While holding O_EXCL control a thread attempts a double increment
+on an integer in the given LBA. If the integer starts even (after the
+first read) then it should remain even if the O_EXCL flag is doing its job.
+The "sg_tst_excl" variant uses the Linux SG_IO v3 interface while the
+"sg_tst_excl2" uses the more generic sg_pt infrastructure.
+
+"sg_tst_excl3" is a variant of "sg_tst_excl2". "sg_tst_excl3" only does
+the double increment from the first thread, each time using O_EXCL on
+open. The remaining threads check the value is even, each time doing
+an open without the O_EXCL flag.
+
+"bsg_queue_tst" sends an INQUIRY command via the Linux SG_IO v4 interface
+which is used by the bsg driver. So it will take device names like
+"/dev/bsg/6:0:0:0". It tests if sending repeated INQUIRYs with
+the BSG_FLAG_Q_AT_HEAD or BSG_FLAG_Q_AT_TAIL flag makes any difference.
+
+"sg_tst_async" is a test harness for the Linux sg driver. It is multi
+threaded, submitting either TEST UNIT READY, READ(16) or WRITE(16) SCSI
+commands asynchronously. Each thread opens a file descriptor and submits
+those commands up to the queue limit (sg driver has a per file descriptor
+queue limit of 16). Multiple threads doing the same thing act as a
+multiplier to that queue limit.
+
+
+NVME Support
+============
+Firstly the author has no intention of extending this package to contain
+general purpose NVMe utilities. That leaves the areas where SCSI overlaps
+with NVMe. There was a SCSI to NVMe Translation Layer (SNTL) driver in the
+Linux kernel based on a white paper from NVM Express. Intel has withdrawn
+that driver and T10 (SCSI) and NVM Express have made no further attempts
+to standardize a SNTL. Given the SCSI to ATA Translation Layer (SATL) which
+is standardized by T10, it is pretty clear what a SNTL should do.
+
+The NVMe Management Interface (NVME-MI) committee have decided to use SES-3
+standard from T10 via the newly added SES Send and SES Receive MI commands.
+So the sg_ses utility and this package's library have been extended to use
+these commands when a NVMe device (typically a disk enclosure) is detected.
+This has been tested by a disk vendor who is happy with the results. Other
+user reports are welcome as the author does not have equipment to test
+this.
+
+Other utilities in this package that use the SES Send and Receive commands,
+or the SNTL in the library are sg_senddiag, sg_inq, sg_raw and sg_readcap.
+
+
+Command line processing
+=======================
+These utilities can be divided into 3 groups when their handling of command
+line arguments is considered:
+ - ad hoc, typically in a short form only, sometimes longer (e.g.
+ "sg_logs -pcb /dev/sdc")
+ - inspired by the dd Unix command (e.g. sg_dd, sgm_dd, sgp_dd, sg_read)
+ - recent utilities use "getopt_long" (see "man getopt_long")
+ type command lines. These have short form (starting with "-")
+ and corresponding longer form (starting with "--") options.
+
+The older utilities that use ad hoc options, in alphabetical order:
+ - sg_emc_trespass, sginfo(1/2), sg_inq, sg_logs, sg_map, sg_modes,
+ sg_opcodes, sg_rbuf, sg_rdac, sg_readcap, sg_reset, sg_scan (Linux),
+ sg_senddiag, sg_start, sg_test_rwbuf, sg_turs
+In sg3_utils version 1.23 the following utilities from this group were
+converted to have a dual getopt_long/ad_hoc interface, defaulting to
+the getop_long interface:
+ - sg_inq, sg_logs, sg_modes, sg_opcodes, sg_rbuf, sg_readcap,
+ sg_senddiag, sg_start, sg_turs
+These can be switched back to the older (backward compatible) ad hoc
+interface by defining the SG3_UTILS_OLD_OPTS environment variable
+or using '-O' as the first command line option.
+
+The more recent utilities that use "getopt_long" only are:
+ - sg_bt_ctl, sg_compare_and_write, sg_decode_sense, sg_format,
+ sg_get_config, sg_get_lba_status, sg_ident, sg_luns, sg_map26,
+ sg_persist, sg_prevent, sg_raw, sg_read_attr, sg_read_block_limits,
+ sg_read_buffer, sg_read_long, sg_reassign, sg_referrals, sg_rep_pip,
+ sg_rep_zones, sg_requests, sg_rmsn, sg_rtpg, sg_safte, sg_sanitize,
+ sg_sat_identify, sg_sat_phy_event, sg_sat_read_gplog,
+ sg_sat_set_features, sg_scan(w), sg_seek, sg_ses, sg_ses_microcode,
+ sg_stpg, sg_stream_ctl, sg_sync, sg_test_rwbuf, sg_timestamp, sg_unmap,
+ sg_verify, sg_vpd, sg_write_buffer, sg_write_long, sg_write_same,
+ sg_write_verify, sg_write_x, sg_wr_mode, sg_zone, sg_z_act_query
+
+
+Dangerous code
+==============
+This C code snippet:
+ unsigned char uc = 0x80;
+ uint64_t ull;
+ ull = (uc << 24);
+Somewhat surprisingly sets ull to:
+ ull: 0xffffffff80000000
+This result is due to the 'unary conversion' of uc to a (32 bit signed)
+'int' before the shift. The resultant type from the shift is also an int
+and it has its top bit set so there is sign extension when it is assigned
+into a 64 bit unsigned integer. Making sure there is no conversion to 'int'
+solves the problem. In this case if uc is declared as unsigned int the
+result will be as expected (i.e. 0x80000000).
+
+
+Bypassing the somewhat dangerous shift operators
+================================================
+The shift operators in C are "<<" and ">>". They can be dangerous (as shown
+in the above section) or tedious and hence error prone to use. However they
+are often needed to cope with the translation of integers on the host OS to
+the corresponding representation within a SCSI command or parameter data
+moved to or from a SCSI device. The Logical Block Address (LBA) is a good
+example; it is either 32 or 64 bits long typically (i.e. 4 or 8 bytes
+respectively). The host machine representation may be big or little endian
+and may prefer or require alignment to a particular memory address boundary
+(e.g. module 4 (or in 'C' code: "(lba % 4) == 0")). For SCSI commands and
+the parameter data moved to or from a SCSI device, the integer
+representation is big endian and it is unaligned.
+
+Recent versions of this package have replaced the explicit use of the C
+shift operators with a group of functions modelled on those found in the
+Linux kernel. These functions contain either "get_unaligned" or
+"put_unaligned" in their names and are found in the asm/unaligned.h
+header. This package contains the sg_unaligned.h header that implements
+a similar set of functions. The current implementation favours correctness
+over speed. The functions in the package use a "sg_" prefix but otherwise
+use the same function name as the Linux kernel for the same action.
+
+An example of the change made to a snippet of sg_write_buffer.c may
+clarify this change. The old code was:
+
+ wbufCmdBlk[3] = (unsigned char)((buffer_offset >> 16) & 0xff);
+ wbufCmdBlk[4] = (unsigned char)((buffer_offset >> 8) & 0xff);
+ wbufCmdBlk[5] = (unsigned char)(buffer_offset & 0xff);
+
+and it has been replaced by:
+
+ sg_put_unaligned_be24(buffer_offset, wbufCmdBlk + 3);
+
+The Linux kernel only supplies "unaligned" functions for 16, 32 and 64
+bit quantities. SCSI commands also have cases of 24 and 48 bit numbers
+so sg_unaligned.h contains support for those plus a variant where the
+byte length is passed as an argument.
+
+The unaligned functions are inlined for speed (at the possible expense of
+space) and now have specializations depending whether the host is big or
+(more likely) little endian. These functions can be broken down to a
+memcpy() and optionally a byte-swap for 16, 32 and 64 bit operations.
+The memcpy() takes care of alignment while the byte-swap (bswap_16(),
+bswap_32() and bswap_64() ) addresses integer endianness. If the host is
+little endian and a little endian variant of the unaligned functions is
+requested, then no byte-swap is required. These specializations can be
+"compiled out" with this configure option: './configure
+--disable-fast-lebe' in which case the classic "C shifting" technique is
+used to implement all the unaligned functions.
+
+Associated with the above change, fixed length integer types seem a better
+fit for SCSI command and parameter integers than the traditional integer
+types in the C language. Fixed length integer types were standardized in
+C99 and require the inclusion of <stdint.h>. For example this means for
+an integer that will represent a 64 bit LBA, to favour using "uint64_t"
+over the "unsigned long long" type. Also "unsigned char" has mostly been
+replaced by "uint8_t" as the 8 bit (unsigned) byte type; "char" is still
+used for ASCII text.
+
+
+Coding Style
+============
+Everyone has their own C/C++ coding style and the author is no different.
+In terms of the GNU indent command:
+ indent -i4 -il0 -nut -br -npcs -ncs -ce
+is pretty close. That is similar to the Linux kernel coding style but
+with 4 space indentations and no tabs.
+
+
+Other SCSI and storage tools
+============================
+See https://sg.danny.cz/sg/tools.html
+
+
+Douglas Gilbert dgilbert@interlog.com
+26th August 2022
diff --git a/README.freebsd b/README.freebsd
new file mode 100644
index 00000000..411f0984
--- /dev/null
+++ b/README.freebsd
@@ -0,0 +1,164 @@
+Introduction
+============
+The FreeBSD port of sg3_utils contains those utilities that are _not_
+specific to Linux. In some cases the FreeBSD camcontrol command supplies
+similar functionality; for example 'sg_map' is similar to
+'camcontrol devlist'.
+
+The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many
+Linux idiosyncrasies to be easily ported. A new package called 'ddpt'
+contains a utility with similar functionality to sg_dd and ddpt is available
+for FreeBSD.
+
+Supported Utilities
+===================
+Here is a list of utilities that have been ported:
+ sg_bg_ctl
+ sg_compare_and_write
+ sg_decode_sense
+ sg_format
+ sg_get_config
+ sg_get_elem_status
+ sg_get_lba_status
+ sg_ident
+ sg_inq [dropped ATA IDENTIFY DEVICE capability]
+ sg_logs
+ sg_luns
+ sg_modes
+ sg_opcodes
+ sg_persist
+ sg_prevent
+ sg_raw
+ sg_rdac
+ sg_read_block_limits
+ sg_read_buffer
+ sg_read_long
+ sg_readcap
+ sg_reassign
+ sg_referrals
+ sg_rep_pip
+ sg_rep_zones
+ sg_requests
+ sg_rmsn
+ sg_rtpg
+ sg_safte
+ sg_sanitize
+ sg_sat_identify
+ sg_sat_phy_event
+ sg_sat_set_features
+ sg_seek
+ sg_senddiag
+ sg_ses
+ sg_start
+ sg_stpg
+ sg_stream_ctl
+ sg_sync
+ sg_turs
+ sg_verify
+ sg_unmap
+ sg_vpd
+ sg_wr_mode
+ sg_write_buffer
+ sg_write_long
+ sg_write_same
+ sg_write_verify
+ sg_write_x
+ sg_zone
+
+Most utility names are indicative of the main SCSI command
+that they execute. Some utilities are slightly higher level, for
+example sg_ses fetches SCSI Enclosure Services (SES) status pages and
+can send control pages. Each utility has a man page (placed in
+section 8). An overview of sg3_utils can be found at:
+https://sg.danny.cz/sg/sg3_utils.html .
+A copy of the "sg3_utils.html" file is in the "doc" subdirectory.
+
+
+The executables and library can be built from the source code in
+the tarball and installed with the familiar
+"./configure ; make ; make install" sequence. If this fails try
+running the "./autogen.sh" script prior to that sequence. There
+are generic instruction on configure and friend in the INSTALL file.
+
+Some man pages have examples which use Linux device names which
+hopefully will not confuse the FreeBSD users.
+
+Device naming
+=============
+In FreeBSD disks have block names like '/dev/da0' with a corresponding
+pass-through device name like '/dev/pass0'. Use this command:
+"camcontrol devlist" to see that SCSI devices available. To list NVMe
+devices: "nvmecontrol devlist" can be used. Any many, but not all
+contexts, the device name can be used without the '/dev/' prefix.
+FreeBSD is relatively unique in this respect and support for this
+abbreviated form has been broken in this package and fixed in
+sg3_utils release 1.46 .
+
+Device naming for NVMe is a bit more complex. Controllers have names
+like /dev/nvme0 and namespaces /dev/nvme0ns1 . Partitions are not
+supported on /dev/nvme0ns1 type nodes. Instead there are /dev/nvd0
+and /dev/nvd0p<m> where <m> is th partition number starting at 1.
+The nvd driver (written by Intel) is not CAM compatible and has its
+own utility nvmecontrol which has similar capabilities as camcontrol
+has for CAM devices. In FreeBSD release 12 the nda driver was
+introduced with names like /dev/nda0 and /dev/nda0n<m>. The difference
+is that nda is CAM compatible. From the point of view of this package,
+the nda driver is preferred as CAM supports NVMe command timeouts and
+the error processing is more mature.
+
+FreeBSD installation
+====================
+The traditional './configure ; make ; make install' sequence from the
+top level of the unpacked tarball will work on FreeBSD. But the man pages
+will be placed under the /usr/local/share/man directory which unfortunately
+is not on the standard manpath. One solution is to add this path by
+creating a file with a name like local_share.conf in the
+/usr/local/etc/man.d/ directory and placing this line in it:
+ MANPATH /usr/local/share/man
+
+FreeBSD 9.0 has a "ports" entry for sg3_utils under the
+/usr/ports/sysutils directory. It points to version 1.28 of sg3_utils
+which is now a bit dated. It could be used as a template to point
+to more recent versions.
+
+kFreeBSD
+========
+sg3_utils can be built into a Debian package for kFreeBSD using the
+./build_debian.sh script in the top level directory. This has been tested
+with Debian 6.0 release.
+
+Details
+=======
+Most of the ported utilities listed above use SCSI command functions
+declared in sg_cmds_*.h headers . Those SCSI command functions are
+implemented in the corresponding ".c" files. The ".c" files pass SCSI
+commands to the host operating system via an interface declared in sg_pt.h .
+There are currently five implementations of that interface depending on
+the host operating system:
+ - sg_pt_linux.c
+ - sg_pt_freebsd.c
+ - sg_pt_osf1.c [Tru64]
+ - sg_pt_win32.c
+ - sg_pt_solaris.c
+
+The sg_pt_freebsd.c file uses the FreeBSD CAM SCSI pass through mechanism.
+Hence only FreeBSD device nodes that support CAM can be used. These can be
+viewed with the "camcontrol devlist" command. To access ATAPI devices (e.g.
+ATAPI DVD drives) the kernel may need to be configured with the "atapicam"
+device.
+
+Attempts to send SCSI commands with data-in or data-out buffers around 64 KB
+and larger failed on a FreeBSD 7.0 with an "argument list too long" error
+message. There is an associated kernel message (viewable with dmesg) that an
+attempt has been made to map <n> bytes which is greater than
+DFLTPHYS(65536). Still a problem in FreeBSD 8.1 . Due to CAM overhead the
+largest power of 2 that can fit through with one command is 32768 bytes (32
+KB).
+
+FreeBSD 9.0 is the most recent version of FreeBSD tested with these
+utilities.
+
+
+
+Douglas Gilbert
+1st May 2021
diff --git a/README.iscsi b/README.iscsi
new file mode 100644
index 00000000..917190a2
--- /dev/null
+++ b/README.iscsi
@@ -0,0 +1,37 @@
+iSCSI support for sg3-utils is available from external patches.
+
+To build sg3-utils from sources and activate built-in iSCSI support
+you need both sg3-utils and the external user-space iSCSI library hosted at :
+
+https://github.com/sahlberg/libiscsi
+
+This library provides a client library for accessing remote iSCSI
+devices and also comes with patches to the sg3-utils source code
+distribution to compile a special version of sg3-utils with iSCSI
+support.
+
+No support for iSCSI is provided by the sg3-utils maintainer.
+
+
+
+Once sg3-utils is compiler and installed with libiscsi support, you
+can specify remote iSCSI devices through a special URL format instead
+of the normal /dev/* syntax.
+
+Example:
+
+sg_inq iscsi://ronnie%password@10.1.1.27/iqn.ronnie.test/1
+standard INQUIRY:
+ PQual=0 Device_type=0 RMB=0 version=0x05 [SPC-3]
+ [AERC=0] [TrmTsk=1] NormACA=0 HiSUP=0 Resp_data_format=2
+ SCCS=0 ACC=0 TPGS=0 3PC=0 Protect=0 BQue=0
+ EncServ=0 MultiP=0 [MChngr=0] [ACKREQQ=0] Addr16=0
+ [RelAdr=0] WBus16=0 Sync=0 Linked=0 [TranDis=0] CmdQue=1
+ [SPI: Clocking=0x0 QAS=0 IUS=0]
+ length=66 (0x42) Peripheral device type: disk
+ Vendor identification: IET
+ Product identification: VIRTUAL-DISK
+ Product revision level: 0001
+ Unit serial number: beaf11
+
+
diff --git a/README.sg_start b/README.sg_start
new file mode 100644
index 00000000..22034d4b
--- /dev/null
+++ b/README.sg_start
@@ -0,0 +1,38 @@
+Hi,
+
+you can use sg_start to start (spin-up, 1) and stop (spin-down, 0) devices.
+I also offers a parameter (-s) to send a synchronize cache command to a
+device, so it should write back its internal buffers to the medium.
+
+Be aware that the Linux SCSI subsystem at this time does not automatically
+starts stopped devices, so stopping a device which is in use may have fatal
+results for you.
+
+So, you should apply with care.
+I use it in my shutdown script at the end (before the poweroff command):
+
+# SG_SHUG_NOS is set in my config file rc.config
+# SG_SHUT_NOS="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"
+if test -x /bin/sg_start; then
+ if test "`basename $command`" = "reboot"; then
+ for no in $SG_SHUT_NOS;
+ do /bin/sg_start /dev/sg$no -s >/dev/null 2>&1;
+ done
+ else
+ for no in $SG_SHUT_NOS;
+ do /bin/sg_start /dev/sg$no -s 0 >/dev/null 2>&1;
+ done
+ fi
+fi
+
+Enjoy!
+Kurt Garloff
+
+
+Postscript
+==========
+sg_start has been reworked to allow a block device (e.g. /dev/sda) in
+addition to the sg device name (e.g. /dev/sg0) in the lk 2.6 series.
+sg_start now has more command line options, see its man page.
+
+ Douglas Gilbert <dgilbert at interlog dot com> 2004/5/8
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 00000000..7dca2b7c
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,168 @@
+Please Note:
+>>> Up to and including sg3_utils-1.33 the Solaris code was built
+>>> and tested on an OpenSolaris VM run with VirtualBox on Ubuntu
+>>> 11.10 . Now with Ubuntu 12.04 those VMs crash immediately when
+>>> started with VirtualBox. Further, Oracle (who owns SUN and thus
+>>> Solaris) no longer supports OpenSolaris and its package
+>>> repository has been withdrawn. The author can find no generic VMs
+>>> for Oracle Solaris 11 that run on VirtualBox or VMWare. The author
+>>> is also displeased with the withdrawal of the Open Software
+>>> OS and is disinclined to build a Solaris 11 system just to
+>>> virtualize it.
+>>> So as of sg3_utils-1.34 the Solaris port is provided "as-is" without
+>>> testing on a Solaris platform.
+
+Douglas Gilbert
+13th October 2012
+
+
+
+Introduction
+============
+The Solaris port of sg3_utils contains those utilities that are
+_not_ specific to Linux.
+
+The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many
+Linux idiosyncrasies to be easily ported. A new package called 'ddpt'
+contains a utility with similar functionality to sg_dd and is available
+for Solaris.
+
+Supported Utilities
+===================
+Here is a list of utilities that have been ported:
+ sg_bg_ctl
+ sg_compare_and_write
+ sg_decode_sense
+ sg_format
+ sg_get_config
+ sg_get_elem_status
+ sg_get_lba_status
+ sg_ident
+ sg_inq [dropped ATA IDENTIFY DEVICE capability]
+ sg_logs
+ sg_luns
+ sg_modes
+ sg_persist
+ sg_opcodes
+ sg_prevent
+ sg_raw
+ sg_rdac
+ sg_read_block_limts
+ sg_read_buffer
+ sg_read_long
+ sg_readcap
+ sg_reassign
+ sg_referrals
+ sg_rep_pip
+ sg_rep_zones
+ sg_requests
+ sg_rmsn
+ sg_rtpg
+ sg_safte
+ sg_sanitize
+ sg_sat_identify
+ sg_sat_phy_event
+ sg_sat_set_features
+ sg_seek
+ sg_senddiag
+ sg_ses
+ sg_start
+ sg_stpg
+ sg_stream_ctl
+ sg_sync
+ sg_turs
+ sg_unmap
+ sg_verify
+ sg_vpd
+ sg_wr_mode
+ sg_write_buffer
+ sg_write_long
+ sg_write_same
+ sg_write_verify
+ sg_write_x
+ sg_zone
+
+Most utility names are indicative of the main SCSI command
+that they execute. Some utilities are slightly higher level, for
+example sg_ses fetches SCSI Enclosure Services (SES) status pages and
+can send control pages. Each utility has a man page (placed in
+section 8). An overview of sg3_utils can be found at:
+https://sg.danny.cz/sg/sg3_utils.html .
+A copy of the "sg3_utils.html" file is in the "doc" subdirectory.
+
+
+The executables and library can be built from the source code in
+the tarball and installed with the familiar
+"./configure ; make ; make install" sequence. If this fails try
+running the "./autogen.sh" script prior to that sequence. There
+are generic instruction on configure and friend in the INSTALL file.
+
+Some man pages have examples which use Linux device names which
+hopefully will not confuse the Solaris users.
+
+Device naming
+=============
+In Solaris, SCSI device names below the '/dev' directory have a
+form like: c5t4d3s2 where the number following "c" is the controller
+(HBA) number, the number following "t" is the target number (from
+the SCSI parallel interface days) and the number following "d" is
+the LUN. Following the "s" is the slice number which is related to
+a partition and by convention "s2" is the whole disk.
+
+OpenSolaris also has a c5t4d3p2 form where the number following
+the "p" is the partition number apart from "p0" which is the whole
+disk. So a whole disk may be referred to as either:
+ - c5t4d3
+ - c5t4d3s2
+ - c5t4d3p0
+
+And these device names are duplicated in the /dev/dsk and /dev/rdsk
+directories. The former is the block device name and the latter
+is for "raw" (or char device) access which is what sg3_utils needs.
+So in OpenSolaris something of the form:
+ sg_inq /dev/rdsk/c5t4d3p0
+should work. If it doesn't add a '-vvv' option. If that is attempted
+on the /dev/dsk/c5t4d3p0 variant an inappropriate ioctl for device
+error will result.
+
+The device names within the /dev directory are typically symbolic
+links to much longer topological names in the /device directory.
+
+In Solaris cd/dvd/bd players seem to be treated as disks and so are
+found in the /dev/rdsk directory. Tape drives appear in the /dev/rmt
+directory.
+
+There is also a sgen (SCSI generic) driver which by default does not
+attach to any device. See the /kernel/drv/sgen.conf file to control
+what is attached. Any attached device will have a device name of
+the form /dev/scsi/c5t4d3 .
+
+Listing available SCSI devices in Solaris seems to be a challenge.
+"Use the 'format' command" advice works but seems a very dangerous
+way to list devices. [It does prompt again before doing any damage.]
+'devfsadm -Cv' cleans out the clutter in the /dev/rdsk directory,
+only leaving what is "live". The "cfgadm -v" command looks promising.
+
+Details
+=======
+The ported utilities listed above, all use SCSI command functions
+declared in sg_cmds_basic.h and sg_cmds_extra.h . Those SCSI command
+functions are implemented in the corresponding ".c" files. The ".c"
+files pass SCSI commands to the host operating system via an interface
+declared in sg_pt.h . There are currently five implementations of that
+interface depending on the host operating system:
+ - sg_pt_linux.c
+ - sg_pt_freebsd.c
+ - sg_pt_osf1.c [Tru64]
+ - sg_pt_solaris.c
+ - sg_pt_win32.c
+
+The sg_pt_solaris.c file uses the "uscsi" SCSI pass through mechanism. There
+seems to be no corresponding ATA pass through and recent SATA disks do not
+seem to have a SAT layer in front of them (within Solaris). If SAT is
+present (perhaps externally or within a HBA) then that would allow SATA
+disks to accept SCSI commands including the SCSI ATA PASS THROUGH commands.
+
+
+Douglas Gilbert
+5th June 2020
diff --git a/README.tru64 b/README.tru64
new file mode 100644
index 00000000..13c57e47
--- /dev/null
+++ b/README.tru64
@@ -0,0 +1,97 @@
+Introduction
+============
+The Tru64 port of sg3_utils contains those utilities that are _not_
+specific to Linux. In some cases a utility could be ported but
+requires more work. An example is sg_dd which needs more work
+beyond the SCSI command pass through mechanism.
+
+Supported Utilities
+===================
+Here is a list of utilities that have been ported:
+ sg_compare_and_write
+ sg_decode_sense
+ sg_format
+ sg_get_config
+ sg_get_lba_status
+ sg_ident
+ sg_inq [dropped ATA IDENTIFY DEVICE capability]
+ sg_logs
+ sg_luns
+ sg_modes
+ sg_opcodes
+ sg_persist
+ sg_prevent
+ sg_raw
+ sg_rdac
+ sg_read_block_limits
+ sg_read_buffer
+ sg_read_long
+ sg_readcap
+ sg_reassign
+ sg_referrals
+ sg_requests
+ sg_rmsn
+ sg_rtpg
+ sg_safte
+ sg_sanitize
+ sg_sat_identify
+ sg_sat_phy_event
+ sg_sat_set_features
+ sg_senddiag
+ sg_ses
+ sg_start
+ sg_stpg
+ sg_sync
+ sg_turs
+ sg_unmap
+ sg_verify
+ sg_vpd
+ sg_wr_mode
+ sg_write_buffer
+ sg_write_long
+ sg_write_same
+
+Most utility names are indicative of the main SCSI command
+that they execute. Some utilities are slightly higher level, for
+example sg_ses fetches SCSI Enclosure Services (SES) status pages and
+can send control pages. Each utility has a man page (placed in
+section 8). An overview of sg3_utils can be found at:
+https://sg.danny.cz/sg/sg3_utils.html .
+A copy of the "sg3_utils.html" file is in the "doc" subdirectory.
+
+This package uses autotools infrastructure with the now common
+"./configure ; make ; make install" sequence needed to build and install
+from the source found in the tarball. If the "./configure" sequence
+fails try using the ./autogen.sh prior to that sequence.
+
+Some man pages have examples which use Linux device names which hopefully
+will not confuse Tru64 users.
+
+
+Details
+=======
+Most of the ported utilities listed above use SCSI command functions
+declared in sg_cmds_*.h headers . Those SCSI command functions are
+implemented in the corresponding ".c" files. The ".c" files pass SCSI
+commands to the host operating system via an interface declared in sg_pt.h .
+There are currently five implementations of that interface depending on
+the host operating system:
+system:
+ - sg_pt_linux.c
+ - sg_pt_osf1.c [Tru64]
+ - sg_pt_freebsd.c
+ - sg_pt_solaris.c
+ - sg_pt_win32.c
+
+The sg_pt_osf1.c file uses the Tru64 CAM SCSI pass through mechanism.
+
+Tru64 does not have general library support for "long" options
+(e.g. "--verbose") which are used extensively by most of the
+utilities in this package. Rather than change all the utilities
+and their man/web pages a local implementation of the missing
+function "getopt_long()" has been placed in the "getopt_long"
+subdirectory. Currently only the Tru64 port uses it.
+
+
+Douglas Gilbert
+14th January 2013
diff --git a/README.win32 b/README.win32
new file mode 100644
index 00000000..4d64c085
--- /dev/null
+++ b/README.win32
@@ -0,0 +1,247 @@
+Introduction
+============
+The win32 port of sg3_utils contains those utilities that are _not_ specific
+to Linux. One utility for listing available devices, sg_scan, has a
+Windows-specific version for this port.
+
+The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many
+Linux idiosyncrasies to be easily ported. A new package called 'ddpt'
+contains a utility with similar functionality to sg_dd and is available
+for Windows.
+
+The Windows port uses the Microsoft SCSI Pass Through (SPT) interface.
+It has two variants: "SPT" where data is double buffered; and "SPTD"
+where data pointers to the user space are passed to the OS. Only Windows
+2000 and later (i.e. not 95, 98 or ME) support SPT.
+
+Two build environments are catered for: cygwin (see www.cygwin.com) and
+MinGW ("Minimalist GNU for Windows", see www.mingw.org). Both are based in
+the gcc compiler (although other C compilers should have little problem with
+the source code). Cygwin is a more sophisticated, commercial product that
+results in executables that depend on cygwin1.dll . No licensing is required
+since sg3_utils is open source (with either BSD or GPL licenses) but users
+will need to fetch that dll. On the other hand MinGW (and its companion MSYS
+shell) builds freestanding console executables. The Unix library support is
+not as advanced with MinGW which has led to some timing functions being
+compiled out when sg3_utils is built for MinGW.
+
+In later versions of Windows these utilities may need to be "run as
+Administrator" for disks and other devices to be seen. If not those devices
+will simply not be found as calls to query them fail with access permission
+problems.
+
+Supported Utilities
+===================
+Here is a list of utilities that have been ported:
+ sg_bg_ctl
+ sg_compare_and_write
+ sg_decode_sense
+ sg_format
+ sg_get_config
+ sg_get_elem_status
+ sg_get_lba_status
+ sg_ident
+ sg_inq [dropped ATA IDENTIFY DEVICE capability]
+ sg_logs
+ sg_luns
+ sg_modes
+ sg_opcodes
+ sg_persist
+ sg_prevent
+ sg_raw
+ sg_rdac
+ sg_read_attr
+ sg_read_block_limits
+ sg_read_buffer
+ sg_read_long
+ sg_readcap
+ sg_reassign
+ sg_referrals
+ sg_rep_pip
+ sg_rep_zones
+ sg_requests
+ sg_reset_wp
+ sg_rmsn
+ sg_rtpg
+ sg_safte
+ sg_sanitize
+ sg_sat_identify
+ sg_sat_phy_event
+ sg_sat_read_gplog
+ sg_sat_set_features
+ sg_scan [this is Windows specific]
+ sg_seek
+ sg_senddiag
+ sg_ses
+ sg_ses_microcode
+ sg_start
+ sg_stpg
+ sg_stream_ctl
+ sg_sync
+ sg_timestamp
+ sg_turs
+ sg_unmap
+ sg_verify
+ sg_vpd
+ sg_wr_mode
+ sg_write_buffer
+ sg_write_long
+ sg_write_same
+ sg_write_verify
+ sg_write_x
+ sg_zone
+
+Most utility names are indicative of the main SCSI command that they execute.
+Some utilities are slightly higher level, for example sg_ses fetches SCSI
+Enclosure Services (SES) status pages and can send control pages. Each
+utility has a man page (placed in section 8). There is summary of the mapping
+between utility names and the SCSI commands they execute in the COVERAGE
+file. An overview of sg3_utils can be found at:
+https://sg.danny.cz/sg/sg3_utils.html .
+A copy of the "sg3_utils.html" file is in the "doc" subdirectory.
+
+Some man pages have examples which use Linux device names which hopefully
+will not confuse Windows users.
+
+Two pass-through variants
+=========================
+The sg_pt_win32.c file uses the Windows SCSI Pass Through interface.
+That is often shortened to SPT or SPTI. There are two DeviceIoControl()
+ioctl variants provided: IOCTL_SCSI_PASS_THROUGH and
+IOCTL_SCSI_PASS_THROUGH_DIRECT. The former involves double handling of
+data (and perhaps an upper limit on the data length associated with
+one SCSI command; MS documentation mentions 16 KB). The "direct"
+variant passes a pointer from the user space and to be faster looks
+and more versatile.
+
+However the "direct" variant has potentially (unquantified) alignment
+requirements and may not be (well) implemented by the hardware driver.
+In practice some users have reported errors (e.g. 1117: a non-descript
+IO error) when the direct variant is used.
+
+Hence the non-direct variant is the default. The default size limit
+on the data buffer is set at 16 KB but if the user asks for more
+the data buffer will be extended. The OS or the hardware drivers
+may reject the extended data buffer but we tried.
+
+The package can be built using the direct variant with:
+ ./configure --enable-win32-spt-direct
+rather than:
+ ./configure
+prior to the 'make' call.
+
+In sg3_utils version 1.31 run-time selection of the direct or indirect
+interface was added with the scsi_pt_win32_direct(int state_direct)
+function declared in sg_pt.h. The default is indirect unless
+'./configure --enable-win32-spt-direct' was used in the build. If
+'state_direct' is 1 then the direct interface is used and if it is 0
+the indirect interface is used.
+
+Both sg_read_buffer and sg_write_buffer can transfer buffers larger
+than 16 KB. So in sg3_utils version 1.31, they use this new function
+to set direct interface mode. This is regardless of whether or
+not "--enable-win32-spt-direct" is given to ./configure .
+
+Details
+=======
+Most of the ported utilities listed above use SCSI command functions
+declared in sg_cmds_*.h headers . Those SCSI command functions are
+implemented in the corresponding ".c" files. The ".c" files pass SCSI
+commands to the host operating system via an interface declared in sg_pt.h .
+There are currently five implementations of that interface depending on
+the host operating system:
+ - sg_pt_linux.c
+ - sg_pt_freebsd.c
+ - sg_pt_osf1.c [Tru64]
+ - sg_pt_solaris.c
+ - sg_pt_win32.c
+
+The ASPI32 interface which requires a dll from Adaptec is not supported.
+
+The sg_scan utility is a special version for Windows and it attempts to show
+the various available storage device names, one per line. Here is an example
+of sg_scan's output:
+
+# sg_scan
+PD0 [C] FUJITSU MHY2160BH 0000
+PD1 [DF] WD 2500BEV External 1.05 WD-WXE90
+CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03
+
+Here is an example with added bus type:
+
+# sg_scan -b
+PD0 [C] <Ata > FUJITSU MHY2160BH 0000
+PD1 [DF] <Usb > WD 2500BEV External 1.05 WD-WXE90
+CDROM0 [E] <Atapi> MATSHITA DVD/CDRW UJDA775 CB03
+
+Here is an example with added SCSI adapter scan:
+
+# sg_scan -b -s
+PD0 [C] <Ata > ST380011A 8.01
+PD1 <Scsi > SEAGATE ST373455SS 2189
+PD2 <Scsi > ATA ST3160812AS D
+PD3 <Scsi > SEAGATE ST336754SS 0003
+CDROM0 [F] <Atapi> HL-DT-ST DVDRAM GSA-4163B A103
+TAPE0 <Scsi > SONY SDT-7000 0192
+
+SCSI0:0,0,0 claimed=1 pdt=0h dubious ST380011 A 8.01
+SCSI1:0,0,0 claimed=1 pdt=5h HL-DT-ST DVDRAM GSA-4163B A103
+SCSI2:0,6,0 claimed=1 pdt=1h SONY SDT-7000 0192
+SCSI5:0,17,0 claimed=1 pdt=0h SEAGATE ST373455SS 2189
+SCSI5:0,19,0 claimed=1 pdt=0h ATA ST3160812AS D
+SCSI5:0,21,0 claimed=1 pdt=0h SEAGATE ST336754SS 0003
+SCSI5:0,112,0 claimed=0 pdt=10h LSI PSEUDO DEVICE 2.34
+
+The storage devices scanned are PhysicalDrive<n> (shortened form PD<n> used),
+CDROM<n> (which includes DVD and BD drives) and TAPE<n>. There is also an
+optional SCSI adapter scan with device names of the form SCSI<n>:<b>:<t>:<l> .
+These only come into play for devices that are not claimed by one of the
+storage class drivers. The "LSI PSEUDO DEVICE" device above is an example
+of an unclaimed device. The SCSI adapter scan does not show USB and IEEE
+1394 connected devices.
+
+Volume names (e.g. "C:") that match a storage device (or perhaps a
+partition within that device) are shown in brackets. Notice there can be
+zero, one or more volume names for each storage device. Up to four volume
+names are listed in brackets, if there are more a "+" is added after the
+fourth.
+
+Several utilities have conditional compilation sections based on
+the SG_LIB_MINGW define. For those who want to try native C compilers
+on Windows setting the SG_LIB_MINGW define may help.
+
+Build environments
+==================
+This package uses autotools infrastructure with the now common
+"./configure ; make ; make install" sequence needed to build and install
+from the source found in the tarball. Two Windows environments for building
+Unix code are supported: cygwin and MinGW. If the "./configure" sequence
+fails try using the ./autogen.sh prior to that sequence. The executables
+produced are console applications that can be executed in either a cygwin,
+MSYS or "cmd" shell. Various build options are available by giving
+command line options to "./configure", see the INSTALL file for generic
+information about the build infrastructure.
+
+MinGW can be used to cross built on some Redhat (Linux) platforms. After
+loading the cross build packages, the ./configure call in the normal
+autotools sequence should be replaced by either mingw32-configure or
+mingw64-configure. These scripts will set up the environment for
+the cross build and then call ./configure (so this invocation should be
+made in the top level of the untarred source). Options given to either
+script (e.g. --enable-win32-spt-direct) will be passed through to
+./configure .
+
+Binary and Text files
+=====================
+A problem has been reported with binary output being written in a MinGW
+environment (or executables build by MinGW). Windows has a concept of text
+and binary files which is not found in Unix. Recent versions of MinGW
+default to opening files in text mode. This can lead to binary output
+(such as when the '--raw' option is given) having 0xa (i.e. LF) translated
+to 0xd,0xa (i.e. CR,LF). sg3_utils version 1.26 attempts to fix this
+problem by changing what it knows to be binary output files to "binary
+mode" with the setmode() Windows command.
+
+
+Douglas Gilbert
+6th June 2020
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 00000000..7b7c8e57
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,10363 @@
+# generated automatically by aclocal 1.16.5 -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_ifndef([AC_AUTOCONF_VERSION],
+ [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],,
+[m4_warning([this file was generated for autoconf 2.71.
+You have another version of autoconf. It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically 'autoreconf'.])])
+
+# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*-
+#
+# Copyright (C) 1996-2001, 2003-2019, 2021-2022 Free Software
+# Foundation, Inc.
+# Written by Gordon Matzigkeit, 1996
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+m4_define([_LT_COPYING], [dnl
+# Copyright (C) 2014 Free Software Foundation, Inc.
+# This is free software; see the source for copying conditions. There is NO
+# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# GNU Libtool 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 of of the License, or
+# (at your option) any later version.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program or library that is built
+# using GNU Libtool, you may include this file under the same
+# distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+])
+
+# serial 59 LT_INIT
+
+
+# LT_PREREQ(VERSION)
+# ------------------
+# Complain and exit if this libtool version is less that VERSION.
+m4_defun([LT_PREREQ],
+[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1,
+ [m4_default([$3],
+ [m4_fatal([Libtool version $1 or higher is required],
+ 63)])],
+ [$2])])
+
+
+# _LT_CHECK_BUILDDIR
+# ------------------
+# Complain if the absolute build directory name contains unusual characters
+m4_defun([_LT_CHECK_BUILDDIR],
+[case `pwd` in
+ *\ * | *\ *)
+ AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;;
+esac
+])
+
+
+# LT_INIT([OPTIONS])
+# ------------------
+AC_DEFUN([LT_INIT],
+[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK
+AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+AC_BEFORE([$0], [LT_LANG])dnl
+AC_BEFORE([$0], [LT_OUTPUT])dnl
+AC_BEFORE([$0], [LTDL_INIT])dnl
+m4_require([_LT_CHECK_BUILDDIR])dnl
+
+dnl Autoconf doesn't catch unexpanded LT_ macros by default:
+m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl
+m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl
+dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4
+dnl unless we require an AC_DEFUNed macro:
+AC_REQUIRE([LTOPTIONS_VERSION])dnl
+AC_REQUIRE([LTSUGAR_VERSION])dnl
+AC_REQUIRE([LTVERSION_VERSION])dnl
+AC_REQUIRE([LTOBSOLETE_VERSION])dnl
+m4_require([_LT_PROG_LTMAIN])dnl
+
+_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}])
+
+dnl Parse OPTIONS
+_LT_SET_OPTIONS([$0], [$1])
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS=$ltmain
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+AC_SUBST(LIBTOOL)dnl
+
+_LT_SETUP
+
+# Only expand once:
+m4_define([LT_INIT])
+])# LT_INIT
+
+# Old names:
+AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT])
+AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PROG_LIBTOOL], [])
+dnl AC_DEFUN([AM_PROG_LIBTOOL], [])
+
+
+# _LT_PREPARE_CC_BASENAME
+# -----------------------
+m4_defun([_LT_PREPARE_CC_BASENAME], [
+# Calculate cc_basename. Skip known compiler wrappers and cross-prefix.
+func_cc_basename ()
+{
+ for cc_temp in @S|@*""; do
+ case $cc_temp in
+ compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;;
+ distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+ done
+ func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+}
+])# _LT_PREPARE_CC_BASENAME
+
+
+# _LT_CC_BASENAME(CC)
+# -------------------
+# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME,
+# but that macro is also expanded into generated libtool script, which
+# arranges for $SED and $ECHO to be set by different means.
+m4_defun([_LT_CC_BASENAME],
+[m4_require([_LT_PREPARE_CC_BASENAME])dnl
+AC_REQUIRE([_LT_DECL_SED])dnl
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl
+func_cc_basename $1
+cc_basename=$func_cc_basename_result
+])
+
+
+# _LT_FILEUTILS_DEFAULTS
+# ----------------------
+# It is okay to use these file commands and assume they have been set
+# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'.
+m4_defun([_LT_FILEUTILS_DEFAULTS],
+[: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+])# _LT_FILEUTILS_DEFAULTS
+
+
+# _LT_SETUP
+# ---------
+m4_defun([_LT_SETUP],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl
+
+_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl
+dnl
+_LT_DECL([], [host_alias], [0], [The host system])dnl
+_LT_DECL([], [host], [0])dnl
+_LT_DECL([], [host_os], [0])dnl
+dnl
+_LT_DECL([], [build_alias], [0], [The build system])dnl
+_LT_DECL([], [build], [0])dnl
+_LT_DECL([], [build_os], [0])dnl
+dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+dnl
+AC_REQUIRE([AC_PROG_LN_S])dnl
+test -z "$LN_S" && LN_S="ln -s"
+_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl
+dnl
+AC_REQUIRE([LT_CMD_MAX_LEN])dnl
+_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl
+_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl
+dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl
+m4_require([_LT_CMD_RELOAD])dnl
+m4_require([_LT_DECL_FILECMD])dnl
+m4_require([_LT_CHECK_MAGIC_METHOD])dnl
+m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl
+m4_require([_LT_CMD_OLD_ARCHIVE])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_WITH_SYSROOT])dnl
+m4_require([_LT_CMD_TRUNCATE])dnl
+
+_LT_CONFIG_LIBTOOL_INIT([
+# See if we are running on zsh, and set the options that allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+fi
+])
+if test -n "${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+fi
+
+_LT_CHECK_OBJDIR
+
+m4_require([_LT_TAG_COMPILER])dnl
+
+case $host_os in
+aix3*)
+ # AIX sometimes has problems with the GCC collect2 program. For some
+ # reason, if we set the COLLECT_NAMES environment variable, the problems
+ # vanish in a puff of smoke.
+ if test set != "${COLLECT_NAMES+set}"; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+ fi
+ ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a '.a' archive for static linking (except MSVC and
+# ICC, which need '.lib').
+libext=a
+
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+old_CC=$CC
+old_CFLAGS=$CFLAGS
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+_LT_CC_BASENAME([$compiler])
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+ if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+ _LT_PATH_MAGIC
+ fi
+ ;;
+esac
+
+# Use C for the default configuration in the libtool script
+LT_SUPPORTED_TAG([CC])
+_LT_LANG_C_CONFIG
+_LT_LANG_DEFAULT_CONFIG
+_LT_CONFIG_COMMANDS
+])# _LT_SETUP
+
+
+# _LT_PREPARE_SED_QUOTE_VARS
+# --------------------------
+# Define a few sed substitution that help us do robust quoting.
+m4_defun([_LT_PREPARE_SED_QUOTE_VARS],
+[# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\([["`$\\]]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([["`\\]]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+])
+
+# _LT_PROG_LTMAIN
+# ---------------
+# Note that this code is called both from 'configure', and 'config.status'
+# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably,
+# 'config.status' has no value for ac_aux_dir unless we are using Automake,
+# so we pass a copy along to make sure it has a sensible value anyway.
+m4_defun([_LT_PROG_LTMAIN],
+[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl
+_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir'])
+ltmain=$ac_aux_dir/ltmain.sh
+])# _LT_PROG_LTMAIN
+
+
+
+# So that we can recreate a full libtool script including additional
+# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS
+# in macros and then make a single call at the end using the 'libtool'
+# label.
+
+
+# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS])
+# ----------------------------------------
+# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL_INIT],
+[m4_ifval([$1],
+ [m4_append([_LT_OUTPUT_LIBTOOL_INIT],
+ [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_INIT])
+
+
+# _LT_CONFIG_LIBTOOL([COMMANDS])
+# ------------------------------
+# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL],
+[m4_ifval([$1],
+ [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS],
+ [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS])
+
+
+# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS])
+# -----------------------------------------------------
+m4_defun([_LT_CONFIG_SAVE_COMMANDS],
+[_LT_CONFIG_LIBTOOL([$1])
+_LT_CONFIG_LIBTOOL_INIT([$2])
+])
+
+
+# _LT_FORMAT_COMMENT([COMMENT])
+# -----------------------------
+# Add leading comment marks to the start of each line, and a trailing
+# full-stop to the whole comment if one is not present already.
+m4_define([_LT_FORMAT_COMMENT],
+[m4_ifval([$1], [
+m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])],
+ [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.])
+)])
+
+
+
+
+
+# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?])
+# -------------------------------------------------------------------
+# CONFIGNAME is the name given to the value in the libtool script.
+# VARNAME is the (base) name used in the configure script.
+# VALUE may be 0, 1 or 2 for a computed quote escaped value based on
+# VARNAME. Any other value will be used directly.
+m4_define([_LT_DECL],
+[lt_if_append_uniq([lt_decl_varnames], [$2], [, ],
+ [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name],
+ [m4_ifval([$1], [$1], [$2])])
+ lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3])
+ m4_ifval([$4],
+ [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])])
+ lt_dict_add_subkey([lt_decl_dict], [$2],
+ [tagged?], [m4_ifval([$5], [yes], [no])])])
+])
+
+
+# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION])
+# --------------------------------------------------------
+m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])])
+
+
+# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_tag_varnames],
+[_lt_decl_filter([tagged?], [yes], $@)])
+
+
+# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..])
+# ---------------------------------------------------------
+m4_define([_lt_decl_filter],
+[m4_case([$#],
+ [0], [m4_fatal([$0: too few arguments: $#])],
+ [1], [m4_fatal([$0: too few arguments: $#: $1])],
+ [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)],
+ [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)],
+ [lt_dict_filter([lt_decl_dict], $@)])[]dnl
+])
+
+
+# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...])
+# --------------------------------------------------
+m4_define([lt_decl_quote_varnames],
+[_lt_decl_filter([value], [1], $@)])
+
+
+# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_dquote_varnames],
+[_lt_decl_filter([value], [2], $@)])
+
+
+# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_varnames_tagged],
+[m4_assert([$# <= 2])dnl
+_$0(m4_quote(m4_default([$1], [[, ]])),
+ m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]),
+ m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))])
+m4_define([_lt_decl_varnames_tagged],
+[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])])
+
+
+# lt_decl_all_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_all_varnames],
+[_$0(m4_quote(m4_default([$1], [[, ]])),
+ m4_if([$2], [],
+ m4_quote(lt_decl_varnames),
+ m4_quote(m4_shift($@))))[]dnl
+])
+m4_define([_lt_decl_all_varnames],
+[lt_join($@, lt_decl_varnames_tagged([$1],
+ lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl
+])
+
+
+# _LT_CONFIG_STATUS_DECLARE([VARNAME])
+# ------------------------------------
+# Quote a variable value, and forward it to 'config.status' so that its
+# declaration there will have the same value as in 'configure'. VARNAME
+# must have a single quote delimited value for this to work.
+m4_define([_LT_CONFIG_STATUS_DECLARE],
+[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`'])
+
+
+# _LT_CONFIG_STATUS_DECLARATIONS
+# ------------------------------
+# We delimit libtool config variables with single quotes, so when
+# we write them to config.status, we have to be sure to quote all
+# embedded single quotes properly. In configure, this macro expands
+# each variable declared with _LT_DECL (and _LT_TAGDECL) into:
+#
+# <var>='`$ECHO "$<var>" | $SED "$delay_single_quote_subst"`'
+m4_defun([_LT_CONFIG_STATUS_DECLARATIONS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames),
+ [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAGS
+# ----------------
+# Output comment and list of tags supported by the script
+m4_defun([_LT_LIBTOOL_TAGS],
+[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl
+available_tags='_LT_TAGS'dnl
+])
+
+
+# _LT_LIBTOOL_DECLARE(VARNAME, [TAG])
+# -----------------------------------
+# Extract the dictionary values for VARNAME (optionally with TAG) and
+# expand to a commented shell variable setting:
+#
+# # Some comment about what VAR is for.
+# visible_name=$lt_internal_name
+m4_define([_LT_LIBTOOL_DECLARE],
+[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1],
+ [description])))[]dnl
+m4_pushdef([_libtool_name],
+ m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl
+m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])),
+ [0], [_libtool_name=[$]$1],
+ [1], [_libtool_name=$lt_[]$1],
+ [2], [_libtool_name=$lt_[]$1],
+ [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl
+m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl
+])
+
+
+# _LT_LIBTOOL_CONFIG_VARS
+# -----------------------
+# Produce commented declarations of non-tagged libtool config variables
+# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool'
+# script. Tagged libtool config variables (even for the LIBTOOL CONFIG
+# section) are produced by _LT_LIBTOOL_TAG_VARS.
+m4_defun([_LT_LIBTOOL_CONFIG_VARS],
+[m4_foreach([_lt_var],
+ m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)),
+ [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAG_VARS(TAG)
+# -------------------------
+m4_define([_LT_LIBTOOL_TAG_VARS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames),
+ [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])])
+
+
+# _LT_TAGVAR(VARNAME, [TAGNAME])
+# ------------------------------
+m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])])
+
+
+# _LT_CONFIG_COMMANDS
+# -------------------
+# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of
+# variables for single and double quote escaping we saved from calls
+# to _LT_DECL, we can put quote escaped variables declarations
+# into 'config.status', and then the shell code to quote escape them in
+# for loops in 'config.status'. Finally, any additional code accumulated
+# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded.
+m4_defun([_LT_CONFIG_COMMANDS],
+[AC_PROVIDE_IFELSE([LT_OUTPUT],
+ dnl If the libtool generation code has been placed in $CONFIG_LT,
+ dnl instead of duplicating it all over again into config.status,
+ dnl then we will have config.status run $CONFIG_LT later, so it
+ dnl needs to know what name is stored there:
+ [AC_CONFIG_COMMANDS([libtool],
+ [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])],
+ dnl If the libtool generation code is destined for config.status,
+ dnl expand the accumulated commands and init code now:
+ [AC_CONFIG_COMMANDS([libtool],
+ [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])])
+])#_LT_CONFIG_COMMANDS
+
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT],
+[
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+_LT_CONFIG_STATUS_DECLARATIONS
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+\$[]1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_quote_varnames); do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[[\\\\\\\`\\"\\\$]]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+# Double-quote double-evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_dquote_varnames); do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[[\\\\\\\`\\"\\\$]]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+_LT_OUTPUT_LIBTOOL_INIT
+])
+
+# _LT_GENERATED_FILE_INIT(FILE, [COMMENT])
+# ------------------------------------
+# Generate a child script FILE with all initialization necessary to
+# reuse the environment learned by the parent script, and make the
+# file executable. If COMMENT is supplied, it is inserted after the
+# '#!' sequence but before initialization text begins. After this
+# macro, additional text can be appended to FILE to form the body of
+# the child script. The macro ends with non-zero status if the
+# file could not be fully written (such as if the disk is full).
+m4_ifdef([AS_INIT_GENERATED],
+[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])],
+[m4_defun([_LT_GENERATED_FILE_INIT],
+[m4_require([AS_PREPARE])]dnl
+[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl
+[lt_write_fail=0
+cat >$1 <<_ASEOF || lt_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+$2
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$1 <<\_ASEOF || lt_write_fail=1
+AS_SHELL_SANITIZE
+_AS_PREPARE
+exec AS_MESSAGE_FD>&1
+_ASEOF
+test 0 = "$lt_write_fail" && chmod +x $1[]dnl
+m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT
+
+# LT_OUTPUT
+# ---------
+# This macro allows early generation of the libtool script (before
+# AC_OUTPUT is called), incase it is used in configure for compilation
+# tests.
+AC_DEFUN([LT_OUTPUT],
+[: ${CONFIG_LT=./config.lt}
+AC_MSG_NOTICE([creating $CONFIG_LT])
+_LT_GENERATED_FILE_INIT(["$CONFIG_LT"],
+[# Run this file to recreate a libtool stub with the current configuration.])
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+lt_cl_silent=false
+exec AS_MESSAGE_LOG_FD>>config.log
+{
+ echo
+ AS_BOX([Running $as_me.])
+} >&AS_MESSAGE_LOG_FD
+
+lt_cl_help="\
+'$as_me' creates a local libtool stub from the current configuration,
+for use in further configure time tests before the real libtool is
+generated.
+
+Usage: $[0] [[OPTIONS]]
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -q, --quiet do not print progress messages
+ -d, --debug don't remove temporary files
+
+Report bugs to <bug-libtool@gnu.org>."
+
+lt_cl_version="\
+m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl
+m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION])
+configured by $[0], generated by m4_PACKAGE_STRING.
+
+Copyright (C) 2011 Free Software Foundation, Inc.
+This config.lt script is free software; the Free Software Foundation
+gives unlimited permision to copy, distribute and modify it."
+
+while test 0 != $[#]
+do
+ case $[1] in
+ --version | --v* | -V )
+ echo "$lt_cl_version"; exit 0 ;;
+ --help | --h* | -h )
+ echo "$lt_cl_help"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --quiet | --q* | --silent | --s* | -q )
+ lt_cl_silent=: ;;
+
+ -*) AC_MSG_ERROR([unrecognized option: $[1]
+Try '$[0] --help' for more information.]) ;;
+
+ *) AC_MSG_ERROR([unrecognized argument: $[1]
+Try '$[0] --help' for more information.]) ;;
+ esac
+ shift
+done
+
+if $lt_cl_silent; then
+ exec AS_MESSAGE_FD>/dev/null
+fi
+_LTEOF
+
+cat >>"$CONFIG_LT" <<_LTEOF
+_LT_OUTPUT_LIBTOOL_COMMANDS_INIT
+_LTEOF
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+AC_MSG_NOTICE([creating $ofile])
+_LT_OUTPUT_LIBTOOL_COMMANDS
+AS_EXIT(0)
+_LTEOF
+chmod +x "$CONFIG_LT"
+
+# configure is writing to config.log, but config.lt does its own redirection,
+# appending to config.log, which fails on DOS, as config.log is still kept
+# open by configure. Here we exec the FD to /dev/null, effectively closing
+# config.log, so it can be properly (re)opened and appended to by config.lt.
+lt_cl_success=:
+test yes = "$silent" &&
+ lt_config_lt_args="$lt_config_lt_args --quiet"
+exec AS_MESSAGE_LOG_FD>/dev/null
+$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false
+exec AS_MESSAGE_LOG_FD>>config.log
+$lt_cl_success || AS_EXIT(1)
+])# LT_OUTPUT
+
+
+# _LT_CONFIG(TAG)
+# ---------------
+# If TAG is the built-in tag, create an initial libtool script with a
+# default configuration from the untagged config vars. Otherwise add code
+# to config.status for appending the configuration named by TAG from the
+# matching tagged config vars.
+m4_defun([_LT_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_CONFIG_SAVE_COMMANDS([
+ m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl
+ m4_if(_LT_TAG, [C], [
+ # See if we are running on zsh, and set the options that allow our
+ # commands through without removal of \ escapes.
+ if test -n "${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+ fi
+
+ cfgfile=${ofile}T
+ trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+ $RM "$cfgfile"
+
+ cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+# Generated automatically by $as_me ($PACKAGE) $VERSION
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+
+# Provide generalized library-building support services.
+# Written by Gordon Matzigkeit, 1996
+
+_LT_COPYING
+_LT_LIBTOOL_TAGS
+
+# Configured defaults for sys_lib_dlsearch_path munging.
+: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"}
+
+# ### BEGIN LIBTOOL CONFIG
+_LT_LIBTOOL_CONFIG_VARS
+_LT_LIBTOOL_TAG_VARS
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+ cat <<'_LT_EOF' >> "$cfgfile"
+
+# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE
+
+_LT_PREPARE_MUNGE_PATH_LIST
+_LT_PREPARE_CC_BASENAME
+
+# ### END FUNCTIONS SHARED WITH CONFIGURE
+
+_LT_EOF
+
+ case $host_os in
+ aix3*)
+ cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program. For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test set != "${COLLECT_NAMES+set}"; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+fi
+_LT_EOF
+ ;;
+ esac
+
+ _LT_PROG_LTMAIN
+
+ # We use sed instead of cat because bash on DJGPP gets confused if
+ # if finds mixed CR/LF and LF-only lines. Since sed operates in
+ # text mode, it properly converts lines to CR/LF. This bash problem
+ # is reportedly fixed, but why not run on old versions too?
+ $SED '$q' "$ltmain" >> "$cfgfile" \
+ || (rm -f "$cfgfile"; exit 1)
+
+ mv -f "$cfgfile" "$ofile" ||
+ (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+ chmod +x "$ofile"
+],
+[cat <<_LT_EOF >> "$ofile"
+
+dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded
+dnl in a comment (ie after a #).
+# ### BEGIN LIBTOOL TAG CONFIG: $1
+_LT_LIBTOOL_TAG_VARS(_LT_TAG)
+# ### END LIBTOOL TAG CONFIG: $1
+_LT_EOF
+])dnl /m4_if
+],
+[m4_if([$1], [], [
+ PACKAGE='$PACKAGE'
+ VERSION='$VERSION'
+ RM='$RM'
+ ofile='$ofile'], [])
+])dnl /_LT_CONFIG_SAVE_COMMANDS
+])# _LT_CONFIG
+
+
+# LT_SUPPORTED_TAG(TAG)
+# ---------------------
+# Trace this macro to discover what tags are supported by the libtool
+# --tag option, using:
+# autoconf --trace 'LT_SUPPORTED_TAG:$1'
+AC_DEFUN([LT_SUPPORTED_TAG], [])
+
+
+# C support is built-in for now
+m4_define([_LT_LANG_C_enabled], [])
+m4_define([_LT_TAGS], [])
+
+
+# LT_LANG(LANG)
+# -------------
+# Enable libtool support for the given language if not already enabled.
+AC_DEFUN([LT_LANG],
+[AC_BEFORE([$0], [LT_OUTPUT])dnl
+m4_case([$1],
+ [C], [_LT_LANG(C)],
+ [C++], [_LT_LANG(CXX)],
+ [Go], [_LT_LANG(GO)],
+ [Java], [_LT_LANG(GCJ)],
+ [Fortran 77], [_LT_LANG(F77)],
+ [Fortran], [_LT_LANG(FC)],
+ [Windows Resource], [_LT_LANG(RC)],
+ [m4_ifdef([_LT_LANG_]$1[_CONFIG],
+ [_LT_LANG($1)],
+ [m4_fatal([$0: unsupported language: "$1"])])])dnl
+])# LT_LANG
+
+
+# _LT_LANG(LANGNAME)
+# ------------------
+m4_defun([_LT_LANG],
+[m4_ifdef([_LT_LANG_]$1[_enabled], [],
+ [LT_SUPPORTED_TAG([$1])dnl
+ m4_append([_LT_TAGS], [$1 ])dnl
+ m4_define([_LT_LANG_]$1[_enabled], [])dnl
+ _LT_LANG_$1_CONFIG($1)])dnl
+])# _LT_LANG
+
+
+m4_ifndef([AC_PROG_GO], [
+# NOTE: This macro has been submitted for inclusion into #
+# GNU Autoconf as AC_PROG_GO. When it is available in #
+# a released version of Autoconf we should remove this #
+# macro and use it instead. #
+m4_defun([AC_PROG_GO],
+[AC_LANG_PUSH(Go)dnl
+AC_ARG_VAR([GOC], [Go compiler command])dnl
+AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl
+_AC_ARG_VAR_LDFLAGS()dnl
+AC_CHECK_TOOL(GOC, gccgo)
+if test -z "$GOC"; then
+ if test -n "$ac_tool_prefix"; then
+ AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo])
+ fi
+fi
+if test -z "$GOC"; then
+ AC_CHECK_PROG(GOC, gccgo, gccgo, false)
+fi
+])#m4_defun
+])#m4_ifndef
+
+
+# _LT_LANG_DEFAULT_CONFIG
+# -----------------------
+m4_defun([_LT_LANG_DEFAULT_CONFIG],
+[AC_PROVIDE_IFELSE([AC_PROG_CXX],
+ [LT_LANG(CXX)],
+ [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_F77],
+ [LT_LANG(F77)],
+ [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_FC],
+ [LT_LANG(FC)],
+ [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])])
+
+dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal
+dnl pulling things in needlessly.
+AC_PROVIDE_IFELSE([AC_PROG_GCJ],
+ [LT_LANG(GCJ)],
+ [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],
+ [LT_LANG(GCJ)],
+ [AC_PROVIDE_IFELSE([LT_PROG_GCJ],
+ [LT_LANG(GCJ)],
+ [m4_ifdef([AC_PROG_GCJ],
+ [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])])
+ m4_ifdef([A][M_PROG_GCJ],
+ [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])])
+ m4_ifdef([LT_PROG_GCJ],
+ [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])])
+
+AC_PROVIDE_IFELSE([AC_PROG_GO],
+ [LT_LANG(GO)],
+ [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])])
+
+AC_PROVIDE_IFELSE([LT_PROG_RC],
+ [LT_LANG(RC)],
+ [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])])
+])# _LT_LANG_DEFAULT_CONFIG
+
+# Obsolete macros:
+AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)])
+AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)])
+AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)])
+AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)])
+AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_CXX], [])
+dnl AC_DEFUN([AC_LIBTOOL_F77], [])
+dnl AC_DEFUN([AC_LIBTOOL_FC], [])
+dnl AC_DEFUN([AC_LIBTOOL_GCJ], [])
+dnl AC_DEFUN([AC_LIBTOOL_RC], [])
+
+
+# _LT_TAG_COMPILER
+# ----------------
+m4_defun([_LT_TAG_COMPILER],
+[AC_REQUIRE([AC_PROG_CC])dnl
+
+_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl
+_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl
+_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl
+_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+])# _LT_TAG_COMPILER
+
+
+# _LT_COMPILER_BOILERPLATE
+# ------------------------
+# Check for compiler boilerplate output or warnings with
+# the simple compiler test code.
+m4_defun([_LT_COMPILER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+])# _LT_COMPILER_BOILERPLATE
+
+
+# _LT_LINKER_BOILERPLATE
+# ----------------------
+# Check for linker boilerplate output or warnings with
+# the simple link test code.
+m4_defun([_LT_LINKER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+])# _LT_LINKER_BOILERPLATE
+
+# _LT_REQUIRED_DARWIN_CHECKS
+# -------------------------
+m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[
+ case $host_os in
+ rhapsody* | darwin*)
+ AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:])
+ AC_CHECK_TOOL([NMEDIT], [nmedit], [:])
+ AC_CHECK_TOOL([LIPO], [lipo], [:])
+ AC_CHECK_TOOL([OTOOL], [otool], [:])
+ AC_CHECK_TOOL([OTOOL64], [otool64], [:])
+ _LT_DECL([], [DSYMUTIL], [1],
+ [Tool to manipulate archived DWARF debug symbol files on Mac OS X])
+ _LT_DECL([], [NMEDIT], [1],
+ [Tool to change global to local symbols on Mac OS X])
+ _LT_DECL([], [LIPO], [1],
+ [Tool to manipulate fat objects and archives on Mac OS X])
+ _LT_DECL([], [OTOOL], [1],
+ [ldd/readelf like tool for Mach-O binaries on Mac OS X])
+ _LT_DECL([], [OTOOL64], [1],
+ [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4])
+
+ AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod],
+ [lt_cv_apple_cc_single_mod=no
+ if test -z "$LT_MULTI_MODULE"; then
+ # By default we will add the -single_module flag. You can override
+ # by either setting the environment variable LT_MULTI_MODULE
+ # non-empty at configure time, or by adding -multi_module to the
+ # link flags.
+ rm -rf libconftest.dylib*
+ echo "int foo(void){return 1;}" > conftest.c
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD
+ $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+ _lt_result=$?
+ # If there is a non-empty error log, and "single_module"
+ # appears in it, assume the flag caused a linker warning
+ if test -s conftest.err && $GREP single_module conftest.err; then
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ # Otherwise, if the output was created with a 0 exit code from
+ # the compiler, it worked.
+ elif test -f libconftest.dylib && test 0 = "$_lt_result"; then
+ lt_cv_apple_cc_single_mod=yes
+ else
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ fi
+ rm -rf libconftest.dylib*
+ rm -f conftest.*
+ fi])
+
+ AC_CACHE_CHECK([for -exported_symbols_list linker flag],
+ [lt_cv_ld_exported_symbols_list],
+ [lt_cv_ld_exported_symbols_list=no
+ save_LDFLAGS=$LDFLAGS
+ echo "_main" > conftest.sym
+ LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+ [lt_cv_ld_exported_symbols_list=yes],
+ [lt_cv_ld_exported_symbols_list=no])
+ LDFLAGS=$save_LDFLAGS
+ ])
+
+ AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load],
+ [lt_cv_ld_force_load=no
+ cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD
+ $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD
+ echo "$AR $AR_FLAGS libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD
+ $AR $AR_FLAGS libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD
+ echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD
+ $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD
+ cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD
+ $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+ _lt_result=$?
+ if test -s conftest.err && $GREP force_load conftest.err; then
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then
+ lt_cv_ld_force_load=yes
+ else
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ fi
+ rm -f conftest.err libconftest.a conftest conftest.c
+ rm -rf conftest.dSYM
+ ])
+ case $host_os in
+ rhapsody* | darwin1.[[012]])
+ _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;;
+ darwin1.*)
+ _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;;
+ darwin*)
+ case $MACOSX_DEPLOYMENT_TARGET,$host in
+ 10.[[012]],*|,*powerpc*-darwin[[5-8]]*)
+ _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;;
+ *)
+ _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;;
+ esac
+ ;;
+ esac
+ if test yes = "$lt_cv_apple_cc_single_mod"; then
+ _lt_dar_single_mod='$single_module'
+ fi
+ if test yes = "$lt_cv_ld_exported_symbols_list"; then
+ _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym'
+ else
+ _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib'
+ fi
+ if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then
+ _lt_dsymutil='~$DSYMUTIL $lib || :'
+ else
+ _lt_dsymutil=
+ fi
+ ;;
+ esac
+])
+
+
+# _LT_DARWIN_LINKER_FEATURES([TAG])
+# ---------------------------------
+# Checks for linker and compiler features on darwin
+m4_defun([_LT_DARWIN_LINKER_FEATURES],
+[
+ m4_require([_LT_REQUIRED_DARWIN_CHECKS])
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_automatic, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+ if test yes = "$lt_cv_ld_force_load"; then
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+ m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes],
+ [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes])
+ else
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=''
+ fi
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined
+ case $cc_basename in
+ ifort*|nagfor*) _lt_dar_can_shared=yes ;;
+ *) _lt_dar_can_shared=$GCC ;;
+ esac
+ if test yes = "$_lt_dar_can_shared"; then
+ output_verbose_link_cmd=func_echo_all
+ _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil"
+ _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil"
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil"
+ _LT_TAGVAR(module_expsym_cmds, $1)="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil"
+ m4_if([$1], [CXX],
+[ if test yes != "$lt_cv_apple_cc_single_mod"; then
+ _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil"
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil"
+ fi
+],[])
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+])
+
+# _LT_SYS_MODULE_PATH_AIX([TAGNAME])
+# ----------------------------------
+# Links a minimal program and checks the executable
+# for the system default hardcoded library path. In most cases,
+# this is /usr/lib:/lib, but when the MPI compilers are used
+# the location of the communication and MPI libs are included too.
+# If we don't find anything, use the default library path according
+# to the aix ld manual.
+# Store the results from the different compilers for each TAGNAME.
+# Allow to override them for all tags through lt_cv_aix_libpath.
+m4_defun([_LT_SYS_MODULE_PATH_AIX],
+[m4_require([_LT_DECL_SED])dnl
+if test set = "${lt_cv_aix_libpath+set}"; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM],[
+ lt_aix_libpath_sed='[
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }]'
+ _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+ _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi],[])
+ if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+ _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib
+ fi
+ ])
+ aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])
+fi
+])# _LT_SYS_MODULE_PATH_AIX
+
+
+# _LT_SHELL_INIT(ARG)
+# -------------------
+m4_define([_LT_SHELL_INIT],
+[m4_divert_text([M4SH-INIT], [$1
+])])# _LT_SHELL_INIT
+
+
+
+# _LT_PROG_ECHO_BACKSLASH
+# -----------------------
+# Find how we can fake an echo command that does not interpret backslash.
+# In particular, with Autoconf 2.60 or later we add some code to the start
+# of the generated configure script that will find a shell with a builtin
+# printf (that we can use as an echo command).
+m4_defun([_LT_PROG_ECHO_BACKSLASH],
+[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+AC_MSG_CHECKING([how to print strings])
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='printf %s\n'
+else
+ # Use this function as a fallback that always works.
+ func_fallback_echo ()
+ {
+ eval 'cat <<_LTECHO_EOF
+$[]1
+_LTECHO_EOF'
+ }
+ ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+ $ECHO "$*"
+}
+
+case $ECHO in
+ printf*) AC_MSG_RESULT([printf]) ;;
+ print*) AC_MSG_RESULT([print -r]) ;;
+ *) AC_MSG_RESULT([cat]) ;;
+esac
+
+m4_ifdef([_AS_DETECT_SUGGESTED],
+[_AS_DETECT_SUGGESTED([
+ test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || (
+ ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+ PATH=/empty FPATH=/empty; export PATH FPATH
+ test "X`printf %s $ECHO`" = "X$ECHO" \
+ || test "X`print -r -- $ECHO`" = "X$ECHO" )])])
+
+_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts])
+_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes])
+])# _LT_PROG_ECHO_BACKSLASH
+
+
+# _LT_WITH_SYSROOT
+# ----------------
+AC_DEFUN([_LT_WITH_SYSROOT],
+[m4_require([_LT_DECL_SED])dnl
+AC_MSG_CHECKING([for sysroot])
+AC_ARG_WITH([sysroot],
+[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@],
+ [Search for dependent libraries within DIR (or the compiler's sysroot
+ if not specified).])],
+[], [with_sysroot=no])
+
+dnl lt_sysroot will always be passed unquoted. We quote it here
+dnl in case the user passed a directory name.
+lt_sysroot=
+case $with_sysroot in #(
+ yes)
+ if test yes = "$GCC"; then
+ lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+ fi
+ ;; #(
+ /*)
+ lt_sysroot=`echo "$with_sysroot" | $SED -e "$sed_quote_subst"`
+ ;; #(
+ no|'')
+ ;; #(
+ *)
+ AC_MSG_RESULT([$with_sysroot])
+ AC_MSG_ERROR([The sysroot must be an absolute path.])
+ ;;
+esac
+
+ AC_MSG_RESULT([${lt_sysroot:-no}])
+_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl
+[dependent libraries, and where our libraries should be installed.])])
+
+# _LT_ENABLE_LOCK
+# ---------------
+m4_defun([_LT_ENABLE_LOCK],
+[AC_ARG_ENABLE([libtool-lock],
+ [AS_HELP_STRING([--disable-libtool-lock],
+ [avoid locking (might break parallel builds)])])
+test no = "$enable_libtool_lock" || enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+ # Find out what ABI is being produced by ac_compile, and set mode
+ # options accordingly.
+ echo 'int i;' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ case `$FILECMD conftest.$ac_objext` in
+ *ELF-32*)
+ HPUX_IA64_MODE=32
+ ;;
+ *ELF-64*)
+ HPUX_IA64_MODE=64
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+*-*-irix6*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ if test yes = "$lt_cv_prog_gnu_ld"; then
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -melf32bsmip"
+ ;;
+ *N32*)
+ LD="${LD-ld} -melf32bmipn32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -melf64bmip"
+ ;;
+ esac
+ else
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -32"
+ ;;
+ *N32*)
+ LD="${LD-ld} -n32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -64"
+ ;;
+ esac
+ fi
+ fi
+ rm -rf conftest*
+ ;;
+
+mips64*-*linux*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ emul=elf
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ emul="${emul}32"
+ ;;
+ *64-bit*)
+ emul="${emul}64"
+ ;;
+ esac
+ case `$FILECMD conftest.$ac_objext` in
+ *MSB*)
+ emul="${emul}btsmip"
+ ;;
+ *LSB*)
+ emul="${emul}ltsmip"
+ ;;
+ esac
+ case `$FILECMD conftest.$ac_objext` in
+ *N32*)
+ emul="${emul}n32"
+ ;;
+ esac
+ LD="${LD-ld} -m $emul"
+ fi
+ rm -rf conftest*
+ ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly. Note that the listed cases only cover the
+ # situations where additional linker options are needed (such as when
+ # doing 32-bit compilation for a host where ld defaults to 64-bit, or
+ # vice versa); the common cases where no linker options are needed do
+ # not appear in the list.
+ echo 'int i;' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ case `$FILECMD conftest.o` in
+ *32-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_i386_fbsd"
+ ;;
+ x86_64-*linux*)
+ case `$FILECMD conftest.o` in
+ *x86-64*)
+ LD="${LD-ld} -m elf32_x86_64"
+ ;;
+ *)
+ LD="${LD-ld} -m elf_i386"
+ ;;
+ esac
+ ;;
+ powerpc64le-*linux*)
+ LD="${LD-ld} -m elf32lppclinux"
+ ;;
+ powerpc64-*linux*)
+ LD="${LD-ld} -m elf32ppclinux"
+ ;;
+ s390x-*linux*)
+ LD="${LD-ld} -m elf_s390"
+ ;;
+ sparc64-*linux*)
+ LD="${LD-ld} -m elf32_sparc"
+ ;;
+ esac
+ ;;
+ *64-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_x86_64_fbsd"
+ ;;
+ x86_64-*linux*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ powerpcle-*linux*)
+ LD="${LD-ld} -m elf64lppc"
+ ;;
+ powerpc-*linux*)
+ LD="${LD-ld} -m elf64ppc"
+ ;;
+ s390*-*linux*|s390*-*tpf*)
+ LD="${LD-ld} -m elf64_s390"
+ ;;
+ sparc*-*linux*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+
+*-*-sco3.2v5*)
+ # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+ SAVE_CFLAGS=$CFLAGS
+ CFLAGS="$CFLAGS -belf"
+ AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf,
+ [AC_LANG_PUSH(C)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no])
+ AC_LANG_POP])
+ if test yes != "$lt_cv_cc_needs_belf"; then
+ # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+ CFLAGS=$SAVE_CFLAGS
+ fi
+ ;;
+*-*solaris*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo 'int i;' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ case `$FILECMD conftest.o` in
+ *64-bit*)
+ case $lt_cv_prog_gnu_ld in
+ yes*)
+ case $host in
+ i?86-*-solaris*|x86_64-*-solaris*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ sparc*-*-solaris*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ # GNU ld 2.21 introduced _sol2 emulations. Use them if available.
+ if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+ LD=${LD-ld}_sol2
+ fi
+ ;;
+ *)
+ if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+ LD="${LD-ld} -64"
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+esac
+
+need_locks=$enable_libtool_lock
+])# _LT_ENABLE_LOCK
+
+
+# _LT_PROG_AR
+# -----------
+m4_defun([_LT_PROG_AR],
+[AC_CHECK_TOOLS(AR, [ar], false)
+: ${AR=ar}
+_LT_DECL([], [AR], [1], [The archiver])
+
+# Use ARFLAGS variable as AR's operation code to sync the variable naming with
+# Automake. If both AR_FLAGS and ARFLAGS are specified, AR_FLAGS should have
+# higher priority because thats what people were doing historically (setting
+# ARFLAGS for automake and AR_FLAGS for libtool). FIXME: Make the AR_FLAGS
+# variable obsoleted/removed.
+
+test ${AR_FLAGS+y} || AR_FLAGS=${ARFLAGS-cr}
+lt_ar_flags=$AR_FLAGS
+_LT_DECL([], [lt_ar_flags], [0], [Flags to create an archive (by configure)])
+
+# Make AR_FLAGS overridable by 'make ARFLAGS='. Don't try to run-time override
+# by AR_FLAGS because that was never working and AR_FLAGS is about to die.
+_LT_DECL([], [AR_FLAGS], [\@S|@{ARFLAGS-"\@S|@lt_ar_flags"}],
+ [Flags to create an archive])
+
+AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file],
+ [lt_cv_ar_at_file=no
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
+ [echo conftest.$ac_objext > conftest.lst
+ lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD'
+ AC_TRY_EVAL([lt_ar_try])
+ if test 0 -eq "$ac_status"; then
+ # Ensure the archiver fails upon bogus file names.
+ rm -f conftest.$ac_objext libconftest.a
+ AC_TRY_EVAL([lt_ar_try])
+ if test 0 -ne "$ac_status"; then
+ lt_cv_ar_at_file=@
+ fi
+ fi
+ rm -f conftest.* libconftest.a
+ ])
+ ])
+
+if test no = "$lt_cv_ar_at_file"; then
+ archiver_list_spec=
+else
+ archiver_list_spec=$lt_cv_ar_at_file
+fi
+_LT_DECL([], [archiver_list_spec], [1],
+ [How to feed a file listing to the archiver])
+])# _LT_PROG_AR
+
+
+# _LT_CMD_OLD_ARCHIVE
+# -------------------
+m4_defun([_LT_CMD_OLD_ARCHIVE],
+[_LT_PROG_AR
+
+AC_CHECK_TOOL(STRIP, strip, :)
+test -z "$STRIP" && STRIP=:
+_LT_DECL([], [STRIP], [1], [A symbol stripping program])
+
+AC_CHECK_TOOL(RANLIB, ranlib, :)
+test -z "$RANLIB" && RANLIB=:
+_LT_DECL([], [RANLIB], [1],
+ [Commands used to install an old-style archive])
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+ case $host_os in
+ bitrig* | openbsd*)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+ ;;
+ *)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+ ;;
+ esac
+ old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+ darwin*)
+ lock_old_archive_extraction=yes ;;
+ *)
+ lock_old_archive_extraction=no ;;
+esac
+_LT_DECL([], [old_postinstall_cmds], [2])
+_LT_DECL([], [old_postuninstall_cmds], [2])
+_LT_TAGDECL([], [old_archive_cmds], [2],
+ [Commands used to build an old-style archive])
+_LT_DECL([], [lock_old_archive_extraction], [0],
+ [Whether to use a lock for old archive extraction])
+])# _LT_CMD_OLD_ARCHIVE
+
+
+# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------------------
+# Check whether the given compiler option works
+AC_DEFUN([_LT_COMPILER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+ [$2=no
+ m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4])
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ $2=yes
+ fi
+ fi
+ $RM conftest*
+])
+
+if test yes = "[$]$2"; then
+ m4_if([$5], , :, [$5])
+else
+ m4_if([$6], , :, [$6])
+fi
+])# _LT_COMPILER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], [])
+
+
+# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+# [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------
+# Check whether the given linker option works
+AC_DEFUN([_LT_LINKER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+ [$2=no
+ save_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS $3"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&AS_MESSAGE_LOG_FD
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ $2=yes
+ fi
+ else
+ $2=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS=$save_LDFLAGS
+])
+
+if test yes = "[$]$2"; then
+ m4_if([$4], , :, [$4])
+else
+ m4_if([$5], , :, [$5])
+fi
+])# _LT_LINKER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], [])
+
+
+# LT_CMD_MAX_LEN
+#---------------
+AC_DEFUN([LT_CMD_MAX_LEN],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+# find the maximum length of command line arguments
+AC_MSG_CHECKING([the maximum length of command line arguments])
+AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl
+ i=0
+ teststring=ABCD
+
+ case $build_os in
+ msdosdjgpp*)
+ # On DJGPP, this test can blow up pretty badly due to problems in libc
+ # (any single argument exceeding 2000 bytes causes a buffer overrun
+ # during glob expansion). Even if it were fixed, the result of this
+ # check would be larger than it should be.
+ lt_cv_sys_max_cmd_len=12288; # 12K is about right
+ ;;
+
+ gnu*)
+ # Under GNU Hurd, this test is not required because there is
+ # no limit to the length of command line arguments.
+ # Libtool will interpret -1 as no limit whatsoever
+ lt_cv_sys_max_cmd_len=-1;
+ ;;
+
+ cygwin* | mingw* | cegcc*)
+ # On Win9x/ME, this test blows up -- it succeeds, but takes
+ # about 5 minutes as the teststring grows exponentially.
+ # Worse, since 9x/ME are not pre-emptively multitasking,
+ # you end up with a "frozen" computer, even though with patience
+ # the test eventually succeeds (with a max line length of 256k).
+ # Instead, let's just punt: use the minimum linelength reported by
+ # all of the supported platforms: 8192 (on NT/2K/XP).
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ mint*)
+ # On MiNT this can take a long time and run out of memory.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ amigaos*)
+ # On AmigaOS with pdksh, this test takes hours, literally.
+ # So we just punt and use a minimum line length of 8192.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ bitrig* | darwin* | dragonfly* | freebsd* | midnightbsd* | netbsd* | openbsd*)
+ # This has been around since 386BSD, at least. Likely further.
+ if test -x /sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+ elif test -x /usr/sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+ else
+ lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs
+ fi
+ # And add a safety zone
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ ;;
+
+ interix*)
+ # We know the value 262144 and hardcode it with a safety zone (like BSD)
+ lt_cv_sys_max_cmd_len=196608
+ ;;
+
+ os2*)
+ # The test takes a long time on OS/2.
+ lt_cv_sys_max_cmd_len=8192
+ ;;
+
+ osf*)
+ # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+ # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+ # nice to cause kernel panics so lets avoid the loop below.
+ # First set a reasonable default.
+ lt_cv_sys_max_cmd_len=16384
+ #
+ if test -x /sbin/sysconfig; then
+ case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+ *1*) lt_cv_sys_max_cmd_len=-1 ;;
+ esac
+ fi
+ ;;
+ sco3.2v5*)
+ lt_cv_sys_max_cmd_len=102400
+ ;;
+ sysv5* | sco5v6* | sysv4.2uw2*)
+ kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+ if test -n "$kargmax"; then
+ lt_cv_sys_max_cmd_len=`echo $kargmax | $SED 's/.*[[ ]]//'`
+ else
+ lt_cv_sys_max_cmd_len=32768
+ fi
+ ;;
+ *)
+ lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+ if test -n "$lt_cv_sys_max_cmd_len" && \
+ test undefined != "$lt_cv_sys_max_cmd_len"; then
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ else
+ # Make teststring a little bigger before we do anything with it.
+ # a 1K string should be a reasonable start.
+ for i in 1 2 3 4 5 6 7 8; do
+ teststring=$teststring$teststring
+ done
+ SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+ # If test is not a shell built-in, we'll probably end up computing a
+ # maximum length that is only half of the actual maximum length, but
+ # we can't tell.
+ while { test X`env echo "$teststring$teststring" 2>/dev/null` \
+ = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+ test 17 != "$i" # 1/2 MB should be enough
+ do
+ i=`expr $i + 1`
+ teststring=$teststring$teststring
+ done
+ # Only check the string length outside the loop.
+ lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+ teststring=
+ # Add a significant safety factor because C++ compilers can tack on
+ # massive amounts of additional arguments before passing them to the
+ # linker. It appears as though 1/2 is a usable value.
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+ fi
+ ;;
+ esac
+])
+if test -n "$lt_cv_sys_max_cmd_len"; then
+ AC_MSG_RESULT($lt_cv_sys_max_cmd_len)
+else
+ AC_MSG_RESULT(none)
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+_LT_DECL([], [max_cmd_len], [0],
+ [What is the maximum length of a command?])
+])# LT_CMD_MAX_LEN
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], [])
+
+
+# _LT_HEADER_DLFCN
+# ----------------
+m4_defun([_LT_HEADER_DLFCN],
+[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl
+])# _LT_HEADER_DLFCN
+
+
+# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ----------------------------------------------------------------
+m4_defun([_LT_TRY_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test yes = "$cross_compiling"; then :
+ [$4]
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<_LT_EOF
+[#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* When -fvisibility=hidden is used, assume the code has been annotated
+ correspondingly for the symbols needed. */
+#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else
+ {
+ if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ else puts (dlerror ());
+ }
+ /* dlclose (self); */
+ }
+ else
+ puts (dlerror ());
+
+ return status;
+}]
+_LT_EOF
+ if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then
+ (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) $1 ;;
+ x$lt_dlneed_uscore) $2 ;;
+ x$lt_dlunknown|x*) $3 ;;
+ esac
+ else :
+ # compilation failed
+ $3
+ fi
+fi
+rm -fr conftest*
+])# _LT_TRY_DLOPEN_SELF
+
+
+# LT_SYS_DLOPEN_SELF
+# ------------------
+AC_DEFUN([LT_SYS_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test yes != "$enable_dlopen"; then
+ enable_dlopen=unknown
+ enable_dlopen_self=unknown
+ enable_dlopen_self_static=unknown
+else
+ lt_cv_dlopen=no
+ lt_cv_dlopen_libs=
+
+ case $host_os in
+ beos*)
+ lt_cv_dlopen=load_add_on
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ;;
+
+ mingw* | pw32* | cegcc*)
+ lt_cv_dlopen=LoadLibrary
+ lt_cv_dlopen_libs=
+ ;;
+
+ cygwin*)
+ lt_cv_dlopen=dlopen
+ lt_cv_dlopen_libs=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ AC_CHECK_LIB([dl], [dlopen],
+ [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[
+ lt_cv_dlopen=dyld
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ])
+ ;;
+
+ tpf*)
+ # Don't try to run any link tests for TPF. We know it's impossible
+ # because TPF is a cross-compiler, and we know how we open DSOs.
+ lt_cv_dlopen=dlopen
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=no
+ ;;
+
+ *)
+ AC_CHECK_FUNC([shl_load],
+ [lt_cv_dlopen=shl_load],
+ [AC_CHECK_LIB([dld], [shl_load],
+ [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld],
+ [AC_CHECK_FUNC([dlopen],
+ [lt_cv_dlopen=dlopen],
+ [AC_CHECK_LIB([dl], [dlopen],
+ [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],
+ [AC_CHECK_LIB([svld], [dlopen],
+ [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld],
+ [AC_CHECK_LIB([dld], [dld_link],
+ [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld])
+ ])
+ ])
+ ])
+ ])
+ ])
+ ;;
+ esac
+
+ if test no = "$lt_cv_dlopen"; then
+ enable_dlopen=no
+ else
+ enable_dlopen=yes
+ fi
+
+ case $lt_cv_dlopen in
+ dlopen)
+ save_CPPFLAGS=$CPPFLAGS
+ test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+ save_LDFLAGS=$LDFLAGS
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+ save_LIBS=$LIBS
+ LIBS="$lt_cv_dlopen_libs $LIBS"
+
+ AC_CACHE_CHECK([whether a program can dlopen itself],
+ lt_cv_dlopen_self, [dnl
+ _LT_TRY_DLOPEN_SELF(
+ lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes,
+ lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross)
+ ])
+
+ if test yes = "$lt_cv_dlopen_self"; then
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+ AC_CACHE_CHECK([whether a statically linked program can dlopen itself],
+ lt_cv_dlopen_self_static, [dnl
+ _LT_TRY_DLOPEN_SELF(
+ lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes,
+ lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross)
+ ])
+ fi
+
+ CPPFLAGS=$save_CPPFLAGS
+ LDFLAGS=$save_LDFLAGS
+ LIBS=$save_LIBS
+ ;;
+ esac
+
+ case $lt_cv_dlopen_self in
+ yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+ *) enable_dlopen_self=unknown ;;
+ esac
+
+ case $lt_cv_dlopen_self_static in
+ yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+ *) enable_dlopen_self_static=unknown ;;
+ esac
+fi
+_LT_DECL([dlopen_support], [enable_dlopen], [0],
+ [Whether dlopen is supported])
+_LT_DECL([dlopen_self], [enable_dlopen_self], [0],
+ [Whether dlopen of programs is supported])
+_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0],
+ [Whether dlopen of statically linked programs is supported])
+])# LT_SYS_DLOPEN_SELF
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], [])
+
+
+# _LT_COMPILER_C_O([TAGNAME])
+# ---------------------------
+# Check to see if options -c and -o are simultaneously supported by compiler.
+# This macro does not hard code the compiler like AC_PROG_CC_C_O.
+m4_defun([_LT_COMPILER_C_O],
+[m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext],
+ [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)],
+ [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&AS_MESSAGE_LOG_FD
+ echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+ fi
+ fi
+ chmod u+w . 2>&AS_MESSAGE_LOG_FD
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+])
+_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1],
+ [Does compiler simultaneously support -c and -o options?])
+])# _LT_COMPILER_C_O
+
+
+# _LT_COMPILER_FILE_LOCKS([TAGNAME])
+# ----------------------------------
+# Check to see if we can do hard links to lock some files if needed
+m4_defun([_LT_COMPILER_FILE_LOCKS],
+[m4_require([_LT_ENABLE_LOCK])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_COMPILER_C_O([$1])
+
+hard_links=nottested
+if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then
+ # do not overwrite the value of need_locks provided by the user
+ AC_MSG_CHECKING([if we can lock with hard links])
+ hard_links=yes
+ $RM conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ AC_MSG_RESULT([$hard_links])
+ if test no = "$hard_links"; then
+ AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe])
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?])
+])# _LT_COMPILER_FILE_LOCKS
+
+
+# _LT_CHECK_OBJDIR
+# ----------------
+m4_defun([_LT_CHECK_OBJDIR],
+[AC_CACHE_CHECK([for objdir], [lt_cv_objdir],
+[rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+ lt_cv_objdir=.libs
+else
+ # MS-DOS does not allow filenames that begin with a dot.
+ lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null])
+objdir=$lt_cv_objdir
+_LT_DECL([], [objdir], [0],
+ [The name of the directory that contains temporary libtool files])dnl
+m4_pattern_allow([LT_OBJDIR])dnl
+AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/",
+ [Define to the sub-directory where libtool stores uninstalled libraries.])
+])# _LT_CHECK_OBJDIR
+
+
+# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME])
+# --------------------------------------
+# Check hardcoding attributes.
+m4_defun([_LT_LINKER_HARDCODE_LIBPATH],
+[AC_MSG_CHECKING([how to hardcode library paths into programs])
+_LT_TAGVAR(hardcode_action, $1)=
+if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" ||
+ test -n "$_LT_TAGVAR(runpath_var, $1)" ||
+ test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then
+
+ # We can hardcode non-existent directories.
+ if test no != "$_LT_TAGVAR(hardcode_direct, $1)" &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" &&
+ test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then
+ # Linking always hardcodes the temporary library directory.
+ _LT_TAGVAR(hardcode_action, $1)=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ _LT_TAGVAR(hardcode_action, $1)=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ _LT_TAGVAR(hardcode_action, $1)=unsupported
+fi
+AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)])
+
+if test relink = "$_LT_TAGVAR(hardcode_action, $1)" ||
+ test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test yes = "$shlibpath_overrides_runpath" ||
+ test no = "$enable_shared"; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+_LT_TAGDECL([], [hardcode_action], [0],
+ [How to hardcode a shared library path into an executable])
+])# _LT_LINKER_HARDCODE_LIBPATH
+
+
+# _LT_CMD_STRIPLIB
+# ----------------
+m4_defun([_LT_CMD_STRIPLIB],
+[m4_require([_LT_DECL_EGREP])
+striplib=
+old_striplib=
+AC_MSG_CHECKING([whether stripping libraries is possible])
+if test -z "$STRIP"; then
+ AC_MSG_RESULT([no])
+else
+ if $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+ old_striplib="$STRIP --strip-debug"
+ striplib="$STRIP --strip-unneeded"
+ AC_MSG_RESULT([yes])
+ else
+ case $host_os in
+ darwin*)
+ # FIXME - insert some real tests, host_os isn't really good enough
+ striplib="$STRIP -x"
+ old_striplib="$STRIP -S"
+ AC_MSG_RESULT([yes])
+ ;;
+ freebsd*)
+ if $STRIP -V 2>&1 | $GREP "elftoolchain" >/dev/null; then
+ old_striplib="$STRIP --strip-debug"
+ striplib="$STRIP --strip-unneeded"
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ fi
+ ;;
+ *)
+ AC_MSG_RESULT([no])
+ ;;
+ esac
+ fi
+fi
+_LT_DECL([], [old_striplib], [1], [Commands to strip libraries])
+_LT_DECL([], [striplib], [1])
+])# _LT_CMD_STRIPLIB
+
+
+# _LT_PREPARE_MUNGE_PATH_LIST
+# ---------------------------
+# Make sure func_munge_path_list() is defined correctly.
+m4_defun([_LT_PREPARE_MUNGE_PATH_LIST],
+[[# func_munge_path_list VARIABLE PATH
+# -----------------------------------
+# VARIABLE is name of variable containing _space_ separated list of
+# directories to be munged by the contents of PATH, which is string
+# having a format:
+# "DIR[:DIR]:"
+# string "DIR[ DIR]" will be prepended to VARIABLE
+# ":DIR[:DIR]"
+# string "DIR[ DIR]" will be appended to VARIABLE
+# "DIRP[:DIRP]::[DIRA:]DIRA"
+# string "DIRP[ DIRP]" will be prepended to VARIABLE and string
+# "DIRA[ DIRA]" will be appended to VARIABLE
+# "DIR[:DIR]"
+# VARIABLE will be replaced by "DIR[ DIR]"
+func_munge_path_list ()
+{
+ case x@S|@2 in
+ x)
+ ;;
+ *:)
+ eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\"
+ ;;
+ x:*)
+ eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\"
+ ;;
+ *::*)
+ eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\"
+ eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\"
+ ;;
+ *)
+ eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\"
+ ;;
+ esac
+}
+]])# _LT_PREPARE_PATH_LIST
+
+
+# _LT_SYS_DYNAMIC_LINKER([TAG])
+# -----------------------------
+# PORTME Fill in your ld.so characteristics
+m4_defun([_LT_SYS_DYNAMIC_LINKER],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_OBJDUMP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl
+AC_MSG_CHECKING([dynamic linker characteristics])
+m4_if([$1],
+ [], [
+if test yes = "$GCC"; then
+ case $host_os in
+ darwin*) lt_awk_arg='/^libraries:/,/LR/' ;;
+ *) lt_awk_arg='/^libraries:/' ;;
+ esac
+ case $host_os in
+ mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;;
+ *) lt_sed_strip_eq='s|=/|/|g' ;;
+ esac
+ lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+ case $lt_search_path_spec in
+ *\;*)
+ # if the path contains ";" then we assume it to be the separator
+ # otherwise default to the standard path separator (i.e. ":") - it is
+ # assumed that no part of a normal pathname contains ";" but that should
+ # okay in the real world where ";" in dirpaths is itself problematic.
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+ ;;
+ *)
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ esac
+ # Ok, now we have the path, separated by spaces, we can step through it
+ # and add multilib dir if necessary...
+ lt_tmp_lt_search_path_spec=
+ lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+ # ...but if some path component already ends with the multilib dir we assume
+ # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer).
+ case "$lt_multi_os_dir; $lt_search_path_spec " in
+ "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*)
+ lt_multi_os_dir=
+ ;;
+ esac
+ for lt_sys_path in $lt_search_path_spec; do
+ if test -d "$lt_sys_path$lt_multi_os_dir"; then
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir"
+ elif test -n "$lt_multi_os_dir"; then
+ test -d "$lt_sys_path" && \
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+ fi
+ done
+ lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS = " "; FS = "/|\n";} {
+ lt_foo = "";
+ lt_count = 0;
+ for (lt_i = NF; lt_i > 0; lt_i--) {
+ if ($lt_i != "" && $lt_i != ".") {
+ if ($lt_i == "..") {
+ lt_count++;
+ } else {
+ if (lt_count == 0) {
+ lt_foo = "/" $lt_i lt_foo;
+ } else {
+ lt_count--;
+ }
+ }
+ }
+ }
+ if (lt_foo != "") { lt_freq[[lt_foo]]++; }
+ if (lt_freq[[lt_foo]] == 1) { print lt_foo; }
+}'`
+ # AWK program above erroneously prepends '/' to C:/dos/paths
+ # for these hosts.
+ case $host_os in
+ mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+ $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;;
+ esac
+ sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+ sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi])
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=.so
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+AC_ARG_VAR([LT_SYS_LIBRARY_PATH],
+[User-defined run-time library search path.])
+
+case $host_os in
+aix3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX 3 has no versioning support, so we append a major version to the name.
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+
+aix[[4-9]]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ hardcode_into_libs=yes
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 supports IA64
+ library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line '#! .'. This would cause the generated library to
+ # depend on '.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[[01]] | aix4.[[01]].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # Using Import Files as archive members, it is possible to support
+ # filename-based versioning of shared library archives on AIX. While
+ # this would work for both with and without runtime linking, it will
+ # prevent static linking of such archives. So we do filename-based
+ # shared library versioning with .so extension only, which is used
+ # when both runtime linking and shared linking is enabled.
+ # Unfortunately, runtime linking may impact performance, so we do
+ # not want this to be the default eventually. Also, we use the
+ # versioned .so libs for executables only if there is the -brtl
+ # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only.
+ # To allow for filename-based versioning support, we need to create
+ # libNAME.so.V as an archive file, containing:
+ # *) an Import File, referring to the versioned filename of the
+ # archive as well as the shared archive member, telling the
+ # bitwidth (32 or 64) of that shared object, and providing the
+ # list of exported symbols of that shared object, eventually
+ # decorated with the 'weak' keyword
+ # *) the shared object with the F_LOADONLY flag set, to really avoid
+ # it being seen by the linker.
+ # At run time we better use the real file rather than another symlink,
+ # but for link time we create the symlink libNAME.so -> libNAME.so.V
+
+ case $with_aix_soname,$aix_use_runtimelinking in
+ # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct
+ # soname into executable. Probably we can add versioning support to
+ # collect2, so additional links can be useful in future.
+ aix,yes) # traditional libtool
+ dynamic_linker='AIX unversionable lib.so'
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ ;;
+ aix,no) # traditional AIX only
+ dynamic_linker='AIX lib.a[(]lib.so.V[)]'
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='$libname$release.a $libname.a'
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+ svr4,*) # full svr4 only
+ dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]"
+ library_names_spec='$libname$release$shared_ext$major $libname$shared_ext'
+ # We do not specify a path in Import Files, so LIBPATH fires.
+ shlibpath_overrides_runpath=yes
+ ;;
+ *,yes) # both, prefer svr4
+ dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]"
+ library_names_spec='$libname$release$shared_ext$major $libname$shared_ext'
+ # unpreferred sharedlib libNAME.a needs extra handling
+ postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"'
+ postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"'
+ # We do not specify a path in Import Files, so LIBPATH fires.
+ shlibpath_overrides_runpath=yes
+ ;;
+ *,no) # both, prefer aix
+ dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]"
+ library_names_spec='$libname$release.a $libname.a'
+ soname_spec='$libname$release$shared_ext$major'
+ # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling
+ postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)'
+ postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"'
+ ;;
+ esac
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ case $host_cpu in
+ powerpc)
+ # Since July 2007 AmigaOS4 officially supports .so libraries.
+ # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ ;;
+ m68k)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+ ;;
+ esac
+ ;;
+
+beos*)
+ library_names_spec='$libname$shared_ext'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi[[45]]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+ version_type=windows
+ shrext_cmds=.dll
+ need_version=no
+ need_lib_prefix=no
+
+ case $GCC,$cc_basename in
+ yes,*)
+ # gcc
+ library_names_spec='$libname.dll.a'
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+
+ case $host_os in
+ cygwin*)
+ # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+ soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext'
+m4_if([$1], [],[
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"])
+ ;;
+ mingw* | cegcc*)
+ # MinGW DLLs use traditional 'lib' prefix
+ soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext'
+ ;;
+ pw32*)
+ # pw32 DLLs use 'pw' prefix rather than 'lib'
+ library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ ;;
+
+ *,cl* | *,icl*)
+ # Native MSVC or ICC
+ libname_spec='$name'
+ soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext'
+ library_names_spec='$libname.dll.lib'
+
+ case $build_os in
+ mingw*)
+ sys_lib_search_path_spec=
+ lt_save_ifs=$IFS
+ IFS=';'
+ for lt_path in $LIB
+ do
+ IFS=$lt_save_ifs
+ # Let DOS variable expansion print the short 8.3 style file name.
+ lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+ sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+ done
+ IFS=$lt_save_ifs
+ # Convert to MSYS style.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'`
+ ;;
+ cygwin*)
+ # Convert to unix form, then to dos form, then back to unix form
+ # but this time dos style (no spaces!) so that the unix form looks
+ # like /cygdrive/c/PROGRA~1:/cygdr...
+ sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+ sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+ sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ *)
+ sys_lib_search_path_spec=$LIB
+ if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then
+ # It is most probably a Windows format PATH.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+ else
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ fi
+ # FIXME: find the short name or the path components, as spaces are
+ # common. (e.g. "Program Files" -> "PROGRA~1")
+ ;;
+ esac
+
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+ dynamic_linker='Win32 link.exe'
+ ;;
+
+ *)
+ # Assume MSVC and ICC wrapper
+ library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib'
+ dynamic_linker='Win32 ld.exe'
+ ;;
+ esac
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$major$shared_ext $libname$shared_ext'
+ soname_spec='$libname$release$major$shared_ext'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+m4_if([$1], [],[
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"])
+ sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+ ;;
+
+dgux*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+freebsd* | dragonfly* | midnightbsd*)
+ # DragonFly does not have aout. When/if they implement a new
+ # versioning mechanism, adjust this.
+ if test -x /usr/bin/objformat; then
+ objformat=`/usr/bin/objformat`
+ else
+ case $host_os in
+ freebsd[[23]].*) objformat=aout ;;
+ *) objformat=elf ;;
+ esac
+ fi
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2.*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ freebsd3.[[01]]* | freebsdelf3.[[01]]*)
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \
+ freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ *) # from 4.6 on, and DragonFly
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+haiku*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ dynamic_linker="$host_os runtime_loader"
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ case $host_cpu in
+ ia64*)
+ shrext_cmds='.so'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.so"
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ if test 32 = "$HPUX_IA64_MODE"; then
+ sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+ sys_lib_dlsearch_path_spec=/usr/lib/hpux32
+ else
+ sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+ sys_lib_dlsearch_path_spec=/usr/lib/hpux64
+ fi
+ ;;
+ hppa*64*)
+ shrext_cmds='.sl'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ *)
+ shrext_cmds='.sl'
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+ esac
+ # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+ postinstall_cmds='chmod 555 $lib'
+ # or fails outright, so override atomically:
+ install_override_mode=555
+ ;;
+
+interix[[3-9]]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $host_os in
+ nonstopux*) version_type=nonstopux ;;
+ *)
+ if test yes = "$lt_cv_prog_gnu_ld"; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ else
+ version_type=irix
+ fi ;;
+ esac
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='$libname$release$shared_ext$major'
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext'
+ case $host_os in
+ irix5* | nonstopux*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+ libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+ libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+ libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff"
+ sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff"
+ hardcode_into_libs=yes
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+ dynamic_linker=no
+ ;;
+
+linux*android*)
+ version_type=none # Android doesn't support versioned libraries.
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext'
+ soname_spec='$libname$release$shared_ext'
+ finish_cmds=
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ dynamic_linker='Android linker'
+ # Don't embed -rpath directories since the linker doesn't support them.
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+
+ # Some binutils ld are patched to set DT_RUNPATH
+ AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath],
+ [lt_cv_shlibpath_overrides_runpath=no
+ save_LDFLAGS=$LDFLAGS
+ save_libdir=$libdir
+ eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \
+ LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\""
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+ [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null],
+ [lt_cv_shlibpath_overrides_runpath=yes])])
+ LDFLAGS=$save_LDFLAGS
+ libdir=$save_libdir
+ ])
+ shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # Ideally, we could use ldconfig to report *all* directores which are
+ # searched for libraries, however this is still not possible. Aside from not
+ # being certain /sbin/ldconfig is available, command
+ # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64,
+ # even though it is searched at run-time. Try to do the best guess by
+ # appending ld.so.conf contents (and includes) to the search path.
+ if test -f /etc/ld.so.conf; then
+ lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+ sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+ fi
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsdelf*-gnu)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='NetBSD ld.elf_so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+*nto* | *qnx*)
+ version_type=qnx
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='ldqnx.so'
+ ;;
+
+openbsd* | bitrig*)
+ version_type=sunos
+ sys_lib_dlsearch_path_spec=/usr/lib
+ need_lib_prefix=no
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ need_version=no
+ else
+ need_version=yes
+ fi
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+os2*)
+ libname_spec='$name'
+ version_type=windows
+ shrext_cmds=.dll
+ need_version=no
+ need_lib_prefix=no
+ # OS/2 can only load a DLL with a base name of 8 characters or less.
+ soname_spec='`test -n "$os2dllname" && libname="$os2dllname";
+ v=$($ECHO $release$versuffix | tr -d .-);
+ n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _);
+ $ECHO $n$v`$shared_ext'
+ library_names_spec='${libname}_dll.$libext'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=BEGINLIBPATH
+ sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='$libname$release$shared_ext$major'
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+
+rdos*)
+ dynamic_linker=no
+ ;;
+
+solaris*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test yes = "$with_gnu_ld"; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ need_lib_prefix=no
+ runpath_var=LD_RUN_PATH
+ ;;
+ siemens)
+ need_lib_prefix=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext'
+ soname_spec='$libname$shared_ext.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ version_type=sco
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ if test yes = "$with_gnu_ld"; then
+ sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+ else
+ sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+ case $host_os in
+ sco3.2v5*)
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+ ;;
+ esac
+ fi
+ sys_lib_dlsearch_path_spec='/usr/lib'
+ ;;
+
+tpf*)
+ # TPF is a cross-target only. Preferred cross-host = GNU/Linux.
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+uts4*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+AC_MSG_RESULT([$dynamic_linker])
+test no = "$dynamic_linker" && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test yes = "$GCC"; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then
+ sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec
+fi
+
+if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then
+ sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec
+fi
+
+# remember unaugmented sys_lib_dlsearch_path content for libtool script decls...
+configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec
+
+# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code
+func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH"
+
+# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool
+configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH
+
+_LT_DECL([], [variables_saved_for_relink], [1],
+ [Variables whose values should be saved in libtool wrapper scripts and
+ restored at link time])
+_LT_DECL([], [need_lib_prefix], [0],
+ [Do we need the "lib" prefix for modules?])
+_LT_DECL([], [need_version], [0], [Do we need a version for libraries?])
+_LT_DECL([], [version_type], [0], [Library versioning type])
+_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable])
+_LT_DECL([], [shlibpath_var], [0],[Shared library path variable])
+_LT_DECL([], [shlibpath_overrides_runpath], [0],
+ [Is shlibpath searched before the hard-coded library search path?])
+_LT_DECL([], [libname_spec], [1], [Format of library name prefix])
+_LT_DECL([], [library_names_spec], [1],
+ [[List of archive names. First name is the real one, the rest are links.
+ The last name is the one that the linker finds with -lNAME]])
+_LT_DECL([], [soname_spec], [1],
+ [[The coded name of the library, if different from the real name]])
+_LT_DECL([], [install_override_mode], [1],
+ [Permission mode override for installation of shared libraries])
+_LT_DECL([], [postinstall_cmds], [2],
+ [Command to use after installation of a shared archive])
+_LT_DECL([], [postuninstall_cmds], [2],
+ [Command to use after uninstallation of a shared archive])
+_LT_DECL([], [finish_cmds], [2],
+ [Commands used to finish a libtool library installation in a directory])
+_LT_DECL([], [finish_eval], [1],
+ [[As "finish_cmds", except a single script fragment to be evaled but
+ not shown]])
+_LT_DECL([], [hardcode_into_libs], [0],
+ [Whether we should hardcode library paths into libraries])
+_LT_DECL([], [sys_lib_search_path_spec], [2],
+ [Compile-time system search path for libraries])
+_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2],
+ [Detected run-time system search path for libraries])
+_LT_DECL([], [configure_time_lt_sys_library_path], [2],
+ [Explicit LT_SYS_LIBRARY_PATH set during ./configure time])
+])# _LT_SYS_DYNAMIC_LINKER
+
+
+# _LT_PATH_TOOL_PREFIX(TOOL)
+# --------------------------
+# find a file program that can recognize shared library
+AC_DEFUN([_LT_PATH_TOOL_PREFIX],
+[m4_require([_LT_DECL_EGREP])dnl
+AC_MSG_CHECKING([for $1])
+AC_CACHE_VAL(lt_cv_path_MAGIC_CMD,
+[case $MAGIC_CMD in
+[[\\/*] | ?:[\\/]*])
+ lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path.
+ ;;
+*)
+ lt_save_MAGIC_CMD=$MAGIC_CMD
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+dnl $ac_dummy forces splitting on constant user-supplied paths.
+dnl POSIX.2 word splitting is done only on the output of word expansions,
+dnl not every word. This closes a longstanding sh security hole.
+ ac_dummy="m4_if([$2], , $PATH, [$2])"
+ for ac_dir in $ac_dummy; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$1"; then
+ lt_cv_path_MAGIC_CMD=$ac_dir/"$1"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+ MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS=$lt_save_ifs
+ MAGIC_CMD=$lt_save_MAGIC_CMD
+ ;;
+esac])
+MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+if test -n "$MAGIC_CMD"; then
+ AC_MSG_RESULT($MAGIC_CMD)
+else
+ AC_MSG_RESULT(no)
+fi
+_LT_DECL([], [MAGIC_CMD], [0],
+ [Used to examine libraries when file_magic_cmd begins with "file"])dnl
+])# _LT_PATH_TOOL_PREFIX
+
+# Old name:
+AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], [])
+
+
+# _LT_PATH_MAGIC
+# --------------
+# find a file program that can recognize a shared library
+m4_defun([_LT_PATH_MAGIC],
+[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH)
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+ if test -n "$ac_tool_prefix"; then
+ _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH)
+ else
+ MAGIC_CMD=:
+ fi
+fi
+])# _LT_PATH_MAGIC
+
+
+# LT_PATH_LD
+# ----------
+# find the pathname to the GNU or non-GNU linker
+AC_DEFUN([LT_PATH_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PROG_ECHO_BACKSLASH])dnl
+
+AC_ARG_WITH([gnu-ld],
+ [AS_HELP_STRING([--with-gnu-ld],
+ [assume the C compiler uses GNU ld @<:@default=no@:>@])],
+ [test no = "$withval" || with_gnu_ld=yes],
+ [with_gnu_ld=no])dnl
+
+ac_prog=ld
+if test yes = "$GCC"; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ AC_MSG_CHECKING([for ld used by $CC])
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return, which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [[\\/]]* | ?:[[\\/]]*)
+ re_direlt='/[[^/]][[^/]]*/\.\./'
+ # Canonicalize the pathname of ld
+ ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+ while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD=$ac_prog
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test yes = "$with_gnu_ld"; then
+ AC_MSG_CHECKING([for GNU ld])
+else
+ AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(lt_cv_path_LD,
+[if test -z "$LD"; then
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD=$ac_dir/$ac_prog
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some variants of GNU ld only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+ *GNU* | *'with BFD'*)
+ test no != "$with_gnu_ld" && break
+ ;;
+ *)
+ test yes != "$with_gnu_ld" && break
+ ;;
+ esac
+ fi
+ done
+ IFS=$lt_save_ifs
+else
+ lt_cv_path_LD=$LD # Let the user override the test with a path.
+fi])
+LD=$lt_cv_path_LD
+if test -n "$LD"; then
+ AC_MSG_RESULT($LD)
+else
+ AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+_LT_PATH_LD_GNU
+AC_SUBST([LD])
+
+_LT_TAGDECL([], [LD], [1], [The linker used to build libraries])
+])# LT_PATH_LD
+
+# Old names:
+AU_ALIAS([AM_PROG_LD], [LT_PATH_LD])
+AU_ALIAS([AC_PROG_LD], [LT_PATH_LD])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_LD], [])
+dnl AC_DEFUN([AC_PROG_LD], [])
+
+
+# _LT_PATH_LD_GNU
+#- --------------
+m4_defun([_LT_PATH_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+ lt_cv_prog_gnu_ld=yes
+ ;;
+*)
+ lt_cv_prog_gnu_ld=no
+ ;;
+esac])
+with_gnu_ld=$lt_cv_prog_gnu_ld
+])# _LT_PATH_LD_GNU
+
+
+# _LT_CMD_RELOAD
+# --------------
+# find reload flag for linker
+# -- PORTME Some linkers may need a different reload flag.
+m4_defun([_LT_CMD_RELOAD],
+[AC_CACHE_CHECK([for $LD option to reload object files],
+ lt_cv_ld_reload_flag,
+ [lt_cv_ld_reload_flag='-r'])
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ if test yes != "$GCC"; then
+ reload_cmds=false
+ fi
+ ;;
+ darwin*)
+ if test yes = "$GCC"; then
+ reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs'
+ else
+ reload_cmds='$LD$reload_flag -o $output$reload_objs'
+ fi
+ ;;
+esac
+_LT_TAGDECL([], [reload_flag], [1], [How to create reloadable object files])dnl
+_LT_TAGDECL([], [reload_cmds], [2])dnl
+])# _LT_CMD_RELOAD
+
+
+# _LT_PATH_DD
+# -----------
+# find a working dd
+m4_defun([_LT_PATH_DD],
+[AC_CACHE_CHECK([for a working dd], [ac_cv_path_lt_DD],
+[printf 0123456789abcdef0123456789abcdef >conftest.i
+cat conftest.i conftest.i >conftest2.i
+: ${lt_DD:=$DD}
+AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd],
+[if "$ac_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then
+ cmp -s conftest.i conftest.out \
+ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=:
+fi])
+rm -f conftest.i conftest2.i conftest.out])
+])# _LT_PATH_DD
+
+
+# _LT_CMD_TRUNCATE
+# ----------------
+# find command to truncate a binary pipe
+m4_defun([_LT_CMD_TRUNCATE],
+[m4_require([_LT_PATH_DD])
+AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin],
+[printf 0123456789abcdef0123456789abcdef >conftest.i
+cat conftest.i conftest.i >conftest2.i
+lt_cv_truncate_bin=
+if "$ac_cv_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then
+ cmp -s conftest.i conftest.out \
+ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1"
+fi
+rm -f conftest.i conftest2.i conftest.out
+test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"])
+_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1],
+ [Command to truncate a binary pipe])
+])# _LT_CMD_TRUNCATE
+
+
+# _LT_CHECK_MAGIC_METHOD
+# ----------------------
+# how to check for library dependencies
+# -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_MAGIC_METHOD],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+AC_CACHE_CHECK([how to recognize dependent libraries],
+lt_cv_deplibs_check_method,
+[lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# 'unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# that responds to the $file_magic_cmd with a given extended regex.
+# If you have 'file' or equivalent on your system and you're not sure
+# whether 'pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[[4-9]]*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+beos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+bsdi[[45]]*)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)'
+ lt_cv_file_magic_cmd='$FILECMD -L'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ ;;
+
+cygwin*)
+ # func_win32_libid is a shell function defined in ltmain.sh
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ ;;
+
+mingw* | pw32*)
+ # Base MSYS/MinGW do not provide the 'file' command needed by
+ # func_win32_libid shell function, so use a weaker test based on 'objdump',
+ # unless we find 'file', for example because we are cross-compiling.
+ if ( file / ) >/dev/null 2>&1; then
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ else
+ # Keep this pattern in sync with the one in func_win32_libid.
+ lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ fi
+ ;;
+
+cegcc*)
+ # use the weaker test based on 'objdump'. See mingw*.
+ lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ ;;
+
+darwin* | rhapsody*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+freebsd* | dragonfly* | midnightbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ case $host_cpu in
+ i*86 )
+ # Not sure whether the presence of OpenBSD here was a mistake.
+ # Let's accept both of them until this is cleared up.
+ lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library'
+ lt_cv_file_magic_cmd=$FILECMD
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ ;;
+ esac
+ else
+ lt_cv_deplibs_check_method=pass_all
+ fi
+ ;;
+
+haiku*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+hpux10.20* | hpux11*)
+ lt_cv_file_magic_cmd=$FILECMD
+ case $host_cpu in
+ ia64*)
+ lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64'
+ lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+ ;;
+ hppa*64*)
+ [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]']
+ lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+ ;;
+ *)
+ lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library'
+ lt_cv_file_magic_test_file=/usr/lib/libc.sl
+ ;;
+ esac
+ ;;
+
+interix[[3-9]]*)
+ # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$'
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $LD in
+ *-32|*"-32 ") libmagic=32-bit;;
+ *-n32|*"-n32 ") libmagic=N32;;
+ *-64|*"-64 ") libmagic=64-bit;;
+ *) libmagic=never-match;;
+ esac
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$'
+ fi
+ ;;
+
+newos6*)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)'
+ lt_cv_file_magic_cmd=$FILECMD
+ lt_cv_file_magic_test_file=/usr/lib/libnls.so
+ ;;
+
+*nto* | *qnx*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+openbsd* | bitrig*)
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+ fi
+ ;;
+
+osf3* | osf4* | osf5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+rdos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+solaris*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv4 | sysv4.3*)
+ case $host_vendor in
+ motorola)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]'
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+ ;;
+ ncr)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ sequent)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )'
+ ;;
+ sni)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib"
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+ siemens)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ pc)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ esac
+ ;;
+
+tpf*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+os2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+esac
+])
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+ case $host_os in
+ mingw* | pw32*)
+ if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+ want_nocaseglob=yes
+ else
+ file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"`
+ fi
+ ;;
+ esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+_LT_DECL([], [deplibs_check_method], [1],
+ [Method to check whether dependent libraries are shared objects])
+_LT_DECL([], [file_magic_cmd], [1],
+ [Command to use when deplibs_check_method = "file_magic"])
+_LT_DECL([], [file_magic_glob], [1],
+ [How to find potential files when deplibs_check_method = "file_magic"])
+_LT_DECL([], [want_nocaseglob], [1],
+ [Find potential files using nocaseglob when deplibs_check_method = "file_magic"])
+])# _LT_CHECK_MAGIC_METHOD
+
+
+# LT_PATH_NM
+# ----------
+# find the pathname to a BSD- or MS-compatible name lister
+AC_DEFUN([LT_PATH_NM],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM,
+[if test -n "$NM"; then
+ # Let the user override the test.
+ lt_cv_path_NM=$NM
+else
+ lt_nm_to_check=${ac_tool_prefix}nm
+ if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+ lt_nm_to_check="$lt_nm_to_check nm"
+ fi
+ for lt_tmp_nm in $lt_nm_to_check; do
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ tmp_nm=$ac_dir/$lt_tmp_nm
+ if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then
+ # Check to see if the nm accepts a BSD-compat flag.
+ # Adding the 'sed 1q' prevents false positives on HP-UX, which says:
+ # nm: unknown option "B" ignored
+ # Tru64's nm complains that /dev/null is an invalid object file
+ # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty
+ case $build_os in
+ mingw*) lt_bad_file=conftest.nm/nofile ;;
+ *) lt_bad_file=/dev/null ;;
+ esac
+ case `"$tmp_nm" -B $lt_bad_file 2>&1 | $SED '1q'` in
+ *$lt_bad_file* | *'Invalid file or object type'*)
+ lt_cv_path_NM="$tmp_nm -B"
+ break 2
+ ;;
+ *)
+ case `"$tmp_nm" -p /dev/null 2>&1 | $SED '1q'` in
+ */dev/null*)
+ lt_cv_path_NM="$tmp_nm -p"
+ break 2
+ ;;
+ *)
+ lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+ continue # so that we can try to find one that supports BSD flags
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ done
+ IFS=$lt_save_ifs
+ done
+ : ${lt_cv_path_NM=no}
+fi])
+if test no != "$lt_cv_path_NM"; then
+ NM=$lt_cv_path_NM
+else
+ # Didn't find any BSD compatible name lister, look for dumpbin.
+ if test -n "$DUMPBIN"; then :
+ # Let the user override the test.
+ else
+ AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :)
+ case `$DUMPBIN -symbols -headers /dev/null 2>&1 | $SED '1q'` in
+ *COFF*)
+ DUMPBIN="$DUMPBIN -symbols -headers"
+ ;;
+ *)
+ DUMPBIN=:
+ ;;
+ esac
+ fi
+ AC_SUBST([DUMPBIN])
+ if test : != "$DUMPBIN"; then
+ NM=$DUMPBIN
+ fi
+fi
+test -z "$NM" && NM=nm
+AC_SUBST([NM])
+_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl
+
+AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface],
+ [lt_cv_nm_interface="BSD nm"
+ echo "int some_variable = 0;" > conftest.$ac_ext
+ (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD)
+ (eval "$ac_compile" 2>conftest.err)
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD)
+ (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD)
+ cat conftest.out >&AS_MESSAGE_LOG_FD
+ if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+ lt_cv_nm_interface="MS dumpbin"
+ fi
+ rm -f conftest*])
+])# LT_PATH_NM
+
+# Old names:
+AU_ALIAS([AM_PROG_NM], [LT_PATH_NM])
+AU_ALIAS([AC_PROG_NM], [LT_PATH_NM])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_NM], [])
+dnl AC_DEFUN([AC_PROG_NM], [])
+
+# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+# --------------------------------
+# how to determine the name of the shared library
+# associated with a specific link library.
+# -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+m4_require([_LT_DECL_DLLTOOL])
+AC_CACHE_CHECK([how to associate runtime and link libraries],
+lt_cv_sharedlib_from_linklib_cmd,
+[lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+ # two different shell functions defined in ltmain.sh;
+ # decide which one to use based on capabilities of $DLLTOOL
+ case `$DLLTOOL --help 2>&1` in
+ *--identify-strict*)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+ ;;
+ *)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+ ;;
+ esac
+ ;;
+*)
+ # fallback: assume linklib IS sharedlib
+ lt_cv_sharedlib_from_linklib_cmd=$ECHO
+ ;;
+esac
+])
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+_LT_DECL([], [sharedlib_from_linklib_cmd], [1],
+ [Command to associate shared and link libraries])
+])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+
+
+# _LT_PATH_MANIFEST_TOOL
+# ----------------------
+# locate the manifest tool
+m4_defun([_LT_PATH_MANIFEST_TOOL],
+[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :)
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool],
+ [lt_cv_path_mainfest_tool=no
+ echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD
+ $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+ cat conftest.err >&AS_MESSAGE_LOG_FD
+ if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+ lt_cv_path_mainfest_tool=yes
+ fi
+ rm -f conftest*])
+if test yes != "$lt_cv_path_mainfest_tool"; then
+ MANIFEST_TOOL=:
+fi
+_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl
+])# _LT_PATH_MANIFEST_TOOL
+
+
+# _LT_DLL_DEF_P([FILE])
+# ---------------------
+# True iff FILE is a Windows DLL '.def' file.
+# Keep in sync with func_dll_def_p in the libtool script
+AC_DEFUN([_LT_DLL_DEF_P],
+[dnl
+ test DEF = "`$SED -n dnl
+ -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace
+ -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments
+ -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl
+ -e q dnl Only consider the first "real" line
+ $1`" dnl
+])# _LT_DLL_DEF_P
+
+
+# LT_LIB_M
+# --------
+# check for math library
+AC_DEFUN([LT_LIB_M],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+LIBM=
+case $host in
+*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*)
+ # These system don't have libm, or don't need it
+ ;;
+*-ncr-sysv4.3*)
+ AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw)
+ AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm")
+ ;;
+*)
+ AC_CHECK_LIB(m, cos, LIBM=-lm)
+ ;;
+esac
+AC_SUBST([LIBM])
+])# LT_LIB_M
+
+# Old name:
+AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_CHECK_LIBM], [])
+
+
+# _LT_COMPILER_NO_RTTI([TAGNAME])
+# -------------------------------
+m4_defun([_LT_COMPILER_NO_RTTI],
+[m4_require([_LT_TAG_COMPILER])dnl
+
+_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+
+if test yes = "$GCC"; then
+ case $cc_basename in
+ nvcc*)
+ _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;;
+ esac
+
+ _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions],
+ lt_cv_prog_compiler_rtti_exceptions,
+ [-fno-rtti -fno-exceptions], [],
+ [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"])
+fi
+_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1],
+ [Compiler flag to turn off builtin functions])
+])# _LT_COMPILER_NO_RTTI
+
+
+# _LT_CMD_GLOBAL_SYMBOLS
+# ----------------------
+m4_defun([_LT_CMD_GLOBAL_SYMBOLS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+AC_MSG_CHECKING([command to parse $NM output from $compiler object])
+AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe],
+[
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix. What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[[BCDEGRST]]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+ symcode='[[BCDT]]'
+ ;;
+cygwin* | mingw* | pw32* | cegcc*)
+ symcode='[[ABCDGISTW]]'
+ ;;
+hpux*)
+ if test ia64 = "$host_cpu"; then
+ symcode='[[ABCDEGRST]]'
+ fi
+ ;;
+irix* | nonstopux*)
+ symcode='[[BCDEGRST]]'
+ ;;
+osf*)
+ symcode='[[BCDEGQRST]]'
+ ;;
+solaris*)
+ symcode='[[BDRT]]'
+ ;;
+sco3.2v5*)
+ symcode='[[DT]]'
+ ;;
+sysv4.2uw2*)
+ symcode='[[DT]]'
+ ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+ symcode='[[ABDT]]'
+ ;;
+sysv4)
+ symcode='[[DFNSTU]]'
+ ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+ symcode='[[ABCDGIRSTW]]' ;;
+esac
+
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ # Gets list of data symbols to import.
+ lt_cv_sys_global_symbol_to_import="$SED -n -e 's/^I .* \(.*\)$/\1/p'"
+ # Adjust the below global symbol transforms to fixup imported variables.
+ lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'"
+ lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'"
+ lt_c_name_lib_hook="\
+ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\
+ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'"
+else
+ # Disable hooks by default.
+ lt_cv_sys_global_symbol_to_import=
+ lt_cdecl_hook=
+ lt_c_name_hook=
+ lt_c_name_lib_hook=
+fi
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="$SED -n"\
+$lt_cdecl_hook\
+" -e 's/^T .* \(.*\)$/extern int \1();/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="$SED -n"\
+$lt_c_name_hook\
+" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'"
+
+# Transform an extracted symbol line into symbol name with lib prefix and
+# symbol address.
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="$SED -n"\
+$lt_c_name_lib_hook\
+" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\
+" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+ opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+ ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+ # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+ symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+ # Write the raw and C identifiers.
+ if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ # Fake it for dumpbin and say T for any non-static function,
+ # D for any global variable and I for any imported variable.
+ # Also find C++ and __fastcall symbols from MSVC++ or ICC,
+ # which start with @ or ?.
+ lt_cv_sys_global_symbol_pipe="$AWK ['"\
+" {last_section=section; section=\$ 3};"\
+" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\
+" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\
+" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\
+" \$ 0!~/External *\|/{next};"\
+" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+" {if(hide[section]) next};"\
+" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\
+" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\
+" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\
+" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\
+" ' prfx=^$ac_symprfx]"
+ else
+ lt_cv_sys_global_symbol_pipe="$SED -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+ fi
+ lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | $SED '/ __gnu_lto/d'"
+
+ # Check to see that the pipe works correctly.
+ pipe_works=no
+
+ rm -f conftest*
+ cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+ if AC_TRY_EVAL(ac_compile); then
+ # Now try to grab the symbols.
+ nlist=conftest.nm
+ $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD
+ if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then
+ # Try sorting and uniquifying the output.
+ if sort "$nlist" | uniq > "$nlist"T; then
+ mv -f "$nlist"T "$nlist"
+ else
+ rm -f "$nlist"T
+ fi
+
+ # Make sure that we snagged all the symbols we need.
+ if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+ if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+ cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */
+#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE
+/* DATA imports from DLLs on WIN32 can't be const, because runtime
+ relocations are performed -- see ld's documentation on pseudo-relocs. */
+# define LT@&t@_DLSYM_CONST
+#elif defined __osf__
+/* This system does not cope well with relocations in const data. */
+# define LT@&t@_DLSYM_CONST
+#else
+# define LT@&t@_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+ # Now generate the symbol file.
+ eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+ cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols. */
+LT@&t@_DLSYM_CONST struct {
+ const char *name;
+ void *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[[]] =
+{
+ { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+ $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+ cat <<\_LT_EOF >> conftest.$ac_ext
+ {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+ return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+ # Now try linking the two files.
+ mv conftest.$ac_objext conftstm.$ac_objext
+ lt_globsym_save_LIBS=$LIBS
+ lt_globsym_save_CFLAGS=$CFLAGS
+ LIBS=conftstm.$ac_objext
+ CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)"
+ if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then
+ pipe_works=yes
+ fi
+ LIBS=$lt_globsym_save_LIBS
+ CFLAGS=$lt_globsym_save_CFLAGS
+ else
+ echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD
+ fi
+ else
+ echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD
+ fi
+ else
+ echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD
+ fi
+ else
+ echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD
+ cat conftest.$ac_ext >&5
+ fi
+ rm -rf conftest* conftst*
+
+ # Do not use the global_symbol_pipe unless it works.
+ if test yes = "$pipe_works"; then
+ break
+ else
+ lt_cv_sys_global_symbol_pipe=
+ fi
+done
+])
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+ lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+ AC_MSG_RESULT(failed)
+else
+ AC_MSG_RESULT(ok)
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then
+ nm_file_list_spec='@'
+fi
+
+_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1],
+ [Take the output of nm and produce a listing of raw symbols and C names])
+_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1],
+ [Transform the output of nm in a proper C declaration])
+_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1],
+ [Transform the output of nm into a list of symbols to manually relocate])
+_LT_DECL([global_symbol_to_c_name_address],
+ [lt_cv_sys_global_symbol_to_c_name_address], [1],
+ [Transform the output of nm in a C name address pair])
+_LT_DECL([global_symbol_to_c_name_address_lib_prefix],
+ [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1],
+ [Transform the output of nm in a C name address pair when lib prefix is needed])
+_LT_DECL([nm_interface], [lt_cv_nm_interface], [1],
+ [The name lister interface])
+_LT_DECL([], [nm_file_list_spec], [1],
+ [Specify filename containing input files for $NM])
+]) # _LT_CMD_GLOBAL_SYMBOLS
+
+
+# _LT_COMPILER_PIC([TAGNAME])
+# ---------------------------
+m4_defun([_LT_COMPILER_PIC],
+[m4_require([_LT_TAG_COMPILER])dnl
+_LT_TAGVAR(lt_prog_compiler_wl, $1)=
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+_LT_TAGVAR(lt_prog_compiler_static, $1)=
+
+m4_if([$1], [CXX], [
+ # C++ specific cases for pic, static, wl, etc.
+ if test yes = "$GXX"; then
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+ case $host_os in
+ aix*)
+ # All AIX code is PIC.
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ fi
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ m68k)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the '-m68020' flag to GCC prevents building anything better,
+ # like '-m68040'.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ esac
+ ;;
+
+ beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+ mingw* | cygwin* | os2* | pw32* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ # Although the cygwin gcc ignores -fPIC, still need this for old-style
+ # (--disable-auto-import) libraries
+ m4_if([$1], [GCJ], [],
+ [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+ case $host_os in
+ os2*)
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static'
+ ;;
+ esac
+ ;;
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+ ;;
+ *djgpp*)
+ # DJGPP does not support shared libraries at all
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+ ;;
+ haiku*)
+ # PIC is the default for Haiku.
+ # The "-static" flag exists, but is broken.
+ _LT_TAGVAR(lt_prog_compiler_static, $1)=
+ ;;
+ interix[[3-9]]*)
+ # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+ # Instead, we relocate shared libraries at runtime.
+ ;;
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+ fi
+ ;;
+ hpux*)
+ # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+ # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag
+ # sets the default TLS model and affects inlining.
+ case $host_cpu in
+ hppa*64*)
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ esac
+ ;;
+ *qnx* | *nto*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ esac
+ else
+ case $host_os in
+ aix[[4-9]]*)
+ # All AIX code is PIC.
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ else
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+ chorus*)
+ case $cc_basename in
+ cxch68*)
+ # Green Hills C++ Compiler
+ # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a"
+ ;;
+ esac
+ ;;
+ mingw* | cygwin* | os2* | pw32* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ m4_if([$1], [GCJ], [],
+ [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+ ;;
+ dgux*)
+ case $cc_basename in
+ ec++*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ ;;
+ ghcx*)
+ # Green Hills C++ Compiler
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ freebsd* | dragonfly* | midnightbsd*)
+ # FreeBSD uses GNU C++
+ ;;
+ hpux9* | hpux10* | hpux11*)
+ case $cc_basename in
+ CC*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive'
+ if test ia64 != "$host_cpu"; then
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+ fi
+ ;;
+ aCC*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive'
+ case $host_cpu in
+ hppa*64*|ia64*)
+ # +Z the default
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+ ;;
+ esac
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ interix*)
+ # This is c89, which is MS Visual C++ (no shared libs)
+ # Anyone wants to do a port?
+ ;;
+ irix5* | irix6* | nonstopux*)
+ case $cc_basename in
+ CC*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ # CC pic flag -KPIC is the default.
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ case $cc_basename in
+ KCC*)
+ # KAI C++ Compiler
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ ecpc* )
+ # old Intel C++ for x86_64, which still supported -KPIC.
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ icpc* )
+ # Intel C++, used to be incompatible with GCC.
+ # ICC 10 doesn't accept -KPIC any more.
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ pgCC* | pgcpp*)
+ # Portland Group C++ compiler
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ cxx*)
+ # Compaq C++
+ # Make sure the PIC flag is empty. It appears that all Alpha
+ # Linux and Compaq Tru64 Unix objects are PIC.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+ xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*)
+ # IBM XL 8.0, 9.0 on PPC and BlueGene
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+ ;;
+ *)
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ C*)
+ # Sun C++ 5.9
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+ lynxos*)
+ ;;
+ m88k*)
+ ;;
+ mvs*)
+ case $cc_basename in
+ cxx*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ netbsd* | netbsdelf*-gnu)
+ ;;
+ *qnx* | *nto*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+ ;;
+ osf3* | osf4* | osf5*)
+ case $cc_basename in
+ KCC*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+ ;;
+ RCC*)
+ # Rational C++ 2.4.1
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+ ;;
+ cxx*)
+ # Digital/Compaq C++
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ # Make sure the PIC flag is empty. It appears that all Alpha
+ # Linux and Compaq Tru64 Unix objects are PIC.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ psos*)
+ ;;
+ solaris*)
+ case $cc_basename in
+ CC* | sunCC*)
+ # Sun C++ 4.2, 5.x and Centerline C++
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+ ;;
+ gcx*)
+ # Green Hills C++ Compiler
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ sunos4*)
+ case $cc_basename in
+ CC*)
+ # Sun C++ 4.x
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ lcc*)
+ # Lucid
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ case $cc_basename in
+ CC*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ esac
+ ;;
+ tandem*)
+ case $cc_basename in
+ NCC*)
+ # NonStop-UX NCC 3.20
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ vxworks*)
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+ ;;
+ esac
+ fi
+],
+[
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+ case $host_os in
+ aix*)
+ # All AIX code is PIC.
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ fi
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ m68k)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the '-m68020' flag to GCC prevents building anything better,
+ # like '-m68040'.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ esac
+ ;;
+
+ beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ # Although the cygwin gcc ignores -fPIC, still need this for old-style
+ # (--disable-auto-import) libraries
+ m4_if([$1], [GCJ], [],
+ [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+ case $host_os in
+ os2*)
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static'
+ ;;
+ esac
+ ;;
+
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+ ;;
+
+ haiku*)
+ # PIC is the default for Haiku.
+ # The "-static" flag exists, but is broken.
+ _LT_TAGVAR(lt_prog_compiler_static, $1)=
+ ;;
+
+ hpux*)
+ # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+ # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag
+ # sets the default TLS model and affects inlining.
+ case $host_cpu in
+ hppa*64*)
+ # +Z the default
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ esac
+ ;;
+
+ interix[[3-9]]*)
+ # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+ # Instead, we relocate shared libraries at runtime.
+ ;;
+
+ msdosdjgpp*)
+ # Just because we use GCC doesn't mean we suddenly get shared libraries
+ # on systems that don't support them.
+ _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+ enable_shared=no
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+ fi
+ ;;
+
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ ;;
+ esac
+
+ case $cc_basename in
+ nvcc*) # Cuda Compiler Driver 2.2
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker '
+ if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)"
+ fi
+ ;;
+ esac
+ else
+ # PORTME Check for flag to pass linker flags through the system compiler.
+ case $host_os in
+ aix*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ else
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+ case $cc_basename in
+ nagfor*)
+ # NAG Fortran compiler
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ esac
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ m4_if([$1], [GCJ], [],
+ [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+ case $host_os in
+ os2*)
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static'
+ ;;
+ esac
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+ # not for PA HP-UX.
+ case $host_cpu in
+ hppa*64*|ia64*)
+ # +Z the default
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+ ;;
+ esac
+ # Is there a better lt_prog_compiler_static that works with the bundled CC?
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive'
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ # PIC (with -KPIC) is the default.
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+
+ linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ case $cc_basename in
+ # old Intel for x86_64, which still supported -KPIC.
+ ecc*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ # flang / f18. f95 an alias for gfortran or flang on Debian
+ flang* | f18* | f95*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ # icc used to be incompatible with GCC.
+ # ICC 10 doesn't accept -KPIC any more.
+ icc* | ifort*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ # Lahey Fortran 8.1.
+ lf95*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='--static'
+ ;;
+ nagfor*)
+ # NAG Fortran compiler
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ tcc*)
+ # Fabrice Bellard et al's Tiny C Compiler
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group compilers (*not* the Pentium gcc compiler,
+ # which looks to be a dead project)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ ccc*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ # All Alpha code is PIC.
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+ xl* | bgxl* | bgf* | mpixl*)
+ # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+ ;;
+ *)
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*)
+ # Sun Fortran 8.3 passes all unrecognized flags to the linker
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)=''
+ ;;
+ *Sun\ F* | *Sun*Fortran*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+ ;;
+ *Sun\ C*)
+ # Sun C 5.9
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ ;;
+ *Intel*\ [[CF]]*Compiler*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+ ;;
+ *Portland\ Group*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+ newsos6)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+ ;;
+
+ osf3* | osf4* | osf5*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ # All OSF/1 code is PIC.
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+
+ rdos*)
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+ ;;
+
+ solaris*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ case $cc_basename in
+ f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';;
+ esac
+ ;;
+
+ sunos4*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+
+ sysv4 | sysv4.2uw2* | sysv4.3*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ fi
+ ;;
+
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+
+ unicos*)
+ _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+ _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+ ;;
+
+ uts4*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+ _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+ ;;
+
+ *)
+ _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+ ;;
+ esac
+ fi
+])
+case $host_os in
+ # For platforms that do not support PIC, -DPIC is meaningless:
+ *djgpp*)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+ ;;
+ *)
+ _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])"
+ ;;
+esac
+
+AC_CACHE_CHECK([for $compiler option to produce PIC],
+ [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)],
+ [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)])
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+ _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works],
+ [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)],
+ [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [],
+ [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in
+ "" | " "*) ;;
+ *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;;
+ esac],
+ [_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+ _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no])
+fi
+_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1],
+ [Additional compiler flags for building library objects])
+
+_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1],
+ [How to pass a linker flag through the compiler])
+#
+# Check to make sure the static flag actually works.
+#
+wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\"
+_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works],
+ _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1),
+ $lt_tmp_static_flag,
+ [],
+ [_LT_TAGVAR(lt_prog_compiler_static, $1)=])
+_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1],
+ [Compiler flag to prevent dynamic linking])
+])# _LT_COMPILER_PIC
+
+
+# _LT_LINKER_SHLIBS([TAGNAME])
+# ----------------------------
+# See if the linker supports building shared libraries.
+m4_defun([_LT_LINKER_SHLIBS],
+[AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+m4_if([$1], [CXX], [
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+ case $host_os in
+ aix[[4-9]]*)
+ # If we're using GNU nm, then we don't want the "-C" option.
+ # -C means demangle to GNU nm, but means don't demangle to AIX nm.
+ # Without the "-l" option, or with the "-B" option, AIX nm treats
+ # weak defined symbols like other global defined symbols, whereas
+ # GNU nm marks them as "W".
+ # While the 'weak' keyword is ignored in the Export File, we need
+ # it in the Import File for the 'aix-soname' feature, so we have
+ # to replace the "-B" option with "-P" for AIX nm.
+ if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols'
+ else
+ _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols'
+ fi
+ ;;
+ pw32*)
+ _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds
+ ;;
+ cygwin* | mingw* | cegcc*)
+ case $cc_basename in
+ cl* | icl*)
+ _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+ ;;
+ *)
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+ _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+ ;;
+ esac
+ ;;
+ linux* | k*bsd*-gnu | gnu*)
+ _LT_TAGVAR(link_all_deplibs, $1)=no
+ ;;
+ *)
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ ;;
+ esac
+], [
+ runpath_var=
+ _LT_TAGVAR(allow_undefined_flag, $1)=
+ _LT_TAGVAR(always_export_symbols, $1)=no
+ _LT_TAGVAR(archive_cmds, $1)=
+ _LT_TAGVAR(archive_expsym_cmds, $1)=
+ _LT_TAGVAR(compiler_needs_object, $1)=no
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ _LT_TAGVAR(hardcode_automatic, $1)=no
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=
+ _LT_TAGVAR(hardcode_minus_L, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+ _LT_TAGVAR(inherit_rpath, $1)=no
+ _LT_TAGVAR(link_all_deplibs, $1)=unknown
+ _LT_TAGVAR(module_cmds, $1)=
+ _LT_TAGVAR(module_expsym_cmds, $1)=
+ _LT_TAGVAR(old_archive_from_new_cmds, $1)=
+ _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)=
+ _LT_TAGVAR(thread_safe_flag_spec, $1)=
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=
+ # include_expsyms should be a list of space-separated symbols to be *always*
+ # included in the symbol list
+ _LT_TAGVAR(include_expsyms, $1)=
+ # exclude_expsyms can be an extended regexp of symbols to exclude
+ # it will be wrapped by ' (' and ')$', so one must not match beginning or
+ # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc',
+ # as well as any symbol that contains 'd'.
+ _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+ # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+ # platforms (ab)use it in PIC code, but their linkers get confused if
+ # the symbol is explicitly referenced. Since portable code cannot
+ # rely on this symbol name, it's probably fine to never include it in
+ # preloaded symbol tables.
+ # Exclude shared library initialization/finalization symbols.
+dnl Note also adjust exclude_expsyms for C++ above.
+ extract_expsyms_cmds=
+
+ case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ # FIXME: the MSVC++ and ICC port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++ or Intel C++ Compiler.
+ if test yes != "$GCC"; then
+ with_gnu_ld=no
+ fi
+ ;;
+ interix*)
+ # we just hope/assume this is gcc and not c89 (= MSVC++ or ICC)
+ with_gnu_ld=yes
+ ;;
+ openbsd* | bitrig*)
+ with_gnu_ld=no
+ ;;
+ linux* | k*bsd*-gnu | gnu*)
+ _LT_TAGVAR(link_all_deplibs, $1)=no
+ ;;
+ esac
+
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+
+ # On some targets, GNU ld is compatible enough with the native linker
+ # that we're better off using the native interface for both.
+ lt_use_gnu_ld_interface=no
+ if test yes = "$with_gnu_ld"; then
+ case $host_os in
+ aix*)
+ # The AIX port of GNU ld has always aspired to compatibility
+ # with the native linker. However, as the warning in the GNU ld
+ # block says, versions before 2.19.5* couldn't really create working
+ # shared libraries, regardless of the interface used.
+ case `$LD -v 2>&1` in
+ *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+ *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;;
+ *\ \(GNU\ Binutils\)\ [[3-9]]*) ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ fi
+
+ if test yes = "$lt_use_gnu_ld_interface"; then
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ wlarc='$wl'
+
+ # Set some defaults for GNU ld with shared library support. These
+ # are reset later if shared libraries are not supported. Putting them
+ # here allows them to be overridden if necessary.
+ runpath_var=LD_RUN_PATH
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive'
+ else
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=
+ fi
+ supports_anon_versioning=no
+ case `$LD -v | $SED -e 's/([[^)]]\+)\s\+//' 2>&1` in
+ *GNU\ gold*) supports_anon_versioning=yes ;;
+ *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11
+ *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+ *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+ *\ 2.11.*) ;; # other 2.11 versions
+ *) supports_anon_versioning=yes ;;
+ esac
+
+ # See if GNU ld supports shared libraries.
+ case $host_os in
+ aix[[3-9]]*)
+ # On AIX/PPC, the GNU linker is very broken
+ if test ia64 != "$host_cpu"; then
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support. If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)=''
+ ;;
+ m68k)
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ ;;
+ esac
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+ # as there is no search path for DLLs.
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols'
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ _LT_TAGVAR(always_export_symbols, $1)=no
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+ _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+
+ if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ # If the export-symbols file already is a .def file, use it as
+ # is; otherwise, prepend EXPORTS...
+ _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then
+ cp $export_symbols $output_objdir/$soname.def;
+ else
+ echo EXPORTS > $output_objdir/$soname.def;
+ cat $export_symbols >> $output_objdir/$soname.def;
+ fi~
+ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ haiku*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ ;;
+
+ os2*)
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ shrext_cmds=.dll
+ _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ prefix_cmds="$SED"~
+ if test EXPORTS = "`$SED 1q $export_symbols`"; then
+ prefix_cmds="$prefix_cmds -e 1d";
+ fi~
+ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~
+ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='@'
+ ;;
+
+ interix[[3-9]]*)
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+ # Instead, shared libraries are loaded at an image base (0x10000000 by
+ # default) and relocated if they conflict, which is a slow very memory
+ # consuming and fragmenting process. To avoid this, we pick a random,
+ # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+ # time. Moving up from 0x10000000 also allows more sbrk(2) space.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ ;;
+
+ gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+ tmp_diet=no
+ if test linux-dietlibc = "$host_os"; then
+ case $cc_basename in
+ diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn)
+ esac
+ fi
+ if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+ && test no = "$tmp_diet"
+ then
+ tmp_addflag=' $pic_flag'
+ tmp_sharedflag='-shared'
+ case $cc_basename,$host_cpu in
+ pgcc*) # Portland Group C compiler
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ tmp_addflag=' $pic_flag'
+ ;;
+ pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group f77 and f90 compilers
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ tmp_addflag=' $pic_flag -Mnomain' ;;
+ ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64
+ tmp_addflag=' -i_dynamic' ;;
+ efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64
+ tmp_addflag=' -i_dynamic -nofor_main' ;;
+ ifc* | ifort*) # Intel Fortran compiler
+ tmp_addflag=' -nofor_main' ;;
+ lf95*) # Lahey Fortran 8.1
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=
+ tmp_sharedflag='--shared' ;;
+ nagfor*) # NAGFOR 5.3
+ tmp_sharedflag='-Wl,-shared' ;;
+ xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+ tmp_sharedflag='-qmkshrobj'
+ tmp_addflag= ;;
+ nvcc*) # Cuda Compiler Driver 2.2
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ _LT_TAGVAR(compiler_needs_object, $1)=yes
+ ;;
+ esac
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ C*) # Sun C 5.9
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ _LT_TAGVAR(compiler_needs_object, $1)=yes
+ tmp_sharedflag='-G' ;;
+ *Sun\ F*) # Sun Fortran 8.3
+ tmp_sharedflag='-G' ;;
+ esac
+ _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+
+ if test yes = "$supports_anon_versioning"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib'
+ fi
+
+ case $cc_basename in
+ tcc*)
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic'
+ ;;
+ xlf* | bgf* | bgxlf* | mpixlf*)
+ # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+ if test yes = "$supports_anon_versioning"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+ fi
+ ;;
+ esac
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+ wlarc=
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ fi
+ ;;
+
+ solaris*)
+ if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+ case `$LD -v 2>&1` in
+ *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*)
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot
+*** reliably create shared libraries on SCO systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ ;;
+ *)
+ # For security reasons, it is highly recommended that you always
+ # use absolute paths for naming shared libraries, and exclude the
+ # DT_RUNPATH tag from executables and libraries. But doing so
+ # requires that you compile everything twice, which is a pain.
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+ ;;
+
+ sunos4*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ wlarc=
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ *)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+
+ if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then
+ runpath_var=
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=
+ fi
+ else
+ # PORTME fill in a description of your system's linker (not GNU ld)
+ case $host_os in
+ aix3*)
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ _LT_TAGVAR(always_export_symbols, $1)=yes
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ _LT_TAGVAR(hardcode_direct, $1)=unsupported
+ fi
+ ;;
+
+ aix[[4-9]]*)
+ if test ia64 = "$host_cpu"; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=
+ else
+ # If we're using GNU nm, then we don't want the "-C" option.
+ # -C means demangle to GNU nm, but means don't demangle to AIX nm.
+ # Without the "-l" option, or with the "-B" option, AIX nm treats
+ # weak defined symbols like other global defined symbols, whereas
+ # GNU nm marks them as "W".
+ # While the 'weak' keyword is ignored in the Export File, we need
+ # it in the Import File for the 'aix-soname' feature, so we have
+ # to replace the "-B" option with "-P" for AIX nm.
+ if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols'
+ else
+ _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols'
+ fi
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # have runtime linking enabled, and use it for executables.
+ # For shared libraries, we enable/disable runtime linking
+ # depending on the kind of the shared library created -
+ # when "with_aix_soname,aix_use_runtimelinking" is:
+ # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables
+ # "aix,yes" lib.so shared, rtl:yes, for executables
+ # lib.a static archive
+ # "both,no" lib.so.V(shr.o) shared, rtl:yes
+ # lib.a(lib.so.V) shared, rtl:no, for executables
+ # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a(lib.so.V) shared, rtl:no
+ # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a static archive
+ case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+ for ld_flag in $LDFLAGS; do
+ if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # With aix-soname=svr4, we create the lib.so.V shared archives only,
+ # so we don't have lib.a shared libs to link our executables.
+ # We have to force runtime linking in this case.
+ aix_use_runtimelinking=yes
+ LDFLAGS="$LDFLAGS -Wl,-brtl"
+ fi
+ ;;
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ _LT_TAGVAR(archive_cmds, $1)=''
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='$wl-f,'
+ case $with_aix_soname,$aix_use_runtimelinking in
+ aix,*) ;; # traditional, no import file
+ svr4,* | *,yes) # use import file
+ # The Import File defines what to hardcode.
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+ ;;
+ esac
+
+ if test yes = "$GCC"; then
+ case $host_os in aix4.[[012]]|aix4.[[012]].*)
+ # We only want to do this on AIX 4.2 and lower, the check
+ # below for broken collect2 doesn't work under 4.3+
+ collect2name=`$CC -print-prog-name=collect2`
+ if test -f "$collect2name" &&
+ strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ _LT_TAGVAR(hardcode_direct, $1)=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=
+ fi
+ ;;
+ esac
+ shared_flag='-shared'
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag="$shared_flag "'$wl-G'
+ fi
+ # Need to ensure runtime linking is disabled for the traditional
+ # shared library, or the linker may eventually find shared libraries
+ # /with/ Import File - we do not want to mix them.
+ shared_flag_aix='-shared'
+ shared_flag_svr4='-shared $wl-G'
+ else
+ # not using gcc
+ if test ia64 = "$host_cpu"; then
+ # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+ # chokes on -Wl,-G. The following line is correct:
+ shared_flag='-G'
+ else
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag='$wl-G'
+ else
+ shared_flag='$wl-bM:SRE'
+ fi
+ shared_flag_aix='$wl-bM:SRE'
+ shared_flag_svr4='$wl-G'
+ fi
+ fi
+
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall'
+ # It seems that -bexpall does not export symbols beginning with
+ # underscore (_), so it is better to generate a list of symbols to export.
+ _LT_TAGVAR(always_export_symbols, $1)=yes
+ if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ _LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ _LT_SYS_MODULE_PATH_AIX([$1])
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath"
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag
+ else
+ if test ia64 = "$host_cpu"; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib'
+ _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+ _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols"
+ else
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ _LT_SYS_MODULE_PATH_AIX([$1])
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath"
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok'
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok'
+ if test yes = "$with_gnu_ld"; then
+ # We only use this code for GNU lds that support --whole-archive.
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive'
+ else
+ # Exported symbols can be pulled into shared objects from archives
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+ fi
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d'
+ # -brtl affects multiple linker settings, -berok does not and is overridden later
+ compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`'
+ if test svr4 != "$with_aix_soname"; then
+ # This is similar to how AIX traditionally builds its shared libraries.
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname'
+ fi
+ if test aix != "$with_aix_soname"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp'
+ else
+ # used by -dlpreopen to get the symbols
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir'
+ fi
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d'
+ fi
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)=''
+ ;;
+ m68k)
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ ;;
+ esac
+ ;;
+
+ bsdi[[45]]*)
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++ or Intel C++ Compiler.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ case $cc_basename in
+ cl* | icl*)
+ # Native MSVC or ICC
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ _LT_TAGVAR(always_export_symbols, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='@'
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=.dll
+ # FIXME: Setting linknames here is a bad hack.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames='
+ _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then
+ cp "$export_symbols" "$output_objdir/$soname.def";
+ echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp";
+ else
+ $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp;
+ fi~
+ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+ linknames='
+ # The linker will not automatically build a static lib if we build a DLL.
+ # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+ _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols'
+ # Don't use ranlib
+ _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+ _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+ lt_tool_outputfile="@TOOL_OUTPUT@"~
+ case $lt_outputfile in
+ *.exe|*.EXE) ;;
+ *)
+ lt_outputfile=$lt_outputfile.exe
+ lt_tool_outputfile=$lt_tool_outputfile.exe
+ ;;
+ esac~
+ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then
+ $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+ $RM "$lt_outputfile.manifest";
+ fi'
+ ;;
+ *)
+ # Assume MSVC and ICC wrapper
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=.dll
+ # FIXME: Setting linknames here is a bad hack.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+ # The linker will automatically build a .lib file if we build a DLL.
+ _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+ # FIXME: Should let the user specify the lib program.
+ _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ ;;
+ esac
+ ;;
+
+ darwin* | rhapsody*)
+ _LT_DARWIN_LINKER_FEATURES($1)
+ ;;
+
+ dgux*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+ # support. Future versions do this automatically, but an explicit c++rt0.o
+ # does not break anything, and helps significantly (at the cost of a little
+ # extra space).
+ freebsd2.2*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+ freebsd2.*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+ freebsd* | dragonfly* | midnightbsd*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ hpux9*)
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ fi
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ ;;
+
+ hpux10*)
+ if test yes,no = "$GCC,$with_gnu_ld"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ if test no = "$with_gnu_ld"; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ fi
+ ;;
+
+ hpux11*)
+ if test yes,no = "$GCC,$with_gnu_ld"; then
+ case $host_cpu in
+ hppa*64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ else
+ case $host_cpu in
+ hppa*64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ m4_if($1, [], [
+ # Older versions of the 11.00 compiler do not understand -b yet
+ # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+ _LT_LINKER_OPTION([if $CC understands -b],
+ _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b],
+ [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'],
+ [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])],
+ [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'])
+ ;;
+ esac
+ fi
+ if test no = "$with_gnu_ld"; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ case $host_cpu in
+ hppa*64*|ia64*)
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+ *)
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ ;;
+ esac
+ fi
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ # Try to use the -exported_symbol ld option, if it does not
+ # work, assume that -exports_file does not work either and
+ # implicitly export all symbols.
+ # This should be the same for all languages, so no per-tag cache variable.
+ AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol],
+ [lt_cv_irix_exported_symbol],
+ [save_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null"
+ AC_LINK_IFELSE(
+ [AC_LANG_SOURCE(
+ [AC_LANG_CASE([C], [[int foo (void) { return 0; }]],
+ [C++], [[int foo (void) { return 0; }]],
+ [Fortran 77], [[
+ subroutine foo
+ end]],
+ [Fortran], [[
+ subroutine foo
+ end]])])],
+ [lt_cv_irix_exported_symbol=yes],
+ [lt_cv_irix_exported_symbol=no])
+ LDFLAGS=$save_LDFLAGS])
+ if test yes = "$lt_cv_irix_exported_symbol"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib'
+ fi
+ _LT_TAGVAR(link_all_deplibs, $1)=no
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib'
+ fi
+ _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(inherit_rpath, $1)=yes
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ ;;
+
+ linux*)
+ case $cc_basename in
+ tcc*)
+ # Fabrice Bellard et al's Tiny C Compiler
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ ;;
+ esac
+ ;;
+
+ netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF
+ fi
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ newsos6)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ *nto* | *qnx*)
+ ;;
+
+ openbsd* | bitrig*)
+ if test -f /usr/libexec/ld.so; then
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ fi
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ os2*)
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ shrext_cmds=.dll
+ _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ prefix_cmds="$SED"~
+ if test EXPORTS = "`$SED 1q $export_symbols`"; then
+ prefix_cmds="$prefix_cmds -e 1d";
+ fi~
+ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~
+ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='@'
+ ;;
+
+ osf3*)
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ else
+ _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ fi
+ _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ ;;
+
+ osf4* | osf5*) # as osf3* with the addition of -msym flag
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ else
+ _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp'
+
+ # Both c and cxx compiler support -rpath directly
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+ fi
+ _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ ;;
+
+ solaris*)
+ _LT_TAGVAR(no_undefined_flag, $1)=' -z defs'
+ if test yes = "$GCC"; then
+ wlarc='$wl'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ else
+ case `$CC -V 2>&1` in
+ *"Compilers 5.0"*)
+ wlarc=''
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+ ;;
+ *)
+ wlarc='$wl'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ ;;
+ esac
+ fi
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ case $host_os in
+ solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+ *)
+ # The compiler driver will combine and reorder linker options,
+ # but understands '-z linker_flag'. GCC discards it without '$wl',
+ # but is careful enough not to reorder.
+ # Supported since Solaris 2.6 (maybe 2.5.1?)
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract'
+ else
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+ fi
+ ;;
+ esac
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ ;;
+
+ sunos4*)
+ if test sequent = "$host_vendor"; then
+ # Use $CC to link under sequent, because it throws in some extra .o
+ # files that make .init and .fini sections work.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ sysv4)
+ case $host_vendor in
+ sni)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true???
+ ;;
+ siemens)
+ ## LD is ld it makes a PLAMLIB
+ ## CC just makes a GrossModule.
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs'
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ ;;
+ motorola)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie
+ ;;
+ esac
+ runpath_var='LD_RUN_PATH'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ sysv4.3*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+ fi
+ ;;
+
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+ _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text'
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ runpath_var='LD_RUN_PATH'
+
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6*)
+ # Note: We CANNOT use -z defs as we might desire, because we do not
+ # link with -lc, and that would cause any symbols used from libc to
+ # always be unresolved, which means just about no library would
+ # ever link correctly. If we're not using GNU ld we use -z text
+ # though, which does catch some bad symbols but isn't as heavy-handed
+ # as -z defs.
+ _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text'
+ _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs'
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport'
+ runpath_var='LD_RUN_PATH'
+
+ if test yes = "$GCC"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ uts4*)
+ _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+
+ *)
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+
+ if test sni = "$host_vendor"; then
+ case $host in
+ sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym'
+ ;;
+ esac
+ fi
+ fi
+])
+AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no
+
+_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld
+
+_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl
+_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl
+_LT_DECL([], [extract_expsyms_cmds], [2],
+ [The commands to extract the exported symbol list from a shared archive])
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in
+x|xyes)
+ # Assume -lc should be added
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+
+ if test yes,yes = "$GCC,$enable_shared"; then
+ case $_LT_TAGVAR(archive_cmds, $1) in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ AC_CACHE_CHECK([whether -lc should be explicitly linked in],
+ [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1),
+ [$RM conftest*
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ if AC_TRY_EVAL(ac_compile) 2>conftest.err; then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1)
+ pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1)
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1)
+ _LT_TAGVAR(allow_undefined_flag, $1)=
+ if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1)
+ then
+ lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ else
+ lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+ fi
+ _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi
+ $RM conftest*
+ ])
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)
+ ;;
+ esac
+ fi
+ ;;
+esac
+
+_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0],
+ [Whether or not to add -lc for building shared libraries])
+_LT_TAGDECL([allow_libtool_libs_with_static_runtimes],
+ [enable_shared_with_static_runtimes], [0],
+ [Whether or not to disallow shared libs when runtime libs are static])
+_LT_TAGDECL([], [export_dynamic_flag_spec], [1],
+ [Compiler flag to allow reflexive dlopens])
+_LT_TAGDECL([], [whole_archive_flag_spec], [1],
+ [Compiler flag to generate shared objects directly from archives])
+_LT_TAGDECL([], [compiler_needs_object], [1],
+ [Whether the compiler copes with passing no objects directly])
+_LT_TAGDECL([], [old_archive_from_new_cmds], [2],
+ [Create an old-style archive from a shared archive])
+_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2],
+ [Create a temporary old-style archive to link instead of a shared archive])
+_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive])
+_LT_TAGDECL([], [archive_expsym_cmds], [2])
+_LT_TAGDECL([], [module_cmds], [2],
+ [Commands used to build a loadable module if different from building
+ a shared archive.])
+_LT_TAGDECL([], [module_expsym_cmds], [2])
+_LT_TAGDECL([], [with_gnu_ld], [1],
+ [Whether we are building with GNU ld or not])
+_LT_TAGDECL([], [allow_undefined_flag], [1],
+ [Flag that allows shared libraries with undefined symbols to be built])
+_LT_TAGDECL([], [no_undefined_flag], [1],
+ [Flag that enforces no undefined symbols])
+_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1],
+ [Flag to hardcode $libdir into a binary during linking.
+ This must work even if $libdir does not exist])
+_LT_TAGDECL([], [hardcode_libdir_separator], [1],
+ [Whether we need a single "-rpath" flag with a separated argument])
+_LT_TAGDECL([], [hardcode_direct], [0],
+ [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes
+ DIR into the resulting binary])
+_LT_TAGDECL([], [hardcode_direct_absolute], [0],
+ [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes
+ DIR into the resulting binary and the resulting library dependency is
+ "absolute", i.e impossible to change by setting $shlibpath_var if the
+ library is relocated])
+_LT_TAGDECL([], [hardcode_minus_L], [0],
+ [Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+ into the resulting binary])
+_LT_TAGDECL([], [hardcode_shlibpath_var], [0],
+ [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+ into the resulting binary])
+_LT_TAGDECL([], [hardcode_automatic], [0],
+ [Set to "yes" if building a shared library automatically hardcodes DIR
+ into the library and all subsequent libraries and executables linked
+ against it])
+_LT_TAGDECL([], [inherit_rpath], [0],
+ [Set to yes if linker adds runtime paths of dependent libraries
+ to runtime path list])
+_LT_TAGDECL([], [link_all_deplibs], [0],
+ [Whether libtool must link a program against all its dependency libraries])
+_LT_TAGDECL([], [always_export_symbols], [0],
+ [Set to "yes" if exported symbols are required])
+_LT_TAGDECL([], [export_symbols_cmds], [2],
+ [The commands to list exported symbols])
+_LT_TAGDECL([], [exclude_expsyms], [1],
+ [Symbols that should not be listed in the preloaded symbols])
+_LT_TAGDECL([], [include_expsyms], [1],
+ [Symbols that must always be exported])
+_LT_TAGDECL([], [prelink_cmds], [2],
+ [Commands necessary for linking programs (against libraries) with templates])
+_LT_TAGDECL([], [postlink_cmds], [2],
+ [Commands necessary for finishing linking programs])
+_LT_TAGDECL([], [file_list_spec], [1],
+ [Specify filename containing input files])
+dnl FIXME: Not yet implemented
+dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1],
+dnl [Compiler flag to generate thread safe objects])
+])# _LT_LINKER_SHLIBS
+
+
+# _LT_LANG_C_CONFIG([TAG])
+# ------------------------
+# Ensure that the configuration variables for a C compiler are suitably
+# defined. These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_C_CONFIG],
+[m4_require([_LT_DECL_EGREP])dnl
+lt_save_CC=$CC
+AC_LANG_PUSH(C)
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+_LT_TAG_COMPILER
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+if test -n "$compiler"; then
+ _LT_COMPILER_NO_RTTI($1)
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_SYS_DYNAMIC_LINKER($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+ LT_SYS_DLOPEN_SELF
+ _LT_CMD_STRIPLIB
+
+ # Report what library types will actually be built
+ AC_MSG_CHECKING([if libtool supports shared libraries])
+ AC_MSG_RESULT([$can_build_shared])
+
+ AC_MSG_CHECKING([whether to build shared libraries])
+ test no = "$can_build_shared" && enable_shared=no
+
+ # On AIX, shared libraries and static libraries use the same namespace, and
+ # are all built from PIC.
+ case $host_os in
+ aix3*)
+ test yes = "$enable_shared" && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+
+ aix[[4-9]]*)
+ if test ia64 != "$host_cpu"; then
+ case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in
+ yes,aix,yes) ;; # shared object as lib.so file only
+ yes,svr4,*) ;; # shared object as lib.so archive member only
+ yes,*) enable_static=no ;; # shared object in lib.a archive as well
+ esac
+ fi
+ ;;
+ esac
+ AC_MSG_RESULT([$enable_shared])
+
+ AC_MSG_CHECKING([whether to build static libraries])
+ # Make sure either enable_shared or enable_static is yes.
+ test yes = "$enable_shared" || enable_static=yes
+ AC_MSG_RESULT([$enable_static])
+
+ _LT_CONFIG($1)
+fi
+AC_LANG_POP
+CC=$lt_save_CC
+])# _LT_LANG_C_CONFIG
+
+
+# _LT_LANG_CXX_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a C++ compiler are suitably
+# defined. These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_CXX_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+if test -n "$CXX" && ( test no != "$CXX" &&
+ ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) ||
+ (test g++ != "$CXX"))); then
+ AC_PROG_CXXCPP
+else
+ _lt_caught_CXX_error=yes
+fi
+
+AC_LANG_PUSH(C++)
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(compiler_needs_object, $1)=no
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for C++ test sources.
+ac_ext=cpp
+
+# Object file extension for compiled C++ test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the CXX compiler isn't working. Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test yes != "$_lt_caught_CXX_error"; then
+ # Code to be used in simple compile tests
+ lt_simple_compile_test_code="int some_variable = 0;"
+
+ # Code to be used in simple link tests
+ lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }'
+
+ # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+ _LT_TAG_COMPILER
+
+ # save warnings/boilerplate of simple test code
+ _LT_COMPILER_BOILERPLATE
+ _LT_LINKER_BOILERPLATE
+
+ # Allow CC to be a program name with arguments.
+ lt_save_CC=$CC
+ lt_save_CFLAGS=$CFLAGS
+ lt_save_LD=$LD
+ lt_save_GCC=$GCC
+ GCC=$GXX
+ lt_save_with_gnu_ld=$with_gnu_ld
+ lt_save_path_LD=$lt_cv_path_LD
+ if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then
+ lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx
+ else
+ $as_unset lt_cv_prog_gnu_ld
+ fi
+ if test -n "${lt_cv_path_LDCXX+set}"; then
+ lt_cv_path_LD=$lt_cv_path_LDCXX
+ else
+ $as_unset lt_cv_path_LD
+ fi
+ test -z "${LDCXX+set}" || LD=$LDCXX
+ CC=${CXX-"c++"}
+ CFLAGS=$CXXFLAGS
+ compiler=$CC
+ _LT_TAGVAR(compiler, $1)=$CC
+ _LT_CC_BASENAME([$compiler])
+
+ if test -n "$compiler"; then
+ # We don't want -fno-exception when compiling C++ code, so set the
+ # no_builtin_flag separately
+ if test yes = "$GXX"; then
+ _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin'
+ else
+ _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+ fi
+
+ if test yes = "$GXX"; then
+ # Set up default GNU C++ configuration
+
+ LT_PATH_LD
+
+ # Check if GNU C++ uses GNU ld as the underlying linker, since the
+ # archiving commands below assume that GNU ld is being used.
+ if test yes = "$with_gnu_ld"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to
+ # investigate it a little bit more. (MM)
+ wlarc='$wl'
+
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if eval "`$CC -print-prog-name=ld` --help 2>&1" |
+ $GREP 'no-whole-archive' > /dev/null; then
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive'
+ else
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=
+ fi
+ else
+ with_gnu_ld=no
+ wlarc=
+
+ # A generic and very simple default shared library creation
+ # command for GNU C++ for the case where it uses the native
+ # linker, instead of GNU ld. If possible, this setting should
+ # overridden to take advantage of the native linker features on
+ # the platform it is being used on.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+ fi
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"'
+
+ else
+ GXX=no
+ with_gnu_ld=no
+ wlarc=
+ fi
+
+ # PORTME: fill in a description of your system's C++ link characteristics
+ AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+ case $host_os in
+ aix3*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ aix[[4-9]]*)
+ if test ia64 = "$host_cpu"; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=
+ else
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # have runtime linking enabled, and use it for executables.
+ # For shared libraries, we enable/disable runtime linking
+ # depending on the kind of the shared library created -
+ # when "with_aix_soname,aix_use_runtimelinking" is:
+ # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables
+ # "aix,yes" lib.so shared, rtl:yes, for executables
+ # lib.a static archive
+ # "both,no" lib.so.V(shr.o) shared, rtl:yes
+ # lib.a(lib.so.V) shared, rtl:no, for executables
+ # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a(lib.so.V) shared, rtl:no
+ # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a static archive
+ case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+ for ld_flag in $LDFLAGS; do
+ case $ld_flag in
+ *-brtl*)
+ aix_use_runtimelinking=yes
+ break
+ ;;
+ esac
+ done
+ if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # With aix-soname=svr4, we create the lib.so.V shared archives only,
+ # so we don't have lib.a shared libs to link our executables.
+ # We have to force runtime linking in this case.
+ aix_use_runtimelinking=yes
+ LDFLAGS="$LDFLAGS -Wl,-brtl"
+ fi
+ ;;
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ _LT_TAGVAR(archive_cmds, $1)=''
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='$wl-f,'
+ case $with_aix_soname,$aix_use_runtimelinking in
+ aix,*) ;; # no import file
+ svr4,* | *,yes) # use import file
+ # The Import File defines what to hardcode.
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+ ;;
+ esac
+
+ if test yes = "$GXX"; then
+ case $host_os in aix4.[[012]]|aix4.[[012]].*)
+ # We only want to do this on AIX 4.2 and lower, the check
+ # below for broken collect2 doesn't work under 4.3+
+ collect2name=`$CC -print-prog-name=collect2`
+ if test -f "$collect2name" &&
+ strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ _LT_TAGVAR(hardcode_direct, $1)=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=
+ fi
+ esac
+ shared_flag='-shared'
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag=$shared_flag' $wl-G'
+ fi
+ # Need to ensure runtime linking is disabled for the traditional
+ # shared library, or the linker may eventually find shared libraries
+ # /with/ Import File - we do not want to mix them.
+ shared_flag_aix='-shared'
+ shared_flag_svr4='-shared $wl-G'
+ else
+ # not using gcc
+ if test ia64 = "$host_cpu"; then
+ # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+ # chokes on -Wl,-G. The following line is correct:
+ shared_flag='-G'
+ else
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag='$wl-G'
+ else
+ shared_flag='$wl-bM:SRE'
+ fi
+ shared_flag_aix='$wl-bM:SRE'
+ shared_flag_svr4='$wl-G'
+ fi
+ fi
+
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall'
+ # It seems that -bexpall does not export symbols beginning with
+ # underscore (_), so it is better to generate a list of symbols to
+ # export.
+ _LT_TAGVAR(always_export_symbols, $1)=yes
+ if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ # The "-G" linker flag allows undefined symbols.
+ _LT_TAGVAR(no_undefined_flag, $1)='-bernotok'
+ # Determine the default libpath from the value encoded in an empty
+ # executable.
+ _LT_SYS_MODULE_PATH_AIX([$1])
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath"
+
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag
+ else
+ if test ia64 = "$host_cpu"; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib'
+ _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+ _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols"
+ else
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ _LT_SYS_MODULE_PATH_AIX([$1])
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath"
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok'
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok'
+ if test yes = "$with_gnu_ld"; then
+ # We only use this code for GNU lds that support --whole-archive.
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive'
+ else
+ # Exported symbols can be pulled into shared objects from archives
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+ fi
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d'
+ # -brtl affects multiple linker settings, -berok does not and is overridden later
+ compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`'
+ if test svr4 != "$with_aix_soname"; then
+ # This is similar to how AIX traditionally builds its shared
+ # libraries. Need -bnortl late, we may have -brtl in LDFLAGS.
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname'
+ fi
+ if test aix != "$with_aix_soname"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp'
+ else
+ # used by -dlpreopen to get the symbols
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir'
+ fi
+ _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d'
+ fi
+ fi
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ chorus*)
+ case $cc_basename in
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ case $GXX,$cc_basename in
+ ,cl* | no,cl* | ,icl* | no,icl*)
+ # Native MSVC or ICC
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ _LT_TAGVAR(always_export_symbols, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='@'
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=.dll
+ # FIXME: Setting linknames here is a bad hack.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames='
+ _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then
+ cp "$export_symbols" "$output_objdir/$soname.def";
+ echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp";
+ else
+ $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp;
+ fi~
+ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+ linknames='
+ # The linker will not automatically build a static lib if we build a DLL.
+ # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ # Don't use ranlib
+ _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+ _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+ lt_tool_outputfile="@TOOL_OUTPUT@"~
+ case $lt_outputfile in
+ *.exe|*.EXE) ;;
+ *)
+ lt_outputfile=$lt_outputfile.exe
+ lt_tool_outputfile=$lt_tool_outputfile.exe
+ ;;
+ esac~
+ func_to_tool_file "$lt_outputfile"~
+ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then
+ $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+ $RM "$lt_outputfile.manifest";
+ fi'
+ ;;
+ *)
+ # g++
+ # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+ # as there is no search path for DLLs.
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols'
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ _LT_TAGVAR(always_export_symbols, $1)=no
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+
+ if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ # If the export-symbols file already is a .def file, use it as
+ # is; otherwise, prepend EXPORTS...
+ _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then
+ cp $export_symbols $output_objdir/$soname.def;
+ else
+ echo EXPORTS > $output_objdir/$soname.def;
+ cat $export_symbols >> $output_objdir/$soname.def;
+ fi~
+ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+ ;;
+ darwin* | rhapsody*)
+ _LT_DARWIN_LINKER_FEATURES($1)
+ ;;
+
+ os2*)
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes
+ _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+ shrext_cmds=.dll
+ _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ prefix_cmds="$SED"~
+ if test EXPORTS = "`$SED 1q $export_symbols`"; then
+ prefix_cmds="$prefix_cmds -e 1d";
+ fi~
+ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~
+ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def'
+ _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+ _LT_TAGVAR(file_list_spec, $1)='@'
+ ;;
+
+ dgux*)
+ case $cc_basename in
+ ec++*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ ghcx*)
+ # Green Hills C++ Compiler
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+ ;;
+
+ freebsd2.*)
+ # C++ shared libraries reported to be fairly broken before
+ # switch to ELF
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+
+ freebsd-elf*)
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ ;;
+
+ freebsd* | dragonfly* | midnightbsd*)
+ # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF
+ # conventions
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+ ;;
+
+ haiku*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ ;;
+
+ hpux9*)
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+ # but as the default
+ # location of the library.
+
+ case $cc_basename in
+ CC*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ aCC*)
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test yes = "$GXX"; then
+ _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ else
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+ ;;
+
+ hpux10*|hpux11*)
+ if test no = "$with_gnu_ld"; then
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ case $host_cpu in
+ hppa*64*|ia64*)
+ ;;
+ *)
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ ;;
+ esac
+ fi
+ case $host_cpu in
+ hppa*64*|ia64*)
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ ;;
+ *)
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+ # but as the default
+ # location of the library.
+ ;;
+ esac
+
+ case $cc_basename in
+ CC*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ aCC*)
+ case $host_cpu in
+ hppa*64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ ia64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ esac
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test yes = "$GXX"; then
+ if test no = "$with_gnu_ld"; then
+ case $host_cpu in
+ hppa*64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ ia64*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ esac
+ fi
+ else
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+ ;;
+
+ interix[[3-9]]*)
+ _LT_TAGVAR(hardcode_direct, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+ # Instead, shared libraries are loaded at an image base (0x10000000 by
+ # default) and relocated if they conflict, which is a slow very memory
+ # consuming and fragmenting process. To avoid this, we pick a random,
+ # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+ # time. Moving up from 0x10000000 also allows more sbrk(2) space.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ ;;
+ irix5* | irix6*)
+ case $cc_basename in
+ CC*)
+ # SGI C++
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+
+ # Archives containing C++ object files must be created using
+ # "CC -ar", where "CC" is the IRIX C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs'
+ ;;
+ *)
+ if test yes = "$GXX"; then
+ if test no = "$with_gnu_ld"; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ else
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib'
+ fi
+ fi
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ ;;
+ esac
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+ _LT_TAGVAR(inherit_rpath, $1)=yes
+ ;;
+
+ linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ case $cc_basename in
+ KCC*)
+ # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+ # KCC will only create a shared library if the output file
+ # ends with ".so" (or ".sl" for HP-UX), so rename the library
+ # to its proper name (with version) after linking.
+ _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib'
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+
+ # Archives containing C++ object files must be created using
+ # "CC -Bstatic", where "CC" is the KAI C++ compiler.
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs'
+ ;;
+ icpc* | ecpc* )
+ # Intel C++
+ with_gnu_ld=yes
+ # version 8.0 and above of icpc choke on multiply defined symbols
+ # if we add $predep_objects and $postdep_objects, however 7.1 and
+ # earlier do not add the objects themselves.
+ case `$CC -V 2>&1` in
+ *"Version 7."*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ *) # Version 8.0 or newer
+ tmp_idyn=
+ case $host_cpu in
+ ia64*) tmp_idyn=' -i_dynamic';;
+ esac
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ esac
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive'
+ ;;
+ pgCC* | pgcpp*)
+ # Portland Group C++ compiler
+ case `$CC -V` in
+ *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*)
+ _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~
+ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"'
+ _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~
+ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~
+ $RANLIB $oldlib'
+ _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ *) # Version 6 and above use weak symbols
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ esac
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ ;;
+ cxx*)
+ # Compaq C++
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols'
+
+ runpath_var=LD_RUN_PATH
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed'
+ ;;
+ xl* | mpixl* | bgxl*)
+ # IBM XL 8.0 on PPC, with GNU ld
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ if test yes = "$supports_anon_versioning"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib'
+ fi
+ ;;
+ *)
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ C*)
+ # Sun C++ 5.9
+ _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ _LT_TAGVAR(compiler_needs_object, $1)=yes
+
+ # Not sure whether something based on
+ # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1
+ # would be better.
+ output_verbose_link_cmd='func_echo_all'
+
+ # Archives containing C++ object files must be created using
+ # "CC -xar", where "CC" is the Sun C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+ lynxos*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+
+ m88k*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+
+ mvs*)
+ case $cc_basename in
+ cxx*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags'
+ wlarc=
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ fi
+ # Workaround some broken pre-1.5 toolchains
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"'
+ ;;
+
+ *nto* | *qnx*)
+ _LT_TAGVAR(ld_shlibs, $1)=yes
+ ;;
+
+ openbsd* | bitrig*)
+ if test -f /usr/libexec/ld.so; then
+ _LT_TAGVAR(hardcode_direct, $1)=yes
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib'
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E'
+ _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive'
+ fi
+ output_verbose_link_cmd=func_echo_all
+ else
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+
+ osf3* | osf4* | osf5*)
+ case $cc_basename in
+ KCC*)
+ # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+ # KCC will only create a shared library if the output file
+ # ends with ".so" (or ".sl" for HP-UX), so rename the library
+ # to its proper name (with version) after linking.
+ _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ # Archives containing C++ object files must be created using
+ # the KAI C++ compiler.
+ case $host in
+ osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;;
+ *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;;
+ esac
+ ;;
+ RCC*)
+ # Rational C++ 2.4.1
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ cxx*)
+ case $host in
+ osf3*)
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ ;;
+ *)
+ _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~
+ echo "-hidden">> $lib.exp~
+ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~
+ $RM $lib.exp'
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+ ;;
+ esac
+
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test yes,no = "$GXX,$with_gnu_ld"; then
+ _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*'
+ case $host in
+ osf3*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ ;;
+ esac
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"'
+
+ else
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ fi
+ ;;
+ esac
+ ;;
+
+ psos*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+
+ sunos4*)
+ case $cc_basename in
+ CC*)
+ # Sun C++ 4.x
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ lcc*)
+ # Lucid
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+ ;;
+
+ solaris*)
+ case $cc_basename in
+ CC* | sunCC*)
+ # Sun C++ 4.2, 5.x and Centerline C++
+ _LT_TAGVAR(archive_cmds_need_lc,$1)=yes
+ _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ case $host_os in
+ solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+ *)
+ # The compiler driver will combine and reorder linker options,
+ # but understands '-z linker_flag'.
+ # Supported since Solaris 2.6 (maybe 2.5.1?)
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+ ;;
+ esac
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+
+ output_verbose_link_cmd='func_echo_all'
+
+ # Archives containing C++ object files must be created using
+ # "CC -xar", where "CC" is the Sun C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+ ;;
+ gcx*)
+ # Green Hills C++ Compiler
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib'
+
+ # The C++ compiler must be used to create the archive.
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs'
+ ;;
+ *)
+ # GNU C++ compiler with Solaris linker
+ if test yes,no = "$GXX,$with_gnu_ld"; then
+ _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs'
+ if $CC --version | $GREP -v '^2\.7' > /dev/null; then
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"'
+ else
+ # g++ 2.7 appears to require '-G' NOT '-shared' on this
+ # platform.
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"'
+ fi
+
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir'
+ case $host_os in
+ solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+ *)
+ _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract'
+ ;;
+ esac
+ fi
+ ;;
+ esac
+ ;;
+
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+ _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text'
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ runpath_var='LD_RUN_PATH'
+
+ case $cc_basename in
+ CC*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6*)
+ # Note: We CANNOT use -z defs as we might desire, because we do not
+ # link with -lc, and that would cause any symbols used from libc to
+ # always be unresolved, which means just about no library would
+ # ever link correctly. If we're not using GNU ld we use -z text
+ # though, which does catch some bad symbols but isn't as heavy-handed
+ # as -z defs.
+ _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text'
+ _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs'
+ _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+ _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+ _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir'
+ _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+ _LT_TAGVAR(link_all_deplibs, $1)=yes
+ _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport'
+ runpath_var='LD_RUN_PATH'
+
+ case $cc_basename in
+ CC*)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~
+ '"$_LT_TAGVAR(old_archive_cmds, $1)"
+ _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~
+ '"$_LT_TAGVAR(reload_cmds, $1)"
+ ;;
+ *)
+ _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ ;;
+
+ tandem*)
+ case $cc_basename in
+ NCC*)
+ # NonStop-UX NCC 3.20
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+ ;;
+
+ vxworks*)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+
+ *)
+ # FIXME: insert proper C++ library support
+ _LT_TAGVAR(ld_shlibs, $1)=no
+ ;;
+ esac
+
+ AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+ test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no
+
+ _LT_TAGVAR(GCC, $1)=$GXX
+ _LT_TAGVAR(LD, $1)=$LD
+
+ ## CAVEAT EMPTOR:
+ ## There is no encapsulation within the following macros, do not change
+ ## the running order or otherwise move them around unless you know exactly
+ ## what you are doing...
+ _LT_SYS_HIDDEN_LIBDEPS($1)
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_SYS_DYNAMIC_LINKER($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+
+ _LT_CONFIG($1)
+ fi # test -n "$compiler"
+
+ CC=$lt_save_CC
+ CFLAGS=$lt_save_CFLAGS
+ LDCXX=$LD
+ LD=$lt_save_LD
+ GCC=$lt_save_GCC
+ with_gnu_ld=$lt_save_with_gnu_ld
+ lt_cv_path_LDCXX=$lt_cv_path_LD
+ lt_cv_path_LD=$lt_save_path_LD
+ lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld
+ lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld
+fi # test yes != "$_lt_caught_CXX_error"
+
+AC_LANG_POP
+])# _LT_LANG_CXX_CONFIG
+
+
+# _LT_FUNC_STRIPNAME_CNF
+# ----------------------
+# func_stripname_cnf prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+#
+# This function is identical to the (non-XSI) version of func_stripname,
+# except this one can be used by m4 code that may be executed by configure,
+# rather than the libtool script.
+m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl
+AC_REQUIRE([_LT_DECL_SED])
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])
+func_stripname_cnf ()
+{
+ case @S|@2 in
+ .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;;
+ *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;;
+ esac
+} # func_stripname_cnf
+])# _LT_FUNC_STRIPNAME_CNF
+
+
+# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME])
+# ---------------------------------
+# Figure out "hidden" library dependencies from verbose
+# compiler output when linking a shared library.
+# Parse the compiler output and extract the necessary
+# objects, libraries and library flags.
+m4_defun([_LT_SYS_HIDDEN_LIBDEPS],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl
+# Dependencies to place before and after the object being linked:
+_LT_TAGVAR(predep_objects, $1)=
+_LT_TAGVAR(postdep_objects, $1)=
+_LT_TAGVAR(predeps, $1)=
+_LT_TAGVAR(postdeps, $1)=
+_LT_TAGVAR(compiler_lib_search_path, $1)=
+
+dnl we can't use the lt_simple_compile_test_code here,
+dnl because it contains code intended for an executable,
+dnl not a library. It's possible we should let each
+dnl tag define a new lt_????_link_test_code variable,
+dnl but it's only used here...
+m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF
+int a;
+void foo (void) { a = 0; }
+_LT_EOF
+], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF
+class Foo
+{
+public:
+ Foo (void) { a = 0; }
+private:
+ int a;
+};
+_LT_EOF
+], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF
+ subroutine foo
+ implicit none
+ integer*4 a
+ a=0
+ return
+ end
+_LT_EOF
+], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF
+ subroutine foo
+ implicit none
+ integer a
+ a=0
+ return
+ end
+_LT_EOF
+], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF
+public class foo {
+ private int a;
+ public void bar (void) {
+ a = 0;
+ }
+};
+_LT_EOF
+], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF
+package foo
+func foo() {
+}
+_LT_EOF
+])
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+dnl Parse the compiler output and extract the necessary
+dnl objects, libraries and library flags.
+if AC_TRY_EVAL(ac_compile); then
+ # Parse the compiler output and extract the necessary
+ # objects, libraries and library flags.
+
+ # Sentinel used to keep track of whether or not we are before
+ # the conftest object file.
+ pre_test_object_deps_done=no
+
+ for p in `eval "$output_verbose_link_cmd"`; do
+ case $prev$p in
+
+ -L* | -R* | -l*)
+ # Some compilers place space between "-{L,R}" and the path.
+ # Remove the space.
+ if test x-L = "$p" ||
+ test x-R = "$p"; then
+ prev=$p
+ continue
+ fi
+
+ # Expand the sysroot to ease extracting the directories later.
+ if test -z "$prev"; then
+ case $p in
+ -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+ -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+ -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+ esac
+ fi
+ case $p in
+ =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+ esac
+ if test no = "$pre_test_object_deps_done"; then
+ case $prev in
+ -L | -R)
+ # Internal compiler library paths should come after those
+ # provided the user. The postdeps already come after the
+ # user supplied libs so there is no need to process them.
+ if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then
+ _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p
+ else
+ _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p"
+ fi
+ ;;
+ # The "-l" case would never come before the object being
+ # linked, so don't bother handling this case.
+ esac
+ else
+ if test -z "$_LT_TAGVAR(postdeps, $1)"; then
+ _LT_TAGVAR(postdeps, $1)=$prev$p
+ else
+ _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p"
+ fi
+ fi
+ prev=
+ ;;
+
+ *.lto.$objext) ;; # Ignore GCC LTO objects
+ *.$objext)
+ # This assumes that the test object file only shows up
+ # once in the compiler output.
+ if test "$p" = "conftest.$objext"; then
+ pre_test_object_deps_done=yes
+ continue
+ fi
+
+ if test no = "$pre_test_object_deps_done"; then
+ if test -z "$_LT_TAGVAR(predep_objects, $1)"; then
+ _LT_TAGVAR(predep_objects, $1)=$p
+ else
+ _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p"
+ fi
+ else
+ if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then
+ _LT_TAGVAR(postdep_objects, $1)=$p
+ else
+ _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p"
+ fi
+ fi
+ ;;
+
+ *) ;; # Ignore the rest.
+
+ esac
+ done
+
+ # Clean up.
+ rm -f a.out a.exe
+else
+ echo "libtool.m4: error: problem compiling $1 test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+m4_if([$1], [CXX],
+[case $host_os in
+interix[[3-9]]*)
+ # Interix 3.5 installs completely hosed .la files for C++, so rather than
+ # hack all around it, let's just trust "g++" to DTRT.
+ _LT_TAGVAR(predep_objects,$1)=
+ _LT_TAGVAR(postdep_objects,$1)=
+ _LT_TAGVAR(postdeps,$1)=
+ ;;
+esac
+])
+
+case " $_LT_TAGVAR(postdeps, $1) " in
+*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;;
+esac
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=
+if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'`
+fi
+_LT_TAGDECL([], [compiler_lib_search_dirs], [1],
+ [The directories searched by this compiler when creating a shared library])
+_LT_TAGDECL([], [predep_objects], [1],
+ [Dependencies to place before and after the objects being linked to
+ create a shared library])
+_LT_TAGDECL([], [postdep_objects], [1])
+_LT_TAGDECL([], [predeps], [1])
+_LT_TAGDECL([], [postdeps], [1])
+_LT_TAGDECL([], [compiler_lib_search_path], [1],
+ [The library search path used internally by the compiler when linking
+ a shared library])
+])# _LT_SYS_HIDDEN_LIBDEPS
+
+
+# _LT_LANG_F77_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a Fortran 77 compiler are
+# suitably defined. These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_F77_CONFIG],
+[AC_LANG_PUSH(Fortran 77)
+if test -z "$F77" || test no = "$F77"; then
+ _lt_disable_F77=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for f77 test sources.
+ac_ext=f
+
+# Object file extension for compiled f77 test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the F77 compiler isn't working. Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test yes != "$_lt_disable_F77"; then
+ # Code to be used in simple compile tests
+ lt_simple_compile_test_code="\
+ subroutine t
+ return
+ end
+"
+
+ # Code to be used in simple link tests
+ lt_simple_link_test_code="\
+ program t
+ end
+"
+
+ # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+ _LT_TAG_COMPILER
+
+ # save warnings/boilerplate of simple test code
+ _LT_COMPILER_BOILERPLATE
+ _LT_LINKER_BOILERPLATE
+
+ # Allow CC to be a program name with arguments.
+ lt_save_CC=$CC
+ lt_save_GCC=$GCC
+ lt_save_CFLAGS=$CFLAGS
+ CC=${F77-"f77"}
+ CFLAGS=$FFLAGS
+ compiler=$CC
+ _LT_TAGVAR(compiler, $1)=$CC
+ _LT_CC_BASENAME([$compiler])
+ GCC=$G77
+ if test -n "$compiler"; then
+ AC_MSG_CHECKING([if libtool supports shared libraries])
+ AC_MSG_RESULT([$can_build_shared])
+
+ AC_MSG_CHECKING([whether to build shared libraries])
+ test no = "$can_build_shared" && enable_shared=no
+
+ # On AIX, shared libraries and static libraries use the same namespace, and
+ # are all built from PIC.
+ case $host_os in
+ aix3*)
+ test yes = "$enable_shared" && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+ aix[[4-9]]*)
+ if test ia64 != "$host_cpu"; then
+ case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in
+ yes,aix,yes) ;; # shared object as lib.so file only
+ yes,svr4,*) ;; # shared object as lib.so archive member only
+ yes,*) enable_static=no ;; # shared object in lib.a archive as well
+ esac
+ fi
+ ;;
+ esac
+ AC_MSG_RESULT([$enable_shared])
+
+ AC_MSG_CHECKING([whether to build static libraries])
+ # Make sure either enable_shared or enable_static is yes.
+ test yes = "$enable_shared" || enable_static=yes
+ AC_MSG_RESULT([$enable_static])
+
+ _LT_TAGVAR(GCC, $1)=$G77
+ _LT_TAGVAR(LD, $1)=$LD
+
+ ## CAVEAT EMPTOR:
+ ## There is no encapsulation within the following macros, do not change
+ ## the running order or otherwise move them around unless you know exactly
+ ## what you are doing...
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_SYS_DYNAMIC_LINKER($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+
+ _LT_CONFIG($1)
+ fi # test -n "$compiler"
+
+ GCC=$lt_save_GCC
+ CC=$lt_save_CC
+ CFLAGS=$lt_save_CFLAGS
+fi # test yes != "$_lt_disable_F77"
+
+AC_LANG_POP
+])# _LT_LANG_F77_CONFIG
+
+
+# _LT_LANG_FC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for a Fortran compiler are
+# suitably defined. These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_FC_CONFIG],
+[AC_LANG_PUSH(Fortran)
+
+if test -z "$FC" || test no = "$FC"; then
+ _lt_disable_FC=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for fc test sources.
+ac_ext=${ac_fc_srcext-f}
+
+# Object file extension for compiled fc test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the FC compiler isn't working. Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test yes != "$_lt_disable_FC"; then
+ # Code to be used in simple compile tests
+ lt_simple_compile_test_code="\
+ subroutine t
+ return
+ end
+"
+
+ # Code to be used in simple link tests
+ lt_simple_link_test_code="\
+ program t
+ end
+"
+
+ # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+ _LT_TAG_COMPILER
+
+ # save warnings/boilerplate of simple test code
+ _LT_COMPILER_BOILERPLATE
+ _LT_LINKER_BOILERPLATE
+
+ # Allow CC to be a program name with arguments.
+ lt_save_CC=$CC
+ lt_save_GCC=$GCC
+ lt_save_CFLAGS=$CFLAGS
+ CC=${FC-"f95"}
+ CFLAGS=$FCFLAGS
+ compiler=$CC
+ GCC=$ac_cv_fc_compiler_gnu
+
+ _LT_TAGVAR(compiler, $1)=$CC
+ _LT_CC_BASENAME([$compiler])
+
+ if test -n "$compiler"; then
+ AC_MSG_CHECKING([if libtool supports shared libraries])
+ AC_MSG_RESULT([$can_build_shared])
+
+ AC_MSG_CHECKING([whether to build shared libraries])
+ test no = "$can_build_shared" && enable_shared=no
+
+ # On AIX, shared libraries and static libraries use the same namespace, and
+ # are all built from PIC.
+ case $host_os in
+ aix3*)
+ test yes = "$enable_shared" && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+ aix[[4-9]]*)
+ if test ia64 != "$host_cpu"; then
+ case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in
+ yes,aix,yes) ;; # shared object as lib.so file only
+ yes,svr4,*) ;; # shared object as lib.so archive member only
+ yes,*) enable_static=no ;; # shared object in lib.a archive as well
+ esac
+ fi
+ ;;
+ esac
+ AC_MSG_RESULT([$enable_shared])
+
+ AC_MSG_CHECKING([whether to build static libraries])
+ # Make sure either enable_shared or enable_static is yes.
+ test yes = "$enable_shared" || enable_static=yes
+ AC_MSG_RESULT([$enable_static])
+
+ _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu
+ _LT_TAGVAR(LD, $1)=$LD
+
+ ## CAVEAT EMPTOR:
+ ## There is no encapsulation within the following macros, do not change
+ ## the running order or otherwise move them around unless you know exactly
+ ## what you are doing...
+ _LT_SYS_HIDDEN_LIBDEPS($1)
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_SYS_DYNAMIC_LINKER($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+
+ _LT_CONFIG($1)
+ fi # test -n "$compiler"
+
+ GCC=$lt_save_GCC
+ CC=$lt_save_CC
+ CFLAGS=$lt_save_CFLAGS
+fi # test yes != "$_lt_disable_FC"
+
+AC_LANG_POP
+])# _LT_LANG_FC_CONFIG
+
+
+# _LT_LANG_GCJ_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Java Compiler compiler
+# are suitably defined. These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_GCJ_CONFIG],
+[AC_REQUIRE([LT_PROG_GCJ])dnl
+AC_LANG_SAVE
+
+# Source file extension for Java test sources.
+ac_ext=java
+
+# Object file extension for compiled Java test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="class foo {}"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GCJ-"gcj"}
+CFLAGS=$GCJFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)=$LD
+_LT_CC_BASENAME([$compiler])
+
+# GCJ did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+ _LT_COMPILER_NO_RTTI($1)
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+
+ _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GCJ_CONFIG
+
+
+# _LT_LANG_GO_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Go compiler
+# are suitably defined. These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_GO_CONFIG],
+[AC_REQUIRE([LT_PROG_GO])dnl
+AC_LANG_SAVE
+
+# Source file extension for Go test sources.
+ac_ext=go
+
+# Object file extension for compiled Go test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="package main; func main() { }"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='package main; func main() { }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GOC-"gccgo"}
+CFLAGS=$GOFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)=$LD
+_LT_CC_BASENAME([$compiler])
+
+# Go did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+ _LT_COMPILER_NO_RTTI($1)
+ _LT_COMPILER_PIC($1)
+ _LT_COMPILER_C_O($1)
+ _LT_COMPILER_FILE_LOCKS($1)
+ _LT_LINKER_SHLIBS($1)
+ _LT_LINKER_HARDCODE_LIBPATH($1)
+
+ _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GO_CONFIG
+
+
+# _LT_LANG_RC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for the Windows resource compiler
+# are suitably defined. These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to 'libtool'.
+m4_defun([_LT_LANG_RC_CONFIG],
+[AC_REQUIRE([LT_PROG_RC])dnl
+AC_LANG_SAVE
+
+# Source file extension for RC test sources.
+ac_ext=rc
+
+# Object file extension for compiled RC test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }'
+
+# Code to be used in simple link tests
+lt_simple_link_test_code=$lt_simple_compile_test_code
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=
+CC=${RC-"windres"}
+CFLAGS=
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_CC_BASENAME([$compiler])
+_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+
+if test -n "$compiler"; then
+ :
+ _LT_CONFIG($1)
+fi
+
+GCC=$lt_save_GCC
+AC_LANG_RESTORE
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_RC_CONFIG
+
+
+# LT_PROG_GCJ
+# -----------
+AC_DEFUN([LT_PROG_GCJ],
+[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ],
+ [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ],
+ [AC_CHECK_TOOL(GCJ, gcj,)
+ test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2"
+ AC_SUBST(GCJFLAGS)])])[]dnl
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_GCJ], [])
+
+
+# LT_PROG_GO
+# ----------
+AC_DEFUN([LT_PROG_GO],
+[AC_CHECK_TOOL(GOC, gccgo,)
+])
+
+
+# LT_PROG_RC
+# ----------
+AC_DEFUN([LT_PROG_RC],
+[AC_CHECK_TOOL(RC, windres,)
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_RC], [])
+
+
+# _LT_DECL_EGREP
+# --------------
+# If we don't have a new enough Autoconf to choose the best grep
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_EGREP],
+[AC_REQUIRE([AC_PROG_EGREP])dnl
+AC_REQUIRE([AC_PROG_FGREP])dnl
+test -z "$GREP" && GREP=grep
+_LT_DECL([], [GREP], [1], [A grep program that handles long lines])
+_LT_DECL([], [EGREP], [1], [An ERE matcher])
+_LT_DECL([], [FGREP], [1], [A literal string matcher])
+dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too
+AC_SUBST([GREP])
+])
+
+
+# _LT_DECL_OBJDUMP
+# --------------
+# If we don't have a new enough Autoconf to choose the best objdump
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_OBJDUMP],
+[AC_CHECK_TOOL(OBJDUMP, objdump, false)
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper])
+AC_SUBST([OBJDUMP])
+])
+
+# _LT_DECL_DLLTOOL
+# ----------------
+# Ensure DLLTOOL variable is set.
+m4_defun([_LT_DECL_DLLTOOL],
+[AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])
+AC_SUBST([DLLTOOL])
+])
+
+# _LT_DECL_FILECMD
+# ----------------
+# Check for a file(cmd) program that can be used to detect file type and magic
+m4_defun([_LT_DECL_FILECMD],
+[AC_CHECK_TOOL([FILECMD], [file], [:])
+_LT_DECL([], [FILECMD], [1], [A file(cmd) program that detects file types])
+])# _LD_DECL_FILECMD
+
+# _LT_DECL_SED
+# ------------
+# Check for a fully-functional sed program, that truncates
+# as few characters as possible. Prefer GNU sed if found.
+m4_defun([_LT_DECL_SED],
+[AC_PROG_SED
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+_LT_DECL([], [SED], [1], [A sed program that does not truncate output])
+_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"],
+ [Sed that helps us avoid accidentally triggering echo(1) options like -n])
+])# _LT_DECL_SED
+
+m4_ifndef([AC_PROG_SED], [
+# NOTE: This macro has been submitted for inclusion into #
+# GNU Autoconf as AC_PROG_SED. When it is available in #
+# a released version of Autoconf we should remove this #
+# macro and use it instead. #
+
+m4_defun([AC_PROG_SED],
+[AC_MSG_CHECKING([for a sed that does not truncate output])
+AC_CACHE_VAL(lt_cv_path_SED,
+[# Loop through the user's path and test for sed and gsed.
+# Then use that list of sed's as ones to test for truncation.
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for lt_ac_prog in sed gsed; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then
+ lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext"
+ fi
+ done
+ done
+done
+IFS=$as_save_IFS
+lt_ac_max=0
+lt_ac_count=0
+# Add /usr/xpg4/bin/sed as it is typically found on Solaris
+# along with /bin/sed that truncates output.
+for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do
+ test ! -f "$lt_ac_sed" && continue
+ cat /dev/null > conftest.in
+ lt_ac_count=0
+ echo $ECHO_N "0123456789$ECHO_C" >conftest.in
+ # Check for GNU sed and select it if it is found.
+ if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then
+ lt_cv_path_SED=$lt_ac_sed
+ break
+ fi
+ while true; do
+ cat conftest.in conftest.in >conftest.tmp
+ mv conftest.tmp conftest.in
+ cp conftest.in conftest.nl
+ echo >>conftest.nl
+ $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break
+ cmp -s conftest.out conftest.nl || break
+ # 10000 chars as input seems more than enough
+ test 10 -lt "$lt_ac_count" && break
+ lt_ac_count=`expr $lt_ac_count + 1`
+ if test "$lt_ac_count" -gt "$lt_ac_max"; then
+ lt_ac_max=$lt_ac_count
+ lt_cv_path_SED=$lt_ac_sed
+ fi
+ done
+done
+])
+SED=$lt_cv_path_SED
+AC_SUBST([SED])
+AC_MSG_RESULT([$SED])
+])#AC_PROG_SED
+])#m4_ifndef
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_SED], [])
+
+
+# _LT_CHECK_SHELL_FEATURES
+# ------------------------
+# Find out whether the shell is Bourne or XSI compatible,
+# or has some other useful features.
+m4_defun([_LT_CHECK_SHELL_FEATURES],
+[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ lt_unset=unset
+else
+ lt_unset=false
+fi
+_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+ # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+ lt_SP2NL='tr \040 \012'
+ lt_NL2SP='tr \015\012 \040\040'
+ ;;
+ *) # EBCDIC based system
+ lt_SP2NL='tr \100 \n'
+ lt_NL2SP='tr \r\n \100\100'
+ ;;
+esac
+_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl
+_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl
+])# _LT_CHECK_SHELL_FEATURES
+
+
+# _LT_PATH_CONVERSION_FUNCTIONS
+# -----------------------------
+# Determine what file name conversion functions should be used by
+# func_to_host_file (and, implicitly, by func_to_host_path). These are needed
+# for certain cross-compile configurations and native mingw.
+m4_defun([_LT_PATH_CONVERSION_FUNCTIONS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_MSG_CHECKING([how to convert $build file names to $host format])
+AC_CACHE_VAL(lt_cv_to_host_file_cmd,
+[case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+ ;;
+ esac
+ ;;
+ *-*-cygwin* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+ ;;
+ esac
+ ;;
+ * ) # unhandled hosts (and "normal" native builds)
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+esac
+])
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+AC_MSG_RESULT([$lt_cv_to_host_file_cmd])
+_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd],
+ [0], [convert $build file names to $host format])dnl
+
+AC_MSG_CHECKING([how to convert $build file names to toolchain format])
+AC_CACHE_VAL(lt_cv_to_tool_file_cmd,
+[#assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ esac
+ ;;
+esac
+])
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+AC_MSG_RESULT([$lt_cv_to_tool_file_cmd])
+_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd],
+ [0], [convert $build files to toolchain format])dnl
+])# _LT_PATH_CONVERSION_FUNCTIONS
+
+# Helper functions for option handling. -*- Autoconf -*-
+#
+# Copyright (C) 2004-2005, 2007-2009, 2011-2019, 2021-2022 Free
+# Software Foundation, Inc.
+# Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 8 ltoptions.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
+
+
+# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
+# ------------------------------------------
+m4_define([_LT_MANGLE_OPTION],
+[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
+
+
+# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
+# ---------------------------------------
+# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
+# matching handler defined, dispatch to it. Other OPTION-NAMEs are
+# saved as a flag.
+m4_define([_LT_SET_OPTION],
+[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
+m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
+ _LT_MANGLE_DEFUN([$1], [$2]),
+ [m4_warning([Unknown $1 option '$2'])])[]dnl
+])
+
+
+# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
+# ------------------------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+m4_define([_LT_IF_OPTION],
+[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
+
+
+# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
+# -------------------------------------------------------
+# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
+# are set.
+m4_define([_LT_UNLESS_OPTIONS],
+[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+ [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
+ [m4_define([$0_found])])])[]dnl
+m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
+])[]dnl
+])
+
+
+# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
+# ----------------------------------------
+# OPTION-LIST is a space-separated list of Libtool options associated
+# with MACRO-NAME. If any OPTION has a matching handler declared with
+# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
+# the unknown option and exit.
+m4_defun([_LT_SET_OPTIONS],
+[# Set options
+m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+ [_LT_SET_OPTION([$1], _LT_Option)])
+
+m4_if([$1],[LT_INIT],[
+ dnl
+ dnl Simply set some default values (i.e off) if boolean options were not
+ dnl specified:
+ _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
+ ])
+ _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
+ ])
+ dnl
+ dnl If no reference was made to various pairs of opposing options, then
+ dnl we run the default mode handler for the pair. For example, if neither
+ dnl 'shared' nor 'disable-shared' was passed, we enable building of shared
+ dnl archives by default:
+ _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
+ _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
+ _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
+ _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
+ [_LT_ENABLE_FAST_INSTALL])
+ _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4],
+ [_LT_WITH_AIX_SONAME([aix])])
+ ])
+])# _LT_SET_OPTIONS
+
+
+
+# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
+# -----------------------------------------
+m4_define([_LT_MANGLE_DEFUN],
+[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
+
+
+# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
+# -----------------------------------------------
+m4_define([LT_OPTION_DEFINE],
+[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
+])# LT_OPTION_DEFINE
+
+
+# dlopen
+# ------
+LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
+])
+
+AU_DEFUN([AC_LIBTOOL_DLOPEN],
+[_LT_SET_OPTION([LT_INIT], [dlopen])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the 'dlopen' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
+
+
+# win32-dll
+# ---------
+# Declare package support for building win32 dll's.
+LT_OPTION_DEFINE([LT_INIT], [win32-dll],
+[enable_win32_dll=yes
+
+case $host in
+*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)
+ AC_CHECK_TOOL(AS, as, false)
+ AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+ AC_CHECK_TOOL(OBJDUMP, objdump, false)
+ ;;
+esac
+
+test -z "$AS" && AS=as
+_LT_DECL([], [AS], [1], [Assembler program])dnl
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl
+])# win32-dll
+
+AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+_LT_SET_OPTION([LT_INIT], [win32-dll])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the 'win32-dll' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
+
+
+# _LT_ENABLE_SHARED([DEFAULT])
+# ----------------------------
+# implement the --enable-shared flag, and supports the 'shared' and
+# 'disable-shared' LT_INIT options.
+# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
+m4_define([_LT_ENABLE_SHARED],
+[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([shared],
+ [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
+ [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
+ [p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_shared=yes ;;
+ no) enable_shared=no ;;
+ *)
+ enable_shared=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_shared=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac],
+ [enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
+
+ _LT_DECL([build_libtool_libs], [enable_shared], [0],
+ [Whether or not to build shared libraries])
+])# _LT_ENABLE_SHARED
+
+LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
+])
+
+AC_DEFUN([AC_DISABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], [disable-shared])
+])
+
+AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
+AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_SHARED], [])
+dnl AC_DEFUN([AM_DISABLE_SHARED], [])
+
+
+
+# _LT_ENABLE_STATIC([DEFAULT])
+# ----------------------------
+# implement the --enable-static flag, and support the 'static' and
+# 'disable-static' LT_INIT options.
+# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
+m4_define([_LT_ENABLE_STATIC],
+[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([static],
+ [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
+ [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
+ [p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_static=yes ;;
+ no) enable_static=no ;;
+ *)
+ enable_static=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_static=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac],
+ [enable_static=]_LT_ENABLE_STATIC_DEFAULT)
+
+ _LT_DECL([build_old_libs], [enable_static], [0],
+ [Whether or not to build static libraries])
+])# _LT_ENABLE_STATIC
+
+LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
+])
+
+AC_DEFUN([AC_DISABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], [disable-static])
+])
+
+AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
+AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_STATIC], [])
+dnl AC_DEFUN([AM_DISABLE_STATIC], [])
+
+
+
+# _LT_ENABLE_FAST_INSTALL([DEFAULT])
+# ----------------------------------
+# implement the --enable-fast-install flag, and support the 'fast-install'
+# and 'disable-fast-install' LT_INIT options.
+# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
+m4_define([_LT_ENABLE_FAST_INSTALL],
+[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([fast-install],
+ [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
+ [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
+ [p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_fast_install=yes ;;
+ no) enable_fast_install=no ;;
+ *)
+ enable_fast_install=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_fast_install=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac],
+ [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
+
+_LT_DECL([fast_install], [enable_fast_install], [0],
+ [Whether or not to optimize for fast installation])dnl
+])# _LT_ENABLE_FAST_INSTALL
+
+LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
+
+# Old names:
+AU_DEFUN([AC_ENABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the 'fast-install' option into LT_INIT's first parameter.])
+])
+
+AU_DEFUN([AC_DISABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the 'disable-fast-install' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
+dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
+
+
+# _LT_WITH_AIX_SONAME([DEFAULT])
+# ----------------------------------
+# implement the --with-aix-soname flag, and support the `aix-soname=aix'
+# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT
+# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'.
+m4_define([_LT_WITH_AIX_SONAME],
+[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl
+shared_archive_member_spec=
+case $host,$enable_shared in
+power*-*-aix[[5-9]]*,yes)
+ AC_MSG_CHECKING([which variant of shared library versioning to provide])
+ AC_ARG_WITH([aix-soname],
+ [AS_HELP_STRING([--with-aix-soname=aix|svr4|both],
+ [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])],
+ [case $withval in
+ aix|svr4|both)
+ ;;
+ *)
+ AC_MSG_ERROR([Unknown argument to --with-aix-soname])
+ ;;
+ esac
+ lt_cv_with_aix_soname=$with_aix_soname],
+ [AC_CACHE_VAL([lt_cv_with_aix_soname],
+ [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT)
+ with_aix_soname=$lt_cv_with_aix_soname])
+ AC_MSG_RESULT([$with_aix_soname])
+ if test aix != "$with_aix_soname"; then
+ # For the AIX way of multilib, we name the shared archive member
+ # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o',
+ # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File.
+ # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag,
+ # the AIX toolchain works better with OBJECT_MODE set (default 32).
+ if test 64 = "${OBJECT_MODE-32}"; then
+ shared_archive_member_spec=shr_64
+ else
+ shared_archive_member_spec=shr
+ fi
+ fi
+ ;;
+*)
+ with_aix_soname=aix
+ ;;
+esac
+
+_LT_DECL([], [shared_archive_member_spec], [0],
+ [Shared archive member basename, for filename based shared library versioning on AIX])dnl
+])# _LT_WITH_AIX_SONAME
+
+LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])])
+LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])])
+LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])])
+
+
+# _LT_WITH_PIC([MODE])
+# --------------------
+# implement the --with-pic flag, and support the 'pic-only' and 'no-pic'
+# LT_INIT options.
+# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'.
+m4_define([_LT_WITH_PIC],
+[AC_ARG_WITH([pic],
+ [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],
+ [try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
+ [lt_p=${PACKAGE-default}
+ case $withval in
+ yes|no) pic_mode=$withval ;;
+ *)
+ pic_mode=default
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for lt_pkg in $withval; do
+ IFS=$lt_save_ifs
+ if test "X$lt_pkg" = "X$lt_p"; then
+ pic_mode=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac],
+ [pic_mode=m4_default([$1], [default])])
+
+_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
+])# _LT_WITH_PIC
+
+LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
+
+# Old name:
+AU_DEFUN([AC_LIBTOOL_PICMODE],
+[_LT_SET_OPTION([LT_INIT], [pic-only])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the 'pic-only' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
+
+
+m4_define([_LTDL_MODE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
+ [m4_define([_LTDL_MODE], [nonrecursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [recursive],
+ [m4_define([_LTDL_MODE], [recursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [subproject],
+ [m4_define([_LTDL_MODE], [subproject])])
+
+m4_define([_LTDL_TYPE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [installable],
+ [m4_define([_LTDL_TYPE], [installable])])
+LT_OPTION_DEFINE([LTDL_INIT], [convenience],
+ [m4_define([_LTDL_TYPE], [convenience])])
+
+# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*-
+#
+# Copyright (C) 2004-2005, 2007-2008, 2011-2019, 2021-2022 Free Software
+# Foundation, Inc.
+# Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 6 ltsugar.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
+
+
+# lt_join(SEP, ARG1, [ARG2...])
+# -----------------------------
+# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
+# associated separator.
+# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
+# versions in m4sugar had bugs.
+m4_define([lt_join],
+[m4_if([$#], [1], [],
+ [$#], [2], [[$2]],
+ [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
+m4_define([_lt_join],
+[m4_if([$#$2], [2], [],
+ [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
+
+
+# lt_car(LIST)
+# lt_cdr(LIST)
+# ------------
+# Manipulate m4 lists.
+# These macros are necessary as long as will still need to support
+# Autoconf-2.59, which quotes differently.
+m4_define([lt_car], [[$1]])
+m4_define([lt_cdr],
+[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
+ [$#], 1, [],
+ [m4_dquote(m4_shift($@))])])
+m4_define([lt_unquote], $1)
+
+
+# lt_append(MACRO-NAME, STRING, [SEPARATOR])
+# ------------------------------------------
+# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'.
+# Note that neither SEPARATOR nor STRING are expanded; they are appended
+# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
+# No SEPARATOR is output if MACRO-NAME was previously undefined (different
+# than defined and empty).
+#
+# This macro is needed until we can rely on Autoconf 2.62, since earlier
+# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
+m4_define([lt_append],
+[m4_define([$1],
+ m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
+
+
+
+# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
+# ----------------------------------------------------------
+# Produce a SEP delimited list of all paired combinations of elements of
+# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list
+# has the form PREFIXmINFIXSUFFIXn.
+# Needed until we can rely on m4_combine added in Autoconf 2.62.
+m4_define([lt_combine],
+[m4_if(m4_eval([$# > 3]), [1],
+ [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
+[[m4_foreach([_Lt_prefix], [$2],
+ [m4_foreach([_Lt_suffix],
+ ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
+ [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
+
+
+# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
+# -----------------------------------------------------------------------
+# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
+# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
+m4_define([lt_if_append_uniq],
+[m4_ifdef([$1],
+ [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
+ [lt_append([$1], [$2], [$3])$4],
+ [$5])],
+ [lt_append([$1], [$2], [$3])$4])])
+
+
+# lt_dict_add(DICT, KEY, VALUE)
+# -----------------------------
+m4_define([lt_dict_add],
+[m4_define([$1($2)], [$3])])
+
+
+# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
+# --------------------------------------------
+m4_define([lt_dict_add_subkey],
+[m4_define([$1($2:$3)], [$4])])
+
+
+# lt_dict_fetch(DICT, KEY, [SUBKEY])
+# ----------------------------------
+m4_define([lt_dict_fetch],
+[m4_ifval([$3],
+ m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
+ m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
+
+
+# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
+# -----------------------------------------------------------------
+m4_define([lt_if_dict_fetch],
+[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
+ [$5],
+ [$6])])
+
+
+# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
+# --------------------------------------------------------------
+m4_define([lt_dict_filter],
+[m4_if([$5], [], [],
+ [lt_join(m4_quote(m4_default([$4], [[, ]])),
+ lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
+ [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
+])
+
+# ltversion.m4 -- version numbers -*- Autoconf -*-
+#
+# Copyright (C) 2004, 2011-2019, 2021-2022 Free Software Foundation,
+# Inc.
+# Written by Scott James Remnant, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# @configure_input@
+
+# serial 4245 ltversion.m4
+# This file is part of GNU Libtool
+
+m4_define([LT_PACKAGE_VERSION], [2.4.7])
+m4_define([LT_PACKAGE_REVISION], [2.4.7])
+
+AC_DEFUN([LTVERSION_VERSION],
+[macro_version='2.4.7'
+macro_revision='2.4.7'
+_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
+_LT_DECL(, macro_revision, 0)
+])
+
+# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*-
+#
+# Copyright (C) 2004-2005, 2007, 2009, 2011-2019, 2021-2022 Free
+# Software Foundation, Inc.
+# Written by Scott James Remnant, 2004.
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 5 lt~obsolete.m4
+
+# These exist entirely to fool aclocal when bootstrapping libtool.
+#
+# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN),
+# which have later been changed to m4_define as they aren't part of the
+# exported API, or moved to Autoconf or Automake where they belong.
+#
+# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN
+# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
+# using a macro with the same name in our local m4/libtool.m4 it'll
+# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
+# and doesn't know about Autoconf macros at all.)
+#
+# So we provide this file, which has a silly filename so it's always
+# included after everything else. This provides aclocal with the
+# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
+# because those macros already exist, or will be overwritten later.
+# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6.
+#
+# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
+# Yes, that means every name once taken will need to remain here until
+# we give up compatibility with versions before 1.7, at which point
+# we need to keep only those names which we still refer to.
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
+
+m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
+m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])])
+m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])])
+m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
+m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])])
+m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])])
+m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
+m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])])
+m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])])
+m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])])
+m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
+m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
+m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
+m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
+m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])])
+m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
+m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
+m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])])
+m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])])
+m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
+m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
+m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
+m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
+m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])])
+m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])])
+m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])])
+m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
+m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])])
+m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])])
+m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])])
+m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])])
+m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
+m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])])
+m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
+m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])])
+m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])])
+m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])])
+m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
+m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
+m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
+m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
+m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
+m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
+m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])])
+m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
+m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
+m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])])
+m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
+m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])])
+m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])])
+m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])])
+
+# Copyright (C) 2002-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.16'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version. Point them to the right macro.
+m4_if([$1], [1.16.5], [],
+ [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too. Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.16.5])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+ [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# Copyright (C) 2011-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_AR([ACT-IF-FAIL])
+# -------------------------
+# Try to determine the archiver interface, and trigger the ar-lib wrapper
+# if it is needed. If the detection of archiver interface fails, run
+# ACT-IF-FAIL (default is to abort configure with a proper error message).
+AC_DEFUN([AM_PROG_AR],
+[AC_BEFORE([$0], [LT_INIT])dnl
+AC_BEFORE([$0], [AC_PROG_LIBTOOL])dnl
+AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([ar-lib])dnl
+AC_CHECK_TOOLS([AR], [ar lib "link -lib"], [false])
+: ${AR=ar}
+
+AC_CACHE_CHECK([the archiver ($AR) interface], [am_cv_ar_interface],
+ [AC_LANG_PUSH([C])
+ am_cv_ar_interface=ar
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int some_variable = 0;]])],
+ [am_ar_try='$AR cru libconftest.a conftest.$ac_objext >&AS_MESSAGE_LOG_FD'
+ AC_TRY_EVAL([am_ar_try])
+ if test "$ac_status" -eq 0; then
+ am_cv_ar_interface=ar
+ else
+ am_ar_try='$AR -NOLOGO -OUT:conftest.lib conftest.$ac_objext >&AS_MESSAGE_LOG_FD'
+ AC_TRY_EVAL([am_ar_try])
+ if test "$ac_status" -eq 0; then
+ am_cv_ar_interface=lib
+ else
+ am_cv_ar_interface=unknown
+ fi
+ fi
+ rm -f conftest.lib libconftest.a
+ ])
+ AC_LANG_POP([C])])
+
+case $am_cv_ar_interface in
+ar)
+ ;;
+lib)
+ # Microsoft lib, so override with the ar-lib wrapper script.
+ # FIXME: It is wrong to rewrite AR.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__AR in this case,
+ # and then we could set am__AR="$am_aux_dir/ar-lib \$(AR)" or something
+ # similar.
+ AR="$am_aux_dir/ar-lib $AR"
+ ;;
+unknown)
+ m4_default([$1],
+ [AC_MSG_ERROR([could not determine $AR interface])])
+ ;;
+esac
+AC_SUBST([AR])dnl
+])
+
+# AM_AUX_DIR_EXPAND -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to
+# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory. The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run. This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+# fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+# fails if $ac_aux_dir is absolute,
+# fails when called from a subdirectory in a VPATH build with
+# a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir. In an in-source build this is usually
+# harmless because $srcdir is '.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir. That would be:
+# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+# MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH. The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+])
+
+# AM_CONDITIONAL -*- Autoconf -*-
+
+# Copyright (C) 1997-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])],
+ [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+ $1_TRUE=
+ $1_FALSE='#'
+else
+ $1_TRUE='#'
+ $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+ AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery. Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+m4_if([$1], [CC], [depcc="$CC" am_compiler_list=],
+ [$1], [CXX], [depcc="$CXX" am_compiler_list=],
+ [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+ [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'],
+ [$1], [UPC], [depcc="$UPC" am_compiler_list=],
+ [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'],
+ [depcc="$$1" am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+ [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+ # We make a subdir and do the tests there. Otherwise we can end up
+ # making bogus files that we don't know about and never remove. For
+ # instance it was reported that on HP-UX the gcc test will end up
+ # making a dummy file named 'D' -- because '-MD' means "put the output
+ # in D".
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ # Copy depcomp to subdir because otherwise we won't find it if we're
+ # using a relative directory.
+ cp "$am_depcomp" conftest.dir
+ cd conftest.dir
+ # We will build objects and dependencies in a subdirectory because
+ # it helps to detect inapplicable dependency modes. For instance
+ # both Tru64's cc and ICC support -MD to output dependencies as a
+ # side effect of compilation, but ICC will put the dependencies in
+ # the current directory while Tru64 will put them in the object
+ # directory.
+ mkdir sub
+
+ am_cv_$1_dependencies_compiler_type=none
+ if test "$am_compiler_list" = ""; then
+ am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+ fi
+ am__universal=false
+ m4_case([$1], [CC],
+ [case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac],
+ [CXX],
+ [case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac])
+
+ for depmode in $am_compiler_list; do
+ # Setup a source with many dependencies, because some compilers
+ # like to wrap large dependency lists on column 80 (with \), and
+ # we should not choose a depcomp mode which is confused by this.
+ #
+ # We need to recreate these files for each test, as the compiler may
+ # overwrite some of them when testing with obscure command lines.
+ # This happens at least with the AIX C compiler.
+ : > sub/conftest.c
+ for i in 1 2 3 4 5 6; do
+ echo '#include "conftst'$i'.h"' >> sub/conftest.c
+ # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+ # Solaris 10 /bin/sh.
+ echo '/* dummy */' > sub/conftst$i.h
+ done
+ echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+ # We check with '-c' and '-o' for the sake of the "dashmstdout"
+ # mode. It turns out that the SunPro C++ compiler does not properly
+ # handle '-M -o', and we need to detect this. Also, some Intel
+ # versions had trouble with output in subdirs.
+ am__obj=sub/conftest.${OBJEXT-o}
+ am__minus_obj="-o $am__obj"
+ case $depmode in
+ gcc)
+ # This depmode causes a compiler race in universal mode.
+ test "$am__universal" = false || continue
+ ;;
+ nosideeffect)
+ # After this tag, mechanisms are not by side-effect, so they'll
+ # only be used when explicitly requested.
+ if test "x$enable_dependency_tracking" = xyes; then
+ continue
+ else
+ break
+ fi
+ ;;
+ msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+ # This compiler won't grok '-c -o', but also, the minuso test has
+ # not run yet. These depmodes are late enough in the game, and
+ # so weak that their functioning should not be impacted.
+ am__obj=conftest.${OBJEXT-o}
+ am__minus_obj=
+ ;;
+ none) break ;;
+ esac
+ if depmode=$depmode \
+ source=sub/conftest.c object=$am__obj \
+ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+ >/dev/null 2>conftest.err &&
+ grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+ ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+ # icc doesn't choke on unknown options, it will just issue warnings
+ # or remarks (even with -Werror). So we grep stderr for any message
+ # that says an option was ignored or not supported.
+ # When given -MP, icc 7.0 and 7.1 complain thusly:
+ # icc: Command line warning: ignoring option '-M'; no argument required
+ # The diagnosis changed in icc 8.0:
+ # icc: Command line remark: option '-MP' not supported
+ if (grep 'ignoring option' conftest.err ||
+ grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+ am_cv_$1_dependencies_compiler_type=$depmode
+ break
+ fi
+ fi
+ done
+
+ cd ..
+ rm -rf conftest.dir
+else
+ am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+ test "x$enable_dependency_tracking" != xno \
+ && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES.
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE([dependency-tracking], [dnl
+AS_HELP_STRING(
+ [--enable-dependency-tracking],
+ [do not reject slow dependency extractors])
+AS_HELP_STRING(
+ [--disable-dependency-tracking],
+ [speeds up one-time build])])
+if test "x$enable_dependency_tracking" != xno; then
+ am_depcomp="$ac_aux_dir/depcomp"
+ AMDEPBACKSLASH='\'
+ am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking. -*- Autoconf -*-
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+ # Older Autoconf quotes --file arguments for eval, but not when files
+ # are listed without --file. Let's play safe and only enable the eval
+ # if we detect the quoting.
+ # TODO: see whether this extra hack can be removed once we start
+ # requiring Autoconf 2.70 or later.
+ AS_CASE([$CONFIG_FILES],
+ [*\'*], [eval set x "$CONFIG_FILES"],
+ [*], [set x $CONFIG_FILES])
+ shift
+ # Used to flag and report bootstrapping failures.
+ am_rc=0
+ for am_mf
+ do
+ # Strip MF so we end up with the name of the file.
+ am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'`
+ # Check whether this is an Automake generated Makefile which includes
+ # dependency-tracking related rules and includes.
+ # Grep'ing the whole file directly is not great: AIX grep has a line
+ # limit of 2048, but all sed's we know have understand at least 4000.
+ sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
+ || continue
+ am_dirpart=`AS_DIRNAME(["$am_mf"])`
+ am_filepart=`AS_BASENAME(["$am_mf"])`
+ AM_RUN_LOG([cd "$am_dirpart" \
+ && sed -e '/# am--include-marker/d' "$am_filepart" \
+ | $MAKE -f - am--depfiles]) || am_rc=$?
+ done
+ if test $am_rc -ne 0; then
+ AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments
+ for automatic dependency tracking. If GNU make was not used, consider
+ re-running the configure script with MAKE="gmake" (or whatever is
+ necessary). You can also try re-running configure with the
+ '--disable-dependency-tracking' option to at least be able to build
+ the package (albeit without support for automatic dependency tracking).])
+ fi
+ AS_UNSET([am_dirpart])
+ AS_UNSET([am_filepart])
+ AS_UNSET([am_mf])
+ AS_UNSET([am_rc])
+ rm -f conftest-deps.mk
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking is enabled.
+# This creates each '.Po' and '.Plo' makefile fragment that we'll need in
+# order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+ [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+ [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])])
+
+# Do all the work for Automake. -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This macro actually does too much. Some checks are only needed if
+# your package does certain things. But this isn't really a big deal.
+
+dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O.
+m4_define([AC_PROG_CC],
+m4_defn([AC_PROG_CC])
+[_AM_PROG_CC_C_O
+])
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out. PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition. After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.65])dnl
+m4_ifdef([_$0_ALREADY_INIT],
+ [m4_fatal([$0 expanded multiple times
+]m4_defn([_$0_ALREADY_INIT]))],
+ [m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl
+dnl Autoconf wants to disallow AM_ names. We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+ # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+ # is not polluted with repeated "-I."
+ AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+ # test to see if srcdir already configured
+ if test -f $srcdir/config.status; then
+ AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+ fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+ if (cygpath --version) >/dev/null 2>/dev/null; then
+ CYGPATH_W='cygpath -w'
+ else
+ CYGPATH_W=echo
+ fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[AC_DIAGNOSE([obsolete],
+ [$0: two- and three-arguments forms are deprecated.])
+m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(
+ m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]),
+ [ok:ok],,
+ [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package])
+ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}])
+AM_MISSING_PROG([AUTOCONF], [autoconf])
+AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}])
+AM_MISSING_PROG([AUTOHEADER], [autoheader])
+AM_MISSING_PROG([MAKEINFO], [makeinfo])
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+# For better backward compatibility. To be removed once Automake 1.9.x
+# dies out for good. For more background, see:
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
+# We need awk for the "check" target (and possibly the TAP driver). The
+# system "awk" is bad on some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+ [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+ [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+ [_AM_DEPENDENCIES([CC])],
+ [m4_define([AC_PROG_CC],
+ m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+ [_AM_DEPENDENCIES([CXX])],
+ [m4_define([AC_PROG_CXX],
+ m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+ [_AM_DEPENDENCIES([OBJC])],
+ [m4_define([AC_PROG_OBJC],
+ m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
+ [_AM_DEPENDENCIES([OBJCXX])],
+ [m4_define([AC_PROG_OBJCXX],
+ m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
+])
+# Variables for tags utilities; see am/tags.am
+if test -z "$CTAGS"; then
+ CTAGS=ctags
+fi
+AC_SUBST([CTAGS])
+if test -z "$ETAGS"; then
+ ETAGS=etags
+fi
+AC_SUBST([ETAGS])
+if test -z "$CSCOPE"; then
+ CSCOPE=cscope
+fi
+AC_SUBST([CSCOPE])
+
+AC_REQUIRE([AM_SILENT_RULES])dnl
+dnl The testsuite driver may need to know about EXEEXT, so add the
+dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This
+dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+ [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes. So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+ cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present. This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message. This
+can help us improve future automake versions.
+
+END
+ if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+ echo 'Configuration will proceed anyway, since you have set the' >&2
+ echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+ echo >&2
+ else
+ cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <https://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+ AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
+ fi
+fi
+dnl The trailing newline in this macro's definition is deliberate, for
+dnl backward compatibility and to allow trailing 'dnl'-style comments
+dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841.
+])
+
+dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated. The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+ case $_am_header in
+ $_am_arg | $_am_arg:* )
+ break ;;
+ * )
+ _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+ esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+ *)
+ install_sh="\${SHELL} $am_aux_dir/install-sh"
+ esac
+fi
+AC_SUBST([install_sh])])
+
+# Copyright (C) 2003-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot. For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+ am__leading_dot=.
+else
+ am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Add --enable-maintainer-mode option to configure. -*- Autoconf -*-
+# From Jim Meyering
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAINTAINER_MODE([DEFAULT-MODE])
+# ----------------------------------
+# Control maintainer-specific portions of Makefiles.
+# Default is to disable them, unless 'enable' is passed literally.
+# For symmetry, 'disable' may be passed as well. Anyway, the user
+# can override the default with the --enable/--disable switch.
+AC_DEFUN([AM_MAINTAINER_MODE],
+[m4_case(m4_default([$1], [disable]),
+ [enable], [m4_define([am_maintainer_other], [disable])],
+ [disable], [m4_define([am_maintainer_other], [enable])],
+ [m4_define([am_maintainer_other], [enable])
+ m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])])
+AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles])
+ dnl maintainer-mode's default is 'disable' unless 'enable' is passed
+ AC_ARG_ENABLE([maintainer-mode],
+ [AS_HELP_STRING([--]am_maintainer_other[-maintainer-mode],
+ am_maintainer_other[ make rules and dependencies not useful
+ (and sometimes confusing) to the casual installer])],
+ [USE_MAINTAINER_MODE=$enableval],
+ [USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes]))
+ AC_MSG_RESULT([$USE_MAINTAINER_MODE])
+ AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes])
+ MAINT=$MAINTAINER_MODE_TRUE
+ AC_SUBST([MAINT])dnl
+]
+)
+
+# Check to see how 'make' treats includes. -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check whether make has an 'include' directive that can support all
+# the idioms we need for our automatic dependency tracking code.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive])
+cat > confinc.mk << 'END'
+am__doit:
+ @echo this is the am__doit target >confinc.out
+.PHONY: am__doit
+END
+am__include="#"
+am__quote=
+# BSD make does it like this.
+echo '.include "confinc.mk" # ignored' > confmf.BSD
+# Other make implementations (GNU, Solaris 10, AIX) do it like this.
+echo 'include confinc.mk # ignored' > confmf.GNU
+_am_result=no
+for s in GNU BSD; do
+ AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out])
+ AS_CASE([$?:`cat confinc.out 2>/dev/null`],
+ ['0:this is the am__doit target'],
+ [AS_CASE([$s],
+ [BSD], [am__include='.include' am__quote='"'],
+ [am__include='include' am__quote=''])])
+ if test "$am__include" != "#"; then
+ _am_result="yes ($s style)"
+ break
+ fi
+done
+rm -f confinc.* confmf.*
+AC_MSG_RESULT([${_am_result}])
+AC_SUBST([am__include])])
+AC_SUBST([am__quote])])
+
+# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
+
+# Copyright (C) 1997-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it is modern enough.
+# If it is, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+ MISSING="\${SHELL} '$am_aux_dir/missing'"
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+ am_missing_run="$MISSING "
+else
+ am_missing_run=
+ AC_MSG_WARN(['missing' script is too old or missing])
+fi
+])
+
+# -*- Autoconf -*-
+# Obsolete and "removed" macros, that must however still report explicit
+# error messages when used, to smooth transition.
+#
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([AM_CONFIG_HEADER],
+[AC_DIAGNOSE([obsolete],
+['$0': this macro is obsolete.
+You should use the 'AC][_CONFIG_HEADERS' macro instead.])dnl
+AC_CONFIG_HEADERS($@)])
+
+AC_DEFUN([AM_PROG_CC_STDC],
+[AC_PROG_CC
+am_cv_prog_cc_stdc=$ac_cv_prog_cc_stdc
+AC_DIAGNOSE([obsolete],
+['$0': this macro is obsolete.
+You should simply use the 'AC][_PROG_CC' macro instead.
+Also, your code should no longer depend upon 'am_cv_prog_cc_stdc',
+but upon 'ac_cv_prog_cc_stdc'.])])
+
+AC_DEFUN([AM_C_PROTOTYPES],
+ [AC_FATAL([automatic de-ANSI-fication support has been removed])])
+AU_DEFUN([fp_C_PROTOTYPES], [AM_C_PROTOTYPES])
+
+# Helper functions for option handling. -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME. Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), [1])])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_CC_C_O
+# ---------------
+# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC
+# to automatically call this.
+AC_DEFUN([_AM_PROG_CC_C_O],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([compile])dnl
+AC_LANG_PUSH([C])dnl
+AC_CACHE_CHECK(
+ [whether $CC understands -c and -o together],
+ [am_cv_prog_cc_c_o],
+ [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])])
+ # Make sure it works both with $CC and with simple cc.
+ # Following AC_PROG_CC_C_O, we do the test twice because some
+ # compilers refuse to overwrite an existing .o file with -o,
+ # though they will create one.
+ am_cv_prog_cc_c_o=yes
+ for am_i in 1 2; do
+ if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \
+ && test -f conftest2.$ac_objext; then
+ : OK
+ else
+ am_cv_prog_cc_c_o=no
+ break
+ fi
+ done
+ rm -f core conftest*
+ unset am_i])
+if test "$am_cv_prog_cc_c_o" != yes; then
+ # Losing compiler, so override with the script.
+ # FIXME: It is wrong to rewrite CC.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__CC in this case,
+ # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+ CC="$am_aux_dir/compile $CC"
+fi
+AC_LANG_POP([C])])
+
+# For backward compatibility.
+AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_RUN_LOG(COMMAND)
+# -------------------
+# Run COMMAND, save the exit status in ac_status, and log it.
+# (This has been adapted from Autoconf's _AC_RUN_LOG macro.)
+AC_DEFUN([AM_RUN_LOG],
+[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD
+ ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+ (exit $ac_status); }])
+
+# Check to make sure that the build environment is sane. -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name. Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+ *[[\\\"\#\$\&\'\`$am_lf]]*)
+ AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+ *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*)
+ AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ am_has_slept=no
+ for am_try in 1 2; do
+ echo "timestamp, slept: $am_has_slept" > conftest.file
+ set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+ if test "$[*]" = "X"; then
+ # -L didn't work.
+ set X `ls -t "$srcdir/configure" conftest.file`
+ fi
+ if test "$[*]" != "X $srcdir/configure conftest.file" \
+ && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken
+ alias in your environment])
+ fi
+ if test "$[2]" = conftest.file || test $am_try -eq 2; then
+ break
+ fi
+ # Just in case.
+ sleep 1
+ am_has_slept=yes
+ done
+ test "$[2]" = conftest.file
+ )
+then
+ # Ok.
+ :
+else
+ AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT([yes])
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+ ( sleep 1 ) &
+ am_sleep_pid=$!
+fi
+AC_CONFIG_COMMANDS_PRE(
+ [AC_MSG_CHECKING([that generated files are newer than configure])
+ if test -n "$am_sleep_pid"; then
+ # Hide warnings about reused PIDs.
+ wait $am_sleep_pid 2>/dev/null
+ fi
+ AC_MSG_RESULT([done])])
+rm -f conftest.file
+])
+
+# Copyright (C) 2009-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# ("yes" being less verbose, "no" or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules], [dnl
+AS_HELP_STRING(
+ [--enable-silent-rules],
+ [less verbose build output (undo: "make V=1")])
+AS_HELP_STRING(
+ [--disable-silent-rules],
+ [verbose build output (undo: "make V=0")])dnl
+])
+case $enable_silent_rules in @%:@ (((
+ yes) AM_DEFAULT_VERBOSITY=0;;
+ no) AM_DEFAULT_VERBOSITY=1;;
+ *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few 'make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+ [am_cv_make_support_nested_variables],
+ [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+ @$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+ am_cv_make_support_nested_variables=yes
+else
+ am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+ dnl Using '$V' instead of '$(V)' breaks IRIX make.
+ AM_V='$(V)'
+ AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+ AM_V=$AM_DEFAULT_VERBOSITY
+ AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor 'install' (even GNU) is that you can't
+# specify the program used to strip binaries. This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in "make install-strip", and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip". However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be 'maybe'.
+if test "$cross_compiling" != no; then
+ AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball. -*- Autoconf -*-
+
+# Copyright (C) 2004-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of 'v7', 'ustar', or 'pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+# tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+# $(am__untar) < result.tar
+#
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+
+m4_if([$1], [v7],
+ [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+
+ [m4_case([$1],
+ [ustar],
+ [# The POSIX 1988 'ustar' format is defined with fixed-size fields.
+ # There is notably a 21 bits limit for the UID and the GID. In fact,
+ # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343
+ # and bug#13588).
+ am_max_uid=2097151 # 2^21 - 1
+ am_max_gid=$am_max_uid
+ # The $UID and $GID variables are not portable, so we need to resort
+ # to the POSIX-mandated id(1) utility. Errors in the 'id' calls
+ # below are definitely unexpected, so allow the users to see them
+ # (that is, avoid stderr redirection).
+ am_uid=`id -u || echo unknown`
+ am_gid=`id -g || echo unknown`
+ AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format])
+ if test $am_uid -le $am_max_uid; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ _am_tools=none
+ fi
+ AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format])
+ if test $am_gid -le $am_max_gid; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ _am_tools=none
+ fi],
+
+ [pax],
+ [],
+
+ [m4_fatal([Unknown tar format])])
+
+ AC_MSG_CHECKING([how to create a $1 tar archive])
+
+ # Go ahead even if we have the value already cached. We do so because we
+ # need to set the values for the 'am__tar' and 'am__untar' variables.
+ _am_tools=${am_cv_prog_tar_$1-$_am_tools}
+
+ for _am_tool in $_am_tools; do
+ case $_am_tool in
+ gnutar)
+ for _am_tar in tar gnutar gtar; do
+ AM_RUN_LOG([$_am_tar --version]) && break
+ done
+ am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+ am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+ am__untar="$_am_tar -xf -"
+ ;;
+ plaintar)
+ # Must skip GNU tar: if it does not support --format= it doesn't create
+ # ustar tarball either.
+ (tar --version) >/dev/null 2>&1 && continue
+ am__tar='tar chf - "$$tardir"'
+ am__tar_='tar chf - "$tardir"'
+ am__untar='tar xf -'
+ ;;
+ pax)
+ am__tar='pax -L -x $1 -w "$$tardir"'
+ am__tar_='pax -L -x $1 -w "$tardir"'
+ am__untar='pax -r'
+ ;;
+ cpio)
+ am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+ am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+ am__untar='cpio -i -H $1 -d'
+ ;;
+ none)
+ am__tar=false
+ am__tar_=false
+ am__untar=false
+ ;;
+ esac
+
+ # If the value was cached, stop now. We just wanted to have am__tar
+ # and am__untar set.
+ test -n "${am_cv_prog_tar_$1}" && break
+
+ # tar/untar a dummy directory, and stop if the command works.
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ echo GrepMe > conftest.dir/file
+ AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+ rm -rf conftest.dir
+ if test -s conftest.tar; then
+ AM_RUN_LOG([$am__untar <conftest.tar])
+ AM_RUN_LOG([cat conftest.dir/file])
+ grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+ fi
+ done
+ rm -rf conftest.dir
+
+ AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+ AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
diff --git a/ar-lib b/ar-lib
new file mode 100755
index 00000000..c349042c
--- /dev/null
+++ b/ar-lib
@@ -0,0 +1,271 @@
+#! /bin/sh
+# Wrapper for Microsoft lib.exe
+
+me=ar-lib
+scriptversion=2019-07-04.01; # UTC
+
+# Copyright (C) 2010-2021 Free Software Foundation, Inc.
+# Written by Peter Rosin <peda@lysator.liu.se>.
+#
+# 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 distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+
+# func_error message
+func_error ()
+{
+ echo "$me: $1" 1>&2
+ exit 1
+}
+
+file_conv=
+
+# func_file_conv build_file
+# Convert a $build file to $host form and store it in $file
+# Currently only supports Windows hosts.
+func_file_conv ()
+{
+ file=$1
+ case $file in
+ / | /[!/]*) # absolute file, and not a UNC file
+ if test -z "$file_conv"; then
+ # lazily determine how to convert abs files
+ case `uname -s` in
+ MINGW*)
+ file_conv=mingw
+ ;;
+ CYGWIN* | MSYS*)
+ file_conv=cygwin
+ ;;
+ *)
+ file_conv=wine
+ ;;
+ esac
+ fi
+ case $file_conv in
+ mingw)
+ file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
+ ;;
+ cygwin | msys)
+ file=`cygpath -m "$file" || echo "$file"`
+ ;;
+ wine)
+ file=`winepath -w "$file" || echo "$file"`
+ ;;
+ esac
+ ;;
+ esac
+}
+
+# func_at_file at_file operation archive
+# Iterate over all members in AT_FILE performing OPERATION on ARCHIVE
+# for each of them.
+# When interpreting the content of the @FILE, do NOT use func_file_conv,
+# since the user would need to supply preconverted file names to
+# binutils ar, at least for MinGW.
+func_at_file ()
+{
+ operation=$2
+ archive=$3
+ at_file_contents=`cat "$1"`
+ eval set x "$at_file_contents"
+ shift
+
+ for member
+ do
+ $AR -NOLOGO $operation:"$member" "$archive" || exit $?
+ done
+}
+
+case $1 in
+ '')
+ func_error "no command. Try '$0 --help' for more information."
+ ;;
+ -h | --h*)
+ cat <<EOF
+Usage: $me [--help] [--version] PROGRAM ACTION ARCHIVE [MEMBER...]
+
+Members may be specified in a file named with @FILE.
+EOF
+ exit $?
+ ;;
+ -v | --v*)
+ echo "$me, version $scriptversion"
+ exit $?
+ ;;
+esac
+
+if test $# -lt 3; then
+ func_error "you must specify a program, an action and an archive"
+fi
+
+AR=$1
+shift
+while :
+do
+ if test $# -lt 2; then
+ func_error "you must specify a program, an action and an archive"
+ fi
+ case $1 in
+ -lib | -LIB \
+ | -ltcg | -LTCG \
+ | -machine* | -MACHINE* \
+ | -subsystem* | -SUBSYSTEM* \
+ | -verbose | -VERBOSE \
+ | -wx* | -WX* )
+ AR="$AR $1"
+ shift
+ ;;
+ *)
+ action=$1
+ shift
+ break
+ ;;
+ esac
+done
+orig_archive=$1
+shift
+func_file_conv "$orig_archive"
+archive=$file
+
+# strip leading dash in $action
+action=${action#-}
+
+delete=
+extract=
+list=
+quick=
+replace=
+index=
+create=
+
+while test -n "$action"
+do
+ case $action in
+ d*) delete=yes ;;
+ x*) extract=yes ;;
+ t*) list=yes ;;
+ q*) quick=yes ;;
+ r*) replace=yes ;;
+ s*) index=yes ;;
+ S*) ;; # the index is always updated implicitly
+ c*) create=yes ;;
+ u*) ;; # TODO: don't ignore the update modifier
+ v*) ;; # TODO: don't ignore the verbose modifier
+ *)
+ func_error "unknown action specified"
+ ;;
+ esac
+ action=${action#?}
+done
+
+case $delete$extract$list$quick$replace,$index in
+ yes,* | ,yes)
+ ;;
+ yesyes*)
+ func_error "more than one action specified"
+ ;;
+ *)
+ func_error "no action specified"
+ ;;
+esac
+
+if test -n "$delete"; then
+ if test ! -f "$orig_archive"; then
+ func_error "archive not found"
+ fi
+ for member
+ do
+ case $1 in
+ @*)
+ func_at_file "${1#@}" -REMOVE "$archive"
+ ;;
+ *)
+ func_file_conv "$1"
+ $AR -NOLOGO -REMOVE:"$file" "$archive" || exit $?
+ ;;
+ esac
+ done
+
+elif test -n "$extract"; then
+ if test ! -f "$orig_archive"; then
+ func_error "archive not found"
+ fi
+ if test $# -gt 0; then
+ for member
+ do
+ case $1 in
+ @*)
+ func_at_file "${1#@}" -EXTRACT "$archive"
+ ;;
+ *)
+ func_file_conv "$1"
+ $AR -NOLOGO -EXTRACT:"$file" "$archive" || exit $?
+ ;;
+ esac
+ done
+ else
+ $AR -NOLOGO -LIST "$archive" | tr -d '\r' | sed -e 's/\\/\\\\/g' \
+ | while read member
+ do
+ $AR -NOLOGO -EXTRACT:"$member" "$archive" || exit $?
+ done
+ fi
+
+elif test -n "$quick$replace"; then
+ if test ! -f "$orig_archive"; then
+ if test -z "$create"; then
+ echo "$me: creating $orig_archive"
+ fi
+ orig_archive=
+ else
+ orig_archive=$archive
+ fi
+
+ for member
+ do
+ case $1 in
+ @*)
+ func_file_conv "${1#@}"
+ set x "$@" "@$file"
+ ;;
+ *)
+ func_file_conv "$1"
+ set x "$@" "$file"
+ ;;
+ esac
+ shift
+ shift
+ done
+
+ if test -n "$orig_archive"; then
+ $AR -NOLOGO -OUT:"$archive" "$orig_archive" "$@" || exit $?
+ else
+ $AR -NOLOGO -OUT:"$archive" "$@" || exit $?
+ fi
+
+elif test -n "$list"; then
+ if test ! -f "$orig_archive"; then
+ func_error "archive not found"
+ fi
+ $AR -NOLOGO -LIST "$archive" || exit $?
+fi
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 00000000..5bce9bab
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,1578 @@
+#!/bin/sh
+# a u t o g e n . s h
+#
+# Copyright (c) 2005-2009 United States Government as represented by
+# the U.S. Army Research Laboratory.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of the author may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+###
+#
+# Script for automatically preparing the sources for compilation by
+# performing the myriad of necessary steps. The script attempts to
+# detect proper version support, and outputs warnings about particular
+# systems that have autotool peculiarities.
+#
+# Basically, if everything is set up and installed correctly, the
+# script will validate that minimum versions of the GNU Build System
+# tools are installed, account for several common configuration
+# issues, and then simply run autoreconf for you.
+#
+# If autoreconf fails, which can happen for many valid configurations,
+# this script proceeds to run manual preparation steps effectively
+# providing a POSIX shell script (mostly complete) reimplementation of
+# autoreconf.
+#
+# The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER
+# environment variables and corresponding _OPTIONS variables (e.g.
+# AUTORECONF_OPTIONS) may be used to override the default automatic
+# detection behaviors. Similarly the _VERSION variables will override
+# the minimum required version numbers.
+#
+# Examples:
+#
+# To obtain help on usage:
+# ./autogen.sh --help
+#
+# To obtain verbose output:
+# ./autogen.sh --verbose
+#
+# To skip autoreconf and prepare manually:
+# AUTORECONF=false ./autogen.sh
+#
+# To verbosely try running with an older (unsupported) autoconf:
+# AUTOCONF_VERSION=2.50 ./autogen.sh --verbose
+#
+# Author:
+# Christopher Sean Morrison <morrison@brlcad.org>
+#
+# Patches:
+# Sebastian Pipping <sebastian@pipping.org>
+#
+######################################################################
+
+# set to minimum acceptable version of autoconf
+if [ "x$AUTOCONF_VERSION" = "x" ] ; then
+ AUTOCONF_VERSION=2.52
+fi
+# set to minimum acceptable version of automake
+if [ "x$AUTOMAKE_VERSION" = "x" ] ; then
+ AUTOMAKE_VERSION=1.6.0
+fi
+# set to minimum acceptable version of libtool
+if [ "x$LIBTOOL_VERSION" = "x" ] ; then
+ LIBTOOL_VERSION=1.4.2
+fi
+
+
+##################
+# ident function #
+##################
+ident ( ) {
+ # extract copyright from header
+ __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`"
+ if [ "x$__copyright" = "x" ] ; then
+ __copyright="`date +%Y`"
+ fi
+
+ # extract version from CVS Id string
+ __id="$Id: autogen.sh 33925 2009-03-01 23:27:06Z brlcad $"
+ __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`"
+ if [ "x$__version" = "x" ] ; then
+ __version=""
+ fi
+
+ echo "autogen.sh build preparation script by Christopher Sean Morrison"
+ echo " + config.guess download patch by Sebastian Pipping (2008-12-03)"
+ echo "revised 3-clause BSD-style license, copyright (c) $__copyright"
+ echo "script version $__version, ISO/IEC 9945 POSIX shell script"
+}
+
+
+##################
+# USAGE FUNCTION #
+##################
+usage ( ) {
+ echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [-d|--download] [--version]"
+ echo " --help Help on $NAME_OF_AUTOGEN usage"
+ echo " --verbose Verbose progress output"
+ echo " --quiet Quiet suppressed progress output"
+ echo " --download Download the latest config.guess from gnulib"
+ echo " --version Only perform GNU Build System version checks"
+ echo
+ echo "Description: This script will validate that minimum versions of the"
+ echo "GNU Build System tools are installed and then run autoreconf for you."
+ echo "Should autoreconf fail, manual preparation steps will be run"
+ echo "potentially accounting for several common preparation issues. The"
+
+ echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER,"
+ echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS"
+ echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the"
+ echo "default automatic detection behavior."
+ echo
+
+ ident
+
+ return 0
+}
+
+
+##########################
+# VERSION_ERROR FUNCTION #
+##########################
+version_error ( ) {
+ if [ "x$1" = "x" ] ; then
+ echo "INTERNAL ERROR: version_error was not provided a version"
+ exit 1
+ fi
+ if [ "x$2" = "x" ] ; then
+ echo "INTERNAL ERROR: version_error was not provided an application name"
+ exit 1
+ fi
+ $ECHO
+ $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch,"
+ $ECHO " at least version $1 of $2 must be installed."
+ $ECHO
+ $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will"
+ $ECHO "run configure or make. Either the GNU Autotools will need to be installed"
+ $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source"
+ $ECHO "code on another system and then transferred to here. -- Cheers!"
+ $ECHO
+}
+
+##########################
+# VERSION_CHECK FUNCTION #
+##########################
+version_check ( ) {
+ if [ "x$1" = "x" ] ; then
+ echo "INTERNAL ERROR: version_check was not provided a minimum version"
+ exit 1
+ fi
+ _min="$1"
+ if [ "x$2" = "x" ] ; then
+ echo "INTERNAL ERROR: version check was not provided a comparison version"
+ exit 1
+ fi
+ _cur="$2"
+
+ # needed to handle versions like 1.10 and 1.4-p6
+ _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`"
+ _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`"
+
+ _min_major="`echo $_min | cut -d. -f1`"
+ _min_minor="`echo $_min | cut -d. -f2`"
+ _min_patch="`echo $_min | cut -d. -f3`"
+
+ _cur_major="`echo $_cur | cut -d. -f1`"
+ _cur_minor="`echo $_cur | cut -d. -f2`"
+ _cur_patch="`echo $_cur | cut -d. -f3`"
+
+ if [ "x$_min_major" = "x" ] ; then
+ _min_major=0
+ fi
+ if [ "x$_min_minor" = "x" ] ; then
+ _min_minor=0
+ fi
+ if [ "x$_min_patch" = "x" ] ; then
+ _min_patch=0
+ fi
+ if [ "x$_cur_minor" = "x" ] ; then
+ _cur_major=0
+ fi
+ if [ "x$_cur_minor" = "x" ] ; then
+ _cur_minor=0
+ fi
+ if [ "x$_cur_patch" = "x" ] ; then
+ _cur_patch=0
+ fi
+
+ $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}"
+
+ if [ $_min_major -lt $_cur_major ] ; then
+ return 0
+ elif [ $_min_major -eq $_cur_major ] ; then
+ if [ $_min_minor -lt $_cur_minor ] ; then
+ return 0
+ elif [ $_min_minor -eq $_cur_minor ] ; then
+ if [ $_min_patch -lt $_cur_patch ] ; then
+ return 0
+ elif [ $_min_patch -eq $_cur_patch ] ; then
+ return 0
+ fi
+ fi
+ fi
+ return 1
+}
+
+
+######################################
+# LOCATE_CONFIGURE_TEMPLATE FUNCTION #
+######################################
+locate_configure_template ( ) {
+ _pwd="`pwd`"
+ if test -f "./configure.ac" ; then
+ echo "./configure.ac"
+ elif test -f "./configure.in" ; then
+ echo "./configure.in"
+ elif test -f "$_pwd/configure.ac" ; then
+ echo "$_pwd/configure.ac"
+ elif test -f "$_pwd/configure.in" ; then
+ echo "$_pwd/configure.in"
+ elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then
+ echo "$PATH_TO_AUTOGEN/configure.ac"
+ elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then
+ echo "$PATH_TO_AUTOGEN/configure.in"
+ fi
+}
+
+
+##################
+# argument check #
+##################
+ARGS="$*"
+PATH_TO_AUTOGEN="`dirname $0`"
+NAME_OF_AUTOGEN="`basename $0`"
+AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN"
+
+LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4"
+
+if [ "x$HELP" = "x" ] ; then
+ HELP=no
+fi
+if [ "x$QUIET" = "x" ] ; then
+ QUIET=no
+fi
+if [ "x$VERBOSE" = "x" ] ; then
+ VERBOSE=no
+fi
+if [ "x$VERSION_ONLY" = "x" ] ; then
+ VERSION_ONLY=no
+fi
+if [ "x$DOWNLOAD" = "x" ] ; then
+ DOWNLOAD=no
+fi
+if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then
+ AUTORECONF_OPTIONS="-i -f"
+fi
+if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then
+ AUTOCONF_OPTIONS="-f"
+fi
+if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then
+ AUTOMAKE_OPTIONS="-a -c -f"
+fi
+ALT_AUTOMAKE_OPTIONS="-a -c"
+if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then
+ LIBTOOLIZE_OPTIONS="--automake -c -f"
+fi
+ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force"
+if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then
+ ACLOCAL_OPTIONS=""
+fi
+if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then
+ AUTOHEADER_OPTIONS=""
+fi
+if [ "x$CONFIG_GUESS_URL" = "x" ] ; then
+ CONFIG_GUESS_URL="http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=build-aux/config.guess;hb=HEAD"
+fi
+for arg in $ARGS ; do
+ case "x$arg" in
+ x--help) HELP=yes ;;
+ x-[hH]) HELP=yes ;;
+ x--quiet) QUIET=yes ;;
+ x-[qQ]) QUIET=yes ;;
+ x--verbose) VERBOSE=yes ;;
+ x-[dD]) DOWNLOAD=yes ;;
+ x--download) DOWNLOAD=yes ;;
+ x-[vV]) VERBOSE=yes ;;
+ x--version) VERSION_ONLY=yes ;;
+ *)
+ echo "Unknown option: $arg"
+ echo
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+
+#####################
+# environment check #
+#####################
+
+# sanity check before recursions potentially begin
+if [ ! -f "$AUTOGEN_SH" ] ; then
+ echo "INTERNAL ERROR: $AUTOGEN_SH does not exist"
+ if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then
+ echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH"
+ fi
+ exit 1
+fi
+
+# force locale setting to C so things like date output as expected
+LC_ALL=C
+
+# commands that this script expects
+for __cmd in echo head tail pwd ; do
+ echo "test" | $__cmd > /dev/null 2>&1
+ if [ $? != 0 ] ; then
+ echo "INTERNAL ERROR: '${__cmd}' command is required"
+ exit 2
+ fi
+done
+echo "test" | grep "test" > /dev/null 2>&1
+if test ! x$? = x0 ; then
+ echo "INTERNAL ERROR: grep command is required"
+ exit 1
+fi
+echo "test" | sed "s/test/test/" > /dev/null 2>&1
+if test ! x$? = x0 ; then
+ echo "INTERNAL ERROR: sed command is required"
+ exit 1
+fi
+
+
+# determine the behavior of echo
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+# determine the behavior of head
+case "x`echo 'head' | head -n 1 2>&1`" in
+ *xhead*) HEAD_N="n " ;;
+ *) HEAD_N="" ;;
+esac
+
+# determine the behavior of tail
+case "x`echo 'tail' | tail -n 1 2>&1`" in
+ *xtail*) TAIL_N="n " ;;
+ *) TAIL_N="" ;;
+esac
+
+VERBOSE_ECHO=:
+ECHO=:
+if [ "x$QUIET" = "xyes" ] ; then
+ if [ "x$VERBOSE" = "xyes" ] ; then
+ echo "Verbose output quelled by quiet option. Further output disabled."
+ fi
+else
+ ECHO=echo
+ if [ "x$VERBOSE" = "xyes" ] ; then
+ echo "Verbose output enabled"
+ VERBOSE_ECHO=echo
+ fi
+fi
+
+
+# allow a recursive run to disable further recursions
+if [ "x$RUN_RECURSIVE" = "x" ] ; then
+ RUN_RECURSIVE=yes
+fi
+
+
+################################################
+# check for help arg and bypass version checks #
+################################################
+if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then
+ HELP=yes
+fi
+if [ "x$HELP" = "xyes" ] ; then
+ usage
+ $ECHO "---"
+ $ECHO "Help was requested. No preparation or configuration will be performed."
+ exit 0
+fi
+
+
+#######################
+# set up signal traps #
+#######################
+untrap_abnormal ( ) {
+ for sig in 1 2 13 15; do
+ trap - $sig
+ done
+}
+
+# do this cleanup whenever we exit.
+trap '
+ # start from the root
+ if test -d "$START_PATH" ; then
+ cd "$START_PATH"
+ fi
+
+ # restore/delete backup files
+ if test "x$PFC_INIT" = "x1" ; then
+ recursive_restore
+ fi
+' 0
+
+# trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15)
+for sig in 1 2 13 15; do
+ trap '
+ $ECHO ""
+ $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'"
+
+ # start from the root
+ if test -d "$START_PATH" ; then
+ cd "$START_PATH"
+ fi
+
+ # clean up on abnormal exit
+ $VERBOSE_ECHO "rm -rf autom4te.cache"
+ rm -rf autom4te.cache
+
+ if test -f "acinclude.m4.$$.backup" ; then
+ $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4"
+ chmod u+w acinclude.m4
+ cat acinclude.m4.$$.backup > acinclude.m4
+
+ $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup"
+ rm -f acinclude.m4.$$.backup
+ fi
+
+ { (exit 1); exit 1; }
+' $sig
+done
+
+
+#############################
+# look for a configure file #
+#############################
+if [ "x$CONFIGURE" = "x" ] ; then
+ CONFIGURE="`locate_configure_template`"
+ if [ ! "x$CONFIGURE" = "x" ] ; then
+ $VERBOSE_ECHO "Found a configure template: $CONFIGURE"
+ fi
+else
+ $ECHO "Using CONFIGURE environment variable override: $CONFIGURE"
+fi
+if [ "x$CONFIGURE" = "x" ] ; then
+ if [ "x$VERSION_ONLY" = "xyes" ] ; then
+ CONFIGURE=/dev/null
+ else
+ $ECHO
+ $ECHO "A configure.ac or configure.in file could not be located implying"
+ $ECHO "that the GNU Build System is at least not used in this directory. In"
+ $ECHO "any case, there is nothing to do here without one of those files."
+ $ECHO
+ $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`"
+ exit 1
+ fi
+fi
+
+####################
+# get project name #
+####################
+if [ "x$PROJECT" = "x" ] ; then
+ PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ if [ "x$PROJECT" = "xAC_INIT" ] ; then
+ # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead
+ PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ fi
+ if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then
+ PROJECT="project"
+ fi
+ if [ "x$PROJECT" = "x" ] ; then
+ PROJECT="project"
+ fi
+else
+ $ECHO "Using PROJECT environment variable override: $PROJECT"
+fi
+$ECHO "Preparing the $PROJECT build system...please wait"
+$ECHO
+
+
+########################
+# check for autoreconf #
+########################
+HAVE_AUTORECONF=no
+if [ "x$AUTORECONF" = "x" ] ; then
+ for AUTORECONF in autoreconf ; do
+ $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version"
+ $AUTORECONF --version > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ HAVE_AUTORECONF=yes
+ break
+ fi
+ done
+else
+ HAVE_AUTORECONF=yes
+ $ECHO "Using AUTORECONF environment variable override: $AUTORECONF"
+fi
+
+
+##########################
+# autoconf version check #
+##########################
+_acfound=no
+if [ "x$AUTOCONF" = "x" ] ; then
+ for AUTOCONF in autoconf ; do
+ $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version"
+ $AUTOCONF --version > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ _acfound=yes
+ break
+ fi
+ done
+else
+ _acfound=yes
+ $ECHO "Using AUTOCONF environment variable override: $AUTOCONF"
+fi
+
+_report_error=no
+if [ ! "x$_acfound" = "xyes" ] ; then
+ $ECHO "ERROR: Unable to locate GNU Autoconf."
+ _report_error=yes
+else
+ _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`"
+ if [ "x$_version" = "x" ] ; then
+ _version="0.0.0"
+ fi
+ $ECHO "Found GNU Autoconf version $_version"
+ version_check "$AUTOCONF_VERSION" "$_version"
+ if [ $? -ne 0 ] ; then
+ _report_error=yes
+ fi
+fi
+if [ "x$_report_error" = "xyes" ] ; then
+ version_error "$AUTOCONF_VERSION" "GNU Autoconf"
+ exit 1
+fi
+
+
+##########################
+# automake version check #
+##########################
+_amfound=no
+if [ "x$AUTOMAKE" = "x" ] ; then
+ for AUTOMAKE in automake ; do
+ $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version"
+ $AUTOMAKE --version > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ _amfound=yes
+ break
+ fi
+ done
+else
+ _amfound=yes
+ $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE"
+fi
+
+
+_report_error=no
+if [ ! "x$_amfound" = "xyes" ] ; then
+ $ECHO
+ $ECHO "ERROR: Unable to locate GNU Automake."
+ _report_error=yes
+else
+ _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`"
+ if [ "x$_version" = "x" ] ; then
+ _version="0.0.0"
+ fi
+ $ECHO "Found GNU Automake version $_version"
+ version_check "$AUTOMAKE_VERSION" "$_version"
+ if [ $? -ne 0 ] ; then
+ _report_error=yes
+ fi
+fi
+if [ "x$_report_error" = "xyes" ] ; then
+ version_error "$AUTOMAKE_VERSION" "GNU Automake"
+ exit 1
+fi
+
+
+########################
+# check for libtoolize #
+########################
+HAVE_LIBTOOLIZE=yes
+HAVE_ALT_LIBTOOLIZE=no
+_ltfound=no
+if [ "x$LIBTOOLIZE" = "x" ] ; then
+ LIBTOOLIZE=libtoolize
+ $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version"
+ $LIBTOOLIZE --version > /dev/null 2>&1
+ if [ ! $? = 0 ] ; then
+ HAVE_LIBTOOLIZE=no
+ $ECHO
+ if [ "x$HAVE_AUTORECONF" = "xno" ] ; then
+ $ECHO "Warning: libtoolize does not appear to be available."
+ else
+ $ECHO "Warning: libtoolize does not appear to be available. This means that"
+ $ECHO "the automatic build preparation via autoreconf will probably not work."
+ $ECHO "Preparing the build by running each step individually, however, should"
+ $ECHO "work and will be done automatically for you if autoreconf fails."
+ fi
+
+ # look for some alternates
+ for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do
+ $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version"
+ _glibtoolize="`$tool --version > /dev/null 2>&1`"
+ if [ $? = 0 ] ; then
+ $VERBOSE_ECHO "Found $tool --version"
+ _glti="`which $tool`"
+ if [ "x$_glti" = "x" ] ; then
+ $VERBOSE_ECHO "Cannot find $tool with which"
+ continue;
+ fi
+ if test ! -f "$_glti" ; then
+ $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file"
+ continue;
+ fi
+ _gltidir="`dirname $_glti`"
+ if [ "x$_gltidir" = "x" ] ; then
+ $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti"
+ continue;
+ fi
+ if test ! -d "$_gltidir" ; then
+ $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory"
+ continue;
+ fi
+ HAVE_ALT_LIBTOOLIZE=yes
+ LIBTOOLIZE="$tool"
+ $ECHO
+ $ECHO "Fortunately, $tool was found which means that your system may simply"
+ $ECHO "have a non-standard or incomplete GNU Autotools install. If you have"
+ $ECHO "sufficient system access, it may be possible to quell this warning by"
+ $ECHO "running:"
+ $ECHO
+ sudo -V > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ $ECHO " sudo ln -s $_glti $_gltidir/libtoolize"
+ $ECHO
+ else
+ $ECHO " ln -s $_glti $_gltidir/libtoolize"
+ $ECHO
+ $ECHO "Run that as root or with proper permissions to the $_gltidir directory"
+ $ECHO
+ fi
+ _ltfound=yes
+ break
+ fi
+ done
+ else
+ _ltfound=yes
+ fi
+else
+ _ltfound=yes
+ $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE"
+fi
+
+
+############################
+# libtoolize version check #
+############################
+_report_error=no
+if [ ! "x$_ltfound" = "xyes" ] ; then
+ $ECHO
+ $ECHO "ERROR: Unable to locate GNU Libtool."
+ _report_error=yes
+else
+ _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`"
+ if [ "x$_version" = "x" ] ; then
+ _version="0.0.0"
+ fi
+ $ECHO "Found GNU Libtool version $_version"
+ version_check "$LIBTOOL_VERSION" "$_version"
+ if [ $? -ne 0 ] ; then
+ _report_error=yes
+ fi
+fi
+if [ "x$_report_error" = "xyes" ] ; then
+ version_error "$LIBTOOL_VERSION" "GNU Libtool"
+ exit 1
+fi
+
+
+#####################
+# check for aclocal #
+#####################
+if [ "x$ACLOCAL" = "x" ] ; then
+ for ACLOCAL in aclocal ; do
+ $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version"
+ $ACLOCAL --version > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ break
+ fi
+ done
+else
+ $ECHO "Using ACLOCAL environment variable override: $ACLOCAL"
+fi
+
+
+########################
+# check for autoheader #
+########################
+if [ "x$AUTOHEADER" = "x" ] ; then
+ for AUTOHEADER in autoheader ; do
+ $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version"
+ $AUTOHEADER --version > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ break
+ fi
+ done
+else
+ $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER"
+fi
+
+
+#########################
+# check if version only #
+#########################
+$VERBOSE_ECHO "Checking whether to only output version information"
+if [ "x$VERSION_ONLY" = "xyes" ] ; then
+ $ECHO
+ ident
+ $ECHO "---"
+ $ECHO "Version requested. No preparation or configuration will be performed."
+ exit 0
+fi
+
+
+#################################
+# PROTECT_FROM_CLOBBER FUNCTION #
+#################################
+protect_from_clobber ( ) {
+ PFC_INIT=1
+
+ # protect COPYING & INSTALL from overwrite by automake. the
+ # automake force option will (inappropriately) ignore the existing
+ # contents of a COPYING and/or INSTALL files (depending on the
+ # version) instead of just forcing *missing* files like it does
+ # for AUTHORS, NEWS, and README. this is broken but extremely
+ # prevalent behavior, so we protect against it by keeping a backup
+ # of the file that can later be restored.
+
+ for file in COPYING INSTALL ; do
+ if test -f ${file} ; then
+ if test -f ${file}.$$.protect_from_automake.backup ; then
+ $VERBOSE_ECHO "Already backed up ${file} in `pwd`"
+ else
+ $VERBOSE_ECHO "Backing up ${file} in `pwd`"
+ $VERBOSE_ECHO "cp -p ${file} ${file}.$$.protect_from_automake.backup"
+ cp -p ${file} ${file}.$$.protect_from_automake.backup
+ fi
+ fi
+ done
+}
+
+
+##############################
+# RECURSIVE_PROTECT FUNCTION #
+##############################
+recursive_protect ( ) {
+
+ # for projects using recursive configure, run the build
+ # preparation steps for the subdirectories. this function assumes
+ # START_PATH was set to pwd before recursion begins so that
+ # relative paths work.
+
+ # git 'r done, protect COPYING and INSTALL from being clobbered
+ protect_from_clobber
+
+ if test -d autom4te.cache ; then
+ $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it"
+ $VERBOSE_ECHO "rm -rf autom4te.cache"
+ rm -rf autom4te.cache
+ fi
+
+ # find configure template
+ _configure="`locate_configure_template`"
+ if [ "x$_configure" = "x" ] ; then
+ return
+ fi
+ # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure"
+
+ # look for subdirs
+ # $VERBOSE_ECHO "Looking for subdirs in `pwd`"
+ _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ CHECK_DIRS=""
+ for dir in $_det_config_subdirs ; do
+ if test -d "`pwd`/$dir" ; then
+ CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\""
+ fi
+ done
+
+ # process subdirs
+ if [ ! "x$CHECK_DIRS" = "x" ] ; then
+ $VERBOSE_ECHO "Recursively scanning the following directories:"
+ $VERBOSE_ECHO " $CHECK_DIRS"
+ for dir in $CHECK_DIRS ; do
+ $VERBOSE_ECHO "Protecting files from automake in $dir"
+ cd "$START_PATH"
+ eval "cd $dir"
+
+ # recursively git 'r done
+ recursive_protect
+ done
+ fi
+} # end of recursive_protect
+
+
+#############################
+# RESTORE_CLOBBERED FUNCION #
+#############################
+restore_clobbered ( ) {
+
+ # The automake (and autoreconf by extension) -f/--force-missing
+ # option may overwrite COPYING and INSTALL even if they do exist.
+ # Here we restore the files if necessary.
+
+ spacer=no
+
+ for file in COPYING INSTALL ; do
+ if test -f ${file}.$$.protect_from_automake.backup ; then
+ if test -f ${file} ; then
+ # compare entire content, restore if needed
+ if test "x`cat ${file}`" != "x`cat ${file}.$$.protect_from_automake.backup`" ; then
+ if test "x$spacer" = "xno" ; then
+ $VERBOSE_ECHO
+ spacer=yes
+ fi
+ # restore the backup
+ $VERBOSE_ECHO "Restoring ${file} from backup (automake -f likely clobbered it)"
+ $VERBOSE_ECHO "rm -f ${file}"
+ rm -f ${file}
+ $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}"
+ mv ${file}.$$.protect_from_automake.backup ${file}
+ fi # check contents
+ elif test -f ${file}.$$.protect_from_automake.backup ; then
+ $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}"
+ mv ${file}.$$.protect_from_automake.backup ${file}
+ fi # -f ${file}
+
+ # just in case
+ $VERBOSE_ECHO "rm -f ${file}.$$.protect_from_automake.backup"
+ rm -f ${file}.$$.protect_from_automake.backup
+ fi # -f ${file}.$$.protect_from_automake.backup
+ done
+
+ CONFIGURE="`locate_configure_template`"
+ if [ "x$CONFIGURE" = "x" ] ; then
+ return
+ fi
+
+ _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ if test ! -d "$_aux_dir" ; then
+ _aux_dir=.
+ fi
+
+ for file in config.guess config.sub ltmain.sh ; do
+ if test -f "${_aux_dir}/${file}" ; then
+ $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\""
+ rm -f "${_aux_dir}/${file}.backup"
+ fi
+ done
+} # end of restore_clobbered
+
+
+##############################
+# RECURSIVE_RESTORE FUNCTION #
+##############################
+recursive_restore ( ) {
+
+ # restore COPYING and INSTALL from backup if they were clobbered
+ # for each directory recursively.
+
+ # git 'r undone
+ restore_clobbered
+
+ # find configure template
+ _configure="`locate_configure_template`"
+ if [ "x$_configure" = "x" ] ; then
+ return
+ fi
+
+ # look for subdirs
+ _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ CHECK_DIRS=""
+ for dir in $_det_config_subdirs ; do
+ if test -d "`pwd`/$dir" ; then
+ CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\""
+ fi
+ done
+
+ # process subdirs
+ if [ ! "x$CHECK_DIRS" = "x" ] ; then
+ $VERBOSE_ECHO "Recursively scanning the following directories:"
+ $VERBOSE_ECHO " $CHECK_DIRS"
+ for dir in $CHECK_DIRS ; do
+ $VERBOSE_ECHO "Checking files for automake damage in $dir"
+ cd "$START_PATH"
+ eval "cd $dir"
+
+ # recursively git 'r undone
+ recursive_restore
+ done
+ fi
+} # end of recursive_restore
+
+
+#######################
+# INITIALIZE FUNCTION #
+#######################
+initialize ( ) {
+
+ # this routine performs a variety of directory-specific
+ # initializations. some are sanity checks, some are preventive,
+ # and some are necessary setup detection.
+ #
+ # this function sets:
+ # CONFIGURE
+ # SEARCH_DIRS
+ # CONFIG_SUBDIRS
+
+ ##################################
+ # check for a configure template #
+ ##################################
+ CONFIGURE="`locate_configure_template`"
+ if [ "x$CONFIGURE" = "x" ] ; then
+ $ECHO
+ $ECHO "A configure.ac or configure.in file could not be located implying"
+ $ECHO "that the GNU Build System is at least not used in this directory. In"
+ $ECHO "any case, there is nothing to do here without one of those files."
+ $ECHO
+ $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`"
+ exit 1
+ fi
+
+ #####################
+ # detect an aux dir #
+ #####################
+ _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ if test ! -d "$_aux_dir" ; then
+ _aux_dir=.
+ else
+ $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir"
+ fi
+
+ ################################
+ # detect a recursive configure #
+ ################################
+ CONFIG_SUBDIRS=""
+ _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`"
+ for dir in $_det_config_subdirs ; do
+ if test -d "`pwd`/$dir" ; then
+ $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir"
+ CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir"
+ fi
+ done
+
+ ###########################################################
+ # make sure certain required files exist for GNU projects #
+ ###########################################################
+ _marker_found=""
+ _marker_found_message_intro='Detected non-GNU marker "'
+ _marker_found_message_mid='" in '
+ for marker in foreign cygnus ; do
+ _marker_found_message=${_marker_found_message_intro}${marker}${_marker_found_message_mid}
+ _marker_found="`grep 'AM_INIT_AUTOMAKE.*'${marker} $CONFIGURE`"
+ if [ ! "x$_marker_found" = "x" ] ; then
+ $VERBOSE_ECHO "${_marker_found_message}`basename \"$CONFIGURE\"`"
+ break
+ fi
+ if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then
+ _marker_found="`grep 'AUTOMAKE_OPTIONS.*'${marker} Makefile.am`"
+ if [ ! "x$_marker_found" = "x" ] ; then
+ $VERBOSE_ECHO "${_marker_found_message}Makefile.am"
+ break
+ fi
+ fi
+ done
+ if [ "x${_marker_found}" = "x" ] ; then
+ _suggest_foreign=no
+ for file in AUTHORS COPYING ChangeLog INSTALL NEWS README ; do
+ if [ ! -f $file ] ; then
+ $VERBOSE_ECHO "Touching ${file} since it does not exist"
+ _suggest_foreign=yes
+ touch $file
+ fi
+ done
+
+ if [ "x${_suggest_foreign}" = "xyes" ] ; then
+ $ECHO
+ $ECHO "Warning: Several files expected of projects that conform to the GNU"
+ $ECHO "coding standards were not found. The files were automatically added"
+ $ECHO "for you since you do not have a 'foreign' declaration specified."
+ $ECHO
+ $ECHO "Considered adding 'foreign' to AM_INIT_AUTOMAKE in `basename \"$CONFIGURE\"`"
+ if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then
+ $ECHO "or to AUTOMAKE_OPTIONS in your top-level Makefile.am file."
+ fi
+ $ECHO
+ fi
+ fi
+
+ ##################################################
+ # make sure certain generated files do not exist #
+ ##################################################
+ for file in config.guess config.sub ltmain.sh ; do
+ if test -f "${_aux_dir}/${file}" ; then
+ $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\""
+ mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup"
+ fi
+ done
+
+ ############################
+ # search alternate m4 dirs #
+ ############################
+ SEARCH_DIRS=""
+ for dir in m4 ; do
+ if [ -d $dir ] ; then
+ $VERBOSE_ECHO "Found extra aclocal search directory: $dir"
+ SEARCH_DIRS="$SEARCH_DIRS -I $dir"
+ fi
+ done
+
+ ######################################
+ # remove any previous build products #
+ ######################################
+ if test -d autom4te.cache ; then
+ $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it"
+ $VERBOSE_ECHO "rm -rf autom4te.cache"
+ rm -rf autom4te.cache
+ fi
+# tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it
+# if test -f aclocal.m4 ; then
+# $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it"
+# $VERBOSE_ECHO "rm -f aclocal.m4"
+# rm -f aclocal.m4
+# fi
+
+} # end of initialize()
+
+
+##############
+# initialize #
+##############
+
+# stash path
+START_PATH="`pwd`"
+
+# Before running autoreconf or manual steps, some prep detection work
+# is necessary or useful. Only needs to occur once per directory, but
+# does need to traverse the entire subconfigure hierarchy to protect
+# files from being clobbered even by autoreconf.
+recursive_protect
+
+# start from where we started
+cd "$START_PATH"
+
+# get ready to process
+initialize
+
+
+#########################################
+# DOWNLOAD_GNULIB_CONFIG_GUESS FUNCTION #
+#########################################
+
+# TODO - should make sure wget/curl exist and/or work before trying to
+# use them.
+
+download_gnulib_config_guess () {
+ # abuse gitweb to download gnulib's latest config.guess via HTTP
+ config_guess_temp="config.guess.$$.download"
+ ret=1
+ for __cmd in wget curl fetch ; do
+ $VERBOSE_ECHO "Checking for command ${__cmd}"
+ ${__cmd} --version > /dev/null 2>&1
+ ret=$?
+ if [ ! $ret = 0 ] ; then
+ continue
+ fi
+
+ __cmd_version=`${__cmd} --version | head -n 1 | sed -e 's/^[^0-9]\+//' -e 's/ .*//'`
+ $VERBOSE_ECHO "Found ${__cmd} ${__cmd_version}"
+
+ opts=""
+ case ${__cmd} in
+ wget)
+ opts="-O"
+ ;;
+ curl)
+ opts="-o"
+ ;;
+ fetch)
+ opts="-t 5 -f"
+ ;;
+ esac
+
+ $VERBOSE_ECHO "Running $__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\""
+ eval "$__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" > /dev/null 2>&1
+ if [ $? = 0 ] ; then
+ mv -f "${config_guess_temp}" ${_aux_dir}/config.guess
+ ret=0
+ break
+ fi
+ done
+
+ if [ ! $ret = 0 ] ; then
+ $ECHO "Warning: config.guess download failed from: $CONFIG_GUESS_URL"
+ rm -f "${config_guess_temp}"
+ fi
+}
+
+
+##############################
+# LIBTOOLIZE_NEEDED FUNCTION #
+##############################
+libtoolize_needed () {
+ ret=1 # means no, don't need libtoolize
+ for feature in AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ ret=0 # means yes, need to run libtoolize
+ break
+ fi
+ done
+ return ${ret}
+}
+
+
+
+############################################
+# prepare build via autoreconf or manually #
+############################################
+reconfigure_manually=no
+if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then
+ $ECHO
+ $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C"
+
+ $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS"
+ autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$autoreconf_output"
+
+ if [ ! $ret = 0 ] ; then
+ if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then
+ if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then
+ $ECHO
+ $ECHO "Warning: autoreconf failed but due to what is usually a common libtool"
+ $ECHO "misconfiguration issue. This problem is encountered on systems that"
+ $ECHO "have installed libtoolize under a different name without providing a"
+ $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable."
+ $ECHO
+ $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE"
+
+ export LIBTOOLIZE
+ RUN_RECURSIVE=no
+ export RUN_RECURSIVE
+ untrap_abnormal
+
+ $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
+ sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
+ exit $?
+ fi
+ fi
+
+ $ECHO "Warning: $AUTORECONF failed"
+
+ if test -f ltmain.sh ; then
+ $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should"
+ fi
+
+ $ECHO "Attempting to run the preparation steps individually"
+ reconfigure_manually=yes
+ else
+ if [ "x$DOWNLOAD" = "xyes" ] ; then
+ if libtoolize_needed ; then
+ download_gnulib_config_guess
+ fi
+ fi
+ fi
+else
+ reconfigure_manually=yes
+fi
+
+
+############################
+# LIBTOOL_FAILURE FUNCTION #
+############################
+libtool_failure ( ) {
+
+ # libtool is rather error-prone in comparison to the other
+ # autotools and this routine attempts to compensate for some
+ # common failures. the output after a libtoolize failure is
+ # parsed for an error related to AC_PROG_LIBTOOL and if found, we
+ # attempt to inject a project-provided libtool.m4 file.
+
+ _autoconf_output="$1"
+
+ if [ "x$RUN_RECURSIVE" = "xno" ] ; then
+ # we already tried the libtool.m4, don't try again
+ return 1
+ fi
+
+ if test -f "$LIBTOOL_M4" ; then
+ found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`"
+ if test ! "x$found_libtool" = "x" ; then
+ if test -f acinclude.m4 ; then
+ rm -f acinclude.m4.$$.backup
+ $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup"
+ cat acinclude.m4 > acinclude.m4.$$.backup
+ fi
+ $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4"
+ chmod u+w acinclude.m4
+ cat "$LIBTOOL_M4" >> acinclude.m4
+
+ # don't keep doing this
+ RUN_RECURSIVE=no
+ export RUN_RECURSIVE
+ untrap_abnormal
+
+ $ECHO
+ $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4"
+ $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
+ sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
+ exit $?
+ fi
+ fi
+}
+
+
+###########################
+# MANUAL_AUTOGEN FUNCTION #
+###########################
+manual_autogen ( ) {
+
+ ##################################################
+ # Manual preparation steps taken are as follows: #
+ # aclocal [-I m4] #
+ # libtoolize --automake -c -f #
+ # aclocal [-I m4] #
+ # autoconf -f #
+ # autoheader #
+ # automake -a -c -f #
+ ##################################################
+
+ ###########
+ # aclocal #
+ ###########
+ $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS"
+ aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$aclocal_output"
+ if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi
+
+ ##############
+ # libtoolize #
+ ##############
+ if libtoolize_needed ; then
+ if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then
+ $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS"
+ libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$libtoolize_output"
+
+ if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi
+ else
+ if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then
+ $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS"
+ libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$libtoolize_output"
+
+ if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi
+ fi
+ fi
+
+ ###########
+ # aclocal #
+ ###########
+ # re-run again as instructed by libtoolize
+ $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS"
+ aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$aclocal_output"
+
+ # libtoolize might put ltmain.sh in the wrong place
+ if test -f ltmain.sh ; then
+ if test ! -f "${_aux_dir}/ltmain.sh" ; then
+ $ECHO
+ $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory"
+ $ECHO
+ $ECHO "Fortunately, the problem can be worked around by simply copying the"
+ $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you."
+ $ECHO
+ $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\""
+ cp -p ltmain.sh "${_aux_dir}/ltmain.sh"
+ $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C"
+ fi
+ fi # ltmain.sh
+
+ if [ "x$DOWNLOAD" = "xyes" ] ; then
+ download_gnulib_config_guess
+ fi
+ fi # libtoolize_needed
+
+ ############
+ # autoconf #
+ ############
+ $VERBOSE_ECHO
+ $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS"
+ autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$autoconf_output"
+
+ if [ ! $ret = 0 ] ; then
+ # retry without the -f and check for usage of macros that are too new
+ ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE"
+ ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE"
+ ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T"
+
+ macros_to_search=""
+ ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`"
+ ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`"
+
+ if [ $ac_major -lt 2 ] ; then
+ macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros"
+ else
+ if [ $ac_minor -lt 54 ] ; then
+ macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros"
+ elif [ $ac_minor -lt 55 ] ; then
+ macros_to_search="$ac2_59_macros $ac2_55_macros"
+ elif [ $ac_minor -lt 59 ] ; then
+ macros_to_search="$ac2_59_macros"
+ fi
+ fi
+
+ configure_ac_macros=__none__
+ for feature in $macros_to_search ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ if [ "x$configure_ac_macros" = "x__none__" ] ; then
+ configure_ac_macros="$feature"
+ else
+ configure_ac_macros="$feature $configure_ac_macros"
+ fi
+ fi
+ done
+ if [ ! "x$configure_ac_macros" = "x__none__" ] ; then
+ $ECHO
+ $ECHO "Warning: Unsupported macros were found in $CONFIGURE"
+ $ECHO
+ $ECHO "The `basename \"$CONFIGURE\"` file was scanned in order to determine if any"
+ $ECHO "unsupported macros are used that exceed the minimum version"
+ $ECHO "settings specified within this file. As such, the following macros"
+ $ECHO "should be removed from configure.ac or the version numbers in this"
+ $ECHO "file should be increased:"
+ $ECHO
+ $ECHO "$configure_ac_macros"
+ $ECHO
+ $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C"
+ fi
+
+ ###################
+ # autoconf, retry #
+ ###################
+ $VERBOSE_ECHO
+ $VERBOSE_ECHO "$AUTOCONF"
+ autoconf_output="`$AUTOCONF 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$autoconf_output"
+
+ if [ ! $ret = 0 ] ; then
+ # test if libtool is busted
+ libtool_failure "$autoconf_output"
+
+ # let the user know what went wrong
+ cat <<EOF
+$autoconf_output
+EOF
+ $ECHO "ERROR: $AUTOCONF failed"
+ exit 2
+ else
+ # autoconf sans -f and possibly sans unsupported options succeed so warn verbosely
+ $ECHO
+ $ECHO "Warning: autoconf seems to have succeeded by removing the following options:"
+ $ECHO " AUTOCONF_OPTIONS=\"$AUTOCONF_OPTIONS\""
+ $ECHO
+ $ECHO "Removing those options should not be necessary and indicate some other"
+ $ECHO "problem with the build system. The build preparation is highly suspect"
+ $ECHO "and may result in configuration or compilation errors. Consider"
+ if [ "x$VERBOSE_ECHO" = "x:" ] ; then
+ $ECHO "rerunning the build preparation with verbose output enabled."
+ $ECHO " $AUTOGEN_SH --verbose"
+ else
+ $ECHO "reviewing the minimum GNU Autotools version settings contained in"
+ $ECHO "this script along with the macros being used in your `basename \"$CONFIGURE\"` file."
+ fi
+ $ECHO
+ $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C"
+ fi # autoconf ret = 0
+ fi # autoconf ret = 0
+
+ ##############
+ # autoheader #
+ ##############
+ need_autoheader=no
+ for feature in AM_CONFIG_HEADER AC_CONFIG_HEADER ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ need_autoheader=yes
+ break
+ fi
+ done
+ if [ "x$need_autoheader" = "xyes" ] ; then
+ $VERBOSE_ECHO "$AUTOHEADER $AUTOHEADER_OPTIONS"
+ autoheader_output="`$AUTOHEADER $AUTOHEADER_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$autoheader_output"
+ if [ ! $ret = 0 ] ; then $ECHO "ERROR: $AUTOHEADER failed" && exit 2 ; fi
+ fi # need_autoheader
+
+ ############
+ # automake #
+ ############
+ need_automake=no
+ for feature in AM_INIT_AUTOMAKE ; do
+ $VERBOSE_ECHO "Searching for $feature in $CONFIGURE"
+ found="`grep \"^$feature.*\" $CONFIGURE`"
+ if [ ! "x$found" = "x" ] ; then
+ need_automake=yes
+ break
+ fi
+ done
+
+ if [ "x$need_automake" = "xyes" ] ; then
+ $VERBOSE_ECHO "$AUTOMAKE $AUTOMAKE_OPTIONS"
+ automake_output="`$AUTOMAKE $AUTOMAKE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$automake_output"
+
+ if [ ! $ret = 0 ] ; then
+
+ ###################
+ # automake, retry #
+ ###################
+ $VERBOSE_ECHO
+ $VERBOSE_ECHO "$AUTOMAKE $ALT_AUTOMAKE_OPTIONS"
+ # retry without the -f
+ automake_output="`$AUTOMAKE $ALT_AUTOMAKE_OPTIONS 2>&1`"
+ ret=$?
+ $VERBOSE_ECHO "$automake_output"
+
+ if [ ! $ret = 0 ] ; then
+ # test if libtool is busted
+ libtool_failure "$automake_output"
+
+ # let the user know what went wrong
+ cat <<EOF
+$automake_output
+EOF
+ $ECHO "ERROR: $AUTOMAKE failed"
+ exit 2
+ fi # automake retry
+ fi # automake ret = 0
+ fi # need_automake
+} # end of manual_autogen
+
+
+#####################################
+# RECURSIVE_MANUAL_AUTOGEN FUNCTION #
+#####################################
+recursive_manual_autogen ( ) {
+
+ # run the build preparation steps manually for this directory
+ manual_autogen
+
+ # for projects using recursive configure, run the build
+ # preparation steps for the subdirectories.
+ if [ ! "x$CONFIG_SUBDIRS" = "x" ] ; then
+ $VERBOSE_ECHO "Recursively configuring the following directories:"
+ $VERBOSE_ECHO " $CONFIG_SUBDIRS"
+ for dir in $CONFIG_SUBDIRS ; do
+ $VERBOSE_ECHO "Processing recursive configure in $dir"
+ cd "$START_PATH"
+ cd "$dir"
+
+ # new directory, prepare
+ initialize
+
+ # run manual steps for the subdir and any others below
+ recursive_manual_autogen
+ done
+ fi
+}
+
+
+################################
+# run manual preparation steps #
+################################
+if [ "x$reconfigure_manually" = "xyes" ] ; then
+ $ECHO
+ $ECHO $ECHO_N "Preparing build ... $ECHO_C"
+
+ recursive_manual_autogen
+fi
+
+
+#########################
+# restore and summarize #
+#########################
+cd "$START_PATH"
+
+# restore COPYING and INSTALL from backup if necessary
+recursive_restore
+
+# make sure we end up with a configure script
+config_ac="`locate_configure_template`"
+config="`echo $config_ac | sed 's/\.ac$//' | sed 's/\.in$//'`"
+if [ "x$config" = "x" ] ; then
+ $VERBOSE_ECHO "Could not locate the configure template (from `pwd`)"
+fi
+
+# summarize
+$ECHO "done"
+$ECHO
+if test "x$config" = "x" -o ! -f "$config" ; then
+ $ECHO "WARNING: The $PROJECT build system should now be prepared but there"
+ $ECHO "does not seem to be a resulting configure file. This is unexpected"
+ $ECHO "and likely the result of an error. You should run $NAME_OF_AUTOGEN"
+ $ECHO "with the --verbose option to get more details on a potential"
+ $ECHO "misconfiguration."
+else
+ $ECHO "The $PROJECT build system is now prepared. To build here, run:"
+ $ECHO " $config"
+ $ECHO " make"
+fi
+
+
+# Local Variables:
+# mode: sh
+# tab-width: 8
+# sh-basic-offset: 4
+# sh-indentation: 4
+# indent-tabs-mode: t
+# End:
+# ex: shiftwidth=4 tabstop=8
diff --git a/build_debian.sh b/build_debian.sh
new file mode 100755
index 00000000..72dcfff9
--- /dev/null
+++ b/build_debian.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# If this script fails on a Debian 4.0 ("etch") system then read
+# the debian/README.debian4 file.
+
+echo "chmod +x debian/rules"
+chmod +x debian/rules
+
+# in some environments the '-rfakeroot' can cause a failure (e.g. when
+# building as root). If so, remove that argument from the following:
+echo "dpkg-buildpackage -b -rfakeroot -us -uc"
+dpkg-buildpackage -b -rfakeroot -us -uc
+
+# If the above succeeds then the ".deb" binary package is placed in the
+# parent directory.
diff --git a/compile b/compile
new file mode 100755
index 00000000..df363c8f
--- /dev/null
+++ b/compile
@@ -0,0 +1,348 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand '-c -o'.
+
+scriptversion=2018-03-07.03; # UTC
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# 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 distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+nl='
+'
+
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent tools from complaining about whitespace usage.
+IFS=" "" $nl"
+
+file_conv=
+
+# func_file_conv build_file lazy
+# Convert a $build file to $host form and store it in $file
+# Currently only supports Windows hosts. If the determined conversion
+# type is listed in (the comma separated) LAZY, no conversion will
+# take place.
+func_file_conv ()
+{
+ file=$1
+ case $file in
+ / | /[!/]*) # absolute file, and not a UNC file
+ if test -z "$file_conv"; then
+ # lazily determine how to convert abs files
+ case `uname -s` in
+ MINGW*)
+ file_conv=mingw
+ ;;
+ CYGWIN* | MSYS*)
+ file_conv=cygwin
+ ;;
+ *)
+ file_conv=wine
+ ;;
+ esac
+ fi
+ case $file_conv/,$2, in
+ *,$file_conv,*)
+ ;;
+ mingw/*)
+ file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
+ ;;
+ cygwin/* | msys/*)
+ file=`cygpath -m "$file" || echo "$file"`
+ ;;
+ wine/*)
+ file=`winepath -w "$file" || echo "$file"`
+ ;;
+ esac
+ ;;
+ esac
+}
+
+# func_cl_dashL linkdir
+# Make cl look for libraries in LINKDIR
+func_cl_dashL ()
+{
+ func_file_conv "$1"
+ if test -z "$lib_path"; then
+ lib_path=$file
+ else
+ lib_path="$lib_path;$file"
+ fi
+ linker_opts="$linker_opts -LIBPATH:$file"
+}
+
+# func_cl_dashl library
+# Do a library search-path lookup for cl
+func_cl_dashl ()
+{
+ lib=$1
+ found=no
+ save_IFS=$IFS
+ IFS=';'
+ for dir in $lib_path $LIB
+ do
+ IFS=$save_IFS
+ if $shared && test -f "$dir/$lib.dll.lib"; then
+ found=yes
+ lib=$dir/$lib.dll.lib
+ break
+ fi
+ if test -f "$dir/$lib.lib"; then
+ found=yes
+ lib=$dir/$lib.lib
+ break
+ fi
+ if test -f "$dir/lib$lib.a"; then
+ found=yes
+ lib=$dir/lib$lib.a
+ break
+ fi
+ done
+ IFS=$save_IFS
+
+ if test "$found" != yes; then
+ lib=$lib.lib
+ fi
+}
+
+# func_cl_wrapper cl arg...
+# Adjust compile command to suit cl
+func_cl_wrapper ()
+{
+ # Assume a capable shell
+ lib_path=
+ shared=:
+ linker_opts=
+ for arg
+ do
+ if test -n "$eat"; then
+ eat=
+ else
+ case $1 in
+ -o)
+ # configure might choose to run compile as 'compile cc -o foo foo.c'.
+ eat=1
+ case $2 in
+ *.o | *.[oO][bB][jJ])
+ func_file_conv "$2"
+ set x "$@" -Fo"$file"
+ shift
+ ;;
+ *)
+ func_file_conv "$2"
+ set x "$@" -Fe"$file"
+ shift
+ ;;
+ esac
+ ;;
+ -I)
+ eat=1
+ func_file_conv "$2" mingw
+ set x "$@" -I"$file"
+ shift
+ ;;
+ -I*)
+ func_file_conv "${1#-I}" mingw
+ set x "$@" -I"$file"
+ shift
+ ;;
+ -l)
+ eat=1
+ func_cl_dashl "$2"
+ set x "$@" "$lib"
+ shift
+ ;;
+ -l*)
+ func_cl_dashl "${1#-l}"
+ set x "$@" "$lib"
+ shift
+ ;;
+ -L)
+ eat=1
+ func_cl_dashL "$2"
+ ;;
+ -L*)
+ func_cl_dashL "${1#-L}"
+ ;;
+ -static)
+ shared=false
+ ;;
+ -Wl,*)
+ arg=${1#-Wl,}
+ save_ifs="$IFS"; IFS=','
+ for flag in $arg; do
+ IFS="$save_ifs"
+ linker_opts="$linker_opts $flag"
+ done
+ IFS="$save_ifs"
+ ;;
+ -Xlinker)
+ eat=1
+ linker_opts="$linker_opts $2"
+ ;;
+ -*)
+ set x "$@" "$1"
+ shift
+ ;;
+ *.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
+ func_file_conv "$1"
+ set x "$@" -Tp"$file"
+ shift
+ ;;
+ *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
+ func_file_conv "$1" mingw
+ set x "$@" "$file"
+ shift
+ ;;
+ *)
+ set x "$@" "$1"
+ shift
+ ;;
+ esac
+ fi
+ shift
+ done
+ if test -n "$linker_opts"; then
+ linker_opts="-link$linker_opts"
+ fi
+ exec "$@" $linker_opts
+ exit 1
+}
+
+eat=
+
+case $1 in
+ '')
+ echo "$0: No command. Try '$0 --help' for more information." 1>&2
+ exit 1;
+ ;;
+ -h | --h*)
+ cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand '-c -o'.
+Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file 'INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+ exit $?
+ ;;
+ -v | --v*)
+ echo "compile $scriptversion"
+ exit $?
+ ;;
+ cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \
+ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe )
+ func_cl_wrapper "$@" # Doesn't return...
+ ;;
+esac
+
+ofile=
+cfile=
+
+for arg
+do
+ if test -n "$eat"; then
+ eat=
+ else
+ case $1 in
+ -o)
+ # configure might choose to run compile as 'compile cc -o foo foo.c'.
+ # So we strip '-o arg' only if arg is an object.
+ eat=1
+ case $2 in
+ *.o | *.obj)
+ ofile=$2
+ ;;
+ *)
+ set x "$@" -o "$2"
+ shift
+ ;;
+ esac
+ ;;
+ *.c)
+ cfile=$1
+ set x "$@" "$1"
+ shift
+ ;;
+ *)
+ set x "$@" "$1"
+ shift
+ ;;
+ esac
+ fi
+ shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+ # If no '-o' option was seen then we might have been invoked from a
+ # pattern rule where we don't need one. That is ok -- this is a
+ # normal compilation that the losing compiler can handle. If no
+ # '.c' file was seen then we are probably linking. That is also
+ # ok.
+ exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use '[/\\:.-]' here to ensure that we don't use the same name
+# that we are using for the .o file. Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
+while true; do
+ if mkdir "$lockdir" >/dev/null 2>&1; then
+ break
+ fi
+ sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+ test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+ test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/config.guess b/config.guess
new file mode 100755
index 00000000..7f76b622
--- /dev/null
+++ b/config.guess
@@ -0,0 +1,1754 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+# Copyright 1992-2022 Free Software Foundation, Inc.
+
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2022-01-09'
+
+# This file 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program. This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+#
+# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
+#
+# You can get the latest version of this script from:
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
+#
+# Please send patches to <config-patches@gnu.org>.
+
+
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX. However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Options:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright 1992-2022 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help" >&2
+ exit 1 ;;
+ * )
+ break ;;
+ esac
+done
+
+if test $# != 0; then
+ echo "$me: too many arguments$help" >&2
+ exit 1
+fi
+
+# Just in case it came from the environment.
+GUESS=
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+tmp=
+# shellcheck disable=SC2172
+trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15
+
+set_cc_for_build() {
+ # prevent multiple calls if $tmp is already set
+ test "$tmp" && return 0
+ : "${TMPDIR=/tmp}"
+ # shellcheck disable=SC2039,SC3028
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; }
+ dummy=$tmp/dummy
+ case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in
+ ,,) echo "int x;" > "$dummy.c"
+ for driver in cc gcc c89 c99 ; do
+ if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then
+ CC_FOR_BUILD=$driver
+ break
+ fi
+ done
+ if test x"$CC_FOR_BUILD" = x ; then
+ CC_FOR_BUILD=no_compiler_found
+ fi
+ ;;
+ ,,*) CC_FOR_BUILD=$CC ;;
+ ,*,*) CC_FOR_BUILD=$HOST_CC ;;
+ esac
+}
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if test -f /.attbin/uname ; then
+ PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+case $UNAME_SYSTEM in
+Linux|GNU|GNU/*)
+ LIBC=unknown
+
+ set_cc_for_build
+ cat <<-EOF > "$dummy.c"
+ #include <features.h>
+ #if defined(__UCLIBC__)
+ LIBC=uclibc
+ #elif defined(__dietlibc__)
+ LIBC=dietlibc
+ #elif defined(__GLIBC__)
+ LIBC=gnu
+ #else
+ #include <stdarg.h>
+ /* First heuristic to detect musl libc. */
+ #ifdef __DEFINED_va_list
+ LIBC=musl
+ #endif
+ #endif
+ EOF
+ cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`
+ eval "$cc_set_libc"
+
+ # Second heuristic to detect musl libc.
+ if [ "$LIBC" = unknown ] &&
+ command -v ldd >/dev/null &&
+ ldd --version 2>&1 | grep -q ^musl; then
+ LIBC=musl
+ fi
+
+ # If the system lacks a compiler, then just pick glibc.
+ # We could probably try harder.
+ if [ "$LIBC" = unknown ]; then
+ LIBC=gnu
+ fi
+ ;;
+esac
+
+# Note: order is significant - the case branches are not exclusive.
+
+case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in
+ *:NetBSD:*:*)
+ # NetBSD (nbsd) targets should (where applicable) match one or
+ # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
+ # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
+ # switched to ELF, *-*-netbsd* would select the old
+ # object file format. This provides both forward
+ # compatibility and a consistent mechanism for selecting the
+ # object file format.
+ #
+ # Note: NetBSD doesn't particularly care about the vendor
+ # portion of the name. We always set it to "unknown".
+ UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
+ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \
+ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
+ echo unknown)`
+ case $UNAME_MACHINE_ARCH in
+ aarch64eb) machine=aarch64_be-unknown ;;
+ armeb) machine=armeb-unknown ;;
+ arm*) machine=arm-unknown ;;
+ sh3el) machine=shl-unknown ;;
+ sh3eb) machine=sh-unknown ;;
+ sh5el) machine=sh5le-unknown ;;
+ earmv*)
+ arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
+ endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'`
+ machine=${arch}${endian}-unknown
+ ;;
+ *) machine=$UNAME_MACHINE_ARCH-unknown ;;
+ esac
+ # The Operating System including object format, if it has switched
+ # to ELF recently (or will in the future) and ABI.
+ case $UNAME_MACHINE_ARCH in
+ earm*)
+ os=netbsdelf
+ ;;
+ arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+ set_cc_for_build
+ if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ELF__
+ then
+ # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+ # Return netbsd for either. FIX?
+ os=netbsd
+ else
+ os=netbsdelf
+ fi
+ ;;
+ *)
+ os=netbsd
+ ;;
+ esac
+ # Determine ABI tags.
+ case $UNAME_MACHINE_ARCH in
+ earm*)
+ expr='s/^earmv[0-9]/-eabi/;s/eb$//'
+ abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"`
+ ;;
+ esac
+ # The OS release
+ # Debian GNU/NetBSD machines have a different userland, and
+ # thus, need a distinct triplet. However, they do not need
+ # kernel version information, so it can be replaced with a
+ # suitable tag, in the style of linux-gnu.
+ case $UNAME_VERSION in
+ Debian*)
+ release='-gnu'
+ ;;
+ *)
+ release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2`
+ ;;
+ esac
+ # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+ # contains redundant information, the shorter form:
+ # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+ GUESS=$machine-${os}${release}${abi-}
+ ;;
+ *:Bitrig:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
+ GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE
+ ;;
+ *:OpenBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+ GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE
+ ;;
+ *:SecBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'`
+ GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE
+ ;;
+ *:LibertyBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
+ GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE
+ ;;
+ *:MidnightBSD:*:*)
+ GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE
+ ;;
+ *:ekkoBSD:*:*)
+ GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE
+ ;;
+ *:SolidBSD:*:*)
+ GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE
+ ;;
+ *:OS108:*:*)
+ GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE
+ ;;
+ macppc:MirBSD:*:*)
+ GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE
+ ;;
+ *:MirBSD:*:*)
+ GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE
+ ;;
+ *:Sortix:*:*)
+ GUESS=$UNAME_MACHINE-unknown-sortix
+ ;;
+ *:Twizzler:*:*)
+ GUESS=$UNAME_MACHINE-unknown-twizzler
+ ;;
+ *:Redox:*:*)
+ GUESS=$UNAME_MACHINE-unknown-redox
+ ;;
+ mips:OSF1:*.*)
+ GUESS=mips-dec-osf1
+ ;;
+ alpha:OSF1:*:*)
+ # Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+ trap '' 0
+ case $UNAME_RELEASE in
+ *4.0)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+ ;;
+ *5.*)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+ ;;
+ esac
+ # According to Compaq, /usr/sbin/psrinfo has been available on
+ # OSF/1 and Tru64 systems produced since 1995. I hope that
+ # covers most systems running today. This code pipes the CPU
+ # types through head -n 1, so we only detect the type of CPU 0.
+ ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+ case $ALPHA_CPU_TYPE in
+ "EV4 (21064)")
+ UNAME_MACHINE=alpha ;;
+ "EV4.5 (21064)")
+ UNAME_MACHINE=alpha ;;
+ "LCA4 (21066/21068)")
+ UNAME_MACHINE=alpha ;;
+ "EV5 (21164)")
+ UNAME_MACHINE=alphaev5 ;;
+ "EV5.6 (21164A)")
+ UNAME_MACHINE=alphaev56 ;;
+ "EV5.6 (21164PC)")
+ UNAME_MACHINE=alphapca56 ;;
+ "EV5.7 (21164PC)")
+ UNAME_MACHINE=alphapca57 ;;
+ "EV6 (21264)")
+ UNAME_MACHINE=alphaev6 ;;
+ "EV6.7 (21264A)")
+ UNAME_MACHINE=alphaev67 ;;
+ "EV6.8CB (21264C)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.8AL (21264B)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.8CX (21264D)")
+ UNAME_MACHINE=alphaev68 ;;
+ "EV6.9A (21264/EV69A)")
+ UNAME_MACHINE=alphaev69 ;;
+ "EV7 (21364)")
+ UNAME_MACHINE=alphaev7 ;;
+ "EV7.9 (21364A)")
+ UNAME_MACHINE=alphaev79 ;;
+ esac
+ # A Pn.n version is a patched version.
+ # A Vn.n version is a released version.
+ # A Tn.n version is a released field test version.
+ # A Xn.n version is an unreleased experimental baselevel.
+ # 1.2 uses "1.2" for uname -r.
+ OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+ GUESS=$UNAME_MACHINE-dec-osf$OSF_REL
+ ;;
+ Amiga*:UNIX_System_V:4.0:*)
+ GUESS=m68k-unknown-sysv4
+ ;;
+ *:[Aa]miga[Oo][Ss]:*:*)
+ GUESS=$UNAME_MACHINE-unknown-amigaos
+ ;;
+ *:[Mm]orph[Oo][Ss]:*:*)
+ GUESS=$UNAME_MACHINE-unknown-morphos
+ ;;
+ *:OS/390:*:*)
+ GUESS=i370-ibm-openedition
+ ;;
+ *:z/VM:*:*)
+ GUESS=s390-ibm-zvmoe
+ ;;
+ *:OS400:*:*)
+ GUESS=powerpc-ibm-os400
+ ;;
+ arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+ GUESS=arm-acorn-riscix$UNAME_RELEASE
+ ;;
+ arm*:riscos:*:*|arm*:RISCOS:*:*)
+ GUESS=arm-unknown-riscos
+ ;;
+ SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+ GUESS=hppa1.1-hitachi-hiuxmpp
+ ;;
+ Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+ # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+ case `(/bin/universe) 2>/dev/null` in
+ att) GUESS=pyramid-pyramid-sysv3 ;;
+ *) GUESS=pyramid-pyramid-bsd ;;
+ esac
+ ;;
+ NILE*:*:*:dcosx)
+ GUESS=pyramid-pyramid-svr4
+ ;;
+ DRS?6000:unix:4.0:6*)
+ GUESS=sparc-icl-nx6
+ ;;
+ DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+ case `/usr/bin/uname -p` in
+ sparc) GUESS=sparc-icl-nx7 ;;
+ esac
+ ;;
+ s390x:SunOS:*:*)
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL
+ ;;
+ sun4H:SunOS:5.*:*)
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=sparc-hal-solaris2$SUN_REL
+ ;;
+ sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=sparc-sun-solaris2$SUN_REL
+ ;;
+ i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+ GUESS=i386-pc-auroraux$UNAME_RELEASE
+ ;;
+ i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+ set_cc_for_build
+ SUN_ARCH=i386
+ # If there is a compiler, see if it is configured for 64-bit objects.
+ # Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+ # This test works for both compilers.
+ if test "$CC_FOR_BUILD" != no_compiler_found; then
+ if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \
+ grep IS_64BIT_ARCH >/dev/null
+ then
+ SUN_ARCH=x86_64
+ fi
+ fi
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=$SUN_ARCH-pc-solaris2$SUN_REL
+ ;;
+ sun4*:SunOS:6*:*)
+ # According to config.sub, this is the proper way to canonicalize
+ # SunOS6. Hard to guess exactly what SunOS6 will be like, but
+ # it's likely to be more like Solaris than SunOS4.
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=sparc-sun-solaris3$SUN_REL
+ ;;
+ sun4*:SunOS:*:*)
+ case `/usr/bin/arch -k` in
+ Series*|S4*)
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+ # Japanese Language versions have a version number like `4.1.3-JL'.
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'`
+ GUESS=sparc-sun-sunos$SUN_REL
+ ;;
+ sun3*:SunOS:*:*)
+ GUESS=m68k-sun-sunos$UNAME_RELEASE
+ ;;
+ sun*:*:4.2BSD:*)
+ UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+ test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3
+ case `/bin/arch` in
+ sun3)
+ GUESS=m68k-sun-sunos$UNAME_RELEASE
+ ;;
+ sun4)
+ GUESS=sparc-sun-sunos$UNAME_RELEASE
+ ;;
+ esac
+ ;;
+ aushp:SunOS:*:*)
+ GUESS=sparc-auspex-sunos$UNAME_RELEASE
+ ;;
+ # The situation for MiNT is a little confusing. The machine name
+ # can be virtually everything (everything which is not
+ # "atarist" or "atariste" at least should have a processor
+ # > m68000). The system name ranges from "MiNT" over "FreeMiNT"
+ # to the lowercase version "mint" (or "freemint"). Finally
+ # the system name "TOS" denotes a system which is actually not
+ # MiNT. But MiNT is downward compatible to TOS, so this should
+ # be no problem.
+ atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+ GUESS=m68k-atari-mint$UNAME_RELEASE
+ ;;
+ atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+ GUESS=m68k-atari-mint$UNAME_RELEASE
+ ;;
+ *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+ GUESS=m68k-atari-mint$UNAME_RELEASE
+ ;;
+ milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+ GUESS=m68k-milan-mint$UNAME_RELEASE
+ ;;
+ hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+ GUESS=m68k-hades-mint$UNAME_RELEASE
+ ;;
+ *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+ GUESS=m68k-unknown-mint$UNAME_RELEASE
+ ;;
+ m68k:machten:*:*)
+ GUESS=m68k-apple-machten$UNAME_RELEASE
+ ;;
+ powerpc:machten:*:*)
+ GUESS=powerpc-apple-machten$UNAME_RELEASE
+ ;;
+ RISC*:Mach:*:*)
+ GUESS=mips-dec-mach_bsd4.3
+ ;;
+ RISC*:ULTRIX:*:*)
+ GUESS=mips-dec-ultrix$UNAME_RELEASE
+ ;;
+ VAX*:ULTRIX*:*:*)
+ GUESS=vax-dec-ultrix$UNAME_RELEASE
+ ;;
+ 2020:CLIX:*:* | 2430:CLIX:*:*)
+ GUESS=clipper-intergraph-clix$UNAME_RELEASE
+ ;;
+ mips:*:*:UMIPS | mips:*:*:RISCos)
+ set_cc_for_build
+ sed 's/^ //' << EOF > "$dummy.c"
+#ifdef __cplusplus
+#include <stdio.h> /* for printf() prototype */
+ int main (int argc, char *argv[]) {
+#else
+ int main (argc, argv) int argc; char *argv[]; {
+#endif
+ #if defined (host_mips) && defined (MIPSEB)
+ #if defined (SYSTYPE_SYSV)
+ printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_SVR4)
+ printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+ printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0);
+ #endif
+ #endif
+ exit (-1);
+ }
+EOF
+ $CC_FOR_BUILD -o "$dummy" "$dummy.c" &&
+ dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+ SYSTEM_NAME=`"$dummy" "$dummyarg"` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ GUESS=mips-mips-riscos$UNAME_RELEASE
+ ;;
+ Motorola:PowerMAX_OS:*:*)
+ GUESS=powerpc-motorola-powermax
+ ;;
+ Motorola:*:4.3:PL8-*)
+ GUESS=powerpc-harris-powermax
+ ;;
+ Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+ GUESS=powerpc-harris-powermax
+ ;;
+ Night_Hawk:Power_UNIX:*:*)
+ GUESS=powerpc-harris-powerunix
+ ;;
+ m88k:CX/UX:7*:*)
+ GUESS=m88k-harris-cxux7
+ ;;
+ m88k:*:4*:R4*)
+ GUESS=m88k-motorola-sysv4
+ ;;
+ m88k:*:3*:R3*)
+ GUESS=m88k-motorola-sysv3
+ ;;
+ AViiON:dgux:*:*)
+ # DG/UX returns AViiON for all architectures
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110
+ then
+ if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \
+ test "$TARGET_BINARY_INTERFACE"x = x
+ then
+ GUESS=m88k-dg-dgux$UNAME_RELEASE
+ else
+ GUESS=m88k-dg-dguxbcs$UNAME_RELEASE
+ fi
+ else
+ GUESS=i586-dg-dgux$UNAME_RELEASE
+ fi
+ ;;
+ M88*:DolphinOS:*:*) # DolphinOS (SVR3)
+ GUESS=m88k-dolphin-sysv3
+ ;;
+ M88*:*:R3*:*)
+ # Delta 88k system running SVR3
+ GUESS=m88k-motorola-sysv3
+ ;;
+ XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+ GUESS=m88k-tektronix-sysv3
+ ;;
+ Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+ GUESS=m68k-tektronix-bsd
+ ;;
+ *:IRIX*:*:*)
+ IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'`
+ GUESS=mips-sgi-irix$IRIX_REL
+ ;;
+ ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+ GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id
+ ;; # Note that: echo "'`uname -s`'" gives 'AIX '
+ i*86:AIX:*:*)
+ GUESS=i386-ibm-aix
+ ;;
+ ia64:AIX:*:*)
+ if test -x /usr/bin/oslevel ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
+ fi
+ GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV
+ ;;
+ *:AIX:2:3)
+ if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+ set_cc_for_build
+ sed 's/^ //' << EOF > "$dummy.c"
+ #include <sys/systemcfg.h>
+
+ main()
+ {
+ if (!__power_pc())
+ exit(1);
+ puts("powerpc-ibm-aix3.2.5");
+ exit(0);
+ }
+EOF
+ if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"`
+ then
+ GUESS=$SYSTEM_NAME
+ else
+ GUESS=rs6000-ibm-aix3.2.5
+ fi
+ elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+ GUESS=rs6000-ibm-aix3.2.4
+ else
+ GUESS=rs6000-ibm-aix3.2
+ fi
+ ;;
+ *:AIX:*:[4567])
+ IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+ if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then
+ IBM_ARCH=rs6000
+ else
+ IBM_ARCH=powerpc
+ fi
+ if test -x /usr/bin/lslpp ; then
+ IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \
+ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
+ else
+ IBM_REV=$UNAME_VERSION.$UNAME_RELEASE
+ fi
+ GUESS=$IBM_ARCH-ibm-aix$IBM_REV
+ ;;
+ *:AIX:*:*)
+ GUESS=rs6000-ibm-aix
+ ;;
+ ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*)
+ GUESS=romp-ibm-bsd4.4
+ ;;
+ ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
+ GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to
+ ;; # report: romp-ibm BSD 4.3
+ *:BOSX:*:*)
+ GUESS=rs6000-bull-bosx
+ ;;
+ DPX/2?00:B.O.S.:*:*)
+ GUESS=m68k-bull-sysv3
+ ;;
+ 9000/[34]??:4.3bsd:1.*:*)
+ GUESS=m68k-hp-bsd
+ ;;
+ hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+ GUESS=m68k-hp-bsd4.4
+ ;;
+ 9000/[34678]??:HP-UX:*:*)
+ HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+ case $UNAME_MACHINE in
+ 9000/31?) HP_ARCH=m68000 ;;
+ 9000/[34]??) HP_ARCH=m68k ;;
+ 9000/[678][0-9][0-9])
+ if test -x /usr/bin/getconf; then
+ sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+ sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+ case $sc_cpu_version in
+ 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
+ 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
+ 532) # CPU_PA_RISC2_0
+ case $sc_kernel_bits in
+ 32) HP_ARCH=hppa2.0n ;;
+ 64) HP_ARCH=hppa2.0w ;;
+ '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20
+ esac ;;
+ esac
+ fi
+ if test "$HP_ARCH" = ""; then
+ set_cc_for_build
+ sed 's/^ //' << EOF > "$dummy.c"
+
+ #define _HPUX_SOURCE
+ #include <stdlib.h>
+ #include <unistd.h>
+
+ int main ()
+ {
+ #if defined(_SC_KERNEL_BITS)
+ long bits = sysconf(_SC_KERNEL_BITS);
+ #endif
+ long cpu = sysconf (_SC_CPU_VERSION);
+
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+ case CPU_PA_RISC2_0:
+ #if defined(_SC_KERNEL_BITS)
+ switch (bits)
+ {
+ case 64: puts ("hppa2.0w"); break;
+ case 32: puts ("hppa2.0n"); break;
+ default: puts ("hppa2.0"); break;
+ } break;
+ #else /* !defined(_SC_KERNEL_BITS) */
+ puts ("hppa2.0"); break;
+ #endif
+ default: puts ("hppa1.0"); break;
+ }
+ exit (0);
+ }
+EOF
+ (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"`
+ test -z "$HP_ARCH" && HP_ARCH=hppa
+ fi ;;
+ esac
+ if test "$HP_ARCH" = hppa2.0w
+ then
+ set_cc_for_build
+
+ # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+ # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler
+ # generating 64-bit code. GNU and HP use different nomenclature:
+ #
+ # $ CC_FOR_BUILD=cc ./config.guess
+ # => hppa2.0w-hp-hpux11.23
+ # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+ # => hppa64-hp-hpux11.23
+
+ if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
+ grep -q __LP64__
+ then
+ HP_ARCH=hppa2.0w
+ else
+ HP_ARCH=hppa64
+ fi
+ fi
+ GUESS=$HP_ARCH-hp-hpux$HPUX_REV
+ ;;
+ ia64:HP-UX:*:*)
+ HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'`
+ GUESS=ia64-hp-hpux$HPUX_REV
+ ;;
+ 3050*:HI-UX:*:*)
+ set_cc_for_build
+ sed 's/^ //' << EOF > "$dummy.c"
+ #include <unistd.h>
+ int
+ main ()
+ {
+ long cpu = sysconf (_SC_CPU_VERSION);
+ /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+ true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
+ results, however. */
+ if (CPU_IS_PA_RISC (cpu))
+ {
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+ default: puts ("hppa-hitachi-hiuxwe2"); break;
+ }
+ }
+ else if (CPU_IS_HP_MC68K (cpu))
+ puts ("m68k-hitachi-hiuxwe2");
+ else puts ("unknown-hitachi-hiuxwe2");
+ exit (0);
+ }
+EOF
+ $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ GUESS=unknown-hitachi-hiuxwe2
+ ;;
+ 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*)
+ GUESS=hppa1.1-hp-bsd
+ ;;
+ 9000/8??:4.3bsd:*:*)
+ GUESS=hppa1.0-hp-bsd
+ ;;
+ *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+ GUESS=hppa1.0-hp-mpeix
+ ;;
+ hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*)
+ GUESS=hppa1.1-hp-osf
+ ;;
+ hp8??:OSF1:*:*)
+ GUESS=hppa1.0-hp-osf
+ ;;
+ i*86:OSF1:*:*)
+ if test -x /usr/sbin/sysversion ; then
+ GUESS=$UNAME_MACHINE-unknown-osf1mk
+ else
+ GUESS=$UNAME_MACHINE-unknown-osf1
+ fi
+ ;;
+ parisc*:Lites*:*:*)
+ GUESS=hppa1.1-hp-lites
+ ;;
+ C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+ GUESS=c1-convex-bsd
+ ;;
+ C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit ;;
+ C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+ GUESS=c34-convex-bsd
+ ;;
+ C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+ GUESS=c38-convex-bsd
+ ;;
+ C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+ GUESS=c4-convex-bsd
+ ;;
+ CRAY*Y-MP:*:*:*)
+ CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+ GUESS=ymp-cray-unicos$CRAY_REL
+ ;;
+ CRAY*[A-Z]90:*:*:*)
+ echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \
+ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+ -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*TS:*:*:*)
+ CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+ GUESS=t90-cray-unicos$CRAY_REL
+ ;;
+ CRAY*T3E:*:*:*)
+ CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+ GUESS=alphaev5-cray-unicosmk$CRAY_REL
+ ;;
+ CRAY*SV1:*:*:*)
+ CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+ GUESS=sv1-cray-unicos$CRAY_REL
+ ;;
+ *:UNICOS/mp:*:*)
+ CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'`
+ GUESS=craynv-cray-unicosmp$CRAY_REL
+ ;;
+ F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+ FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+ FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'`
+ GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+ ;;
+ 5000:UNIX_System_V:4.*:*)
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+ FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
+ GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}
+ ;;
+ i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+ GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE
+ ;;
+ sparc*:BSD/OS:*:*)
+ GUESS=sparc-unknown-bsdi$UNAME_RELEASE
+ ;;
+ *:BSD/OS:*:*)
+ GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE
+ ;;
+ arm:FreeBSD:*:*)
+ UNAME_PROCESSOR=`uname -p`
+ set_cc_for_build
+ if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_PCS_VFP
+ then
+ FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+ GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi
+ else
+ FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+ GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf
+ fi
+ ;;
+ *:FreeBSD:*:*)
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ case $UNAME_PROCESSOR in
+ amd64)
+ UNAME_PROCESSOR=x86_64 ;;
+ i386)
+ UNAME_PROCESSOR=i586 ;;
+ esac
+ FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+ GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL
+ ;;
+ i*:CYGWIN*:*)
+ GUESS=$UNAME_MACHINE-pc-cygwin
+ ;;
+ *:MINGW64*:*)
+ GUESS=$UNAME_MACHINE-pc-mingw64
+ ;;
+ *:MINGW*:*)
+ GUESS=$UNAME_MACHINE-pc-mingw32
+ ;;
+ *:MSYS*:*)
+ GUESS=$UNAME_MACHINE-pc-msys
+ ;;
+ i*:PW*:*)
+ GUESS=$UNAME_MACHINE-pc-pw32
+ ;;
+ *:SerenityOS:*:*)
+ GUESS=$UNAME_MACHINE-pc-serenity
+ ;;
+ *:Interix*:*)
+ case $UNAME_MACHINE in
+ x86)
+ GUESS=i586-pc-interix$UNAME_RELEASE
+ ;;
+ authenticamd | genuineintel | EM64T)
+ GUESS=x86_64-unknown-interix$UNAME_RELEASE
+ ;;
+ IA64)
+ GUESS=ia64-unknown-interix$UNAME_RELEASE
+ ;;
+ esac ;;
+ i*:UWIN*:*)
+ GUESS=$UNAME_MACHINE-pc-uwin
+ ;;
+ amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+ GUESS=x86_64-pc-cygwin
+ ;;
+ prep*:SunOS:5.*:*)
+ SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`
+ GUESS=powerpcle-unknown-solaris2$SUN_REL
+ ;;
+ *:GNU:*:*)
+ # the GNU system
+ GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'`
+ GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'`
+ GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL
+ ;;
+ *:GNU/*:*:*)
+ # other systems with GNU libc and userland
+ GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"`
+ GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+ GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC
+ ;;
+ *:Minix:*:*)
+ GUESS=$UNAME_MACHINE-unknown-minix
+ ;;
+ aarch64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ aarch64_be:Linux:*:*)
+ UNAME_MACHINE=aarch64_be
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ alpha:Linux:*:*)
+ case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in
+ EV5) UNAME_MACHINE=alphaev5 ;;
+ EV56) UNAME_MACHINE=alphaev56 ;;
+ PCA56) UNAME_MACHINE=alphapca56 ;;
+ PCA57) UNAME_MACHINE=alphapca56 ;;
+ EV6) UNAME_MACHINE=alphaev6 ;;
+ EV67) UNAME_MACHINE=alphaev67 ;;
+ EV68*) UNAME_MACHINE=alphaev68 ;;
+ esac
+ objdump --private-headers /bin/sh | grep -q ld.so.1
+ if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ arm*:Linux:*:*)
+ set_cc_for_build
+ if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_EABI__
+ then
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ else
+ if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_PCS_VFP
+ then
+ GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi
+ else
+ GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf
+ fi
+ fi
+ ;;
+ avr32*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ cris:Linux:*:*)
+ GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+ ;;
+ crisv32:Linux:*:*)
+ GUESS=$UNAME_MACHINE-axis-linux-$LIBC
+ ;;
+ e2k:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ frv:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ hexagon:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ i*86:Linux:*:*)
+ GUESS=$UNAME_MACHINE-pc-linux-$LIBC
+ ;;
+ ia64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ k1om:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ m32r*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ m68*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ mips:Linux:*:* | mips64:Linux:*:*)
+ set_cc_for_build
+ IS_GLIBC=0
+ test x"${LIBC}" = xgnu && IS_GLIBC=1
+ sed 's/^ //' << EOF > "$dummy.c"
+ #undef CPU
+ #undef mips
+ #undef mipsel
+ #undef mips64
+ #undef mips64el
+ #if ${IS_GLIBC} && defined(_ABI64)
+ LIBCABI=gnuabi64
+ #else
+ #if ${IS_GLIBC} && defined(_ABIN32)
+ LIBCABI=gnuabin32
+ #else
+ LIBCABI=${LIBC}
+ #endif
+ #endif
+
+ #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+ CPU=mipsisa64r6
+ #else
+ #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6
+ CPU=mipsisa32r6
+ #else
+ #if defined(__mips64)
+ CPU=mips64
+ #else
+ CPU=mips
+ #endif
+ #endif
+ #endif
+
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ MIPS_ENDIAN=el
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ MIPS_ENDIAN=
+ #else
+ MIPS_ENDIAN=
+ #endif
+ #endif
+EOF
+ cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`
+ eval "$cc_set_vars"
+ test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; }
+ ;;
+ mips64el:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ openrisc*:Linux:*:*)
+ GUESS=or1k-unknown-linux-$LIBC
+ ;;
+ or32:Linux:*:* | or1k*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ padre:Linux:*:*)
+ GUESS=sparc-unknown-linux-$LIBC
+ ;;
+ parisc64:Linux:*:* | hppa64:Linux:*:*)
+ GUESS=hppa64-unknown-linux-$LIBC
+ ;;
+ parisc:Linux:*:* | hppa:Linux:*:*)
+ # Look for CPU level
+ case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+ PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;;
+ PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;;
+ *) GUESS=hppa-unknown-linux-$LIBC ;;
+ esac
+ ;;
+ ppc64:Linux:*:*)
+ GUESS=powerpc64-unknown-linux-$LIBC
+ ;;
+ ppc:Linux:*:*)
+ GUESS=powerpc-unknown-linux-$LIBC
+ ;;
+ ppc64le:Linux:*:*)
+ GUESS=powerpc64le-unknown-linux-$LIBC
+ ;;
+ ppcle:Linux:*:*)
+ GUESS=powerpcle-unknown-linux-$LIBC
+ ;;
+ riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ s390:Linux:*:* | s390x:Linux:*:*)
+ GUESS=$UNAME_MACHINE-ibm-linux-$LIBC
+ ;;
+ sh64*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ sh*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ sparc:Linux:*:* | sparc64:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ tile*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ vax:Linux:*:*)
+ GUESS=$UNAME_MACHINE-dec-linux-$LIBC
+ ;;
+ x86_64:Linux:*:*)
+ set_cc_for_build
+ LIBCABI=$LIBC
+ if test "$CC_FOR_BUILD" != no_compiler_found; then
+ if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_X32 >/dev/null
+ then
+ LIBCABI=${LIBC}x32
+ fi
+ fi
+ GUESS=$UNAME_MACHINE-pc-linux-$LIBCABI
+ ;;
+ xtensa*:Linux:*:*)
+ GUESS=$UNAME_MACHINE-unknown-linux-$LIBC
+ ;;
+ i*86:DYNIX/ptx:4*:*)
+ # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+ # earlier versions are messed up and put the nodename in both
+ # sysname and nodename.
+ GUESS=i386-sequent-sysv4
+ ;;
+ i*86:UNIX_SV:4.2MP:2.*)
+ # Unixware is an offshoot of SVR4, but it has its own version
+ # number series starting with 2...
+ # I am not positive that other SVR4 systems won't match this,
+ # I just have to hope. -- rms.
+ # Use sysv4.2uw... so that sysv4* matches it.
+ GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION
+ ;;
+ i*86:OS/2:*:*)
+ # If we were able to find `uname', then EMX Unix compatibility
+ # is probably installed.
+ GUESS=$UNAME_MACHINE-pc-os2-emx
+ ;;
+ i*86:XTS-300:*:STOP)
+ GUESS=$UNAME_MACHINE-unknown-stop
+ ;;
+ i*86:atheos:*:*)
+ GUESS=$UNAME_MACHINE-unknown-atheos
+ ;;
+ i*86:syllable:*:*)
+ GUESS=$UNAME_MACHINE-pc-syllable
+ ;;
+ i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+ GUESS=i386-unknown-lynxos$UNAME_RELEASE
+ ;;
+ i*86:*DOS:*:*)
+ GUESS=$UNAME_MACHINE-pc-msdosdjgpp
+ ;;
+ i*86:*:4.*:*)
+ UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'`
+ if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+ GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL
+ else
+ GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL
+ fi
+ ;;
+ i*86:*:5:[678]*)
+ # UnixWare 7.x, OpenUNIX and OpenServer 6.
+ case `/bin/uname -X | grep "^Machine"` in
+ *486*) UNAME_MACHINE=i486 ;;
+ *Pentium) UNAME_MACHINE=i586 ;;
+ *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+ esac
+ GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+ ;;
+ i*86:*:3.2:*)
+ if test -f /usr/options/cb.name; then
+ UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+ GUESS=$UNAME_MACHINE-pc-isc$UNAME_REL
+ elif /bin/uname -X 2>/dev/null >/dev/null ; then
+ UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+ (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+ (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+ && UNAME_MACHINE=i586
+ (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+ && UNAME_MACHINE=i686
+ (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+ && UNAME_MACHINE=i686
+ GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL
+ else
+ GUESS=$UNAME_MACHINE-pc-sysv32
+ fi
+ ;;
+ pc:*:*:*)
+ # Left here for compatibility:
+ # uname -m prints for DJGPP always 'pc', but it prints nothing about
+ # the processor, so we play safe by assuming i586.
+ # Note: whatever this is, it MUST be the same as what config.sub
+ # prints for the "djgpp" host, or else GDB configure will decide that
+ # this is a cross-build.
+ GUESS=i586-pc-msdosdjgpp
+ ;;
+ Intel:Mach:3*:*)
+ GUESS=i386-pc-mach3
+ ;;
+ paragon:*:*:*)
+ GUESS=i860-intel-osf1
+ ;;
+ i860:*:4.*:*) # i860-SVR4
+ if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+ GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4
+ else # Add other i860-SVR4 vendors below as they are discovered.
+ GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4
+ fi
+ ;;
+ mini*:CTIX:SYS*5:*)
+ # "miniframe"
+ GUESS=m68010-convergent-sysv
+ ;;
+ mc68k:UNIX:SYSTEM5:3.51m)
+ GUESS=m68k-convergent-sysv
+ ;;
+ M680?0:D-NIX:5.3:*)
+ GUESS=m68k-diab-dnix
+ ;;
+ M68*:*:R3V[5678]*:*)
+ test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+ 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+ OS_REL=''
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
+ 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4; exit; } ;;
+ NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+ OS_REL='.3'
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;;
+ m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+ GUESS=m68k-unknown-lynxos$UNAME_RELEASE
+ ;;
+ mc68030:UNIX_System_V:4.*:*)
+ GUESS=m68k-atari-sysv4
+ ;;
+ TSUNAMI:LynxOS:2.*:*)
+ GUESS=sparc-unknown-lynxos$UNAME_RELEASE
+ ;;
+ rs6000:LynxOS:2.*:*)
+ GUESS=rs6000-unknown-lynxos$UNAME_RELEASE
+ ;;
+ PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+ GUESS=powerpc-unknown-lynxos$UNAME_RELEASE
+ ;;
+ SM[BE]S:UNIX_SV:*:*)
+ GUESS=mips-dde-sysv$UNAME_RELEASE
+ ;;
+ RM*:ReliantUNIX-*:*:*)
+ GUESS=mips-sni-sysv4
+ ;;
+ RM*:SINIX-*:*:*)
+ GUESS=mips-sni-sysv4
+ ;;
+ *:SINIX-*:*:*)
+ if uname -p 2>/dev/null >/dev/null ; then
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ GUESS=$UNAME_MACHINE-sni-sysv4
+ else
+ GUESS=ns32k-sni-sysv
+ fi
+ ;;
+ PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ GUESS=i586-unisys-sysv4
+ ;;
+ *:UNIX_System_V:4*:FTX*)
+ # From Gerald Hewes <hewes@openmarket.com>.
+ # How about differentiating between stratus architectures? -djm
+ GUESS=hppa1.1-stratus-sysv4
+ ;;
+ *:*:*:FTX*)
+ # From seanf@swdc.stratus.com.
+ GUESS=i860-stratus-sysv4
+ ;;
+ i*86:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ GUESS=$UNAME_MACHINE-stratus-vos
+ ;;
+ *:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ GUESS=hppa1.1-stratus-vos
+ ;;
+ mc68*:A/UX:*:*)
+ GUESS=m68k-apple-aux$UNAME_RELEASE
+ ;;
+ news*:NEWS-OS:6*:*)
+ GUESS=mips-sony-newsos6
+ ;;
+ R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+ if test -d /usr/nec; then
+ GUESS=mips-nec-sysv$UNAME_RELEASE
+ else
+ GUESS=mips-unknown-sysv$UNAME_RELEASE
+ fi
+ ;;
+ BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
+ GUESS=powerpc-be-beos
+ ;;
+ BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
+ GUESS=powerpc-apple-beos
+ ;;
+ BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
+ GUESS=i586-pc-beos
+ ;;
+ BePC:Haiku:*:*) # Haiku running on Intel PC compatible.
+ GUESS=i586-pc-haiku
+ ;;
+ x86_64:Haiku:*:*)
+ GUESS=x86_64-unknown-haiku
+ ;;
+ SX-4:SUPER-UX:*:*)
+ GUESS=sx4-nec-superux$UNAME_RELEASE
+ ;;
+ SX-5:SUPER-UX:*:*)
+ GUESS=sx5-nec-superux$UNAME_RELEASE
+ ;;
+ SX-6:SUPER-UX:*:*)
+ GUESS=sx6-nec-superux$UNAME_RELEASE
+ ;;
+ SX-7:SUPER-UX:*:*)
+ GUESS=sx7-nec-superux$UNAME_RELEASE
+ ;;
+ SX-8:SUPER-UX:*:*)
+ GUESS=sx8-nec-superux$UNAME_RELEASE
+ ;;
+ SX-8R:SUPER-UX:*:*)
+ GUESS=sx8r-nec-superux$UNAME_RELEASE
+ ;;
+ SX-ACE:SUPER-UX:*:*)
+ GUESS=sxace-nec-superux$UNAME_RELEASE
+ ;;
+ Power*:Rhapsody:*:*)
+ GUESS=powerpc-apple-rhapsody$UNAME_RELEASE
+ ;;
+ *:Rhapsody:*:*)
+ GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE
+ ;;
+ arm64:Darwin:*:*)
+ GUESS=aarch64-apple-darwin$UNAME_RELEASE
+ ;;
+ *:Darwin:*:*)
+ UNAME_PROCESSOR=`uname -p`
+ case $UNAME_PROCESSOR in
+ unknown) UNAME_PROCESSOR=powerpc ;;
+ esac
+ if command -v xcode-select > /dev/null 2> /dev/null && \
+ ! xcode-select --print-path > /dev/null 2> /dev/null ; then
+ # Avoid executing cc if there is no toolchain installed as
+ # cc will be a stub that puts up a graphical alert
+ # prompting the user to install developer tools.
+ CC_FOR_BUILD=no_compiler_found
+ else
+ set_cc_for_build
+ fi
+ if test "$CC_FOR_BUILD" != no_compiler_found; then
+ if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_64BIT_ARCH >/dev/null
+ then
+ case $UNAME_PROCESSOR in
+ i386) UNAME_PROCESSOR=x86_64 ;;
+ powerpc) UNAME_PROCESSOR=powerpc64 ;;
+ esac
+ fi
+ # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc
+ if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
+ grep IS_PPC >/dev/null
+ then
+ UNAME_PROCESSOR=powerpc
+ fi
+ elif test "$UNAME_PROCESSOR" = i386 ; then
+ # uname -m returns i386 or x86_64
+ UNAME_PROCESSOR=$UNAME_MACHINE
+ fi
+ GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE
+ ;;
+ *:procnto*:*:* | *:QNX:[0123456789]*:*)
+ UNAME_PROCESSOR=`uname -p`
+ if test "$UNAME_PROCESSOR" = x86; then
+ UNAME_PROCESSOR=i386
+ UNAME_MACHINE=pc
+ fi
+ GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE
+ ;;
+ *:QNX:*:4*)
+ GUESS=i386-pc-qnx
+ ;;
+ NEO-*:NONSTOP_KERNEL:*:*)
+ GUESS=neo-tandem-nsk$UNAME_RELEASE
+ ;;
+ NSE-*:NONSTOP_KERNEL:*:*)
+ GUESS=nse-tandem-nsk$UNAME_RELEASE
+ ;;
+ NSR-*:NONSTOP_KERNEL:*:*)
+ GUESS=nsr-tandem-nsk$UNAME_RELEASE
+ ;;
+ NSV-*:NONSTOP_KERNEL:*:*)
+ GUESS=nsv-tandem-nsk$UNAME_RELEASE
+ ;;
+ NSX-*:NONSTOP_KERNEL:*:*)
+ GUESS=nsx-tandem-nsk$UNAME_RELEASE
+ ;;
+ *:NonStop-UX:*:*)
+ GUESS=mips-compaq-nonstopux
+ ;;
+ BS2000:POSIX*:*:*)
+ GUESS=bs2000-siemens-sysv
+ ;;
+ DS/*:UNIX_System_V:*:*)
+ GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE
+ ;;
+ *:Plan9:*:*)
+ # "uname -m" is not consistent, so use $cputype instead. 386
+ # is converted to i386 for consistency with other x86
+ # operating systems.
+ if test "${cputype-}" = 386; then
+ UNAME_MACHINE=i386
+ elif test "x${cputype-}" != x; then
+ UNAME_MACHINE=$cputype
+ fi
+ GUESS=$UNAME_MACHINE-unknown-plan9
+ ;;
+ *:TOPS-10:*:*)
+ GUESS=pdp10-unknown-tops10
+ ;;
+ *:TENEX:*:*)
+ GUESS=pdp10-unknown-tenex
+ ;;
+ KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+ GUESS=pdp10-dec-tops20
+ ;;
+ XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+ GUESS=pdp10-xkl-tops20
+ ;;
+ *:TOPS-20:*:*)
+ GUESS=pdp10-unknown-tops20
+ ;;
+ *:ITS:*:*)
+ GUESS=pdp10-unknown-its
+ ;;
+ SEI:*:*:SEIUX)
+ GUESS=mips-sei-seiux$UNAME_RELEASE
+ ;;
+ *:DragonFly:*:*)
+ DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'`
+ GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL
+ ;;
+ *:*VMS:*:*)
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ case $UNAME_MACHINE in
+ A*) GUESS=alpha-dec-vms ;;
+ I*) GUESS=ia64-dec-vms ;;
+ V*) GUESS=vax-dec-vms ;;
+ esac ;;
+ *:XENIX:*:SysV)
+ GUESS=i386-pc-xenix
+ ;;
+ i*86:skyos:*:*)
+ SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`
+ GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL
+ ;;
+ i*86:rdos:*:*)
+ GUESS=$UNAME_MACHINE-pc-rdos
+ ;;
+ i*86:Fiwix:*:*)
+ GUESS=$UNAME_MACHINE-pc-fiwix
+ ;;
+ *:AROS:*:*)
+ GUESS=$UNAME_MACHINE-unknown-aros
+ ;;
+ x86_64:VMkernel:*:*)
+ GUESS=$UNAME_MACHINE-unknown-esx
+ ;;
+ amd64:Isilon\ OneFS:*:*)
+ GUESS=x86_64-unknown-onefs
+ ;;
+ *:Unleashed:*:*)
+ GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE
+ ;;
+esac
+
+# Do we have a guess based on uname results?
+if test "x$GUESS" != x; then
+ echo "$GUESS"
+ exit
+fi
+
+# No uname command or uname output not recognized.
+set_cc_for_build
+cat > "$dummy.c" <<EOF
+#ifdef _SEQUENT_
+#include <sys/types.h>
+#include <sys/utsname.h>
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#include <signal.h>
+#if defined(_SIZE_T_) || defined(SIGLOST)
+#include <sys/utsname.h>
+#endif
+#endif
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+ /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed,
+ I don't know.... */
+ printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+ printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+ "4"
+#else
+ ""
+#endif
+ ); exit (0);
+#endif
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+ int version;
+ version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+ if (version < 4)
+ printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+ else
+ printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+ exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+ printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+ printf ("ns32k-encore-mach\n"); exit (0);
+#else
+ printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+ printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+ printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+ printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+ struct utsname un;
+
+ uname(&un);
+ if (strncmp(un.version, "V2", 2) == 0) {
+ printf ("i386-sequent-ptx2\n"); exit (0);
+ }
+ if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+ printf ("i386-sequent-ptx1\n"); exit (0);
+ }
+ printf ("i386-sequent-ptx\n"); exit (0);
+#endif
+
+#if defined (vax)
+#if !defined (ultrix)
+#include <sys/param.h>
+#if defined (BSD)
+#if BSD == 43
+ printf ("vax-dec-bsd4.3\n"); exit (0);
+#else
+#if BSD == 199006
+ printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#else
+ printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#endif
+#else
+ printf ("vax-dec-bsd\n"); exit (0);
+#endif
+#else
+#if defined(_SIZE_T_) || defined(SIGLOST)
+ struct utsname un;
+ uname (&un);
+ printf ("vax-dec-ultrix%s\n", un.release); exit (0);
+#else
+ printf ("vax-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__)
+#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__)
+#if defined(_SIZE_T_) || defined(SIGLOST)
+ struct utsname *un;
+ uname (&un);
+ printf ("mips-dec-ultrix%s\n", un.release); exit (0);
+#else
+ printf ("mips-dec-ultrix\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (alliant) && defined (i860)
+ printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+ exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` &&
+ { echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; }
+
+echo "$0: unable to guess system type" >&2
+
+case $UNAME_MACHINE:$UNAME_SYSTEM in
+ mips:Linux | mips64:Linux)
+ # If we got here on MIPS GNU/Linux, output extra information.
+ cat >&2 <<EOF
+
+NOTE: MIPS GNU/Linux systems require a C compiler to fully recognize
+the system type. Please install a C compiler and try again.
+EOF
+ ;;
+esac
+
+cat >&2 <<EOF
+
+This script (version $timestamp), has failed to recognize the
+operating system you are using. If your script is old, overwrite *all*
+copies of config.guess and config.sub with the latest versions from:
+
+ https://git.savannah.gnu.org/cgit/config.git/plain/config.guess
+and
+ https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
+EOF
+
+our_year=`echo $timestamp | sed 's,-.*,,'`
+thisyear=`date +%Y`
+# shellcheck disable=SC2003
+script_age=`expr "$thisyear" - "$our_year"`
+if test "$script_age" -lt 3 ; then
+ cat >&2 <<EOF
+
+If $0 has already been updated, send the following data and any
+information you think might be pertinent to config-patches@gnu.org to
+provide the necessary information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo = `(hostinfo) 2>/dev/null`
+/bin/universe = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = "$UNAME_MACHINE"
+UNAME_RELEASE = "$UNAME_RELEASE"
+UNAME_SYSTEM = "$UNAME_SYSTEM"
+UNAME_VERSION = "$UNAME_VERSION"
+EOF
+fi
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 00000000..cb34f65f
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,177 @@
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the <byteswap.h> header file. */
+#undef HAVE_BYTESWAP_H
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#undef HAVE_CLOCK_GETTIME
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the `getopt_long' function. */
+#undef HAVE_GETOPT_LONG
+
+/* Found sys/random.h */
+#undef HAVE_GETRANDOM
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <linux/bsg.h> header file. */
+#undef HAVE_LINUX_BSG_H
+
+/* Define to 1 if you have the <linux/kdev_t.h> header file. */
+#undef HAVE_LINUX_KDEV_T_H
+
+/* Define to 1 if you have the <linux/nvme_ioctl.h> header file. */
+#undef HAVE_LINUX_NVME_IOCTL_H
+
+/* Have Linux sg v4 header */
+#undef HAVE_LINUX_SG_V4_HDR
+
+/* Define to 1 if you have the <linux/types.h> header file. */
+#undef HAVE_LINUX_TYPES_H
+
+/* Define to 1 if you have the `lseek64' function. */
+#undef HAVE_LSEEK64
+
+/* Found NVMe */
+#undef HAVE_NVME
+
+/* Define to 1 if you have the `posix_fadvise' function. */
+#undef HAVE_POSIX_FADVISE
+
+/* Define to 1 if you have the `posix_memalign' function. */
+#undef HAVE_POSIX_MEMALIGN
+
+/* Define to 1 if you have the `pthread_cancel' function. */
+#undef HAVE_PTHREAD_CANCEL
+
+/* Define to 1 if you have the `pthread_kill' function. */
+#undef HAVE_PTHREAD_KILL
+
+/* Define to 1 if you have the `srand48_r' function. */
+#undef HAVE_SRAND48_R
+
+/* Define to 1 if you have the <stdatomic.h> header file. */
+#undef HAVE_STDATOMIC_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `sysconf' function. */
+#undef HAVE_SYSCONF
+
+/* Define to 1 if you have the <sys/random.h> header file. */
+#undef HAVE_SYS_RANDOM_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* use generic little-endian/big-endian instead */
+#undef IGNORE_FAST_LEBE
+
+/* option ignored */
+#undef IGNORE_LINUX_BSG
+
+/* even if Linux sg v4 available, use v3 instead */
+#undef IGNORE_LINUX_SGV4
+
+/* compile out NVMe support */
+#undef IGNORE_NVME
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#undef LT_OBJDIR
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* sg3_utils on android */
+#undef SG_LIB_ANDROID
+
+/* sg3_utils Build Host */
+#undef SG_LIB_BUILD_HOST
+
+/* sg3_utils on FreeBSD */
+#undef SG_LIB_FREEBSD
+
+/* sg3_utils on Haiku */
+#undef SG_LIB_HAIKU
+
+/* sg3_utils on linux */
+#undef SG_LIB_LINUX
+
+/* also MinGW environment */
+#undef SG_LIB_MINGW
+
+/* sg3_utils on NetBSD */
+#undef SG_LIB_NETBSD
+
+/* sg3_utils on OpenBSD */
+#undef SG_LIB_OPENBSD
+
+/* sg3_utils on Tru64 UNIX */
+#undef SG_LIB_OSF1
+
+/* sg3_utils on other */
+#undef SG_LIB_OTHER
+
+/* sg3_utils on Solaris */
+#undef SG_LIB_SOLARIS
+
+/* sg3_utils on Win32 */
+#undef SG_LIB_WIN32
+
+/* full SCSI sense strings and NVMe status strings */
+#undef SG_SCSI_STRINGS
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+ required in a freestanding environment). This macro is provided for
+ backward compatibility; new code need not use it. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* enable Win32 SPT Direct */
+#undef WIN32_SPT_DIRECT
diff --git a/config.sub b/config.sub
new file mode 100755
index 00000000..dba16e84
--- /dev/null
+++ b/config.sub
@@ -0,0 +1,1890 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+# Copyright 1992-2022 Free Software Foundation, Inc.
+
+# shellcheck disable=SC2006,SC2268 # see below for rationale
+
+timestamp='2022-01-03'
+
+# This file 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program. This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+
+
+# Please send patches to <config-patches@gnu.org>.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support. The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+# The "shellcheck disable" line above the timestamp inhibits complaints
+# about features and limitations of the classic Bourne shell that were
+# superseded or lifted in POSIX. However, this script identifies a wide
+# variety of pre-POSIX systems that do not have POSIX shells at all, and
+# even some reasonably current systems (Solaris 10 as case-in-point) still
+# have a pre-POSIX /bin/sh.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
+
+Canonicalize a configuration name.
+
+Options:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright 1992-2022 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help" >&2
+ exit 1 ;;
+
+ *local*)
+ # First pass through any local machine types.
+ echo "$1"
+ exit ;;
+
+ * )
+ break ;;
+ esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+ exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+ exit 1;;
+esac
+
+# Split fields of configuration type
+# shellcheck disable=SC2162
+saved_IFS=$IFS
+IFS="-" read field1 field2 field3 field4 <<EOF
+$1
+EOF
+IFS=$saved_IFS
+
+# Separate into logical components for further validation
+case $1 in
+ *-*-*-*-*)
+ echo Invalid configuration \`"$1"\': more than four components >&2
+ exit 1
+ ;;
+ *-*-*-*)
+ basic_machine=$field1-$field2
+ basic_os=$field3-$field4
+ ;;
+ *-*-*)
+ # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two
+ # parts
+ maybe_os=$field2-$field3
+ case $maybe_os in
+ nto-qnx* | linux-* | uclinux-uclibc* \
+ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \
+ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \
+ | storm-chaos* | os2-emx* | rtmk-nova*)
+ basic_machine=$field1
+ basic_os=$maybe_os
+ ;;
+ android-linux)
+ basic_machine=$field1-unknown
+ basic_os=linux-android
+ ;;
+ *)
+ basic_machine=$field1-$field2
+ basic_os=$field3
+ ;;
+ esac
+ ;;
+ *-*)
+ # A lone config we happen to match not fitting any pattern
+ case $field1-$field2 in
+ decstation-3100)
+ basic_machine=mips-dec
+ basic_os=
+ ;;
+ *-*)
+ # Second component is usually, but not always the OS
+ case $field2 in
+ # Prevent following clause from handling this valid os
+ sun*os*)
+ basic_machine=$field1
+ basic_os=$field2
+ ;;
+ zephyr*)
+ basic_machine=$field1-unknown
+ basic_os=$field2
+ ;;
+ # Manufacturers
+ dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \
+ | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \
+ | unicom* | ibm* | next | hp | isi* | apollo | altos* \
+ | convergent* | ncr* | news | 32* | 3600* | 3100* \
+ | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \
+ | ultra | tti* | harris | dolphin | highlevel | gould \
+ | cbm | ns | masscomp | apple | axis | knuth | cray \
+ | microblaze* | sim | cisco \
+ | oki | wec | wrs | winbond)
+ basic_machine=$field1-$field2
+ basic_os=
+ ;;
+ *)
+ basic_machine=$field1
+ basic_os=$field2
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+ *)
+ # Convert single-component short-hands not valid as part of
+ # multi-component configurations.
+ case $field1 in
+ 386bsd)
+ basic_machine=i386-pc
+ basic_os=bsd
+ ;;
+ a29khif)
+ basic_machine=a29k-amd
+ basic_os=udi
+ ;;
+ adobe68k)
+ basic_machine=m68010-adobe
+ basic_os=scout
+ ;;
+ alliant)
+ basic_machine=fx80-alliant
+ basic_os=
+ ;;
+ altos | altos3068)
+ basic_machine=m68k-altos
+ basic_os=
+ ;;
+ am29k)
+ basic_machine=a29k-none
+ basic_os=bsd
+ ;;
+ amdahl)
+ basic_machine=580-amdahl
+ basic_os=sysv
+ ;;
+ amiga)
+ basic_machine=m68k-unknown
+ basic_os=
+ ;;
+ amigaos | amigados)
+ basic_machine=m68k-unknown
+ basic_os=amigaos
+ ;;
+ amigaunix | amix)
+ basic_machine=m68k-unknown
+ basic_os=sysv4
+ ;;
+ apollo68)
+ basic_machine=m68k-apollo
+ basic_os=sysv
+ ;;
+ apollo68bsd)
+ basic_machine=m68k-apollo
+ basic_os=bsd
+ ;;
+ aros)
+ basic_machine=i386-pc
+ basic_os=aros
+ ;;
+ aux)
+ basic_machine=m68k-apple
+ basic_os=aux
+ ;;
+ balance)
+ basic_machine=ns32k-sequent
+ basic_os=dynix
+ ;;
+ blackfin)
+ basic_machine=bfin-unknown
+ basic_os=linux
+ ;;
+ cegcc)
+ basic_machine=arm-unknown
+ basic_os=cegcc
+ ;;
+ convex-c1)
+ basic_machine=c1-convex
+ basic_os=bsd
+ ;;
+ convex-c2)
+ basic_machine=c2-convex
+ basic_os=bsd
+ ;;
+ convex-c32)
+ basic_machine=c32-convex
+ basic_os=bsd
+ ;;
+ convex-c34)
+ basic_machine=c34-convex
+ basic_os=bsd
+ ;;
+ convex-c38)
+ basic_machine=c38-convex
+ basic_os=bsd
+ ;;
+ cray)
+ basic_machine=j90-cray
+ basic_os=unicos
+ ;;
+ crds | unos)
+ basic_machine=m68k-crds
+ basic_os=
+ ;;
+ da30)
+ basic_machine=m68k-da30
+ basic_os=
+ ;;
+ decstation | pmax | pmin | dec3100 | decstatn)
+ basic_machine=mips-dec
+ basic_os=
+ ;;
+ delta88)
+ basic_machine=m88k-motorola
+ basic_os=sysv3
+ ;;
+ dicos)
+ basic_machine=i686-pc
+ basic_os=dicos
+ ;;
+ djgpp)
+ basic_machine=i586-pc
+ basic_os=msdosdjgpp
+ ;;
+ ebmon29k)
+ basic_machine=a29k-amd
+ basic_os=ebmon
+ ;;
+ es1800 | OSE68k | ose68k | ose | OSE)
+ basic_machine=m68k-ericsson
+ basic_os=ose
+ ;;
+ gmicro)
+ basic_machine=tron-gmicro
+ basic_os=sysv
+ ;;
+ go32)
+ basic_machine=i386-pc
+ basic_os=go32
+ ;;
+ h8300hms)
+ basic_machine=h8300-hitachi
+ basic_os=hms
+ ;;
+ h8300xray)
+ basic_machine=h8300-hitachi
+ basic_os=xray
+ ;;
+ h8500hms)
+ basic_machine=h8500-hitachi
+ basic_os=hms
+ ;;
+ harris)
+ basic_machine=m88k-harris
+ basic_os=sysv3
+ ;;
+ hp300 | hp300hpux)
+ basic_machine=m68k-hp
+ basic_os=hpux
+ ;;
+ hp300bsd)
+ basic_machine=m68k-hp
+ basic_os=bsd
+ ;;
+ hppaosf)
+ basic_machine=hppa1.1-hp
+ basic_os=osf
+ ;;
+ hppro)
+ basic_machine=hppa1.1-hp
+ basic_os=proelf
+ ;;
+ i386mach)
+ basic_machine=i386-mach
+ basic_os=mach
+ ;;
+ isi68 | isi)
+ basic_machine=m68k-isi
+ basic_os=sysv
+ ;;
+ m68knommu)
+ basic_machine=m68k-unknown
+ basic_os=linux
+ ;;
+ magnum | m3230)
+ basic_machine=mips-mips
+ basic_os=sysv
+ ;;
+ merlin)
+ basic_machine=ns32k-utek
+ basic_os=sysv
+ ;;
+ mingw64)
+ basic_machine=x86_64-pc
+ basic_os=mingw64
+ ;;
+ mingw32)
+ basic_machine=i686-pc
+ basic_os=mingw32
+ ;;
+ mingw32ce)
+ basic_machine=arm-unknown
+ basic_os=mingw32ce
+ ;;
+ monitor)
+ basic_machine=m68k-rom68k
+ basic_os=coff
+ ;;
+ morphos)
+ basic_machine=powerpc-unknown
+ basic_os=morphos
+ ;;
+ moxiebox)
+ basic_machine=moxie-unknown
+ basic_os=moxiebox
+ ;;
+ msdos)
+ basic_machine=i386-pc
+ basic_os=msdos
+ ;;
+ msys)
+ basic_machine=i686-pc
+ basic_os=msys
+ ;;
+ mvs)
+ basic_machine=i370-ibm
+ basic_os=mvs
+ ;;
+ nacl)
+ basic_machine=le32-unknown
+ basic_os=nacl
+ ;;
+ ncr3000)
+ basic_machine=i486-ncr
+ basic_os=sysv4
+ ;;
+ netbsd386)
+ basic_machine=i386-pc
+ basic_os=netbsd
+ ;;
+ netwinder)
+ basic_machine=armv4l-rebel
+ basic_os=linux
+ ;;
+ news | news700 | news800 | news900)
+ basic_machine=m68k-sony
+ basic_os=newsos
+ ;;
+ news1000)
+ basic_machine=m68030-sony
+ basic_os=newsos
+ ;;
+ necv70)
+ basic_machine=v70-nec
+ basic_os=sysv
+ ;;
+ nh3000)
+ basic_machine=m68k-harris
+ basic_os=cxux
+ ;;
+ nh[45]000)
+ basic_machine=m88k-harris
+ basic_os=cxux
+ ;;
+ nindy960)
+ basic_machine=i960-intel
+ basic_os=nindy
+ ;;
+ mon960)
+ basic_machine=i960-intel
+ basic_os=mon960
+ ;;
+ nonstopux)
+ basic_machine=mips-compaq
+ basic_os=nonstopux
+ ;;
+ os400)
+ basic_machine=powerpc-ibm
+ basic_os=os400
+ ;;
+ OSE68000 | ose68000)
+ basic_machine=m68000-ericsson
+ basic_os=ose
+ ;;
+ os68k)
+ basic_machine=m68k-none
+ basic_os=os68k
+ ;;
+ paragon)
+ basic_machine=i860-intel
+ basic_os=osf
+ ;;
+ parisc)
+ basic_machine=hppa-unknown
+ basic_os=linux
+ ;;
+ psp)
+ basic_machine=mipsallegrexel-sony
+ basic_os=psp
+ ;;
+ pw32)
+ basic_machine=i586-unknown
+ basic_os=pw32
+ ;;
+ rdos | rdos64)
+ basic_machine=x86_64-pc
+ basic_os=rdos
+ ;;
+ rdos32)
+ basic_machine=i386-pc
+ basic_os=rdos
+ ;;
+ rom68k)
+ basic_machine=m68k-rom68k
+ basic_os=coff
+ ;;
+ sa29200)
+ basic_machine=a29k-amd
+ basic_os=udi
+ ;;
+ sei)
+ basic_machine=mips-sei
+ basic_os=seiux
+ ;;
+ sequent)
+ basic_machine=i386-sequent
+ basic_os=
+ ;;
+ sps7)
+ basic_machine=m68k-bull
+ basic_os=sysv2
+ ;;
+ st2000)
+ basic_machine=m68k-tandem
+ basic_os=
+ ;;
+ stratus)
+ basic_machine=i860-stratus
+ basic_os=sysv4
+ ;;
+ sun2)
+ basic_machine=m68000-sun
+ basic_os=
+ ;;
+ sun2os3)
+ basic_machine=m68000-sun
+ basic_os=sunos3
+ ;;
+ sun2os4)
+ basic_machine=m68000-sun
+ basic_os=sunos4
+ ;;
+ sun3)
+ basic_machine=m68k-sun
+ basic_os=
+ ;;
+ sun3os3)
+ basic_machine=m68k-sun
+ basic_os=sunos3
+ ;;
+ sun3os4)
+ basic_machine=m68k-sun
+ basic_os=sunos4
+ ;;
+ sun4)
+ basic_machine=sparc-sun
+ basic_os=
+ ;;
+ sun4os3)
+ basic_machine=sparc-sun
+ basic_os=sunos3
+ ;;
+ sun4os4)
+ basic_machine=sparc-sun
+ basic_os=sunos4
+ ;;
+ sun4sol2)
+ basic_machine=sparc-sun
+ basic_os=solaris2
+ ;;
+ sun386 | sun386i | roadrunner)
+ basic_machine=i386-sun
+ basic_os=
+ ;;
+ sv1)
+ basic_machine=sv1-cray
+ basic_os=unicos
+ ;;
+ symmetry)
+ basic_machine=i386-sequent
+ basic_os=dynix
+ ;;
+ t3e)
+ basic_machine=alphaev5-cray
+ basic_os=unicos
+ ;;
+ t90)
+ basic_machine=t90-cray
+ basic_os=unicos
+ ;;
+ toad1)
+ basic_machine=pdp10-xkl
+ basic_os=tops20
+ ;;
+ tpf)
+ basic_machine=s390x-ibm
+ basic_os=tpf
+ ;;
+ udi29k)
+ basic_machine=a29k-amd
+ basic_os=udi
+ ;;
+ ultra3)
+ basic_machine=a29k-nyu
+ basic_os=sym1
+ ;;
+ v810 | necv810)
+ basic_machine=v810-nec
+ basic_os=none
+ ;;
+ vaxv)
+ basic_machine=vax-dec
+ basic_os=sysv
+ ;;
+ vms)
+ basic_machine=vax-dec
+ basic_os=vms
+ ;;
+ vsta)
+ basic_machine=i386-pc
+ basic_os=vsta
+ ;;
+ vxworks960)
+ basic_machine=i960-wrs
+ basic_os=vxworks
+ ;;
+ vxworks68)
+ basic_machine=m68k-wrs
+ basic_os=vxworks
+ ;;
+ vxworks29k)
+ basic_machine=a29k-wrs
+ basic_os=vxworks
+ ;;
+ xbox)
+ basic_machine=i686-pc
+ basic_os=mingw32
+ ;;
+ ymp)
+ basic_machine=ymp-cray
+ basic_os=unicos
+ ;;
+ *)
+ basic_machine=$1
+ basic_os=
+ ;;
+ esac
+ ;;
+esac
+
+# Decode 1-component or ad-hoc basic machines
+case $basic_machine in
+ # Here we handle the default manufacturer of certain CPU types. It is in
+ # some cases the only manufacturer, in others, it is the most popular.
+ w89k)
+ cpu=hppa1.1
+ vendor=winbond
+ ;;
+ op50n)
+ cpu=hppa1.1
+ vendor=oki
+ ;;
+ op60c)
+ cpu=hppa1.1
+ vendor=oki
+ ;;
+ ibm*)
+ cpu=i370
+ vendor=ibm
+ ;;
+ orion105)
+ cpu=clipper
+ vendor=highlevel
+ ;;
+ mac | mpw | mac-mpw)
+ cpu=m68k
+ vendor=apple
+ ;;
+ pmac | pmac-mpw)
+ cpu=powerpc
+ vendor=apple
+ ;;
+
+ # Recognize the various machine names and aliases which stand
+ # for a CPU type and a company and sometimes even an OS.
+ 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+ cpu=m68000
+ vendor=att
+ ;;
+ 3b*)
+ cpu=we32k
+ vendor=att
+ ;;
+ bluegene*)
+ cpu=powerpc
+ vendor=ibm
+ basic_os=cnk
+ ;;
+ decsystem10* | dec10*)
+ cpu=pdp10
+ vendor=dec
+ basic_os=tops10
+ ;;
+ decsystem20* | dec20*)
+ cpu=pdp10
+ vendor=dec
+ basic_os=tops20
+ ;;
+ delta | 3300 | motorola-3300 | motorola-delta \
+ | 3300-motorola | delta-motorola)
+ cpu=m68k
+ vendor=motorola
+ ;;
+ dpx2*)
+ cpu=m68k
+ vendor=bull
+ basic_os=sysv3
+ ;;
+ encore | umax | mmax)
+ cpu=ns32k
+ vendor=encore
+ ;;
+ elxsi)
+ cpu=elxsi
+ vendor=elxsi
+ basic_os=${basic_os:-bsd}
+ ;;
+ fx2800)
+ cpu=i860
+ vendor=alliant
+ ;;
+ genix)
+ cpu=ns32k
+ vendor=ns
+ ;;
+ h3050r* | hiux*)
+ cpu=hppa1.1
+ vendor=hitachi
+ basic_os=hiuxwe2
+ ;;
+ hp3k9[0-9][0-9] | hp9[0-9][0-9])
+ cpu=hppa1.0
+ vendor=hp
+ ;;
+ hp9k2[0-9][0-9] | hp9k31[0-9])
+ cpu=m68000
+ vendor=hp
+ ;;
+ hp9k3[2-9][0-9])
+ cpu=m68k
+ vendor=hp
+ ;;
+ hp9k6[0-9][0-9] | hp6[0-9][0-9])
+ cpu=hppa1.0
+ vendor=hp
+ ;;
+ hp9k7[0-79][0-9] | hp7[0-79][0-9])
+ cpu=hppa1.1
+ vendor=hp
+ ;;
+ hp9k78[0-9] | hp78[0-9])
+ # FIXME: really hppa2.0-hp
+ cpu=hppa1.1
+ vendor=hp
+ ;;
+ hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+ # FIXME: really hppa2.0-hp
+ cpu=hppa1.1
+ vendor=hp
+ ;;
+ hp9k8[0-9][13679] | hp8[0-9][13679])
+ cpu=hppa1.1
+ vendor=hp
+ ;;
+ hp9k8[0-9][0-9] | hp8[0-9][0-9])
+ cpu=hppa1.0
+ vendor=hp
+ ;;
+ i*86v32)
+ cpu=`echo "$1" | sed -e 's/86.*/86/'`
+ vendor=pc
+ basic_os=sysv32
+ ;;
+ i*86v4*)
+ cpu=`echo "$1" | sed -e 's/86.*/86/'`
+ vendor=pc
+ basic_os=sysv4
+ ;;
+ i*86v)
+ cpu=`echo "$1" | sed -e 's/86.*/86/'`
+ vendor=pc
+ basic_os=sysv
+ ;;
+ i*86sol2)
+ cpu=`echo "$1" | sed -e 's/86.*/86/'`
+ vendor=pc
+ basic_os=solaris2
+ ;;
+ j90 | j90-cray)
+ cpu=j90
+ vendor=cray
+ basic_os=${basic_os:-unicos}
+ ;;
+ iris | iris4d)
+ cpu=mips
+ vendor=sgi
+ case $basic_os in
+ irix*)
+ ;;
+ *)
+ basic_os=irix4
+ ;;
+ esac
+ ;;
+ miniframe)
+ cpu=m68000
+ vendor=convergent
+ ;;
+ *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*)
+ cpu=m68k
+ vendor=atari
+ basic_os=mint
+ ;;
+ news-3600 | risc-news)
+ cpu=mips
+ vendor=sony
+ basic_os=newsos
+ ;;
+ next | m*-next)
+ cpu=m68k
+ vendor=next
+ case $basic_os in
+ openstep*)
+ ;;
+ nextstep*)
+ ;;
+ ns2*)
+ basic_os=nextstep2
+ ;;
+ *)
+ basic_os=nextstep3
+ ;;
+ esac
+ ;;
+ np1)
+ cpu=np1
+ vendor=gould
+ ;;
+ op50n-* | op60c-*)
+ cpu=hppa1.1
+ vendor=oki
+ basic_os=proelf
+ ;;
+ pa-hitachi)
+ cpu=hppa1.1
+ vendor=hitachi
+ basic_os=hiuxwe2
+ ;;
+ pbd)
+ cpu=sparc
+ vendor=tti
+ ;;
+ pbb)
+ cpu=m68k
+ vendor=tti
+ ;;
+ pc532)
+ cpu=ns32k
+ vendor=pc532
+ ;;
+ pn)
+ cpu=pn
+ vendor=gould
+ ;;
+ power)
+ cpu=power
+ vendor=ibm
+ ;;
+ ps2)
+ cpu=i386
+ vendor=ibm
+ ;;
+ rm[46]00)
+ cpu=mips
+ vendor=siemens
+ ;;
+ rtpc | rtpc-*)
+ cpu=romp
+ vendor=ibm
+ ;;
+ sde)
+ cpu=mipsisa32
+ vendor=sde
+ basic_os=${basic_os:-elf}
+ ;;
+ simso-wrs)
+ cpu=sparclite
+ vendor=wrs
+ basic_os=vxworks
+ ;;
+ tower | tower-32)
+ cpu=m68k
+ vendor=ncr
+ ;;
+ vpp*|vx|vx-*)
+ cpu=f301
+ vendor=fujitsu
+ ;;
+ w65)
+ cpu=w65
+ vendor=wdc
+ ;;
+ w89k-*)
+ cpu=hppa1.1
+ vendor=winbond
+ basic_os=proelf
+ ;;
+ none)
+ cpu=none
+ vendor=none
+ ;;
+ leon|leon[3-9])
+ cpu=sparc
+ vendor=$basic_machine
+ ;;
+ leon-*|leon[3-9]-*)
+ cpu=sparc
+ vendor=`echo "$basic_machine" | sed 's/-.*//'`
+ ;;
+
+ *-*)
+ # shellcheck disable=SC2162
+ saved_IFS=$IFS
+ IFS="-" read cpu vendor <<EOF
+$basic_machine
+EOF
+ IFS=$saved_IFS
+ ;;
+ # We use `pc' rather than `unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+ cpu=$basic_machine
+ vendor=pc
+ ;;
+ # These rules are duplicated from below for sake of the special case above;
+ # i.e. things that normalized to x86 arches should also default to "pc"
+ pc98)
+ cpu=i386
+ vendor=pc
+ ;;
+ x64 | amd64)
+ cpu=x86_64
+ vendor=pc
+ ;;
+ # Recognize the basic CPU types without company name.
+ *)
+ cpu=$basic_machine
+ vendor=unknown
+ ;;
+esac
+
+unset -v basic_machine
+
+# Decode basic machines in the full and proper CPU-Company form.
+case $cpu-$vendor in
+ # Here we handle the default manufacturer of certain CPU types in canonical form. It is in
+ # some cases the only manufacturer, in others, it is the most popular.
+ craynv-unknown)
+ vendor=cray
+ basic_os=${basic_os:-unicosmp}
+ ;;
+ c90-unknown | c90-cray)
+ vendor=cray
+ basic_os=${Basic_os:-unicos}
+ ;;
+ fx80-unknown)
+ vendor=alliant
+ ;;
+ romp-unknown)
+ vendor=ibm
+ ;;
+ mmix-unknown)
+ vendor=knuth
+ ;;
+ microblaze-unknown | microblazeel-unknown)
+ vendor=xilinx
+ ;;
+ rs6000-unknown)
+ vendor=ibm
+ ;;
+ vax-unknown)
+ vendor=dec
+ ;;
+ pdp11-unknown)
+ vendor=dec
+ ;;
+ we32k-unknown)
+ vendor=att
+ ;;
+ cydra-unknown)
+ vendor=cydrome
+ ;;
+ i370-ibm*)
+ vendor=ibm
+ ;;
+ orion-unknown)
+ vendor=highlevel
+ ;;
+ xps-unknown | xps100-unknown)
+ cpu=xps100
+ vendor=honeywell
+ ;;
+
+ # Here we normalize CPU types with a missing or matching vendor
+ armh-unknown | armh-alt)
+ cpu=armv7l
+ vendor=alt
+ basic_os=${basic_os:-linux-gnueabihf}
+ ;;
+ dpx20-unknown | dpx20-bull)
+ cpu=rs6000
+ vendor=bull
+ basic_os=${basic_os:-bosx}
+ ;;
+
+ # Here we normalize CPU types irrespective of the vendor
+ amd64-*)
+ cpu=x86_64
+ ;;
+ blackfin-*)
+ cpu=bfin
+ basic_os=linux
+ ;;
+ c54x-*)
+ cpu=tic54x
+ ;;
+ c55x-*)
+ cpu=tic55x
+ ;;
+ c6x-*)
+ cpu=tic6x
+ ;;
+ e500v[12]-*)
+ cpu=powerpc
+ basic_os=${basic_os}"spe"
+ ;;
+ mips3*-*)
+ cpu=mips64
+ ;;
+ ms1-*)
+ cpu=mt
+ ;;
+ m68knommu-*)
+ cpu=m68k
+ basic_os=linux
+ ;;
+ m9s12z-* | m68hcs12z-* | hcs12z-* | s12z-*)
+ cpu=s12z
+ ;;
+ openrisc-*)
+ cpu=or32
+ ;;
+ parisc-*)
+ cpu=hppa
+ basic_os=linux
+ ;;
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ cpu=i586
+ ;;
+ pentiumpro-* | p6-* | 6x86-* | athlon-* | athalon_*-*)
+ cpu=i686
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+ cpu=i686
+ ;;
+ pentium4-*)
+ cpu=i786
+ ;;
+ pc98-*)
+ cpu=i386
+ ;;
+ ppc-* | ppcbe-*)
+ cpu=powerpc
+ ;;
+ ppcle-* | powerpclittle-*)
+ cpu=powerpcle
+ ;;
+ ppc64-*)
+ cpu=powerpc64
+ ;;
+ ppc64le-* | powerpc64little-*)
+ cpu=powerpc64le
+ ;;
+ sb1-*)
+ cpu=mipsisa64sb1
+ ;;
+ sb1el-*)
+ cpu=mipsisa64sb1el
+ ;;
+ sh5e[lb]-*)
+ cpu=`echo "$cpu" | sed 's/^\(sh.\)e\(.\)$/\1\2e/'`
+ ;;
+ spur-*)
+ cpu=spur
+ ;;
+ strongarm-* | thumb-*)
+ cpu=arm
+ ;;
+ tx39-*)
+ cpu=mipstx39
+ ;;
+ tx39el-*)
+ cpu=mipstx39el
+ ;;
+ x64-*)
+ cpu=x86_64
+ ;;
+ xscale-* | xscalee[bl]-*)
+ cpu=`echo "$cpu" | sed 's/^xscale/arm/'`
+ ;;
+ arm64-* | aarch64le-*)
+ cpu=aarch64
+ ;;
+
+ # Recognize the canonical CPU Types that limit and/or modify the
+ # company names they are paired with.
+ cr16-*)
+ basic_os=${basic_os:-elf}
+ ;;
+ crisv32-* | etraxfs*-*)
+ cpu=crisv32
+ vendor=axis
+ ;;
+ cris-* | etrax*-*)
+ cpu=cris
+ vendor=axis
+ ;;
+ crx-*)
+ basic_os=${basic_os:-elf}
+ ;;
+ neo-tandem)
+ cpu=neo
+ vendor=tandem
+ ;;
+ nse-tandem)
+ cpu=nse
+ vendor=tandem
+ ;;
+ nsr-tandem)
+ cpu=nsr
+ vendor=tandem
+ ;;
+ nsv-tandem)
+ cpu=nsv
+ vendor=tandem
+ ;;
+ nsx-tandem)
+ cpu=nsx
+ vendor=tandem
+ ;;
+ mipsallegrexel-sony)
+ cpu=mipsallegrexel
+ vendor=sony
+ ;;
+ tile*-*)
+ basic_os=${basic_os:-linux-gnu}
+ ;;
+
+ *)
+ # Recognize the canonical CPU types that are allowed with any
+ # company name.
+ case $cpu in
+ 1750a | 580 \
+ | a29k \
+ | aarch64 | aarch64_be \
+ | abacus \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] \
+ | alphapca5[67] | alpha64pca5[67] \
+ | am33_2.0 \
+ | amdgcn \
+ | arc | arceb | arc32 | arc64 \
+ | arm | arm[lb]e | arme[lb] | armv* \
+ | avr | avr32 \
+ | asmjs \
+ | ba \
+ | be32 | be64 \
+ | bfin | bpf | bs2000 \
+ | c[123]* | c30 | [cjt]90 | c4x \
+ | c8051 | clipper | craynv | csky | cydra \
+ | d10v | d30v | dlx | dsp16xx \
+ | e2k | elxsi | epiphany \
+ | f30[01] | f700 | fido | fr30 | frv | ft32 | fx80 \
+ | h8300 | h8500 \
+ | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | hexagon \
+ | i370 | i*86 | i860 | i960 | ia16 | ia64 \
+ | ip2k | iq2000 \
+ | k1om \
+ | le32 | le64 \
+ | lm32 \
+ | loongarch32 | loongarch64 | loongarchx32 \
+ | m32c | m32r | m32rle \
+ | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
+ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
+ | m88110 | m88k | maxq | mb | mcore | mep | metag \
+ | microblaze | microblazeel \
+ | mips | mipsbe | mipseb | mipsel | mipsle \
+ | mips16 \
+ | mips64 | mips64eb | mips64el \
+ | mips64octeon | mips64octeonel \
+ | mips64orion | mips64orionel \
+ | mips64r5900 | mips64r5900el \
+ | mips64vr | mips64vrel \
+ | mips64vr4100 | mips64vr4100el \
+ | mips64vr4300 | mips64vr4300el \
+ | mips64vr5000 | mips64vr5000el \
+ | mips64vr5900 | mips64vr5900el \
+ | mipsisa32 | mipsisa32el \
+ | mipsisa32r2 | mipsisa32r2el \
+ | mipsisa32r3 | mipsisa32r3el \
+ | mipsisa32r5 | mipsisa32r5el \
+ | mipsisa32r6 | mipsisa32r6el \
+ | mipsisa64 | mipsisa64el \
+ | mipsisa64r2 | mipsisa64r2el \
+ | mipsisa64r3 | mipsisa64r3el \
+ | mipsisa64r5 | mipsisa64r5el \
+ | mipsisa64r6 | mipsisa64r6el \
+ | mipsisa64sb1 | mipsisa64sb1el \
+ | mipsisa64sr71k | mipsisa64sr71kel \
+ | mipsr5900 | mipsr5900el \
+ | mipstx39 | mipstx39el \
+ | mmix \
+ | mn10200 | mn10300 \
+ | moxie \
+ | mt \
+ | msp430 \
+ | nds32 | nds32le | nds32be \
+ | nfp \
+ | nios | nios2 | nios2eb | nios2el \
+ | none | np1 | ns16k | ns32k | nvptx \
+ | open8 \
+ | or1k* \
+ | or32 \
+ | orion \
+ | picochip \
+ | pdp10 | pdp11 | pj | pjl | pn | power \
+ | powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
+ | pru \
+ | pyramid \
+ | riscv | riscv32 | riscv32be | riscv64 | riscv64be \
+ | rl78 | romp | rs6000 | rx \
+ | s390 | s390x \
+ | score \
+ | sh | shl \
+ | sh[1234] | sh[24]a | sh[24]ae[lb] | sh[23]e | she[lb] | sh[lb]e \
+ | sh[1234]e[lb] | sh[12345][lb]e | sh[23]ele | sh64 | sh64le \
+ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet \
+ | sparclite \
+ | sparcv8 | sparcv9 | sparcv9b | sparcv9v | sv1 | sx* \
+ | spu \
+ | tahoe \
+ | thumbv7* \
+ | tic30 | tic4x | tic54x | tic55x | tic6x | tic80 \
+ | tron \
+ | ubicom32 \
+ | v70 | v850 | v850e | v850e1 | v850es | v850e2 | v850e2v3 \
+ | vax \
+ | visium \
+ | w65 \
+ | wasm32 | wasm64 \
+ | we32k \
+ | x86 | x86_64 | xc16x | xgate | xps100 \
+ | xstormy16 | xtensa* \
+ | ymp \
+ | z8k | z80)
+ ;;
+
+ *)
+ echo Invalid configuration \`"$1"\': machine \`"$cpu-$vendor"\' not recognized 1>&2
+ exit 1
+ ;;
+ esac
+ ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $vendor in
+ digital*)
+ vendor=dec
+ ;;
+ commodore*)
+ vendor=cbm
+ ;;
+ *)
+ ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if test x$basic_os != x
+then
+
+# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just
+# set os.
+case $basic_os in
+ gnu/linux*)
+ kernel=linux
+ os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'`
+ ;;
+ os2-emx)
+ kernel=os2
+ os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'`
+ ;;
+ nto-qnx*)
+ kernel=nto
+ os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'`
+ ;;
+ *-*)
+ # shellcheck disable=SC2162
+ saved_IFS=$IFS
+ IFS="-" read kernel os <<EOF
+$basic_os
+EOF
+ IFS=$saved_IFS
+ ;;
+ # Default OS when just kernel was specified
+ nto*)
+ kernel=nto
+ os=`echo "$basic_os" | sed -e 's|nto|qnx|'`
+ ;;
+ linux*)
+ kernel=linux
+ os=`echo "$basic_os" | sed -e 's|linux|gnu|'`
+ ;;
+ *)
+ kernel=
+ os=$basic_os
+ ;;
+esac
+
+# Now, normalize the OS (knowing we just have one component, it's not a kernel,
+# etc.)
+case $os in
+ # First match some system type aliases that might get confused
+ # with valid system types.
+ # solaris* is a basic system type, with this one exception.
+ auroraux)
+ os=auroraux
+ ;;
+ bluegene*)
+ os=cnk
+ ;;
+ solaris1 | solaris1.*)
+ os=`echo "$os" | sed -e 's|solaris1|sunos4|'`
+ ;;
+ solaris)
+ os=solaris2
+ ;;
+ unixware*)
+ os=sysv4.2uw
+ ;;
+ # es1800 is here to avoid being matched by es* (a different OS)
+ es1800*)
+ os=ose
+ ;;
+ # Some version numbers need modification
+ chorusos*)
+ os=chorusos
+ ;;
+ isc)
+ os=isc2.2
+ ;;
+ sco6)
+ os=sco5v6
+ ;;
+ sco5)
+ os=sco3.2v5
+ ;;
+ sco4)
+ os=sco3.2v4
+ ;;
+ sco3.2.[4-9]*)
+ os=`echo "$os" | sed -e 's/sco3.2./sco3.2v/'`
+ ;;
+ sco*v* | scout)
+ # Don't match below
+ ;;
+ sco*)
+ os=sco3.2v2
+ ;;
+ psos*)
+ os=psos
+ ;;
+ qnx*)
+ os=qnx
+ ;;
+ hiux*)
+ os=hiuxwe2
+ ;;
+ lynx*178)
+ os=lynxos178
+ ;;
+ lynx*5)
+ os=lynxos5
+ ;;
+ lynxos*)
+ # don't get caught up in next wildcard
+ ;;
+ lynx*)
+ os=lynxos
+ ;;
+ mac[0-9]*)
+ os=`echo "$os" | sed -e 's|mac|macos|'`
+ ;;
+ opened*)
+ os=openedition
+ ;;
+ os400*)
+ os=os400
+ ;;
+ sunos5*)
+ os=`echo "$os" | sed -e 's|sunos5|solaris2|'`
+ ;;
+ sunos6*)
+ os=`echo "$os" | sed -e 's|sunos6|solaris3|'`
+ ;;
+ wince*)
+ os=wince
+ ;;
+ utek*)
+ os=bsd
+ ;;
+ dynix*)
+ os=bsd
+ ;;
+ acis*)
+ os=aos
+ ;;
+ atheos*)
+ os=atheos
+ ;;
+ syllable*)
+ os=syllable
+ ;;
+ 386bsd)
+ os=bsd
+ ;;
+ ctix* | uts*)
+ os=sysv
+ ;;
+ nova*)
+ os=rtmk-nova
+ ;;
+ ns2)
+ os=nextstep2
+ ;;
+ # Preserve the version number of sinix5.
+ sinix5.*)
+ os=`echo "$os" | sed -e 's|sinix|sysv|'`
+ ;;
+ sinix*)
+ os=sysv4
+ ;;
+ tpf*)
+ os=tpf
+ ;;
+ triton*)
+ os=sysv3
+ ;;
+ oss*)
+ os=sysv3
+ ;;
+ svr4*)
+ os=sysv4
+ ;;
+ svr3)
+ os=sysv3
+ ;;
+ sysvr4)
+ os=sysv4
+ ;;
+ ose*)
+ os=ose
+ ;;
+ *mint | mint[0-9]* | *MiNT | MiNT[0-9]*)
+ os=mint
+ ;;
+ dicos*)
+ os=dicos
+ ;;
+ pikeos*)
+ # Until real need of OS specific support for
+ # particular features comes up, bare metal
+ # configurations are quite functional.
+ case $cpu in
+ arm*)
+ os=eabi
+ ;;
+ *)
+ os=elf
+ ;;
+ esac
+ ;;
+ *)
+ # No normalization, but not necessarily accepted, that comes below.
+ ;;
+esac
+
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system. Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+kernel=
+case $cpu-$vendor in
+ score-*)
+ os=elf
+ ;;
+ spu-*)
+ os=elf
+ ;;
+ *-acorn)
+ os=riscix1.2
+ ;;
+ arm*-rebel)
+ kernel=linux
+ os=gnu
+ ;;
+ arm*-semi)
+ os=aout
+ ;;
+ c4x-* | tic4x-*)
+ os=coff
+ ;;
+ c8051-*)
+ os=elf
+ ;;
+ clipper-intergraph)
+ os=clix
+ ;;
+ hexagon-*)
+ os=elf
+ ;;
+ tic54x-*)
+ os=coff
+ ;;
+ tic55x-*)
+ os=coff
+ ;;
+ tic6x-*)
+ os=coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+ os=tops20
+ ;;
+ pdp11-*)
+ os=none
+ ;;
+ *-dec | vax-*)
+ os=ultrix4.2
+ ;;
+ m68*-apollo)
+ os=domain
+ ;;
+ i386-sun)
+ os=sunos4.0.2
+ ;;
+ m68000-sun)
+ os=sunos3
+ ;;
+ m68*-cisco)
+ os=aout
+ ;;
+ mep-*)
+ os=elf
+ ;;
+ mips*-cisco)
+ os=elf
+ ;;
+ mips*-*)
+ os=elf
+ ;;
+ or32-*)
+ os=coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=sysv3
+ ;;
+ sparc-* | *-sun)
+ os=sunos4.1.1
+ ;;
+ pru-*)
+ os=elf
+ ;;
+ *-be)
+ os=beos
+ ;;
+ *-ibm)
+ os=aix
+ ;;
+ *-knuth)
+ os=mmixware
+ ;;
+ *-wec)
+ os=proelf
+ ;;
+ *-winbond)
+ os=proelf
+ ;;
+ *-oki)
+ os=proelf
+ ;;
+ *-hp)
+ os=hpux
+ ;;
+ *-hitachi)
+ os=hiux
+ ;;
+ i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+ os=sysv
+ ;;
+ *-cbm)
+ os=amigaos
+ ;;
+ *-dg)
+ os=dgux
+ ;;
+ *-dolphin)
+ os=sysv3
+ ;;
+ m68k-ccur)
+ os=rtu
+ ;;
+ m88k-omron*)
+ os=luna
+ ;;
+ *-next)
+ os=nextstep
+ ;;
+ *-sequent)
+ os=ptx
+ ;;
+ *-crds)
+ os=unos
+ ;;
+ *-ns)
+ os=genix
+ ;;
+ i370-*)
+ os=mvs
+ ;;
+ *-gould)
+ os=sysv
+ ;;
+ *-highlevel)
+ os=bsd
+ ;;
+ *-encore)
+ os=bsd
+ ;;
+ *-sgi)
+ os=irix
+ ;;
+ *-siemens)
+ os=sysv4
+ ;;
+ *-masscomp)
+ os=rtu
+ ;;
+ f30[01]-fujitsu | f700-fujitsu)
+ os=uxpv
+ ;;
+ *-rom68k)
+ os=coff
+ ;;
+ *-*bug)
+ os=coff
+ ;;
+ *-apple)
+ os=macos
+ ;;
+ *-atari*)
+ os=mint
+ ;;
+ *-wrs)
+ os=vxworks
+ ;;
+ *)
+ os=none
+ ;;
+esac
+
+fi
+
+# Now, validate our (potentially fixed-up) OS.
+case $os in
+ # Sometimes we do "kernel-libc", so those need to count as OSes.
+ musl* | newlib* | relibc* | uclibc*)
+ ;;
+ # Likewise for "kernel-abi"
+ eabi* | gnueabi*)
+ ;;
+ # VxWorks passes extra cpu info in the 4th filed.
+ simlinux | simwindows | spe)
+ ;;
+ # Now accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST end in a * to match a version number.
+ gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \
+ | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \
+ | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \
+ | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \
+ | hiux* | abug | nacl* | netware* | windows* \
+ | os9* | macos* | osx* | ios* \
+ | mpw* | magic* | mmixware* | mon960* | lnews* \
+ | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \
+ | aos* | aros* | cloudabi* | sortix* | twizzler* \
+ | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
+ | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
+ | mirbsd* | netbsd* | dicos* | openedition* | ose* \
+ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
+ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
+ | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
+ | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
+ | udi* | lites* | ieee* | go32* | aux* | hcos* \
+ | chorusrdb* | cegcc* | glidix* | serenity* \
+ | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
+ | midipix* | mingw32* | mingw64* | mint* \
+ | uxpv* | beos* | mpeix* | udk* | moxiebox* \
+ | interix* | uwin* | mks* | rhapsody* | darwin* \
+ | openstep* | oskit* | conix* | pw32* | nonstopux* \
+ | storm-chaos* | tops10* | tenex* | tops20* | its* \
+ | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \
+ | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \
+ | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \
+ | skyos* | haiku* | rdos* | toppers* | drops* | es* \
+ | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
+ | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
+ | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \
+ | fiwix* )
+ ;;
+ # This one is extra strict with allowed versions
+ sco3.2v2 | sco3.2v[4-9]* | sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ ;;
+ none)
+ ;;
+ *)
+ echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+
+# As a final step for OS-related things, validate the OS-kernel combination
+# (given a valid OS), if there is a kernel.
+case $kernel-$os in
+ linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \
+ | linux-musl* | linux-relibc* | linux-uclibc* )
+ ;;
+ uclinux-uclibc* )
+ ;;
+ -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* )
+ # These are just libc implementations, not actual OSes, and thus
+ # require a kernel.
+ echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2
+ exit 1
+ ;;
+ kfreebsd*-gnu* | kopensolaris*-gnu*)
+ ;;
+ vxworks-simlinux | vxworks-simwindows | vxworks-spe)
+ ;;
+ nto-qnx*)
+ ;;
+ os2-emx)
+ ;;
+ *-eabi* | *-gnueabi*)
+ ;;
+ -*)
+ # Blank kernel with real OS is always fine.
+ ;;
+ *-*)
+ echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2
+ exit 1
+ ;;
+esac
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer. We pick the logical manufacturer.
+case $vendor in
+ unknown)
+ case $cpu-$os in
+ *-riscix*)
+ vendor=acorn
+ ;;
+ *-sunos*)
+ vendor=sun
+ ;;
+ *-cnk* | *-aix*)
+ vendor=ibm
+ ;;
+ *-beos*)
+ vendor=be
+ ;;
+ *-hpux*)
+ vendor=hp
+ ;;
+ *-mpeix*)
+ vendor=hp
+ ;;
+ *-hiux*)
+ vendor=hitachi
+ ;;
+ *-unos*)
+ vendor=crds
+ ;;
+ *-dgux*)
+ vendor=dg
+ ;;
+ *-luna*)
+ vendor=omron
+ ;;
+ *-genix*)
+ vendor=ns
+ ;;
+ *-clix*)
+ vendor=intergraph
+ ;;
+ *-mvs* | *-opened*)
+ vendor=ibm
+ ;;
+ *-os400*)
+ vendor=ibm
+ ;;
+ s390-* | s390x-*)
+ vendor=ibm
+ ;;
+ *-ptx*)
+ vendor=sequent
+ ;;
+ *-tpf*)
+ vendor=ibm
+ ;;
+ *-vxsim* | *-vxworks* | *-windiss*)
+ vendor=wrs
+ ;;
+ *-aux*)
+ vendor=apple
+ ;;
+ *-hms*)
+ vendor=hitachi
+ ;;
+ *-mpw* | *-macos*)
+ vendor=apple
+ ;;
+ *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*)
+ vendor=atari
+ ;;
+ *-vos*)
+ vendor=stratus
+ ;;
+ esac
+ ;;
+esac
+
+echo "$cpu-$vendor-${kernel:+$kernel-}$os"
+exit
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/configure b/configure
new file mode 100755
index 00000000..a3f5ce87
--- /dev/null
+++ b/configure
@@ -0,0 +1,16049 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.71 for sg3_utils 1.48.
+#
+# Report bugs to <dgilbert@interlog.com>.
+#
+#
+# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
+# Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+as_nop=:
+if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else $as_nop
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+
+# Reset variables that may have inherited troublesome values from
+# the environment.
+
+# IFS needs to be set, to space, tab, and newline, in precisely that order.
+# (If _AS_PATH_WALK were called with IFS unset, it would have the
+# side effect of setting IFS to empty, thus disabling word splitting.)
+# Quoting is to prevent editors from complaining about space-tab.
+as_nl='
+'
+export as_nl
+IFS=" "" $as_nl"
+
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# Ensure predictable behavior from utilities with locale-dependent output.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# We cannot yet rely on "unset" to work, but we need these variables
+# to be unset--not just set to an empty or harmless value--now, to
+# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct
+# also avoids known problems related to "unset" and subshell syntax
+# in other old shells (e.g. bash 2.01 and pdksh 5.2.14).
+for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH
+do eval test \${$as_var+y} \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+
+# Ensure that fds 0, 1, and 2 are open.
+if (exec 3>&0) 2>/dev/null; then :; else exec 0</dev/null; fi
+if (exec 3>&1) 2>/dev/null; then :; else exec 1>/dev/null; fi
+if (exec 3>&2) ; then :; else exec 2>/dev/null; fi
+
+# The user is always right.
+if ${PATH_SEPARATOR+false} :; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ test -r "$as_dir$0" && as_myself=$as_dir$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+
+# Use a proper internal environment variable to ensure we don't fall
+ # into an infinite loop, continuously re-executing ourselves.
+ if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+ _as_can_reexec=no; export _as_can_reexec;
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+ fi
+ # We don't want this to propagate to other subprocesses.
+ { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+ as_bourne_compatible="as_nop=:
+if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '\${1+\"\$@\"}'='\"\$@\"'
+ setopt NO_GLOB_SUBST
+else \$as_nop
+ case \`(set -o) 2>/dev/null\` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+"
+ as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" )
+then :
+
+else \$as_nop
+ exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+blah=\$(echo \$(echo blah))
+test x\"\$blah\" = xblah || exit 1
+test -x / || exit 1"
+ as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+ as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+ eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+ test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+
+ test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || (
+ ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+ ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+ PATH=/empty FPATH=/empty; export PATH FPATH
+ test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\
+ || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+ if (eval "$as_required") 2>/dev/null
+then :
+ as_have_required=yes
+else $as_nop
+ as_have_required=no
+fi
+ if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null
+then :
+
+else $as_nop
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ as_found=:
+ case $as_dir in #(
+ /*)
+ for as_base in sh bash ksh sh5; do
+ # Try only shells that exist, to save several forks.
+ as_shell=$as_dir$as_base
+ if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+ as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null
+then :
+ CONFIG_SHELL=$as_shell as_have_required=yes
+ if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null
+then :
+ break 2
+fi
+fi
+ done;;
+ esac
+ as_found=false
+done
+IFS=$as_save_IFS
+if $as_found
+then :
+
+else $as_nop
+ if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+ as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null
+then :
+ CONFIG_SHELL=$SHELL as_have_required=yes
+fi
+fi
+
+
+ if test "x$CONFIG_SHELL" != x
+then :
+ export CONFIG_SHELL
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+ if test x$as_have_required = xno
+then :
+ printf "%s\n" "$0: This script requires a shell more modern than all"
+ printf "%s\n" "$0: the shells that I found on your system."
+ if test ${ZSH_VERSION+y} ; then
+ printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+ printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later."
+ else
+ printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and
+$0: dgilbert@interlog.com about your system, including any
+$0: error possibly output before this message. Then install
+$0: a modern shell, or manually run the script under such a
+$0: shell if you do have one."
+ fi
+ exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+# as_fn_nop
+# ---------
+# Do nothing but, unlike ":", preserve the value of $?.
+as_fn_nop ()
+{
+ return $?
+}
+as_nop=as_fn_nop
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null
+then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else $as_nop
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null
+then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else $as_nop
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+# as_fn_nop
+# ---------
+# Do nothing but, unlike ":", preserve the value of $?.
+as_fn_nop ()
+{
+ return $?
+}
+as_nop=as_fn_nop
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ printf "%s\n" "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+ as_lineno_1=$LINENO as_lineno_1a=$LINENO
+ as_lineno_2=$LINENO as_lineno_2a=$LINENO
+ eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+ test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+ # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
+ sed -n '
+ p
+ /[$]LINENO/=
+ ' <$as_myself |
+ sed '
+ s/[$]LINENO.*/&-/
+ t lineno
+ b
+ :lineno
+ N
+ :loop
+ s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+ t loop
+ s/-\n.*//
+ ' >$as_me.lineno &&
+ chmod +x "$as_me.lineno" ||
+ { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+ # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+ # already done that, so ensure we don't try to do so again and fall
+ # in an infinite loop. This has already happened in practice.
+ _as_can_reexec=no; export _as_can_reexec
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensitive to this).
+ . "./$as_me.lineno"
+ # Exit status is that of the last command.
+ exit
+}
+
+
+# Determine whether it's possible to make 'echo' print without a newline.
+# These variables are no longer used directly by Autoconf, but are AC_SUBSTed
+# for compatibility with existing Makefiles.
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+# For backward compatibility with old third-party macros, we provide
+# the shell variables $as_echo and $as_echo_n. New code should use
+# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively.
+as_echo='printf %s\n'
+as_echo_n='printf %s'
+
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='sg3_utils'
+PACKAGE_TARNAME='sg3_utils'
+PACKAGE_VERSION='1.48'
+PACKAGE_STRING='sg3_utils 1.48'
+PACKAGE_BUGREPORT='dgilbert@interlog.com'
+PACKAGE_URL=''
+
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stddef.h>
+#ifdef HAVE_STDIO_H
+# include <stdio.h>
+#endif
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_header_c_list=
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+LIBOBJS
+PT_DUMMY_FALSE
+PT_DUMMY_TRUE
+DEBUG_FALSE
+DEBUG_TRUE
+OS_OTHER_FALSE
+OS_OTHER_TRUE
+OS_HAIKU_FALSE
+OS_HAIKU_TRUE
+OS_OPENBSD_FALSE
+OS_OPENBSD_TRUE
+OS_NETBSD_FALSE
+OS_NETBSD_TRUE
+OS_ANDROID_FALSE
+OS_ANDROID_TRUE
+OS_WIN32_CYGWIN_FALSE
+OS_WIN32_CYGWIN_TRUE
+OS_WIN32_MINGW_FALSE
+OS_WIN32_MINGW_TRUE
+OS_SOLARIS_FALSE
+OS_SOLARIS_TRUE
+OS_OSF_FALSE
+OS_OSF_TRUE
+OS_LINUX_FALSE
+OS_LINUX_TRUE
+OS_FREEBSD_FALSE
+OS_FREEBSD_TRUE
+os_libs
+os_cflags
+CPP
+GETOPT_O_FILES
+RT_LIB
+PTHREAD_LIB
+LT_SYS_LIBRARY_PATH
+OTOOL64
+OTOOL
+LIPO
+NMEDIT
+DSYMUTIL
+MANIFEST_TOOL
+RANLIB
+DLLTOOL
+OBJDUMP
+FILECMD
+LN_S
+NM
+ac_ct_DUMPBIN
+DUMPBIN
+LD
+FGREP
+EGREP
+GREP
+SED
+host_os
+host_vendor
+host_cpu
+host
+build_os
+build_vendor
+build_cpu
+build
+LIBTOOL
+ac_ct_AR
+AR
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+am__nodep
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__include
+DEPDIR
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+MAINT
+MAINTAINER_MODE_FALSE
+MAINTAINER_MODE_TRUE
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+AM_DEFAULT_V
+AM_V
+CSCOPE
+ETAGS
+CTAGS
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+AWK
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+STRIP
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+runstatedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL
+am__quote'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_silent_rules
+enable_maintainer_mode
+enable_dependency_tracking
+enable_shared
+enable_static
+with_pic
+enable_fast_install
+with_aix_soname
+with_gnu_ld
+with_sysroot
+enable_libtool_lock
+enable_debug
+enable_pt_dummy
+enable_linuxbsg
+enable_win32_spt_direct
+enable_scsistrings
+enable_nvme_supp
+enable_fast_lebe
+enable_linux_sgv4
+'
+ ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+LT_SYS_LIBRARY_PATH
+CPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval $ac_prev=\$ac_option
+ ac_prev=
+ continue
+ fi
+
+ case $ac_option in
+ *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+ *=) ac_optarg= ;;
+ *) ac_optarg=yes ;;
+ esac
+
+ case $ac_dashdash$ac_option in
+ --)
+ ac_dashdash=yes ;;
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=*)
+ datadir=$ac_optarg ;;
+
+ -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+ | --dataroo | --dataro | --datar)
+ ac_prev=datarootdir ;;
+ -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+ datarootdir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: \`$ac_useropt'"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=no ;;
+
+ -docdir | --docdir | --docdi | --doc | --do)
+ ac_prev=docdir ;;
+ -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+ docdir=$ac_optarg ;;
+
+ -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+ ac_prev=dvidir ;;
+ -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+ dvidir=$ac_optarg ;;
+
+ -enable-* | --enable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: \`$ac_useropt'"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=\$ac_optarg ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+ ac_prev=htmldir ;;
+ -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+ | --ht=*)
+ htmldir=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localedir | --localedir | --localedi | --localed | --locale)
+ ac_prev=localedir ;;
+ -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+ localedir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst | --locals)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+ ac_prev=pdfdir ;;
+ -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+ pdfdir=$ac_optarg ;;
+
+ -psdir | --psdir | --psdi | --psd | --ps)
+ ac_prev=psdir ;;
+ -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+ psdir=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: \`$ac_useropt'"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=\$ac_optarg ;;
+
+ -without-* | --without-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: \`$ac_useropt'"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=no ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ case $ac_envvar in #(
+ '' | [0-9]* | *[!_$as_cr_alnum]* )
+ as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+ esac
+ eval $ac_envvar=\$ac_optarg
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+ case $enable_option_checking in
+ no) ;;
+ fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+ *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+ esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
+ datadir sysconfdir sharedstatedir localstatedir includedir \
+ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+ libdir localedir mandir runstatedir
+do
+ eval ac_val=\$$ac_var
+ # Remove trailing slashes.
+ case $ac_val in
+ */ )
+ ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+ eval $ac_var=\$ac_val;;
+ esac
+ # Be sure to have absolute directory names.
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) continue;;
+ NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+ esac
+ as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+ as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+ as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then the parent directory.
+ ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_myself" : 'X\(//\)[^/]' \| \
+ X"$as_myself" : 'X\(//\)$' \| \
+ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$as_myself" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r "$srcdir/$ac_unique_file"; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+ test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+ as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+ cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+ pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+ srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+ eval ac_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_env_${ac_var}_value=\$${ac_var}
+ eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures sg3_utils 1.48 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking ...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --docdir=DIR documentation root [DATAROOTDIR/doc/sg3_utils]
+ --htmldir=DIR html documentation [DOCDIR]
+ --dvidir=DIR dvi documentation [DOCDIR]
+ --pdfdir=DIR pdf documentation [DOCDIR]
+ --psdir=DIR ps documentation [DOCDIR]
+_ACEOF
+
+ cat <<\_ACEOF
+
+Program names:
+ --program-prefix=PREFIX prepend PREFIX to installed program names
+ --program-suffix=SUFFIX append SUFFIX to installed program names
+ --program-transform-name=PROGRAM run sed PROGRAM on installed program names
+
+System types:
+ --build=BUILD configure for building on BUILD [guessed]
+ --host=HOST cross-compile to build programs to run on HOST [BUILD]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of sg3_utils 1.48:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-option-checking ignore unrecognized --enable/--with options
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-silent-rules less verbose build output (undo: "make V=1")
+ --disable-silent-rules verbose build output (undo: "make V=0")
+ --enable-maintainer-mode
+ enable make rules and dependencies not useful (and
+ sometimes confusing) to the casual installer
+ --enable-dependency-tracking
+ do not reject slow dependency extractors
+ --disable-dependency-tracking
+ speeds up one-time build
+ --enable-shared[=PKGS] build shared libraries [default=yes]
+ --enable-static[=PKGS] build static libraries [default=yes]
+ --enable-fast-install[=PKGS]
+ optimize for fast installation [default=yes]
+ --disable-libtool-lock avoid locking (might break parallel builds)
+ --enable-debug Turn on debugging
+ --enable-pt_dummy pass-through codes compiles, does nothing
+ --disable-linuxbsg option ignored, this is placeholder
+ --enable-win32-spt-direct
+ enable Win32 SPT Direct
+ --disable-scsistrings Disable full SCSI sense strings and NVMe status
+ strings
+ --disable-nvme-supp remove all or most NVMe code
+ --disable-fast-lebe use generic little-endian/big-endian code instead
+ --disable-linux-sgv4 for Linux sg driver avoid v4 interface even if
+ available
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use
+ both]
+ --with-aix-soname=aix|svr4|both
+ shared library versioning (aka "SONAME") variant to
+ provide on AIX, [default=aix].
+ --with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-sysroot[=DIR] Search for dependent libraries within DIR (or the
+ compiler's sysroot if not specified).
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ LIBS libraries to pass to the linker, e.g. -l<library>
+ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+ you have headers in a nonstandard directory <include dir>
+ LT_SYS_LIBRARY_PATH
+ User-defined run-time library search path.
+ CPP C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <dgilbert@interlog.com>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d "$ac_dir" ||
+ { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+ continue
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+ cd "$ac_dir" || { ac_status=$?; continue; }
+ # Check for configure.gnu first; this name is used for a wrapper for
+ # Metaconfig's "Configure" on case-insensitive file systems.
+ if test -f "$ac_srcdir/configure.gnu"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+ elif test -f "$ac_srcdir/configure"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure" --help=recursive
+ else
+ printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi || ac_status=$?
+ cd "$ac_pwd" || { ac_status=$?; break; }
+ done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+ cat <<\_ACEOF
+sg3_utils configure 1.48
+generated by GNU Autoconf 2.71
+
+Copyright (C) 2021 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest.beam
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext
+then :
+ ac_retval=0
+else $as_nop
+ printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }
+then :
+ ac_retval=0
+else $as_nop
+ printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ eval "$3=yes"
+else $as_nop
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+ For example, HP-UX 11i <limits.h> declares gettimeofday. */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $2 (); below. */
+
+#include <limits.h>
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main (void)
+{
+return $2 ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ eval "$3=yes"
+else $as_nop
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } > conftest.i && {
+ test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ }
+then :
+ ac_retval=0
+else $as_nop
+ printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+ac_configure_args_raw=
+for ac_arg
+do
+ case $ac_arg in
+ *\'*)
+ ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ as_fn_append ac_configure_args_raw " '$ac_arg'"
+done
+
+case $ac_configure_args_raw in
+ *$as_nl*)
+ ac_safe_unquote= ;;
+ *)
+ ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab.
+ ac_unsafe_a="$ac_unsafe_z#~"
+ ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g"
+ ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;;
+esac
+
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by sg3_utils $as_me 1.48, which was
+generated by GNU Autoconf 2.71. Invocation command line was
+
+ $ $0$ac_configure_args_raw
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ printf "%s\n" "PATH: $as_dir"
+ done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *\'*)
+ ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+ 2)
+ as_fn_append ac_configure_args1 " '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ as_fn_append ac_configure_args " '$ac_arg'"
+ ;;
+ esac
+ done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+ # Sanitize IFS.
+ IFS=" "" $as_nl"
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ printf "%s\n" "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+(
+ for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+ (set) 2>&1 |
+ case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ sed -n \
+ "s/'\''/'\''\\\\'\'''\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+ ;; #(
+ *)
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+)
+ echo
+
+ printf "%s\n" "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ printf "%s\n" "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ printf "%s\n" "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ printf "%s\n" "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ printf "%s\n" "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+ echo
+ cat confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ printf "%s\n" "$as_me: caught signal $ac_signal"
+ printf "%s\n" "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core core.conftest.* &&
+ rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+printf "%s\n" "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h
+
+printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h
+
+printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h
+
+printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h
+
+printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h
+
+printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+if test -n "$CONFIG_SITE"; then
+ ac_site_files="$CONFIG_SITE"
+elif test "x$prefix" != xNONE; then
+ ac_site_files="$prefix/share/config.site $prefix/etc/config.site"
+else
+ ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+fi
+
+for ac_site_file in $ac_site_files
+do
+ case $ac_site_file in #(
+ */*) :
+ ;; #(
+ *) :
+ ac_site_file=./$ac_site_file ;;
+esac
+ if test -f "$ac_site_file" && test -r "$ac_site_file"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file" \
+ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special files
+ # actually), so we avoid doing that. DJGPP emulates it as a regular file.
+ if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+printf "%s\n" "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . "$cache_file";;
+ *) . "./$cache_file";;
+ esac
+ fi
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+printf "%s\n" "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Test code for whether the C compiler supports C89 (global declarations)
+ac_c_conftest_c89_globals='
+/* Does the compiler advertise C89 conformance?
+ Do not test the value of __STDC__, because some compilers set it to 0
+ while being otherwise adequately conformant. */
+#if !defined __STDC__
+# error "Compiler does not advertise C89 conformance"
+#endif
+
+#include <stddef.h>
+#include <stdarg.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */
+struct buf { int x; };
+struct buf * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not \xHH hex character constants.
+ These do not provoke an error unfortunately, instead are silently treated
+ as an "x". The following induces an error, until -std is added to get
+ proper ANSI mode. Curiously \x00 != x always comes out true, for an
+ array size at least. It is necessary to write \x00 == 0 to get something
+ that is true only with -std. */
+int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+ inside strings and character constants. */
+#define FOO(x) '\''x'\''
+int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int),
+ int, int);'
+
+# Test code for whether the C compiler supports C89 (body of main).
+ac_c_conftest_c89_main='
+ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]);
+'
+
+# Test code for whether the C compiler supports C99 (global declarations)
+ac_c_conftest_c99_globals='
+// Does the compiler advertise C99 conformance?
+#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# error "Compiler does not advertise C99 conformance"
+#endif
+
+#include <stdbool.h>
+extern int puts (const char *);
+extern int printf (const char *, ...);
+extern int dprintf (int, const char *, ...);
+extern void *malloc (size_t);
+
+// Check varargs macros. These examples are taken from C99 6.10.3.5.
+// dprintf is used instead of fprintf to avoid needing to declare
+// FILE and stderr.
+#define debug(...) dprintf (2, __VA_ARGS__)
+#define showlist(...) puts (#__VA_ARGS__)
+#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
+static void
+test_varargs_macros (void)
+{
+ int x = 1234;
+ int y = 5678;
+ debug ("Flag");
+ debug ("X = %d\n", x);
+ showlist (The first, second, and third items.);
+ report (x>y, "x is %d but y is %d", x, y);
+}
+
+// Check long long types.
+#define BIG64 18446744073709551615ull
+#define BIG32 4294967295ul
+#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
+#if !BIG_OK
+ #error "your preprocessor is broken"
+#endif
+#if BIG_OK
+#else
+ #error "your preprocessor is broken"
+#endif
+static long long int bignum = -9223372036854775807LL;
+static unsigned long long int ubignum = BIG64;
+
+struct incomplete_array
+{
+ int datasize;
+ double data[];
+};
+
+struct named_init {
+ int number;
+ const wchar_t *name;
+ double average;
+};
+
+typedef const char *ccp;
+
+static inline int
+test_restrict (ccp restrict text)
+{
+ // See if C++-style comments work.
+ // Iterate through items via the restricted pointer.
+ // Also check for declarations in for loops.
+ for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i)
+ continue;
+ return 0;
+}
+
+// Check varargs and va_copy.
+static bool
+test_varargs (const char *format, ...)
+{
+ va_list args;
+ va_start (args, format);
+ va_list args_copy;
+ va_copy (args_copy, args);
+
+ const char *str = "";
+ int number = 0;
+ float fnumber = 0;
+
+ while (*format)
+ {
+ switch (*format++)
+ {
+ case '\''s'\'': // string
+ str = va_arg (args_copy, const char *);
+ break;
+ case '\''d'\'': // int
+ number = va_arg (args_copy, int);
+ break;
+ case '\''f'\'': // float
+ fnumber = va_arg (args_copy, double);
+ break;
+ default:
+ break;
+ }
+ }
+ va_end (args_copy);
+ va_end (args);
+
+ return *str && number && fnumber;
+}
+'
+
+# Test code for whether the C compiler supports C99 (body of main).
+ac_c_conftest_c99_main='
+ // Check bool.
+ _Bool success = false;
+ success |= (argc != 0);
+
+ // Check restrict.
+ if (test_restrict ("String literal") == 0)
+ success = true;
+ char *restrict newvar = "Another string";
+
+ // Check varargs.
+ success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234);
+ test_varargs_macros ();
+
+ // Check flexible array members.
+ struct incomplete_array *ia =
+ malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
+ ia->datasize = 10;
+ for (int i = 0; i < ia->datasize; ++i)
+ ia->data[i] = i * 1.234;
+
+ // Check named initializers.
+ struct named_init ni = {
+ .number = 34,
+ .name = L"Test wide string",
+ .average = 543.34343,
+ };
+
+ ni.number = 58;
+
+ int dynamic_array[ni.number];
+ dynamic_array[0] = argv[0][0];
+ dynamic_array[ni.number - 1] = 543;
+
+ // work around unused variable warnings
+ ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\''
+ || dynamic_array[ni.number - 1] != 543);
+'
+
+# Test code for whether the C compiler supports C11 (global declarations)
+ac_c_conftest_c11_globals='
+// Does the compiler advertise C11 conformance?
+#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L
+# error "Compiler does not advertise C11 conformance"
+#endif
+
+// Check _Alignas.
+char _Alignas (double) aligned_as_double;
+char _Alignas (0) no_special_alignment;
+extern char aligned_as_int;
+char _Alignas (0) _Alignas (int) aligned_as_int;
+
+// Check _Alignof.
+enum
+{
+ int_alignment = _Alignof (int),
+ int_array_alignment = _Alignof (int[100]),
+ char_alignment = _Alignof (char)
+};
+_Static_assert (0 < -_Alignof (int), "_Alignof is signed");
+
+// Check _Noreturn.
+int _Noreturn does_not_return (void) { for (;;) continue; }
+
+// Check _Static_assert.
+struct test_static_assert
+{
+ int x;
+ _Static_assert (sizeof (int) <= sizeof (long int),
+ "_Static_assert does not work in struct");
+ long int y;
+};
+
+// Check UTF-8 literals.
+#define u8 syntax error!
+char const utf8_literal[] = u8"happens to be ASCII" "another string";
+
+// Check duplicate typedefs.
+typedef long *long_ptr;
+typedef long int *long_ptr;
+typedef long_ptr long_ptr;
+
+// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1.
+struct anonymous
+{
+ union {
+ struct { int i; int j; };
+ struct { int k; long int l; } w;
+ };
+ int m;
+} v1;
+'
+
+# Test code for whether the C compiler supports C11 (body of main).
+ac_c_conftest_c11_main='
+ _Static_assert ((offsetof (struct anonymous, i)
+ == offsetof (struct anonymous, w.k)),
+ "Anonymous union alignment botch");
+ v1.i = 2;
+ v1.w.k = 5;
+ ok |= v1.i != 5;
+'
+
+# Test code for whether the C compiler supports C11 (complete).
+ac_c_conftest_c11_program="${ac_c_conftest_c89_globals}
+${ac_c_conftest_c99_globals}
+${ac_c_conftest_c11_globals}
+
+int
+main (int argc, char **argv)
+{
+ int ok = 0;
+ ${ac_c_conftest_c89_main}
+ ${ac_c_conftest_c99_main}
+ ${ac_c_conftest_c11_main}
+ return ok;
+}
+"
+
+# Test code for whether the C compiler supports C99 (complete).
+ac_c_conftest_c99_program="${ac_c_conftest_c89_globals}
+${ac_c_conftest_c99_globals}
+
+int
+main (int argc, char **argv)
+{
+ int ok = 0;
+ ${ac_c_conftest_c89_main}
+ ${ac_c_conftest_c99_main}
+ return ok;
+}
+"
+
+# Test code for whether the C compiler supports C89 (complete).
+ac_c_conftest_c89_program="${ac_c_conftest_c89_globals}
+
+int
+main (int argc, char **argv)
+{
+ int ok = 0;
+ ${ac_c_conftest_c89_main}
+ return ok;
+}
+"
+
+as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H"
+as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H"
+as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H"
+as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H"
+as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H"
+as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H"
+as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H"
+as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H"
+as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H"
+
+# Auxiliary files required by this configure script.
+ac_aux_files="config.guess config.sub ltmain.sh ar-lib compile missing install-sh"
+
+# Locations in which to look for auxiliary files.
+ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.."
+
+# Search for a directory containing all of the required auxiliary files,
+# $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates.
+# If we don't find one directory that contains all the files we need,
+# we report the set of missing files from the *first* directory in
+# $ac_aux_dir_candidates and give up.
+ac_missing_aux_files=""
+ac_first_candidate=:
+printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in $ac_aux_dir_candidates
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ as_found=:
+
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5
+ ac_aux_dir_found=yes
+ ac_install_sh=
+ for ac_aux in $ac_aux_files
+ do
+ # As a special case, if "install-sh" is required, that requirement
+ # can be satisfied by any of "install-sh", "install.sh", or "shtool",
+ # and $ac_install_sh is set appropriately for whichever one is found.
+ if test x"$ac_aux" = x"install-sh"
+ then
+ if test -f "${as_dir}install-sh"; then
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5
+ ac_install_sh="${as_dir}install-sh -c"
+ elif test -f "${as_dir}install.sh"; then
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5
+ ac_install_sh="${as_dir}install.sh -c"
+ elif test -f "${as_dir}shtool"; then
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5
+ ac_install_sh="${as_dir}shtool install -c"
+ else
+ ac_aux_dir_found=no
+ if $ac_first_candidate; then
+ ac_missing_aux_files="${ac_missing_aux_files} install-sh"
+ else
+ break
+ fi
+ fi
+ else
+ if test -f "${as_dir}${ac_aux}"; then
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5
+ else
+ ac_aux_dir_found=no
+ if $ac_first_candidate; then
+ ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}"
+ else
+ break
+ fi
+ fi
+ fi
+ done
+ if test "$ac_aux_dir_found" = yes; then
+ ac_aux_dir="$as_dir"
+ break
+ fi
+ ac_first_candidate=false
+
+ as_found=false
+done
+IFS=$as_save_IFS
+if $as_found
+then :
+
+else $as_nop
+ as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5
+fi
+
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+if test -f "${ac_aux_dir}config.guess"; then
+ ac_config_guess="$SHELL ${ac_aux_dir}config.guess"
+fi
+if test -f "${ac_aux_dir}config.sub"; then
+ ac_config_sub="$SHELL ${ac_aux_dir}config.sub"
+fi
+if test -f "$ac_aux_dir/configure"; then
+ ac_configure="$SHELL ${ac_aux_dir}configure"
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val=\$ac_cv_env_${ac_var}_value
+ eval ac_new_val=\$ac_env_${ac_var}_value
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ # differences in whitespace do not lead to failure.
+ ac_old_val_w=`echo x $ac_old_val`
+ ac_new_val_w=`echo x $ac_new_val`
+ if test "$ac_old_val_w" != "$ac_new_val_w"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ ac_cache_corrupted=:
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+ eval $ac_var=\$ac_old_val
+ fi
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
+printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
+printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;}
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;}
+ as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file'
+ and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+am__api_version='1.16'
+
+
+
+ # Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+printf %s "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if test ${ac_cv_path_install+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ # Account for fact that we put trailing slashes in our PATH walk.
+case $as_dir in #((
+ ./ | /[cC]/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ rm -rf conftest.one conftest.two conftest.dir
+ echo one > conftest.one
+ echo two > conftest.two
+ mkdir conftest.dir
+ if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" &&
+ test -s conftest.one && test -s conftest.two &&
+ test -s conftest.dir/conftest.one &&
+ test -s conftest.dir/conftest.two
+ then
+ ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+
+ done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+ if test ${ac_cv_path_install+y}; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. Don't cache a
+ # value for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the value is a relative name.
+ INSTALL=$ac_install_sh
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+printf "%s\n" "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+printf %s "checking whether build environment is sane... " >&6; }
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name. Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+ *[\\\"\#\$\&\'\`$am_lf]*)
+ as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;;
+esac
+case $srcdir in
+ *[\\\"\#\$\&\'\`$am_lf\ \ ]*)
+ as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments. Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+ am_has_slept=no
+ for am_try in 1 2; do
+ echo "timestamp, slept: $am_has_slept" > conftest.file
+ set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+ if test "$*" = "X"; then
+ # -L didn't work.
+ set X `ls -t "$srcdir/configure" conftest.file`
+ fi
+ if test "$*" != "X $srcdir/configure conftest.file" \
+ && test "$*" != "X conftest.file $srcdir/configure"; then
+
+ # If neither matched, then we have a broken ls. This can happen
+ # if, for instance, CONFIG_SHELL is bash and it inherits a
+ # broken ls alias from the environment. This has actually
+ # happened. Such a system could not be considered "sane".
+ as_fn_error $? "ls -t appears to fail. Make sure there is not a broken
+ alias in your environment" "$LINENO" 5
+ fi
+ if test "$2" = conftest.file || test $am_try -eq 2; then
+ break
+ fi
+ # Just in case.
+ sleep 1
+ am_has_slept=yes
+ done
+ test "$2" = conftest.file
+ )
+then
+ # Ok.
+ :
+else
+ as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+ ( sleep 1 ) &
+ am_sleep_pid=$!
+fi
+
+rm -f conftest.file
+
+test "$program_prefix" != NONE &&
+ program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+ program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`printf "%s\n" "$program_transform_name" | sed "$ac_script"`
+
+
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+
+
+ if test x"${MISSING+set}" != xset; then
+ MISSING="\${SHELL} '$am_aux_dir/missing'"
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+ am_missing_run="$MISSING "
+else
+ am_missing_run=
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5
+printf "%s\n" "$as_me: WARNING: 'missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh+set}" != xset; then
+ case $am_aux_dir in
+ *\ * | *\ *)
+ install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+ *)
+ install_sh="\${SHELL} $am_aux_dir/install-sh"
+ esac
+fi
+
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip". However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_STRIP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+printf "%s\n" "$STRIP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+ ac_ct_STRIP=$STRIP
+ # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_STRIP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_STRIP"; then
+ ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_STRIP="strip"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+printf "%s\n" "$ac_ct_STRIP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_STRIP" = x; then
+ STRIP=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ STRIP=$ac_ct_STRIP
+ fi
+else
+ STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a race-free mkdir -p" >&5
+printf %s "checking for a race-free mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+ if test ${ac_cv_path_mkdir+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in mkdir gmkdir; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext" || continue
+ case `"$as_dir$ac_prog$ac_exec_ext" --version 2>&1` in #(
+ 'mkdir ('*'coreutils) '* | \
+ 'BusyBox '* | \
+ 'mkdir (fileutils) '4.1*)
+ ac_cv_path_mkdir=$as_dir$ac_prog$ac_exec_ext
+ break 3;;
+ esac
+ done
+ done
+ done
+IFS=$as_save_IFS
+
+fi
+
+ test -d ./--version && rmdir ./--version
+ if test ${ac_cv_path_mkdir+y}; then
+ MKDIR_P="$ac_cv_path_mkdir -p"
+ else
+ # As a last resort, use the slow shell script. Don't cache a
+ # value for MKDIR_P within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the value is a relative name.
+ MKDIR_P="$ac_install_sh -d"
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+printf "%s\n" "$MKDIR_P" >&6; }
+
+for ac_prog in gawk mawk nawk awk
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_AWK+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$AWK"; then
+ ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AWK="$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+printf "%s\n" "$AWK" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$AWK" && break
+done
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval test \${ac_cv_prog_make_${ac_make}_set+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+ @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+ *@@@%%%=?*=@@@%%%*)
+ eval ac_cv_prog_make_${ac_make}_set=yes;;
+ *)
+ eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ SET_MAKE=
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+ am__leading_dot=.
+else
+ am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+# Check whether --enable-silent-rules was given.
+if test ${enable_silent_rules+y}
+then :
+ enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in # (((
+ yes) AM_DEFAULT_VERBOSITY=0;;
+ no) AM_DEFAULT_VERBOSITY=1;;
+ *) AM_DEFAULT_VERBOSITY=1;;
+esac
+am_make=${MAKE-make}
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5
+printf %s "checking whether $am_make supports nested variables... " >&6; }
+if test ${am_cv_make_support_nested_variables+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if printf "%s\n" 'TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+ @$(TRUE)
+.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then
+ am_cv_make_support_nested_variables=yes
+else
+ am_cv_make_support_nested_variables=no
+fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5
+printf "%s\n" "$am_cv_make_support_nested_variables" >&6; }
+if test $am_cv_make_support_nested_variables = yes; then
+ AM_V='$(V)'
+ AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+ AM_V=$AM_DEFAULT_VERBOSITY
+ AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AM_BACKSLASH='\'
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+ # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+ # is not polluted with repeated "-I."
+ am__isrc=' -I$(srcdir)'
+ # test to see if srcdir already configured
+ if test -f $srcdir/config.status; then
+ as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+ fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+ if (cygpath --version) >/dev/null 2>/dev/null; then
+ CYGPATH_W='cygpath -w'
+ else
+ CYGPATH_W=echo
+ fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='sg3_utils'
+ VERSION='1.48'
+
+
+printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
+
+
+printf "%s\n" "#define VERSION \"$VERSION\"" >>confdefs.h
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# For better backward compatibility. To be removed once Automake 1.9.x
+# dies out for good. For more background, see:
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+mkdir_p='$(MKDIR_P)'
+
+# We need awk for the "check" target (and possibly the TAP driver). The
+# system "awk" is bad on some platforms.
+# Always define AMTAR for backward compatibility. Yes, it's still used
+# in the wild :-( We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
+
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar pax cpio none'
+
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
+
+
+
+
+
+# Variables for tags utilities; see am/tags.am
+if test -z "$CTAGS"; then
+ CTAGS=ctags
+fi
+
+if test -z "$ETAGS"; then
+ ETAGS=etags
+fi
+
+if test -z "$CSCOPE"; then
+ CSCOPE=cscope
+fi
+
+
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes. So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+ cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present. This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message. This
+can help us improve future automake versions.
+
+END
+ if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+ echo 'Configuration will proceed anyway, since you have set the' >&2
+ echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+ echo >&2
+ else
+ cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <https://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+ as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5
+ fi
+fi
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable maintainer-specific portions of Makefiles" >&5
+printf %s "checking whether to enable maintainer-specific portions of Makefiles... " >&6; }
+ # Check whether --enable-maintainer-mode was given.
+if test ${enable_maintainer_mode+y}
+then :
+ enableval=$enable_maintainer_mode; USE_MAINTAINER_MODE=$enableval
+else $as_nop
+ USE_MAINTAINER_MODE=no
+fi
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $USE_MAINTAINER_MODE" >&5
+printf "%s\n" "$USE_MAINTAINER_MODE" >&6; }
+ if test $USE_MAINTAINER_MODE = yes; then
+ MAINTAINER_MODE_TRUE=
+ MAINTAINER_MODE_FALSE='#'
+else
+ MAINTAINER_MODE_TRUE='#'
+ MAINTAINER_MODE_FALSE=
+fi
+
+ MAINT=$MAINTAINER_MODE_TRUE
+
+
+ac_config_headers="$ac_config_headers config.h"
+
+
+
+
+
+
+
+
+
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}gcc"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="gcc"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}cc"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ fi
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl.exe
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl.exe
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CC" && break
+done
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+fi
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args.
+set dummy ${ac_tool_prefix}clang; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}clang"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "clang", so it can be a program name with args.
+set dummy clang; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="clang"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+fi
+
+
+test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion -version; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+printf %s "checking whether the C compiler works... " >&6; }
+ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+ esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_link_default") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+then :
+ # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile. We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no;
+ then :; else
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ fi
+ # We set ac_cv_exeext here because the later test for it is not
+ # safe: cross compilers may not add the suffix if given an `-o'
+ # argument, so we may need to know it at that point already.
+ # Even if this section looks crufty: it has the advantage of
+ # actually working.
+ break;;
+ * )
+ break;;
+ esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else $as_nop
+ ac_file=''
+fi
+if test -z "$ac_file"
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+printf %s "checking for C compiler default output file name... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+printf "%s\n" "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+printf %s "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+then :
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ break;;
+ * ) break;;
+ esac
+done
+else $as_nop
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+printf "%s\n" "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdio.h>
+int
+main (void)
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+printf %s "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+ { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if { ac_try='./conftest$ac_cv_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+printf "%s\n" "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+printf %s "checking for suffix of object files... " >&6; }
+if test ${ac_cv_objext+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+then :
+ for ac_file in conftest.o conftest.obj conftest.*; do
+ test -f "$ac_file" || continue;
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else $as_nop
+ printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+printf "%s\n" "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5
+printf %s "checking whether the compiler supports GNU C... " >&6; }
+if test ${ac_cv_c_compiler_gnu+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_compiler_gnu=yes
+else $as_nop
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; }
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test $ac_compiler_gnu = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+y}
+ac_save_CFLAGS=$CFLAGS
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+printf %s "checking whether $CC accepts -g... " >&6; }
+if test ${ac_cv_prog_cc_g+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_save_c_werror_flag=$ac_c_werror_flag
+ ac_c_werror_flag=yes
+ ac_cv_prog_cc_g=no
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_cv_prog_cc_g=yes
+else $as_nop
+ CFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+else $as_nop
+ ac_c_werror_flag=$ac_save_c_werror_flag
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+ ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+printf "%s\n" "$ac_cv_prog_cc_g" >&6; }
+if test $ac_test_CFLAGS; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+ac_prog_cc_stdc=no
+if test x$ac_prog_cc_stdc = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5
+printf %s "checking for $CC option to enable C11 features... " >&6; }
+if test ${ac_cv_prog_cc_c11+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_cv_prog_cc_c11=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_c_conftest_c11_program
+_ACEOF
+for ac_arg in '' -std=gnu11
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_cv_prog_cc_c11=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+ test "x$ac_cv_prog_cc_c11" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+fi
+
+if test "x$ac_cv_prog_cc_c11" = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+ if test "x$ac_cv_prog_cc_c11" = x
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
+printf "%s\n" "$ac_cv_prog_cc_c11" >&6; }
+ CC="$CC $ac_cv_prog_cc_c11"
+fi
+ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11
+ ac_prog_cc_stdc=c11
+fi
+fi
+if test x$ac_prog_cc_stdc = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5
+printf %s "checking for $CC option to enable C99 features... " >&6; }
+if test ${ac_cv_prog_cc_c99+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_cv_prog_cc_c99=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_c_conftest_c99_program
+_ACEOF
+for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99=
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_cv_prog_cc_c99=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+ test "x$ac_cv_prog_cc_c99" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+fi
+
+if test "x$ac_cv_prog_cc_c99" = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+ if test "x$ac_cv_prog_cc_c99" = x
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
+printf "%s\n" "$ac_cv_prog_cc_c99" >&6; }
+ CC="$CC $ac_cv_prog_cc_c99"
+fi
+ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
+ ac_prog_cc_stdc=c99
+fi
+fi
+if test x$ac_prog_cc_stdc = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5
+printf %s "checking for $CC option to enable C89 features... " >&6; }
+if test ${ac_cv_prog_cc_c89+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_c_conftest_c89_program
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"
+then :
+ ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+ test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+fi
+
+if test "x$ac_cv_prog_cc_c89" = xno
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+ if test "x$ac_cv_prog_cc_c89" = x
+then :
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+printf "%s\n" "$ac_cv_prog_cc_c89" >&6; }
+ CC="$CC $ac_cv_prog_cc_c89"
+fi
+ ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
+ ac_prog_cc_stdc=c89
+fi
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5
+printf %s "checking whether $CC understands -c and -o together... " >&6; }
+if test ${am_cv_prog_cc_c_o+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ # Make sure it works both with $CC and with simple cc.
+ # Following AC_PROG_CC_C_O, we do the test twice because some
+ # compilers refuse to overwrite an existing .o file with -o,
+ # though they will create one.
+ am_cv_prog_cc_c_o=yes
+ for am_i in 1 2; do
+ if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5
+ ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } \
+ && test -f conftest2.$ac_objext; then
+ : OK
+ else
+ am_cv_prog_cc_c_o=no
+ break
+ fi
+ done
+ rm -f core conftest*
+ unset am_i
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5
+printf "%s\n" "$am_cv_prog_cc_c_o" >&6; }
+if test "$am_cv_prog_cc_c_o" != yes; then
+ # Losing compiler, so override with the script.
+ # FIXME: It is wrong to rewrite CC.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__CC in this case,
+ # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+ CC="$am_aux_dir/compile $CC"
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5
+printf %s "checking whether ${MAKE-make} supports the include directive... " >&6; }
+cat > confinc.mk << 'END'
+am__doit:
+ @echo this is the am__doit target >confinc.out
+.PHONY: am__doit
+END
+am__include="#"
+am__quote=
+# BSD make does it like this.
+echo '.include "confinc.mk" # ignored' > confmf.BSD
+# Other make implementations (GNU, Solaris 10, AIX) do it like this.
+echo 'include confinc.mk # ignored' > confmf.GNU
+_am_result=no
+for s in GNU BSD; do
+ { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5
+ (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+ case $?:`cat confinc.out 2>/dev/null` in #(
+ '0:this is the am__doit target') :
+ case $s in #(
+ BSD) :
+ am__include='.include' am__quote='"' ;; #(
+ *) :
+ am__include='include' am__quote='' ;;
+esac ;; #(
+ *) :
+ ;;
+esac
+ if test "$am__include" != "#"; then
+ _am_result="yes ($s style)"
+ break
+ fi
+done
+rm -f confinc.* confmf.*
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5
+printf "%s\n" "${_am_result}" >&6; }
+
+# Check whether --enable-dependency-tracking was given.
+if test ${enable_dependency_tracking+y}
+then :
+ enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+ am_depcomp="$ac_aux_dir/depcomp"
+ AMDEPBACKSLASH='\'
+ am__nodep='_no'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+ AMDEP_TRUE=
+ AMDEP_FALSE='#'
+else
+ AMDEP_TRUE='#'
+ AMDEP_FALSE=
+fi
+
+
+
+depcc="$CC" am_compiler_list=
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+printf %s "checking dependency style of $depcc... " >&6; }
+if test ${am_cv_CC_dependencies_compiler_type+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+ # We make a subdir and do the tests there. Otherwise we can end up
+ # making bogus files that we don't know about and never remove. For
+ # instance it was reported that on HP-UX the gcc test will end up
+ # making a dummy file named 'D' -- because '-MD' means "put the output
+ # in D".
+ rm -rf conftest.dir
+ mkdir conftest.dir
+ # Copy depcomp to subdir because otherwise we won't find it if we're
+ # using a relative directory.
+ cp "$am_depcomp" conftest.dir
+ cd conftest.dir
+ # We will build objects and dependencies in a subdirectory because
+ # it helps to detect inapplicable dependency modes. For instance
+ # both Tru64's cc and ICC support -MD to output dependencies as a
+ # side effect of compilation, but ICC will put the dependencies in
+ # the current directory while Tru64 will put them in the object
+ # directory.
+ mkdir sub
+
+ am_cv_CC_dependencies_compiler_type=none
+ if test "$am_compiler_list" = ""; then
+ am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+ fi
+ am__universal=false
+ case " $depcc " in #(
+ *\ -arch\ *\ -arch\ *) am__universal=true ;;
+ esac
+
+ for depmode in $am_compiler_list; do
+ # Setup a source with many dependencies, because some compilers
+ # like to wrap large dependency lists on column 80 (with \), and
+ # we should not choose a depcomp mode which is confused by this.
+ #
+ # We need to recreate these files for each test, as the compiler may
+ # overwrite some of them when testing with obscure command lines.
+ # This happens at least with the AIX C compiler.
+ : > sub/conftest.c
+ for i in 1 2 3 4 5 6; do
+ echo '#include "conftst'$i'.h"' >> sub/conftest.c
+ # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+ # Solaris 10 /bin/sh.
+ echo '/* dummy */' > sub/conftst$i.h
+ done
+ echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+ # We check with '-c' and '-o' for the sake of the "dashmstdout"
+ # mode. It turns out that the SunPro C++ compiler does not properly
+ # handle '-M -o', and we need to detect this. Also, some Intel
+ # versions had trouble with output in subdirs.
+ am__obj=sub/conftest.${OBJEXT-o}
+ am__minus_obj="-o $am__obj"
+ case $depmode in
+ gcc)
+ # This depmode causes a compiler race in universal mode.
+ test "$am__universal" = false || continue
+ ;;
+ nosideeffect)
+ # After this tag, mechanisms are not by side-effect, so they'll
+ # only be used when explicitly requested.
+ if test "x$enable_dependency_tracking" = xyes; then
+ continue
+ else
+ break
+ fi
+ ;;
+ msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+ # This compiler won't grok '-c -o', but also, the minuso test has
+ # not run yet. These depmodes are late enough in the game, and
+ # so weak that their functioning should not be impacted.
+ am__obj=conftest.${OBJEXT-o}
+ am__minus_obj=
+ ;;
+ none) break ;;
+ esac
+ if depmode=$depmode \
+ source=sub/conftest.c object=$am__obj \
+ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+ >/dev/null 2>conftest.err &&
+ grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+ grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+ ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+ # icc doesn't choke on unknown options, it will just issue warnings
+ # or remarks (even with -Werror). So we grep stderr for any message
+ # that says an option was ignored or not supported.
+ # When given -MP, icc 7.0 and 7.1 complain thusly:
+ # icc: Command line warning: ignoring option '-M'; no argument required
+ # The diagnosis changed in icc 8.0:
+ # icc: Command line remark: option '-MP' not supported
+ if (grep 'ignoring option' conftest.err ||
+ grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+ am_cv_CC_dependencies_compiler_type=$depmode
+ break
+ fi
+ fi
+ done
+
+ cd ..
+ rm -rf conftest.dir
+else
+ am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+printf "%s\n" "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+ test "x$enable_dependency_tracking" != xno \
+ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+ am__fastdepCC_TRUE=
+ am__fastdepCC_FALSE='#'
+else
+ am__fastdepCC_TRUE='#'
+ am__fastdepCC_FALSE=
+fi
+
+
+# AC_PROG_CXX
+
+
+# AM_PROG_AR is supported and needed since automake v1.12+
+
+
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in ar lib "link -lib"
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_AR+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$AR"; then
+ ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AR="$ac_tool_prefix$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+printf "%s\n" "$AR" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$AR" && break
+ done
+fi
+if test -z "$AR"; then
+ ac_ct_AR=$AR
+ for ac_prog in ar lib "link -lib"
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_AR+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_AR"; then
+ ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_AR="$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+printf "%s\n" "$ac_ct_AR" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_AR" && break
+done
+
+ if test "x$ac_ct_AR" = x; then
+ AR="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ AR=$ac_ct_AR
+ fi
+fi
+
+: ${AR=ar}
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking the archiver ($AR) interface" >&5
+printf %s "checking the archiver ($AR) interface... " >&6; }
+if test ${am_cv_ar_interface+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ am_cv_ar_interface=ar
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+int some_variable = 0;
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ am_ar_try='$AR cru libconftest.a conftest.$ac_objext >&5'
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$am_ar_try\""; } >&5
+ (eval $am_ar_try) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test "$ac_status" -eq 0; then
+ am_cv_ar_interface=ar
+ else
+ am_ar_try='$AR -NOLOGO -OUT:conftest.lib conftest.$ac_objext >&5'
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$am_ar_try\""; } >&5
+ (eval $am_ar_try) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test "$ac_status" -eq 0; then
+ am_cv_ar_interface=lib
+ else
+ am_cv_ar_interface=unknown
+ fi
+ fi
+ rm -f conftest.lib libconftest.a
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_ar_interface" >&5
+printf "%s\n" "$am_cv_ar_interface" >&6; }
+
+case $am_cv_ar_interface in
+ar)
+ ;;
+lib)
+ # Microsoft lib, so override with the ar-lib wrapper script.
+ # FIXME: It is wrong to rewrite AR.
+ # But if we don't then we get into trouble of one sort or another.
+ # A longer-term fix would be to have automake use am__AR in this case,
+ # and then we could set am__AR="$am_aux_dir/ar-lib \$(AR)" or something
+ # similar.
+ AR="$am_aux_dir/ar-lib $AR"
+ ;;
+unknown)
+ as_fn_error $? "could not determine $AR interface" "$LINENO" 5
+ ;;
+esac
+
+
+# Adding libtools to the build seems to bring in C++ environment
+case `pwd` in
+ *\ * | *\ *)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5
+printf "%s\n" "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;;
+esac
+
+
+
+macro_version='2.4.7'
+macro_revision='2.4.7'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ltmain=$ac_aux_dir/ltmain.sh
+
+
+
+ # Make sure we can run config.sub.
+$SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 ||
+ as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+printf %s "checking build system type... " >&6; }
+if test ${ac_cv_build+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+ ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"`
+test "x$ac_build_alias" = x &&
+ as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
+ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` ||
+ as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+printf "%s\n" "$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+printf %s "checking host system type... " >&6; }
+if test ${ac_cv_host+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test "x$host_alias" = x; then
+ ac_cv_host=$ac_cv_build
+else
+ ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` ||
+ as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+printf "%s\n" "$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+
+ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5
+printf %s "checking how to print strings... " >&6; }
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='printf %s\n'
+else
+ # Use this function as a fallback that always works.
+ func_fallback_echo ()
+ {
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+ }
+ ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+ $ECHO ""
+}
+
+case $ECHO in
+ printf*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: printf" >&5
+printf "%s\n" "printf" >&6; } ;;
+ print*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: print -r" >&5
+printf "%s\n" "print -r" >&6; } ;;
+ *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cat" >&5
+printf "%s\n" "cat" >&6; } ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5
+printf %s "checking for a sed that does not truncate output... " >&6; }
+if test ${ac_cv_path_SED+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+ for ac_i in 1 2 3 4 5 6 7; do
+ ac_script="$ac_script$as_nl$ac_script"
+ done
+ echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed
+ { ac_script=; unset ac_script;}
+ if test -z "$SED"; then
+ ac_path_SED_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in sed gsed
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_SED="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_SED" || continue
+# Check for GNU ac_path_SED and select it if it is found.
+ # Check for GNU $ac_path_SED
+case `"$ac_path_SED" --version 2>&1` in
+*GNU*)
+ ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;;
+*)
+ ac_count=0
+ printf %s 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ printf "%s\n" '' >> "conftest.nl"
+ "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_SED_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_SED="$ac_path_SED"
+ ac_path_SED_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_SED_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_SED"; then
+ as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5
+ fi
+else
+ ac_cv_path_SED=$SED
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5
+printf "%s\n" "$ac_cv_path_SED" >&6; }
+ SED="$ac_cv_path_SED"
+ rm -f conftest.sed
+
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+
+
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+printf %s "checking for grep that handles long lines and -e... " >&6; }
+if test ${ac_cv_path_GREP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -z "$GREP"; then
+ ac_path_GREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in grep ggrep
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_GREP="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+ # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+ ac_count=0
+ printf %s 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ printf "%s\n" 'GREP' >> "conftest.nl"
+ "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_GREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_GREP="$ac_path_GREP"
+ ac_path_GREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_GREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_GREP"; then
+ as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+printf "%s\n" "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+printf %s "checking for egrep... " >&6; }
+if test ${ac_cv_path_EGREP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in egrep
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ printf %s 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ printf "%s\n" 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+printf "%s\n" "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5
+printf %s "checking for fgrep... " >&6; }
+if test ${ac_cv_path_FGREP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1
+ then ac_cv_path_FGREP="$GREP -F"
+ else
+ if test -z "$FGREP"; then
+ ac_path_FGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in fgrep
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_FGREP="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_FGREP" || continue
+# Check for GNU ac_path_FGREP and select it if it is found.
+ # Check for GNU $ac_path_FGREP
+case `"$ac_path_FGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;;
+*)
+ ac_count=0
+ printf %s 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ printf "%s\n" 'FGREP' >> "conftest.nl"
+ "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_FGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_FGREP="$ac_path_FGREP"
+ ac_path_FGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_FGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_FGREP"; then
+ as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_FGREP=$FGREP
+fi
+
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5
+printf "%s\n" "$ac_cv_path_FGREP" >&6; }
+ FGREP="$ac_cv_path_FGREP"
+
+
+test -z "$GREP" && GREP=grep
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-gnu-ld was given.
+if test ${with_gnu_ld+y}
+then :
+ withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes
+else $as_nop
+ with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test yes = "$GCC"; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5
+printf %s "checking for ld used by $CC... " >&6; }
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return, which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [\\/]* | ?:[\\/]*)
+ re_direlt='/[^/][^/]*/\.\./'
+ # Canonicalize the pathname of ld
+ ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+ while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD=$ac_prog
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test yes = "$with_gnu_ld"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5
+printf %s "checking for GNU ld... " >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5
+printf %s "checking for non-GNU ld... " >&6; }
+fi
+if test ${lt_cv_path_LD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -z "$LD"; then
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD=$ac_dir/$ac_prog
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some variants of GNU ld only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+ *GNU* | *'with BFD'*)
+ test no != "$with_gnu_ld" && break
+ ;;
+ *)
+ test yes != "$with_gnu_ld" && break
+ ;;
+ esac
+ fi
+ done
+ IFS=$lt_save_ifs
+else
+ lt_cv_path_LD=$LD # Let the user override the test with a path.
+fi
+fi
+
+LD=$lt_cv_path_LD
+if test -n "$LD"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LD" >&5
+printf "%s\n" "$LD" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5
+printf %s "checking if the linker ($LD) is GNU ld... " >&6; }
+if test ${lt_cv_prog_gnu_ld+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ # I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+ lt_cv_prog_gnu_ld=yes
+ ;;
+*)
+ lt_cv_prog_gnu_ld=no
+ ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5
+printf "%s\n" "$lt_cv_prog_gnu_ld" >&6; }
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5
+printf %s "checking for BSD- or MS-compatible name lister (nm)... " >&6; }
+if test ${lt_cv_path_NM+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$NM"; then
+ # Let the user override the test.
+ lt_cv_path_NM=$NM
+else
+ lt_nm_to_check=${ac_tool_prefix}nm
+ if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+ lt_nm_to_check="$lt_nm_to_check nm"
+ fi
+ for lt_tmp_nm in $lt_nm_to_check; do
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ tmp_nm=$ac_dir/$lt_tmp_nm
+ if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then
+ # Check to see if the nm accepts a BSD-compat flag.
+ # Adding the 'sed 1q' prevents false positives on HP-UX, which says:
+ # nm: unknown option "B" ignored
+ # Tru64's nm complains that /dev/null is an invalid object file
+ # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty
+ case $build_os in
+ mingw*) lt_bad_file=conftest.nm/nofile ;;
+ *) lt_bad_file=/dev/null ;;
+ esac
+ case `"$tmp_nm" -B $lt_bad_file 2>&1 | $SED '1q'` in
+ *$lt_bad_file* | *'Invalid file or object type'*)
+ lt_cv_path_NM="$tmp_nm -B"
+ break 2
+ ;;
+ *)
+ case `"$tmp_nm" -p /dev/null 2>&1 | $SED '1q'` in
+ */dev/null*)
+ lt_cv_path_NM="$tmp_nm -p"
+ break 2
+ ;;
+ *)
+ lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+ continue # so that we can try to find one that supports BSD flags
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ done
+ IFS=$lt_save_ifs
+ done
+ : ${lt_cv_path_NM=no}
+fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5
+printf "%s\n" "$lt_cv_path_NM" >&6; }
+if test no != "$lt_cv_path_NM"; then
+ NM=$lt_cv_path_NM
+else
+ # Didn't find any BSD compatible name lister, look for dumpbin.
+ if test -n "$DUMPBIN"; then :
+ # Let the user override the test.
+ else
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in dumpbin "link -dump"
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_DUMPBIN+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$DUMPBIN"; then
+ ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DUMPBIN=$ac_cv_prog_DUMPBIN
+if test -n "$DUMPBIN"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5
+printf "%s\n" "$DUMPBIN" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$DUMPBIN" && break
+ done
+fi
+if test -z "$DUMPBIN"; then
+ ac_ct_DUMPBIN=$DUMPBIN
+ for ac_prog in dumpbin "link -dump"
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_DUMPBIN+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_DUMPBIN"; then
+ ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DUMPBIN="$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN
+if test -n "$ac_ct_DUMPBIN"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5
+printf "%s\n" "$ac_ct_DUMPBIN" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_DUMPBIN" && break
+done
+
+ if test "x$ac_ct_DUMPBIN" = x; then
+ DUMPBIN=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DUMPBIN=$ac_ct_DUMPBIN
+ fi
+fi
+
+ case `$DUMPBIN -symbols -headers /dev/null 2>&1 | $SED '1q'` in
+ *COFF*)
+ DUMPBIN="$DUMPBIN -symbols -headers"
+ ;;
+ *)
+ DUMPBIN=:
+ ;;
+ esac
+ fi
+
+ if test : != "$DUMPBIN"; then
+ NM=$DUMPBIN
+ fi
+fi
+test -z "$NM" && NM=nm
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5
+printf %s "checking the name lister ($NM) interface... " >&6; }
+if test ${lt_cv_nm_interface+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_nm_interface="BSD nm"
+ echo "int some_variable = 0;" > conftest.$ac_ext
+ (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5)
+ (eval "$ac_compile" 2>conftest.err)
+ cat conftest.err >&5
+ (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+ (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+ cat conftest.err >&5
+ (eval echo "\"\$as_me:$LINENO: output\"" >&5)
+ cat conftest.out >&5
+ if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+ lt_cv_nm_interface="MS dumpbin"
+ fi
+ rm -f conftest*
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5
+printf "%s\n" "$lt_cv_nm_interface" >&6; }
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+printf %s "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+printf "%s\n" "no, using $LN_S" >&6; }
+fi
+
+# find the maximum length of command line arguments
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5
+printf %s "checking the maximum length of command line arguments... " >&6; }
+if test ${lt_cv_sys_max_cmd_len+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ i=0
+ teststring=ABCD
+
+ case $build_os in
+ msdosdjgpp*)
+ # On DJGPP, this test can blow up pretty badly due to problems in libc
+ # (any single argument exceeding 2000 bytes causes a buffer overrun
+ # during glob expansion). Even if it were fixed, the result of this
+ # check would be larger than it should be.
+ lt_cv_sys_max_cmd_len=12288; # 12K is about right
+ ;;
+
+ gnu*)
+ # Under GNU Hurd, this test is not required because there is
+ # no limit to the length of command line arguments.
+ # Libtool will interpret -1 as no limit whatsoever
+ lt_cv_sys_max_cmd_len=-1;
+ ;;
+
+ cygwin* | mingw* | cegcc*)
+ # On Win9x/ME, this test blows up -- it succeeds, but takes
+ # about 5 minutes as the teststring grows exponentially.
+ # Worse, since 9x/ME are not pre-emptively multitasking,
+ # you end up with a "frozen" computer, even though with patience
+ # the test eventually succeeds (with a max line length of 256k).
+ # Instead, let's just punt: use the minimum linelength reported by
+ # all of the supported platforms: 8192 (on NT/2K/XP).
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ mint*)
+ # On MiNT this can take a long time and run out of memory.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ amigaos*)
+ # On AmigaOS with pdksh, this test takes hours, literally.
+ # So we just punt and use a minimum line length of 8192.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ bitrig* | darwin* | dragonfly* | freebsd* | midnightbsd* | netbsd* | openbsd*)
+ # This has been around since 386BSD, at least. Likely further.
+ if test -x /sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+ elif test -x /usr/sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+ else
+ lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs
+ fi
+ # And add a safety zone
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ ;;
+
+ interix*)
+ # We know the value 262144 and hardcode it with a safety zone (like BSD)
+ lt_cv_sys_max_cmd_len=196608
+ ;;
+
+ os2*)
+ # The test takes a long time on OS/2.
+ lt_cv_sys_max_cmd_len=8192
+ ;;
+
+ osf*)
+ # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+ # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+ # nice to cause kernel panics so lets avoid the loop below.
+ # First set a reasonable default.
+ lt_cv_sys_max_cmd_len=16384
+ #
+ if test -x /sbin/sysconfig; then
+ case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+ *1*) lt_cv_sys_max_cmd_len=-1 ;;
+ esac
+ fi
+ ;;
+ sco3.2v5*)
+ lt_cv_sys_max_cmd_len=102400
+ ;;
+ sysv5* | sco5v6* | sysv4.2uw2*)
+ kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+ if test -n "$kargmax"; then
+ lt_cv_sys_max_cmd_len=`echo $kargmax | $SED 's/.*[ ]//'`
+ else
+ lt_cv_sys_max_cmd_len=32768
+ fi
+ ;;
+ *)
+ lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+ if test -n "$lt_cv_sys_max_cmd_len" && \
+ test undefined != "$lt_cv_sys_max_cmd_len"; then
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ else
+ # Make teststring a little bigger before we do anything with it.
+ # a 1K string should be a reasonable start.
+ for i in 1 2 3 4 5 6 7 8; do
+ teststring=$teststring$teststring
+ done
+ SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+ # If test is not a shell built-in, we'll probably end up computing a
+ # maximum length that is only half of the actual maximum length, but
+ # we can't tell.
+ while { test X`env echo "$teststring$teststring" 2>/dev/null` \
+ = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+ test 17 != "$i" # 1/2 MB should be enough
+ do
+ i=`expr $i + 1`
+ teststring=$teststring$teststring
+ done
+ # Only check the string length outside the loop.
+ lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+ teststring=
+ # Add a significant safety factor because C++ compilers can tack on
+ # massive amounts of additional arguments before passing them to the
+ # linker. It appears as though 1/2 is a usable value.
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+ fi
+ ;;
+ esac
+
+fi
+
+if test -n "$lt_cv_sys_max_cmd_len"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5
+printf "%s\n" "$lt_cv_sys_max_cmd_len" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none" >&5
+printf "%s\n" "none" >&6; }
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+
+
+
+
+
+
+: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ lt_unset=unset
+else
+ lt_unset=false
+fi
+
+
+
+
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+ # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+ lt_SP2NL='tr \040 \012'
+ lt_NL2SP='tr \015\012 \040\040'
+ ;;
+ *) # EBCDIC based system
+ lt_SP2NL='tr \100 \n'
+ lt_NL2SP='tr \r\n \100\100'
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5
+printf %s "checking how to convert $build file names to $host format... " >&6; }
+if test ${lt_cv_to_host_file_cmd+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+ ;;
+ esac
+ ;;
+ *-*-cygwin* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+ ;;
+ esac
+ ;;
+ * ) # unhandled hosts (and "normal" native builds)
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+esac
+
+fi
+
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5
+printf "%s\n" "$lt_cv_to_host_file_cmd" >&6; }
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5
+printf %s "checking how to convert $build file names to toolchain format... " >&6; }
+if test ${lt_cv_to_tool_file_cmd+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ #assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ esac
+ ;;
+esac
+
+fi
+
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5
+printf "%s\n" "$lt_cv_to_tool_file_cmd" >&6; }
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5
+printf %s "checking for $LD option to reload object files... " >&6; }
+if test ${lt_cv_ld_reload_flag+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_ld_reload_flag='-r'
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5
+printf "%s\n" "$lt_cv_ld_reload_flag" >&6; }
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ if test yes != "$GCC"; then
+ reload_cmds=false
+ fi
+ ;;
+ darwin*)
+ if test yes = "$GCC"; then
+ reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs'
+ else
+ reload_cmds='$LD$reload_flag -o $output$reload_objs'
+ fi
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}file", so it can be a program name with args.
+set dummy ${ac_tool_prefix}file; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_FILECMD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$FILECMD"; then
+ ac_cv_prog_FILECMD="$FILECMD" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_FILECMD="${ac_tool_prefix}file"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+FILECMD=$ac_cv_prog_FILECMD
+if test -n "$FILECMD"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $FILECMD" >&5
+printf "%s\n" "$FILECMD" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_FILECMD"; then
+ ac_ct_FILECMD=$FILECMD
+ # Extract the first word of "file", so it can be a program name with args.
+set dummy file; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_FILECMD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_FILECMD"; then
+ ac_cv_prog_ac_ct_FILECMD="$ac_ct_FILECMD" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_FILECMD="file"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_FILECMD=$ac_cv_prog_ac_ct_FILECMD
+if test -n "$ac_ct_FILECMD"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_FILECMD" >&5
+printf "%s\n" "$ac_ct_FILECMD" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_FILECMD" = x; then
+ FILECMD=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ FILECMD=$ac_ct_FILECMD
+ fi
+else
+ FILECMD="$ac_cv_prog_FILECMD"
+fi
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args.
+set dummy ${ac_tool_prefix}objdump; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_OBJDUMP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$OBJDUMP"; then
+ ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OBJDUMP=$ac_cv_prog_OBJDUMP
+if test -n "$OBJDUMP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5
+printf "%s\n" "$OBJDUMP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OBJDUMP"; then
+ ac_ct_OBJDUMP=$OBJDUMP
+ # Extract the first word of "objdump", so it can be a program name with args.
+set dummy objdump; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_OBJDUMP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_OBJDUMP"; then
+ ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OBJDUMP="objdump"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP
+if test -n "$ac_ct_OBJDUMP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5
+printf "%s\n" "$ac_ct_OBJDUMP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_OBJDUMP" = x; then
+ OBJDUMP="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OBJDUMP=$ac_ct_OBJDUMP
+ fi
+else
+ OBJDUMP="$ac_cv_prog_OBJDUMP"
+fi
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5
+printf %s "checking how to recognize dependent libraries... " >&6; }
+if test ${lt_cv_deplibs_check_method+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# 'unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# that responds to the $file_magic_cmd with a given extended regex.
+# If you have 'file' or equivalent on your system and you're not sure
+# whether 'pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[4-9]*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+beos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+bsdi[45]*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)'
+ lt_cv_file_magic_cmd='$FILECMD -L'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ ;;
+
+cygwin*)
+ # func_win32_libid is a shell function defined in ltmain.sh
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ ;;
+
+mingw* | pw32*)
+ # Base MSYS/MinGW do not provide the 'file' command needed by
+ # func_win32_libid shell function, so use a weaker test based on 'objdump',
+ # unless we find 'file', for example because we are cross-compiling.
+ if ( file / ) >/dev/null 2>&1; then
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ else
+ # Keep this pattern in sync with the one in func_win32_libid.
+ lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ fi
+ ;;
+
+cegcc*)
+ # use the weaker test based on 'objdump'. See mingw*.
+ lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ ;;
+
+darwin* | rhapsody*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+freebsd* | dragonfly* | midnightbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ case $host_cpu in
+ i*86 )
+ # Not sure whether the presence of OpenBSD here was a mistake.
+ # Let's accept both of them until this is cleared up.
+ lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library'
+ lt_cv_file_magic_cmd=$FILECMD
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ ;;
+ esac
+ else
+ lt_cv_deplibs_check_method=pass_all
+ fi
+ ;;
+
+haiku*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+hpux10.20* | hpux11*)
+ lt_cv_file_magic_cmd=$FILECMD
+ case $host_cpu in
+ ia64*)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64'
+ lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+ ;;
+ hppa*64*)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'
+ lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+ ;;
+ *)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library'
+ lt_cv_file_magic_test_file=/usr/lib/libc.sl
+ ;;
+ esac
+ ;;
+
+interix[3-9]*)
+ # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$'
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $LD in
+ *-32|*"-32 ") libmagic=32-bit;;
+ *-n32|*"-n32 ") libmagic=N32;;
+ *-64|*"-64 ") libmagic=64-bit;;
+ *) libmagic=never-match;;
+ esac
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$'
+ fi
+ ;;
+
+newos6*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)'
+ lt_cv_file_magic_cmd=$FILECMD
+ lt_cv_file_magic_test_file=/usr/lib/libnls.so
+ ;;
+
+*nto* | *qnx*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+openbsd* | bitrig*)
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+ fi
+ ;;
+
+osf3* | osf4* | osf5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+rdos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+solaris*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv4 | sysv4.3*)
+ case $host_vendor in
+ motorola)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]'
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+ ;;
+ ncr)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ sequent)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )'
+ ;;
+ sni)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib"
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+ siemens)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ pc)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ esac
+ ;;
+
+tpf*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+os2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+esac
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5
+printf "%s\n" "$lt_cv_deplibs_check_method" >&6; }
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+ case $host_os in
+ mingw* | pw32*)
+ if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+ want_nocaseglob=yes
+ else
+ file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"`
+ fi
+ ;;
+ esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dlltool; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_DLLTOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$DLLTOOL"; then
+ ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DLLTOOL=$ac_cv_prog_DLLTOOL
+if test -n "$DLLTOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5
+printf "%s\n" "$DLLTOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DLLTOOL"; then
+ ac_ct_DLLTOOL=$DLLTOOL
+ # Extract the first word of "dlltool", so it can be a program name with args.
+set dummy dlltool; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_DLLTOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_DLLTOOL"; then
+ ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DLLTOOL="dlltool"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL
+if test -n "$ac_ct_DLLTOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5
+printf "%s\n" "$ac_ct_DLLTOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_DLLTOOL" = x; then
+ DLLTOOL="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DLLTOOL=$ac_ct_DLLTOOL
+ fi
+else
+ DLLTOOL="$ac_cv_prog_DLLTOOL"
+fi
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5
+printf %s "checking how to associate runtime and link libraries... " >&6; }
+if test ${lt_cv_sharedlib_from_linklib_cmd+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+ # two different shell functions defined in ltmain.sh;
+ # decide which one to use based on capabilities of $DLLTOOL
+ case `$DLLTOOL --help 2>&1` in
+ *--identify-strict*)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+ ;;
+ *)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+ ;;
+ esac
+ ;;
+*)
+ # fallback: assume linklib IS sharedlib
+ lt_cv_sharedlib_from_linklib_cmd=$ECHO
+ ;;
+esac
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5
+printf "%s\n" "$lt_cv_sharedlib_from_linklib_cmd" >&6; }
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ for ac_prog in ar
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_AR+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$AR"; then
+ ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AR="$ac_tool_prefix$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+printf "%s\n" "$AR" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$AR" && break
+ done
+fi
+if test -z "$AR"; then
+ ac_ct_AR=$AR
+ for ac_prog in ar
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_AR+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_AR"; then
+ ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_AR="$ac_prog"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+printf "%s\n" "$ac_ct_AR" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_AR" && break
+done
+
+ if test "x$ac_ct_AR" = x; then
+ AR="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ AR=$ac_ct_AR
+ fi
+fi
+
+: ${AR=ar}
+
+
+
+
+
+
+# Use ARFLAGS variable as AR's operation code to sync the variable naming with
+# Automake. If both AR_FLAGS and ARFLAGS are specified, AR_FLAGS should have
+# higher priority because thats what people were doing historically (setting
+# ARFLAGS for automake and AR_FLAGS for libtool). FIXME: Make the AR_FLAGS
+# variable obsoleted/removed.
+
+test ${AR_FLAGS+y} || AR_FLAGS=${ARFLAGS-cr}
+lt_ar_flags=$AR_FLAGS
+
+
+
+
+
+
+# Make AR_FLAGS overridable by 'make ARFLAGS='. Don't try to run-time override
+# by AR_FLAGS because that was never working and AR_FLAGS is about to die.
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5
+printf %s "checking for archiver @FILE support... " >&6; }
+if test ${lt_cv_ar_at_file+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_ar_at_file=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+ echo conftest.$ac_objext > conftest.lst
+ lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5'
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+ (eval $lt_ar_try) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test 0 -eq "$ac_status"; then
+ # Ensure the archiver fails upon bogus file names.
+ rm -f conftest.$ac_objext libconftest.a
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+ (eval $lt_ar_try) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test 0 -ne "$ac_status"; then
+ lt_cv_ar_at_file=@
+ fi
+ fi
+ rm -f conftest.* libconftest.a
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5
+printf "%s\n" "$lt_cv_ar_at_file" >&6; }
+
+if test no = "$lt_cv_ar_at_file"; then
+ archiver_list_spec=
+else
+ archiver_list_spec=$lt_cv_ar_at_file
+fi
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_STRIP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+printf "%s\n" "$STRIP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+ ac_ct_STRIP=$STRIP
+ # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_STRIP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_STRIP"; then
+ ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_STRIP="strip"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+printf "%s\n" "$ac_ct_STRIP" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_STRIP" = x; then
+ STRIP=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ STRIP=$ac_ct_STRIP
+ fi
+else
+ STRIP="$ac_cv_prog_STRIP"
+fi
+
+test -z "$STRIP" && STRIP=:
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_RANLIB+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+printf "%s\n" "$RANLIB" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+ ac_ct_RANLIB=$RANLIB
+ # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_RANLIB+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_RANLIB"; then
+ ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_RANLIB="ranlib"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+printf "%s\n" "$ac_ct_RANLIB" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_RANLIB" = x; then
+ RANLIB=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ RANLIB=$ac_ct_RANLIB
+ fi
+else
+ RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+test -z "$RANLIB" && RANLIB=:
+
+
+
+
+
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+ case $host_os in
+ bitrig* | openbsd*)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+ ;;
+ *)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+ ;;
+ esac
+ old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+ darwin*)
+ lock_old_archive_extraction=yes ;;
+ *)
+ lock_old_archive_extraction=no ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5
+printf %s "checking command to parse $NM output from $compiler object... " >&6; }
+if test ${lt_cv_sys_global_symbol_pipe+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix. What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[BCDEGRST]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([_A-Za-z][_A-Za-z0-9]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+ symcode='[BCDT]'
+ ;;
+cygwin* | mingw* | pw32* | cegcc*)
+ symcode='[ABCDGISTW]'
+ ;;
+hpux*)
+ if test ia64 = "$host_cpu"; then
+ symcode='[ABCDEGRST]'
+ fi
+ ;;
+irix* | nonstopux*)
+ symcode='[BCDEGRST]'
+ ;;
+osf*)
+ symcode='[BCDEGQRST]'
+ ;;
+solaris*)
+ symcode='[BDRT]'
+ ;;
+sco3.2v5*)
+ symcode='[DT]'
+ ;;
+sysv4.2uw2*)
+ symcode='[DT]'
+ ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+ symcode='[ABDT]'
+ ;;
+sysv4)
+ symcode='[DFNSTU]'
+ ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+ symcode='[ABCDGIRSTW]' ;;
+esac
+
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ # Gets list of data symbols to import.
+ lt_cv_sys_global_symbol_to_import="$SED -n -e 's/^I .* \(.*\)$/\1/p'"
+ # Adjust the below global symbol transforms to fixup imported variables.
+ lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'"
+ lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'"
+ lt_c_name_lib_hook="\
+ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\
+ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'"
+else
+ # Disable hooks by default.
+ lt_cv_sys_global_symbol_to_import=
+ lt_cdecl_hook=
+ lt_c_name_hook=
+ lt_c_name_lib_hook=
+fi
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="$SED -n"\
+$lt_cdecl_hook\
+" -e 's/^T .* \(.*\)$/extern int \1();/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="$SED -n"\
+$lt_c_name_hook\
+" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'"
+
+# Transform an extracted symbol line into symbol name with lib prefix and
+# symbol address.
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="$SED -n"\
+$lt_c_name_lib_hook\
+" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\
+" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\
+" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+ opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+ ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+ # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+ symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+ # Write the raw and C identifiers.
+ if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ # Fake it for dumpbin and say T for any non-static function,
+ # D for any global variable and I for any imported variable.
+ # Also find C++ and __fastcall symbols from MSVC++ or ICC,
+ # which start with @ or ?.
+ lt_cv_sys_global_symbol_pipe="$AWK '"\
+" {last_section=section; section=\$ 3};"\
+" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\
+" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\
+" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\
+" \$ 0!~/External *\|/{next};"\
+" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+" {if(hide[section]) next};"\
+" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\
+" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\
+" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\
+" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\
+" ' prfx=^$ac_symprfx"
+ else
+ lt_cv_sys_global_symbol_pipe="$SED -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+ fi
+ lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | $SED '/ __gnu_lto/d'"
+
+ # Check to see that the pipe works correctly.
+ pipe_works=no
+
+ rm -f conftest*
+ cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ # Now try to grab the symbols.
+ nlist=conftest.nm
+ $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5
+ if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then
+ # Try sorting and uniquifying the output.
+ if sort "$nlist" | uniq > "$nlist"T; then
+ mv -f "$nlist"T "$nlist"
+ else
+ rm -f "$nlist"T
+ fi
+
+ # Make sure that we snagged all the symbols we need.
+ if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+ if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+ cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */
+#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE
+/* DATA imports from DLLs on WIN32 can't be const, because runtime
+ relocations are performed -- see ld's documentation on pseudo-relocs. */
+# define LT_DLSYM_CONST
+#elif defined __osf__
+/* This system does not cope well with relocations in const data. */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+ # Now generate the symbol file.
+ eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+ cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols. */
+LT_DLSYM_CONST struct {
+ const char *name;
+ void *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[] =
+{
+ { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+ $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+ cat <<\_LT_EOF >> conftest.$ac_ext
+ {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+ return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+ # Now try linking the two files.
+ mv conftest.$ac_objext conftstm.$ac_objext
+ lt_globsym_save_LIBS=$LIBS
+ lt_globsym_save_CFLAGS=$CFLAGS
+ LIBS=conftstm.$ac_objext
+ CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag"
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s conftest$ac_exeext; then
+ pipe_works=yes
+ fi
+ LIBS=$lt_globsym_save_LIBS
+ CFLAGS=$lt_globsym_save_CFLAGS
+ else
+ echo "cannot find nm_test_func in $nlist" >&5
+ fi
+ else
+ echo "cannot find nm_test_var in $nlist" >&5
+ fi
+ else
+ echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5
+ fi
+ else
+ echo "$progname: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ fi
+ rm -rf conftest* conftst*
+
+ # Do not use the global_symbol_pipe unless it works.
+ if test yes = "$pipe_works"; then
+ break
+ else
+ lt_cv_sys_global_symbol_pipe=
+ fi
+done
+
+fi
+
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+ lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: failed" >&5
+printf "%s\n" "failed" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5
+printf "%s\n" "ok" >&6; }
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then
+ nm_file_list_spec='@'
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5
+printf %s "checking for sysroot... " >&6; }
+
+# Check whether --with-sysroot was given.
+if test ${with_sysroot+y}
+then :
+ withval=$with_sysroot;
+else $as_nop
+ with_sysroot=no
+fi
+
+
+lt_sysroot=
+case $with_sysroot in #(
+ yes)
+ if test yes = "$GCC"; then
+ lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+ fi
+ ;; #(
+ /*)
+ lt_sysroot=`echo "$with_sysroot" | $SED -e "$sed_quote_subst"`
+ ;; #(
+ no|'')
+ ;; #(
+ *)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5
+printf "%s\n" "$with_sysroot" >&6; }
+ as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5
+ ;;
+esac
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5
+printf "%s\n" "${lt_sysroot:-no}" >&6; }
+
+
+
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5
+printf %s "checking for a working dd... " >&6; }
+if test ${ac_cv_path_lt_DD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ printf 0123456789abcdef0123456789abcdef >conftest.i
+cat conftest.i conftest.i >conftest2.i
+: ${lt_DD:=$DD}
+if test -z "$lt_DD"; then
+ ac_path_lt_DD_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in dd
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_lt_DD="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_lt_DD" || continue
+if "$ac_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then
+ cmp -s conftest.i conftest.out \
+ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=:
+fi
+ $ac_path_lt_DD_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_lt_DD"; then
+ :
+ fi
+else
+ ac_cv_path_lt_DD=$lt_DD
+fi
+
+rm -f conftest.i conftest2.i conftest.out
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5
+printf "%s\n" "$ac_cv_path_lt_DD" >&6; }
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5
+printf %s "checking how to truncate binary pipes... " >&6; }
+if test ${lt_cv_truncate_bin+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ printf 0123456789abcdef0123456789abcdef >conftest.i
+cat conftest.i conftest.i >conftest2.i
+lt_cv_truncate_bin=
+if "$ac_cv_path_lt_DD" bs=32 count=1 <conftest2.i >conftest.out 2>/dev/null; then
+ cmp -s conftest.i conftest.out \
+ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1"
+fi
+rm -f conftest.i conftest2.i conftest.out
+test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5
+printf "%s\n" "$lt_cv_truncate_bin" >&6; }
+
+
+
+
+
+
+
+# Calculate cc_basename. Skip known compiler wrappers and cross-prefix.
+func_cc_basename ()
+{
+ for cc_temp in $*""; do
+ case $cc_temp in
+ compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+ distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+ done
+ func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+}
+
+# Check whether --enable-libtool-lock was given.
+if test ${enable_libtool_lock+y}
+then :
+ enableval=$enable_libtool_lock;
+fi
+
+test no = "$enable_libtool_lock" || enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+ # Find out what ABI is being produced by ac_compile, and set mode
+ # options accordingly.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `$FILECMD conftest.$ac_objext` in
+ *ELF-32*)
+ HPUX_IA64_MODE=32
+ ;;
+ *ELF-64*)
+ HPUX_IA64_MODE=64
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+*-*-irix6*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo '#line '$LINENO' "configure"' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ if test yes = "$lt_cv_prog_gnu_ld"; then
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -melf32bsmip"
+ ;;
+ *N32*)
+ LD="${LD-ld} -melf32bmipn32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -melf64bmip"
+ ;;
+ esac
+ else
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -32"
+ ;;
+ *N32*)
+ LD="${LD-ld} -n32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -64"
+ ;;
+ esac
+ fi
+ fi
+ rm -rf conftest*
+ ;;
+
+mips64*-*linux*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo '#line '$LINENO' "configure"' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ emul=elf
+ case `$FILECMD conftest.$ac_objext` in
+ *32-bit*)
+ emul="${emul}32"
+ ;;
+ *64-bit*)
+ emul="${emul}64"
+ ;;
+ esac
+ case `$FILECMD conftest.$ac_objext` in
+ *MSB*)
+ emul="${emul}btsmip"
+ ;;
+ *LSB*)
+ emul="${emul}ltsmip"
+ ;;
+ esac
+ case `$FILECMD conftest.$ac_objext` in
+ *N32*)
+ emul="${emul}n32"
+ ;;
+ esac
+ LD="${LD-ld} -m $emul"
+ fi
+ rm -rf conftest*
+ ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly. Note that the listed cases only cover the
+ # situations where additional linker options are needed (such as when
+ # doing 32-bit compilation for a host where ld defaults to 64-bit, or
+ # vice versa); the common cases where no linker options are needed do
+ # not appear in the list.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `$FILECMD conftest.o` in
+ *32-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_i386_fbsd"
+ ;;
+ x86_64-*linux*)
+ case `$FILECMD conftest.o` in
+ *x86-64*)
+ LD="${LD-ld} -m elf32_x86_64"
+ ;;
+ *)
+ LD="${LD-ld} -m elf_i386"
+ ;;
+ esac
+ ;;
+ powerpc64le-*linux*)
+ LD="${LD-ld} -m elf32lppclinux"
+ ;;
+ powerpc64-*linux*)
+ LD="${LD-ld} -m elf32ppclinux"
+ ;;
+ s390x-*linux*)
+ LD="${LD-ld} -m elf_s390"
+ ;;
+ sparc64-*linux*)
+ LD="${LD-ld} -m elf32_sparc"
+ ;;
+ esac
+ ;;
+ *64-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_x86_64_fbsd"
+ ;;
+ x86_64-*linux*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ powerpcle-*linux*)
+ LD="${LD-ld} -m elf64lppc"
+ ;;
+ powerpc-*linux*)
+ LD="${LD-ld} -m elf64ppc"
+ ;;
+ s390*-*linux*|s390*-*tpf*)
+ LD="${LD-ld} -m elf64_s390"
+ ;;
+ sparc*-*linux*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+
+*-*-sco3.2v5*)
+ # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+ SAVE_CFLAGS=$CFLAGS
+ CFLAGS="$CFLAGS -belf"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5
+printf %s "checking whether the C compiler needs -belf... " >&6; }
+if test ${lt_cv_cc_needs_belf+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ lt_cv_cc_needs_belf=yes
+else $as_nop
+ lt_cv_cc_needs_belf=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5
+printf "%s\n" "$lt_cv_cc_needs_belf" >&6; }
+ if test yes != "$lt_cv_cc_needs_belf"; then
+ # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+ CFLAGS=$SAVE_CFLAGS
+ fi
+ ;;
+*-*solaris*)
+ # Find out what ABI is being produced by ac_compile, and set linker
+ # options accordingly.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `$FILECMD conftest.o` in
+ *64-bit*)
+ case $lt_cv_prog_gnu_ld in
+ yes*)
+ case $host in
+ i?86-*-solaris*|x86_64-*-solaris*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ sparc*-*-solaris*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ # GNU ld 2.21 introduced _sol2 emulations. Use them if available.
+ if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+ LD=${LD-ld}_sol2
+ fi
+ ;;
+ *)
+ if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+ LD="${LD-ld} -64"
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+esac
+
+need_locks=$enable_libtool_lock
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args.
+set dummy ${ac_tool_prefix}mt; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_MANIFEST_TOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$MANIFEST_TOOL"; then
+ ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL
+if test -n "$MANIFEST_TOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5
+printf "%s\n" "$MANIFEST_TOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_MANIFEST_TOOL"; then
+ ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL
+ # Extract the first word of "mt", so it can be a program name with args.
+set dummy mt; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_MANIFEST_TOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_MANIFEST_TOOL"; then
+ ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_MANIFEST_TOOL="mt"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL
+if test -n "$ac_ct_MANIFEST_TOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5
+printf "%s\n" "$ac_ct_MANIFEST_TOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_MANIFEST_TOOL" = x; then
+ MANIFEST_TOOL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL
+ fi
+else
+ MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL"
+fi
+
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5
+printf %s "checking if $MANIFEST_TOOL is a manifest tool... " >&6; }
+if test ${lt_cv_path_mainfest_tool+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_path_mainfest_tool=no
+ echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5
+ $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+ cat conftest.err >&5
+ if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+ lt_cv_path_mainfest_tool=yes
+ fi
+ rm -f conftest*
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5
+printf "%s\n" "$lt_cv_path_mainfest_tool" >&6; }
+if test yes != "$lt_cv_path_mainfest_tool"; then
+ MANIFEST_TOOL=:
+fi
+
+
+
+
+
+
+ case $host_os in
+ rhapsody* | darwin*)
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dsymutil; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_DSYMUTIL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$DSYMUTIL"; then
+ ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DSYMUTIL=$ac_cv_prog_DSYMUTIL
+if test -n "$DSYMUTIL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5
+printf "%s\n" "$DSYMUTIL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DSYMUTIL"; then
+ ac_ct_DSYMUTIL=$DSYMUTIL
+ # Extract the first word of "dsymutil", so it can be a program name with args.
+set dummy dsymutil; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_DSYMUTIL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_DSYMUTIL"; then
+ ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DSYMUTIL="dsymutil"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL
+if test -n "$ac_ct_DSYMUTIL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5
+printf "%s\n" "$ac_ct_DSYMUTIL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_DSYMUTIL" = x; then
+ DSYMUTIL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DSYMUTIL=$ac_ct_DSYMUTIL
+ fi
+else
+ DSYMUTIL="$ac_cv_prog_DSYMUTIL"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args.
+set dummy ${ac_tool_prefix}nmedit; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_NMEDIT+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$NMEDIT"; then
+ ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+NMEDIT=$ac_cv_prog_NMEDIT
+if test -n "$NMEDIT"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5
+printf "%s\n" "$NMEDIT" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_NMEDIT"; then
+ ac_ct_NMEDIT=$NMEDIT
+ # Extract the first word of "nmedit", so it can be a program name with args.
+set dummy nmedit; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_NMEDIT+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_NMEDIT"; then
+ ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_NMEDIT="nmedit"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT
+if test -n "$ac_ct_NMEDIT"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5
+printf "%s\n" "$ac_ct_NMEDIT" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_NMEDIT" = x; then
+ NMEDIT=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ NMEDIT=$ac_ct_NMEDIT
+ fi
+else
+ NMEDIT="$ac_cv_prog_NMEDIT"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args.
+set dummy ${ac_tool_prefix}lipo; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_LIPO+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$LIPO"; then
+ ac_cv_prog_LIPO="$LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_LIPO="${ac_tool_prefix}lipo"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+LIPO=$ac_cv_prog_LIPO
+if test -n "$LIPO"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5
+printf "%s\n" "$LIPO" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_LIPO"; then
+ ac_ct_LIPO=$LIPO
+ # Extract the first word of "lipo", so it can be a program name with args.
+set dummy lipo; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_LIPO+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_LIPO"; then
+ ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_LIPO="lipo"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO
+if test -n "$ac_ct_LIPO"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5
+printf "%s\n" "$ac_ct_LIPO" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_LIPO" = x; then
+ LIPO=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ LIPO=$ac_ct_LIPO
+ fi
+else
+ LIPO="$ac_cv_prog_LIPO"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_OTOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$OTOOL"; then
+ ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OTOOL="${ac_tool_prefix}otool"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL=$ac_cv_prog_OTOOL
+if test -n "$OTOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5
+printf "%s\n" "$OTOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL"; then
+ ac_ct_OTOOL=$OTOOL
+ # Extract the first word of "otool", so it can be a program name with args.
+set dummy otool; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_OTOOL+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_OTOOL"; then
+ ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OTOOL="otool"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL
+if test -n "$ac_ct_OTOOL"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5
+printf "%s\n" "$ac_ct_OTOOL" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_OTOOL" = x; then
+ OTOOL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OTOOL=$ac_ct_OTOOL
+ fi
+else
+ OTOOL="$ac_cv_prog_OTOOL"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool64; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_OTOOL64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$OTOOL64"; then
+ ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL64=$ac_cv_prog_OTOOL64
+if test -n "$OTOOL64"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5
+printf "%s\n" "$OTOOL64" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL64"; then
+ ac_ct_OTOOL64=$OTOOL64
+ # Extract the first word of "otool64", so it can be a program name with args.
+set dummy otool64; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_OTOOL64+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test -n "$ac_ct_OTOOL64"; then
+ ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OTOOL64="otool64"
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64
+if test -n "$ac_ct_OTOOL64"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5
+printf "%s\n" "$ac_ct_OTOOL64" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+ if test "x$ac_ct_OTOOL64" = x; then
+ OTOOL64=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OTOOL64=$ac_ct_OTOOL64
+ fi
+else
+ OTOOL64="$ac_cv_prog_OTOOL64"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5
+printf %s "checking for -single_module linker flag... " >&6; }
+if test ${lt_cv_apple_cc_single_mod+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_apple_cc_single_mod=no
+ if test -z "$LT_MULTI_MODULE"; then
+ # By default we will add the -single_module flag. You can override
+ # by either setting the environment variable LT_MULTI_MODULE
+ # non-empty at configure time, or by adding -multi_module to the
+ # link flags.
+ rm -rf libconftest.dylib*
+ echo "int foo(void){return 1;}" > conftest.c
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&5
+ $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+ _lt_result=$?
+ # If there is a non-empty error log, and "single_module"
+ # appears in it, assume the flag caused a linker warning
+ if test -s conftest.err && $GREP single_module conftest.err; then
+ cat conftest.err >&5
+ # Otherwise, if the output was created with a 0 exit code from
+ # the compiler, it worked.
+ elif test -f libconftest.dylib && test 0 = "$_lt_result"; then
+ lt_cv_apple_cc_single_mod=yes
+ else
+ cat conftest.err >&5
+ fi
+ rm -rf libconftest.dylib*
+ rm -f conftest.*
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5
+printf "%s\n" "$lt_cv_apple_cc_single_mod" >&6; }
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5
+printf %s "checking for -exported_symbols_list linker flag... " >&6; }
+if test ${lt_cv_ld_exported_symbols_list+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_ld_exported_symbols_list=no
+ save_LDFLAGS=$LDFLAGS
+ echo "_main" > conftest.sym
+ LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ lt_cv_ld_exported_symbols_list=yes
+else $as_nop
+ lt_cv_ld_exported_symbols_list=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS=$save_LDFLAGS
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5
+printf "%s\n" "$lt_cv_ld_exported_symbols_list" >&6; }
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5
+printf %s "checking for -force_load linker flag... " >&6; }
+if test ${lt_cv_ld_force_load+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_ld_force_load=no
+ cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5
+ $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5
+ echo "$AR $AR_FLAGS libconftest.a conftest.o" >&5
+ $AR $AR_FLAGS libconftest.a conftest.o 2>&5
+ echo "$RANLIB libconftest.a" >&5
+ $RANLIB libconftest.a 2>&5
+ cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5
+ $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+ _lt_result=$?
+ if test -s conftest.err && $GREP force_load conftest.err; then
+ cat conftest.err >&5
+ elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then
+ lt_cv_ld_force_load=yes
+ else
+ cat conftest.err >&5
+ fi
+ rm -f conftest.err libconftest.a conftest conftest.c
+ rm -rf conftest.dSYM
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5
+printf "%s\n" "$lt_cv_ld_force_load" >&6; }
+ case $host_os in
+ rhapsody* | darwin1.[012])
+ _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;;
+ darwin1.*)
+ _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;;
+ darwin*)
+ case $MACOSX_DEPLOYMENT_TARGET,$host in
+ 10.[012],*|,*powerpc*-darwin[5-8]*)
+ _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;;
+ *)
+ _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;;
+ esac
+ ;;
+ esac
+ if test yes = "$lt_cv_apple_cc_single_mod"; then
+ _lt_dar_single_mod='$single_module'
+ fi
+ if test yes = "$lt_cv_ld_exported_symbols_list"; then
+ _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym'
+ else
+ _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib'
+ fi
+ if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then
+ _lt_dsymutil='~$DSYMUTIL $lib || :'
+ else
+ _lt_dsymutil=
+ fi
+ ;;
+ esac
+
+# func_munge_path_list VARIABLE PATH
+# -----------------------------------
+# VARIABLE is name of variable containing _space_ separated list of
+# directories to be munged by the contents of PATH, which is string
+# having a format:
+# "DIR[:DIR]:"
+# string "DIR[ DIR]" will be prepended to VARIABLE
+# ":DIR[:DIR]"
+# string "DIR[ DIR]" will be appended to VARIABLE
+# "DIRP[:DIRP]::[DIRA:]DIRA"
+# string "DIRP[ DIRP]" will be prepended to VARIABLE and string
+# "DIRA[ DIRA]" will be appended to VARIABLE
+# "DIR[:DIR]"
+# VARIABLE will be replaced by "DIR[ DIR]"
+func_munge_path_list ()
+{
+ case x$2 in
+ x)
+ ;;
+ *:)
+ eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\"
+ ;;
+ x:*)
+ eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\"
+ ;;
+ *::*)
+ eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\"
+ eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\"
+ ;;
+ *)
+ eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\"
+ ;;
+ esac
+}
+
+ac_header= ac_cache=
+for ac_item in $ac_header_c_list
+do
+ if test $ac_cache; then
+ ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default"
+ if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then
+ printf "%s\n" "#define $ac_item 1" >> confdefs.h
+ fi
+ ac_header= ac_cache=
+ elif test $ac_header; then
+ ac_cache=$ac_item
+ else
+ ac_header=$ac_item
+ fi
+done
+
+
+
+
+
+
+
+
+if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes
+then :
+
+printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_dlfcn_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_DLFCN_H 1" >>confdefs.h
+
+fi
+
+
+
+
+
+# Set options
+
+
+
+ enable_dlopen=no
+
+
+ enable_win32_dll=no
+
+
+ # Check whether --enable-shared was given.
+if test ${enable_shared+y}
+then :
+ enableval=$enable_shared; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_shared=yes ;;
+ no) enable_shared=no ;;
+ *)
+ enable_shared=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_shared=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac
+else $as_nop
+ enable_shared=yes
+fi
+
+
+
+
+
+
+
+
+
+ # Check whether --enable-static was given.
+if test ${enable_static+y}
+then :
+ enableval=$enable_static; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_static=yes ;;
+ no) enable_static=no ;;
+ *)
+ enable_static=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_static=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac
+else $as_nop
+ enable_static=yes
+fi
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-pic was given.
+if test ${with_pic+y}
+then :
+ withval=$with_pic; lt_p=${PACKAGE-default}
+ case $withval in
+ yes|no) pic_mode=$withval ;;
+ *)
+ pic_mode=default
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for lt_pkg in $withval; do
+ IFS=$lt_save_ifs
+ if test "X$lt_pkg" = "X$lt_p"; then
+ pic_mode=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac
+else $as_nop
+ pic_mode=default
+fi
+
+
+
+
+
+
+
+
+ # Check whether --enable-fast-install was given.
+if test ${enable_fast_install+y}
+then :
+ enableval=$enable_fast_install; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_fast_install=yes ;;
+ no) enable_fast_install=no ;;
+ *)
+ enable_fast_install=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
+ for pkg in $enableval; do
+ IFS=$lt_save_ifs
+ if test "X$pkg" = "X$p"; then
+ enable_fast_install=yes
+ fi
+ done
+ IFS=$lt_save_ifs
+ ;;
+ esac
+else $as_nop
+ enable_fast_install=yes
+fi
+
+
+
+
+
+
+
+
+ shared_archive_member_spec=
+case $host,$enable_shared in
+power*-*-aix[5-9]*,yes)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5
+printf %s "checking which variant of shared library versioning to provide... " >&6; }
+
+# Check whether --with-aix-soname was given.
+if test ${with_aix_soname+y}
+then :
+ withval=$with_aix_soname; case $withval in
+ aix|svr4|both)
+ ;;
+ *)
+ as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5
+ ;;
+ esac
+ lt_cv_with_aix_soname=$with_aix_soname
+else $as_nop
+ if test ${lt_cv_with_aix_soname+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_with_aix_soname=aix
+fi
+
+ with_aix_soname=$lt_cv_with_aix_soname
+fi
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5
+printf "%s\n" "$with_aix_soname" >&6; }
+ if test aix != "$with_aix_soname"; then
+ # For the AIX way of multilib, we name the shared archive member
+ # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o',
+ # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File.
+ # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag,
+ # the AIX toolchain works better with OBJECT_MODE set (default 32).
+ if test 64 = "${OBJECT_MODE-32}"; then
+ shared_archive_member_spec=shr_64
+ else
+ shared_archive_member_spec=shr
+ fi
+ fi
+ ;;
+*)
+ with_aix_soname=aix
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS=$ltmain
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+test -z "$LN_S" && LN_S="ln -s"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5
+printf %s "checking for objdir... " >&6; }
+if test ${lt_cv_objdir+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+ lt_cv_objdir=.libs
+else
+ # MS-DOS does not allow filenames that begin with a dot.
+ lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5
+printf "%s\n" "$lt_cv_objdir" >&6; }
+objdir=$lt_cv_objdir
+
+
+
+
+
+printf "%s\n" "#define LT_OBJDIR \"$lt_cv_objdir/\"" >>confdefs.h
+
+
+
+
+case $host_os in
+aix3*)
+ # AIX sometimes has problems with the GCC collect2 program. For some
+ # reason, if we set the COLLECT_NAMES environment variable, the problems
+ # vanish in a puff of smoke.
+ if test set != "${COLLECT_NAMES+set}"; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+ fi
+ ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a '.a' archive for static linking (except MSVC and
+# ICC, which need '.lib').
+libext=a
+
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+old_CC=$CC
+old_CFLAGS=$CFLAGS
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+func_cc_basename $compiler
+cc_basename=$func_cc_basename_result
+
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+ if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5
+printf %s "checking for ${ac_tool_prefix}file... " >&6; }
+if test ${lt_cv_path_MAGIC_CMD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ case $MAGIC_CMD in
+[\\/*] | ?:[\\/]*)
+ lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path.
+ ;;
+*)
+ lt_save_MAGIC_CMD=$MAGIC_CMD
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+ for ac_dir in $ac_dummy; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/${ac_tool_prefix}file"; then
+ lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+ MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS=$lt_save_ifs
+ MAGIC_CMD=$lt_save_MAGIC_CMD
+ ;;
+esac
+fi
+
+MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+if test -n "$MAGIC_CMD"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+printf "%s\n" "$MAGIC_CMD" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+
+
+
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+ if test -n "$ac_tool_prefix"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for file" >&5
+printf %s "checking for file... " >&6; }
+if test ${lt_cv_path_MAGIC_CMD+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ case $MAGIC_CMD in
+[\\/*] | ?:[\\/]*)
+ lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path.
+ ;;
+*)
+ lt_save_MAGIC_CMD=$MAGIC_CMD
+ lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR
+ ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+ for ac_dir in $ac_dummy; do
+ IFS=$lt_save_ifs
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/file"; then
+ lt_cv_path_MAGIC_CMD=$ac_dir/"file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+ MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS=$lt_save_ifs
+ MAGIC_CMD=$lt_save_MAGIC_CMD
+ ;;
+esac
+fi
+
+MAGIC_CMD=$lt_cv_path_MAGIC_CMD
+if test -n "$MAGIC_CMD"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+printf "%s\n" "$MAGIC_CMD" >&6; }
+else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+ else
+ MAGIC_CMD=:
+ fi
+fi
+
+ fi
+ ;;
+esac
+
+# Use C for the default configuration in the libtool script
+
+lt_save_CC=$CC
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+objext=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+if test -n "$compiler"; then
+
+lt_prog_compiler_no_builtin_flag=
+
+if test yes = "$GCC"; then
+ case $cc_basename in
+ nvcc*)
+ lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;;
+ *)
+ lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;;
+ esac
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5
+printf %s "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; }
+if test ${lt_cv_prog_compiler_rtti_exceptions+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_rtti_exceptions=no
+ ac_outfile=conftest.$ac_objext
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_rtti_exceptions=yes
+ fi
+ fi
+ $RM conftest*
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5
+printf "%s\n" "$lt_cv_prog_compiler_rtti_exceptions" >&6; }
+
+if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then
+ lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions"
+else
+ :
+fi
+
+fi
+
+
+
+
+
+
+ lt_prog_compiler_wl=
+lt_prog_compiler_pic=
+lt_prog_compiler_static=
+
+
+ if test yes = "$GCC"; then
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_static='-static'
+
+ case $host_os in
+ aix*)
+ # All AIX code is PIC.
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static='-Bstatic'
+ fi
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ m68k)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the '-m68020' flag to GCC prevents building anything better,
+ # like '-m68040'.
+ lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ esac
+ ;;
+
+ beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ # Although the cygwin gcc ignores -fPIC, still need this for old-style
+ # (--disable-auto-import) libraries
+ lt_prog_compiler_pic='-DDLL_EXPORT'
+ case $host_os in
+ os2*)
+ lt_prog_compiler_static='$wl-static'
+ ;;
+ esac
+ ;;
+
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_prog_compiler_pic='-fno-common'
+ ;;
+
+ haiku*)
+ # PIC is the default for Haiku.
+ # The "-static" flag exists, but is broken.
+ lt_prog_compiler_static=
+ ;;
+
+ hpux*)
+ # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+ # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag
+ # sets the default TLS model and affects inlining.
+ case $host_cpu in
+ hppa*64*)
+ # +Z the default
+ ;;
+ *)
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ esac
+ ;;
+
+ interix[3-9]*)
+ # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+ # Instead, we relocate shared libraries at runtime.
+ ;;
+
+ msdosdjgpp*)
+ # Just because we use GCC doesn't mean we suddenly get shared libraries
+ # on systems that don't support them.
+ lt_prog_compiler_can_build_shared=no
+ enable_shared=no
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic='-fPIC -shared'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_prog_compiler_pic=-Kconform_pic
+ fi
+ ;;
+
+ *)
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ esac
+
+ case $cc_basename in
+ nvcc*) # Cuda Compiler Driver 2.2
+ lt_prog_compiler_wl='-Xlinker '
+ if test -n "$lt_prog_compiler_pic"; then
+ lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic"
+ fi
+ ;;
+ esac
+ else
+ # PORTME Check for flag to pass linker flags through the system compiler.
+ case $host_os in
+ aix*)
+ lt_prog_compiler_wl='-Wl,'
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static='-Bstatic'
+ else
+ lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_prog_compiler_pic='-fno-common'
+ case $cc_basename in
+ nagfor*)
+ # NAG Fortran compiler
+ lt_prog_compiler_wl='-Wl,-Wl,,'
+ lt_prog_compiler_pic='-PIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ esac
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_prog_compiler_pic='-DDLL_EXPORT'
+ case $host_os in
+ os2*)
+ lt_prog_compiler_static='$wl-static'
+ ;;
+ esac
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ lt_prog_compiler_wl='-Wl,'
+ # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+ # not for PA HP-UX.
+ case $host_cpu in
+ hppa*64*|ia64*)
+ # +Z the default
+ ;;
+ *)
+ lt_prog_compiler_pic='+Z'
+ ;;
+ esac
+ # Is there a better lt_prog_compiler_static that works with the bundled CC?
+ lt_prog_compiler_static='$wl-a ${wl}archive'
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ lt_prog_compiler_wl='-Wl,'
+ # PIC (with -KPIC) is the default.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ case $cc_basename in
+ # old Intel for x86_64, which still supported -KPIC.
+ ecc*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ # flang / f18. f95 an alias for gfortran or flang on Debian
+ flang* | f18* | f95*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ # icc used to be incompatible with GCC.
+ # ICC 10 doesn't accept -KPIC any more.
+ icc* | ifort*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ # Lahey Fortran 8.1.
+ lf95*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='--shared'
+ lt_prog_compiler_static='--static'
+ ;;
+ nagfor*)
+ # NAG Fortran compiler
+ lt_prog_compiler_wl='-Wl,-Wl,,'
+ lt_prog_compiler_pic='-PIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ tcc*)
+ # Fabrice Bellard et al's Tiny C Compiler
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group compilers (*not* the Pentium gcc compiler,
+ # which looks to be a dead project)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fpic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ ccc*)
+ lt_prog_compiler_wl='-Wl,'
+ # All Alpha code is PIC.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+ xl* | bgxl* | bgf* | mpixl*)
+ # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-qpic'
+ lt_prog_compiler_static='-qstaticlink'
+ ;;
+ *)
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*)
+ # Sun Fortran 8.3 passes all unrecognized flags to the linker
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl=''
+ ;;
+ *Sun\ F* | *Sun*Fortran*)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl='-Qoption ld '
+ ;;
+ *Sun\ C*)
+ # Sun C 5.9
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl='-Wl,'
+ ;;
+ *Intel*\ [CF]*Compiler*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ *Portland\ Group*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fpic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+ newsos6)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic='-fPIC -shared'
+ ;;
+
+ osf3* | osf4* | osf5*)
+ lt_prog_compiler_wl='-Wl,'
+ # All OSF/1 code is PIC.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ rdos*)
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ solaris*)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ case $cc_basename in
+ f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+ lt_prog_compiler_wl='-Qoption ld ';;
+ *)
+ lt_prog_compiler_wl='-Wl,';;
+ esac
+ ;;
+
+ sunos4*)
+ lt_prog_compiler_wl='-Qoption ld '
+ lt_prog_compiler_pic='-PIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ sysv4 | sysv4.2uw2* | sysv4.3*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_prog_compiler_pic='-Kconform_pic'
+ lt_prog_compiler_static='-Bstatic'
+ fi
+ ;;
+
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ unicos*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_can_build_shared=no
+ ;;
+
+ uts4*)
+ lt_prog_compiler_pic='-pic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ *)
+ lt_prog_compiler_can_build_shared=no
+ ;;
+ esac
+ fi
+
+case $host_os in
+ # For platforms that do not support PIC, -DPIC is meaningless:
+ *djgpp*)
+ lt_prog_compiler_pic=
+ ;;
+ *)
+ lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC"
+ ;;
+esac
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+printf %s "checking for $compiler option to produce PIC... " >&6; }
+if test ${lt_cv_prog_compiler_pic+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_pic=$lt_prog_compiler_pic
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5
+printf "%s\n" "$lt_cv_prog_compiler_pic" >&6; }
+lt_prog_compiler_pic=$lt_cv_prog_compiler_pic
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5
+printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; }
+if test ${lt_cv_prog_compiler_pic_works+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_pic_works=no
+ ac_outfile=conftest.$ac_objext
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_pic_works=yes
+ fi
+ fi
+ $RM conftest*
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5
+printf "%s\n" "$lt_cv_prog_compiler_pic_works" >&6; }
+
+if test yes = "$lt_cv_prog_compiler_pic_works"; then
+ case $lt_prog_compiler_pic in
+ "" | " "*) ;;
+ *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;;
+ esac
+else
+ lt_prog_compiler_pic=
+ lt_prog_compiler_can_build_shared=no
+fi
+
+fi
+
+
+
+
+
+
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if test ${lt_cv_prog_compiler_static_works+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_static_works=no
+ save_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&5
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_static_works=yes
+ fi
+ else
+ lt_cv_prog_compiler_static_works=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS=$save_LDFLAGS
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5
+printf "%s\n" "$lt_cv_prog_compiler_static_works" >&6; }
+
+if test yes = "$lt_cv_prog_compiler_static_works"; then
+ :
+else
+ lt_prog_compiler_static=
+fi
+
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if test ${lt_cv_prog_compiler_c_o+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_c_o=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+printf "%s\n" "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if test ${lt_cv_prog_compiler_c_o+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler_c_o=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+printf "%s\n" "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+hard_links=nottested
+if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then
+ # do not overwrite the value of need_locks provided by the user
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+printf %s "checking if we can lock with hard links... " >&6; }
+ hard_links=yes
+ $RM conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+printf "%s\n" "$hard_links" >&6; }
+ if test no = "$hard_links"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5
+printf "%s\n" "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;}
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+printf %s "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+ runpath_var=
+ allow_undefined_flag=
+ always_export_symbols=no
+ archive_cmds=
+ archive_expsym_cmds=
+ compiler_needs_object=no
+ enable_shared_with_static_runtimes=no
+ export_dynamic_flag_spec=
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ hardcode_automatic=no
+ hardcode_direct=no
+ hardcode_direct_absolute=no
+ hardcode_libdir_flag_spec=
+ hardcode_libdir_separator=
+ hardcode_minus_L=no
+ hardcode_shlibpath_var=unsupported
+ inherit_rpath=no
+ link_all_deplibs=unknown
+ module_cmds=
+ module_expsym_cmds=
+ old_archive_from_new_cmds=
+ old_archive_from_expsyms_cmds=
+ thread_safe_flag_spec=
+ whole_archive_flag_spec=
+ # include_expsyms should be a list of space-separated symbols to be *always*
+ # included in the symbol list
+ include_expsyms=
+ # exclude_expsyms can be an extended regexp of symbols to exclude
+ # it will be wrapped by ' (' and ')$', so one must not match beginning or
+ # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc',
+ # as well as any symbol that contains 'd'.
+ exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+ # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+ # platforms (ab)use it in PIC code, but their linkers get confused if
+ # the symbol is explicitly referenced. Since portable code cannot
+ # rely on this symbol name, it's probably fine to never include it in
+ # preloaded symbol tables.
+ # Exclude shared library initialization/finalization symbols.
+ extract_expsyms_cmds=
+
+ case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ # FIXME: the MSVC++ and ICC port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++ or Intel C++ Compiler.
+ if test yes != "$GCC"; then
+ with_gnu_ld=no
+ fi
+ ;;
+ interix*)
+ # we just hope/assume this is gcc and not c89 (= MSVC++ or ICC)
+ with_gnu_ld=yes
+ ;;
+ openbsd* | bitrig*)
+ with_gnu_ld=no
+ ;;
+ linux* | k*bsd*-gnu | gnu*)
+ link_all_deplibs=no
+ ;;
+ esac
+
+ ld_shlibs=yes
+
+ # On some targets, GNU ld is compatible enough with the native linker
+ # that we're better off using the native interface for both.
+ lt_use_gnu_ld_interface=no
+ if test yes = "$with_gnu_ld"; then
+ case $host_os in
+ aix*)
+ # The AIX port of GNU ld has always aspired to compatibility
+ # with the native linker. However, as the warning in the GNU ld
+ # block says, versions before 2.19.5* couldn't really create working
+ # shared libraries, regardless of the interface used.
+ case `$LD -v 2>&1` in
+ *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+ *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;;
+ *\ \(GNU\ Binutils\)\ [3-9]*) ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ fi
+
+ if test yes = "$lt_use_gnu_ld_interface"; then
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ wlarc='$wl'
+
+ # Set some defaults for GNU ld with shared library support. These
+ # are reset later if shared libraries are not supported. Putting them
+ # here allows them to be overridden if necessary.
+ runpath_var=LD_RUN_PATH
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ export_dynamic_flag_spec='$wl--export-dynamic'
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+ whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive'
+ else
+ whole_archive_flag_spec=
+ fi
+ supports_anon_versioning=no
+ case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in
+ *GNU\ gold*) supports_anon_versioning=yes ;;
+ *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11
+ *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+ *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+ *\ 2.11.*) ;; # other 2.11 versions
+ *) supports_anon_versioning=yes ;;
+ esac
+
+ # See if GNU ld supports shared libraries.
+ case $host_os in
+ aix[3-9]*)
+ # On AIX/PPC, the GNU linker is very broken
+ if test ia64 != "$host_cpu"; then
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support. If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds=''
+ ;;
+ m68k)
+ archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ ;;
+ esac
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ allow_undefined_flag=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless,
+ # as there is no search path for DLLs.
+ hardcode_libdir_flag_spec='-L$libdir'
+ export_dynamic_flag_spec='$wl--export-all-symbols'
+ allow_undefined_flag=unsupported
+ always_export_symbols=no
+ enable_shared_with_static_runtimes=yes
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+ exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+
+ if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ # If the export-symbols file already is a .def file, use it as
+ # is; otherwise, prepend EXPORTS...
+ archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then
+ cp $export_symbols $output_objdir/$soname.def;
+ else
+ echo EXPORTS > $output_objdir/$soname.def;
+ cat $export_symbols >> $output_objdir/$soname.def;
+ fi~
+ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ haiku*)
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ link_all_deplibs=yes
+ ;;
+
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ allow_undefined_flag=unsupported
+ shrext_cmds=.dll
+ archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ prefix_cmds="$SED"~
+ if test EXPORTS = "`$SED 1q $export_symbols`"; then
+ prefix_cmds="$prefix_cmds -e 1d";
+ fi~
+ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~
+ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def'
+ enable_shared_with_static_runtimes=yes
+ file_list_spec='@'
+ ;;
+
+ interix[3-9]*)
+ hardcode_direct=no
+ hardcode_shlibpath_var=no
+ hardcode_libdir_flag_spec='$wl-rpath,$libdir'
+ export_dynamic_flag_spec='$wl-E'
+ # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+ # Instead, shared libraries are loaded at an image base (0x10000000 by
+ # default) and relocated if they conflict, which is a slow very memory
+ # consuming and fragmenting process. To avoid this, we pick a random,
+ # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+ # time. Moving up from 0x10000000 also allows more sbrk(2) space.
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ archive_expsym_cmds='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ ;;
+
+ gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+ tmp_diet=no
+ if test linux-dietlibc = "$host_os"; then
+ case $cc_basename in
+ diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn)
+ esac
+ fi
+ if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+ && test no = "$tmp_diet"
+ then
+ tmp_addflag=' $pic_flag'
+ tmp_sharedflag='-shared'
+ case $cc_basename,$host_cpu in
+ pgcc*) # Portland Group C compiler
+ whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ tmp_addflag=' $pic_flag'
+ ;;
+ pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group f77 and f90 compilers
+ whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ tmp_addflag=' $pic_flag -Mnomain' ;;
+ ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64
+ tmp_addflag=' -i_dynamic' ;;
+ efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64
+ tmp_addflag=' -i_dynamic -nofor_main' ;;
+ ifc* | ifort*) # Intel Fortran compiler
+ tmp_addflag=' -nofor_main' ;;
+ lf95*) # Lahey Fortran 8.1
+ whole_archive_flag_spec=
+ tmp_sharedflag='--shared' ;;
+ nagfor*) # NAGFOR 5.3
+ tmp_sharedflag='-Wl,-shared' ;;
+ xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+ tmp_sharedflag='-qmkshrobj'
+ tmp_addflag= ;;
+ nvcc*) # Cuda Compiler Driver 2.2
+ whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ compiler_needs_object=yes
+ ;;
+ esac
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ C*) # Sun C 5.9
+ whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive'
+ compiler_needs_object=yes
+ tmp_sharedflag='-G' ;;
+ *Sun\ F*) # Sun Fortran 8.3
+ tmp_sharedflag='-G' ;;
+ esac
+ archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+
+ if test yes = "$supports_anon_versioning"; then
+ archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib'
+ fi
+
+ case $cc_basename in
+ tcc*)
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ export_dynamic_flag_spec='-rdynamic'
+ ;;
+ xlf* | bgf* | bgxlf* | mpixlf*)
+ # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+ whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive'
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+ if test yes = "$supports_anon_versioning"; then
+ archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+ fi
+ ;;
+ esac
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+ wlarc=
+ else
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ fi
+ ;;
+
+ solaris*)
+ if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+ case `$LD -v 2>&1` in
+ *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot
+*** reliably create shared libraries on SCO systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ ;;
+ *)
+ # For security reasons, it is highly recommended that you always
+ # use absolute paths for naming shared libraries, and exclude the
+ # DT_RUNPATH tag from executables and libraries. But doing so
+ # requires that you compile everything twice, which is a pain.
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+ ;;
+
+ sunos4*)
+ archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ wlarc=
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+
+ if test no = "$ld_shlibs"; then
+ runpath_var=
+ hardcode_libdir_flag_spec=
+ export_dynamic_flag_spec=
+ whole_archive_flag_spec=
+ fi
+ else
+ # PORTME fill in a description of your system's linker (not GNU ld)
+ case $host_os in
+ aix3*)
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ hardcode_minus_L=yes
+ if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ hardcode_direct=unsupported
+ fi
+ ;;
+
+ aix[4-9]*)
+ if test ia64 = "$host_cpu"; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=
+ else
+ # If we're using GNU nm, then we don't want the "-C" option.
+ # -C means demangle to GNU nm, but means don't demangle to AIX nm.
+ # Without the "-l" option, or with the "-B" option, AIX nm treats
+ # weak defined symbols like other global defined symbols, whereas
+ # GNU nm marks them as "W".
+ # While the 'weak' keyword is ignored in the Export File, we need
+ # it in the Import File for the 'aix-soname' feature, so we have
+ # to replace the "-B" option with "-P" for AIX nm.
+ if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+ export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols'
+ else
+ export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols'
+ fi
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # have runtime linking enabled, and use it for executables.
+ # For shared libraries, we enable/disable runtime linking
+ # depending on the kind of the shared library created -
+ # when "with_aix_soname,aix_use_runtimelinking" is:
+ # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables
+ # "aix,yes" lib.so shared, rtl:yes, for executables
+ # lib.a static archive
+ # "both,no" lib.so.V(shr.o) shared, rtl:yes
+ # lib.a(lib.so.V) shared, rtl:no, for executables
+ # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a(lib.so.V) shared, rtl:no
+ # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables
+ # lib.a static archive
+ case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+ for ld_flag in $LDFLAGS; do
+ if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # With aix-soname=svr4, we create the lib.so.V shared archives only,
+ # so we don't have lib.a shared libs to link our executables.
+ # We have to force runtime linking in this case.
+ aix_use_runtimelinking=yes
+ LDFLAGS="$LDFLAGS -Wl,-brtl"
+ fi
+ ;;
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ archive_cmds=''
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ hardcode_libdir_separator=':'
+ link_all_deplibs=yes
+ file_list_spec='$wl-f,'
+ case $with_aix_soname,$aix_use_runtimelinking in
+ aix,*) ;; # traditional, no import file
+ svr4,* | *,yes) # use import file
+ # The Import File defines what to hardcode.
+ hardcode_direct=no
+ hardcode_direct_absolute=no
+ ;;
+ esac
+
+ if test yes = "$GCC"; then
+ case $host_os in aix4.[012]|aix4.[012].*)
+ # We only want to do this on AIX 4.2 and lower, the check
+ # below for broken collect2 doesn't work under 4.3+
+ collect2name=`$CC -print-prog-name=collect2`
+ if test -f "$collect2name" &&
+ strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ hardcode_direct=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ hardcode_minus_L=yes
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_libdir_separator=
+ fi
+ ;;
+ esac
+ shared_flag='-shared'
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag="$shared_flag "'$wl-G'
+ fi
+ # Need to ensure runtime linking is disabled for the traditional
+ # shared library, or the linker may eventually find shared libraries
+ # /with/ Import File - we do not want to mix them.
+ shared_flag_aix='-shared'
+ shared_flag_svr4='-shared $wl-G'
+ else
+ # not using gcc
+ if test ia64 = "$host_cpu"; then
+ # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+ # chokes on -Wl,-G. The following line is correct:
+ shared_flag='-G'
+ else
+ if test yes = "$aix_use_runtimelinking"; then
+ shared_flag='$wl-G'
+ else
+ shared_flag='$wl-bM:SRE'
+ fi
+ shared_flag_aix='$wl-bM:SRE'
+ shared_flag_svr4='$wl-G'
+ fi
+ fi
+
+ export_dynamic_flag_spec='$wl-bexpall'
+ # It seems that -bexpall does not export symbols beginning with
+ # underscore (_), so it is better to generate a list of symbols to export.
+ always_export_symbols=yes
+ if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='-berok'
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ if test set = "${lt_cv_aix_libpath+set}"; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if test ${lt_cv_aix_libpath_+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=/usr/lib:/lib
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath_
+fi
+
+ hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath"
+ archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag
+ else
+ if test ia64 = "$host_cpu"; then
+ hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib'
+ allow_undefined_flag="-z nodefs"
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols"
+ else
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ if test set = "${lt_cv_aix_libpath+set}"; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if test ${lt_cv_aix_libpath_+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=/usr/lib:/lib
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath_
+fi
+
+ hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath"
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ no_undefined_flag=' $wl-bernotok'
+ allow_undefined_flag=' $wl-berok'
+ if test yes = "$with_gnu_ld"; then
+ # We only use this code for GNU lds that support --whole-archive.
+ whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive'
+ else
+ # Exported symbols can be pulled into shared objects from archives
+ whole_archive_flag_spec='$convenience'
+ fi
+ archive_cmds_need_lc=yes
+ archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d'
+ # -brtl affects multiple linker settings, -berok does not and is overridden later
+ compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`'
+ if test svr4 != "$with_aix_soname"; then
+ # This is similar to how AIX traditionally builds its shared libraries.
+ archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname'
+ fi
+ if test aix != "$with_aix_soname"; then
+ archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp'
+ else
+ # used by -dlpreopen to get the symbols
+ archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir'
+ fi
+ archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d'
+ fi
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'
+ archive_expsym_cmds=''
+ ;;
+ m68k)
+ archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ ;;
+ esac
+ ;;
+
+ bsdi[45]*)
+ export_dynamic_flag_spec=-rdynamic
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++ or Intel C++ Compiler.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ case $cc_basename in
+ cl* | icl*)
+ # Native MSVC or ICC
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ file_list_spec='@'
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=.dll
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames='
+ archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then
+ cp "$export_symbols" "$output_objdir/$soname.def";
+ echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp";
+ else
+ $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp;
+ fi~
+ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+ linknames='
+ # The linker will not automatically build a static lib if we build a DLL.
+ # _LT_TAGVAR(old_archive_from_new_cmds, )='true'
+ enable_shared_with_static_runtimes=yes
+ exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols'
+ # Don't use ranlib
+ old_postinstall_cmds='chmod 644 $oldlib'
+ postlink_cmds='lt_outputfile="@OUTPUT@"~
+ lt_tool_outputfile="@TOOL_OUTPUT@"~
+ case $lt_outputfile in
+ *.exe|*.EXE) ;;
+ *)
+ lt_outputfile=$lt_outputfile.exe
+ lt_tool_outputfile=$lt_tool_outputfile.exe
+ ;;
+ esac~
+ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then
+ $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+ $RM "$lt_outputfile.manifest";
+ fi'
+ ;;
+ *)
+ # Assume MSVC and ICC wrapper
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=.dll
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+ # The linker will automatically build a .lib file if we build a DLL.
+ old_archive_from_new_cmds='true'
+ # FIXME: Should let the user specify the lib program.
+ old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs'
+ enable_shared_with_static_runtimes=yes
+ ;;
+ esac
+ ;;
+
+ darwin* | rhapsody*)
+
+
+ archive_cmds_need_lc=no
+ hardcode_direct=no
+ hardcode_automatic=yes
+ hardcode_shlibpath_var=unsupported
+ if test yes = "$lt_cv_ld_force_load"; then
+ whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+
+ else
+ whole_archive_flag_spec=''
+ fi
+ link_all_deplibs=yes
+ allow_undefined_flag=$_lt_dar_allow_undefined
+ case $cc_basename in
+ ifort*|nagfor*) _lt_dar_can_shared=yes ;;
+ *) _lt_dar_can_shared=$GCC ;;
+ esac
+ if test yes = "$_lt_dar_can_shared"; then
+ output_verbose_link_cmd=func_echo_all
+ archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil"
+ module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil"
+ archive_expsym_cmds="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil"
+ module_expsym_cmds="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil"
+
+ else
+ ld_shlibs=no
+ fi
+
+ ;;
+
+ dgux*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+ # support. Future versions do this automatically, but an explicit c++rt0.o
+ # does not break anything, and helps significantly (at the cost of a little
+ # extra space).
+ freebsd2.2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+ freebsd2.*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+ freebsd* | dragonfly* | midnightbsd*)
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ hpux9*)
+ if test yes = "$GCC"; then
+ archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ else
+ archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib'
+ fi
+ hardcode_libdir_flag_spec='$wl+b $wl$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ export_dynamic_flag_spec='$wl-E'
+ ;;
+
+ hpux10*)
+ if test yes,no = "$GCC,$with_gnu_ld"; then
+ archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ if test no = "$with_gnu_ld"; then
+ hardcode_libdir_flag_spec='$wl+b $wl$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ export_dynamic_flag_spec='$wl-E'
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ fi
+ ;;
+
+ hpux11*)
+ if test yes,no = "$GCC,$with_gnu_ld"; then
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ else
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+
+ # Older versions of the 11.00 compiler do not understand -b yet
+ # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5
+printf %s "checking if $CC understands -b... " >&6; }
+if test ${lt_cv_prog_compiler__b+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_prog_compiler__b=no
+ save_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS -b"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&5
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler__b=yes
+ fi
+ else
+ lt_cv_prog_compiler__b=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS=$save_LDFLAGS
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5
+printf "%s\n" "$lt_cv_prog_compiler__b" >&6; }
+
+if test yes = "$lt_cv_prog_compiler__b"; then
+ archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+else
+ archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+fi
+
+ ;;
+ esac
+ fi
+ if test no = "$with_gnu_ld"; then
+ hardcode_libdir_flag_spec='$wl+b $wl$libdir'
+ hardcode_libdir_separator=:
+
+ case $host_cpu in
+ hppa*64*|ia64*)
+ hardcode_direct=no
+ hardcode_shlibpath_var=no
+ ;;
+ *)
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ export_dynamic_flag_spec='$wl-E'
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ ;;
+ esac
+ fi
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ if test yes = "$GCC"; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ # Try to use the -exported_symbol ld option, if it does not
+ # work, assume that -exports_file does not work either and
+ # implicitly export all symbols.
+ # This should be the same for all languages, so no per-tag cache variable.
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5
+printf %s "checking whether the $host_os linker accepts -exported_symbol... " >&6; }
+if test ${lt_cv_irix_exported_symbol+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ save_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+int foo (void) { return 0; }
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ lt_cv_irix_exported_symbol=yes
+else $as_nop
+ lt_cv_irix_exported_symbol=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS=$save_LDFLAGS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5
+printf "%s\n" "$lt_cv_irix_exported_symbol" >&6; }
+ if test yes = "$lt_cv_irix_exported_symbol"; then
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib'
+ fi
+ link_all_deplibs=no
+ else
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ hardcode_libdir_separator=:
+ inherit_rpath=yes
+ link_all_deplibs=yes
+ ;;
+
+ linux*)
+ case $cc_basename in
+ tcc*)
+ # Fabrice Bellard et al's Tiny C Compiler
+ ld_shlibs=yes
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ ;;
+ esac
+ ;;
+
+ netbsd* | netbsdelf*-gnu)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out
+ else
+ archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ newsos6)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ hardcode_libdir_separator=:
+ hardcode_shlibpath_var=no
+ ;;
+
+ *nto* | *qnx*)
+ ;;
+
+ openbsd* | bitrig*)
+ if test -f /usr/libexec/ld.so; then
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ hardcode_direct_absolute=yes
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols'
+ hardcode_libdir_flag_spec='$wl-rpath,$libdir'
+ export_dynamic_flag_spec='$wl-E'
+ else
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='$wl-rpath,$libdir'
+ fi
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ allow_undefined_flag=unsupported
+ shrext_cmds=.dll
+ archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~
+ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~
+ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~
+ $ECHO EXPORTS >> $output_objdir/$libname.def~
+ prefix_cmds="$SED"~
+ if test EXPORTS = "`$SED 1q $export_symbols`"; then
+ prefix_cmds="$prefix_cmds -e 1d";
+ fi~
+ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~
+ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~
+ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~
+ emximp -o $lib $output_objdir/$libname.def'
+ old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def'
+ enable_shared_with_static_runtimes=yes
+ file_list_spec='@'
+ ;;
+
+ osf3*)
+ if test yes = "$GCC"; then
+ allow_undefined_flag=' $wl-expect_unresolved $wl\*'
+ archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ hardcode_libdir_separator=:
+ ;;
+
+ osf4* | osf5*) # as osf3* with the addition of -msym flag
+ if test yes = "$GCC"; then
+ allow_undefined_flag=' $wl-expect_unresolved $wl\*'
+ archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib'
+ hardcode_libdir_flag_spec='$wl-rpath $wl$libdir'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib'
+ archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp'
+
+ # Both c and cxx compiler support -rpath directly
+ hardcode_libdir_flag_spec='-rpath $libdir'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_separator=:
+ ;;
+
+ solaris*)
+ no_undefined_flag=' -z defs'
+ if test yes = "$GCC"; then
+ wlarc='$wl'
+ archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ else
+ case `$CC -V 2>&1` in
+ *"Compilers 5.0"*)
+ wlarc=''
+ archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+ ;;
+ *)
+ wlarc='$wl'
+ archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ ;;
+ esac
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_shlibpath_var=no
+ case $host_os in
+ solaris2.[0-5] | solaris2.[0-5].*) ;;
+ *)
+ # The compiler driver will combine and reorder linker options,
+ # but understands '-z linker_flag'. GCC discards it without '$wl',
+ # but is careful enough not to reorder.
+ # Supported since Solaris 2.6 (maybe 2.5.1?)
+ if test yes = "$GCC"; then
+ whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract'
+ else
+ whole_archive_flag_spec='-z allextract$convenience -z defaultextract'
+ fi
+ ;;
+ esac
+ link_all_deplibs=yes
+ ;;
+
+ sunos4*)
+ if test sequent = "$host_vendor"; then
+ # Use $CC to link under sequent, because it throws in some extra .o
+ # files that make .init and .fini sections work.
+ archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4)
+ case $host_vendor in
+ sni)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes # is this really true???
+ ;;
+ siemens)
+ ## LD is ld it makes a PLAMLIB
+ ## CC just makes a GrossModule.
+ archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+ reload_cmds='$CC -r -o $output$reload_objs'
+ hardcode_direct=no
+ ;;
+ motorola)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+ ;;
+ esac
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4.3*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ export_dynamic_flag_spec='-Bexport'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ ld_shlibs=yes
+ fi
+ ;;
+
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+ no_undefined_flag='$wl-z,text'
+ archive_cmds_need_lc=no
+ hardcode_shlibpath_var=no
+ runpath_var='LD_RUN_PATH'
+
+ if test yes = "$GCC"; then
+ archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6*)
+ # Note: We CANNOT use -z defs as we might desire, because we do not
+ # link with -lc, and that would cause any symbols used from libc to
+ # always be unresolved, which means just about no library would
+ # ever link correctly. If we're not using GNU ld we use -z text
+ # though, which does catch some bad symbols but isn't as heavy-handed
+ # as -z defs.
+ no_undefined_flag='$wl-z,text'
+ allow_undefined_flag='$wl-z,nodefs'
+ archive_cmds_need_lc=no
+ hardcode_shlibpath_var=no
+ hardcode_libdir_flag_spec='$wl-R,$libdir'
+ hardcode_libdir_separator=':'
+ link_all_deplibs=yes
+ export_dynamic_flag_spec='$wl-Bexport'
+ runpath_var='LD_RUN_PATH'
+
+ if test yes = "$GCC"; then
+ archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ uts4*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+
+ if test sni = "$host_vendor"; then
+ case $host in
+ sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ export_dynamic_flag_spec='$wl-Blargedynsym'
+ ;;
+ esac
+ fi
+ fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5
+printf "%s\n" "$ld_shlibs" >&6; }
+test no = "$ld_shlibs" && can_build_shared=no
+
+with_gnu_ld=$with_gnu_ld
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc" in
+x|xyes)
+ # Assume -lc should be added
+ archive_cmds_need_lc=yes
+
+ if test yes,yes = "$GCC,$enable_shared"; then
+ case $archive_cmds in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+printf %s "checking whether -lc should be explicitly linked in... " >&6; }
+if test ${lt_cv_archive_cmds_need_lc+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ $RM conftest*
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } 2>conftest.err; then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$lt_prog_compiler_wl
+ pic_flag=$lt_prog_compiler_pic
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ lt_save_allow_undefined_flag=$allow_undefined_flag
+ allow_undefined_flag=
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+ (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ then
+ lt_cv_archive_cmds_need_lc=no
+ else
+ lt_cv_archive_cmds_need_lc=yes
+ fi
+ allow_undefined_flag=$lt_save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi
+ $RM conftest*
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5
+printf "%s\n" "$lt_cv_archive_cmds_need_lc" >&6; }
+ archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc
+ ;;
+ esac
+ fi
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+printf %s "checking dynamic linker characteristics... " >&6; }
+
+if test yes = "$GCC"; then
+ case $host_os in
+ darwin*) lt_awk_arg='/^libraries:/,/LR/' ;;
+ *) lt_awk_arg='/^libraries:/' ;;
+ esac
+ case $host_os in
+ mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;;
+ *) lt_sed_strip_eq='s|=/|/|g' ;;
+ esac
+ lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+ case $lt_search_path_spec in
+ *\;*)
+ # if the path contains ";" then we assume it to be the separator
+ # otherwise default to the standard path separator (i.e. ":") - it is
+ # assumed that no part of a normal pathname contains ";" but that should
+ # okay in the real world where ";" in dirpaths is itself problematic.
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+ ;;
+ *)
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ esac
+ # Ok, now we have the path, separated by spaces, we can step through it
+ # and add multilib dir if necessary...
+ lt_tmp_lt_search_path_spec=
+ lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+ # ...but if some path component already ends with the multilib dir we assume
+ # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer).
+ case "$lt_multi_os_dir; $lt_search_path_spec " in
+ "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*)
+ lt_multi_os_dir=
+ ;;
+ esac
+ for lt_sys_path in $lt_search_path_spec; do
+ if test -d "$lt_sys_path$lt_multi_os_dir"; then
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir"
+ elif test -n "$lt_multi_os_dir"; then
+ test -d "$lt_sys_path" && \
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+ fi
+ done
+ lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS = " "; FS = "/|\n";} {
+ lt_foo = "";
+ lt_count = 0;
+ for (lt_i = NF; lt_i > 0; lt_i--) {
+ if ($lt_i != "" && $lt_i != ".") {
+ if ($lt_i == "..") {
+ lt_count++;
+ } else {
+ if (lt_count == 0) {
+ lt_foo = "/" $lt_i lt_foo;
+ } else {
+ lt_count--;
+ }
+ }
+ }
+ }
+ if (lt_foo != "") { lt_freq[lt_foo]++; }
+ if (lt_freq[lt_foo] == 1) { print lt_foo; }
+}'`
+ # AWK program above erroneously prepends '/' to C:/dos/paths
+ # for these hosts.
+ case $host_os in
+ mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+ $SED 's|/\([A-Za-z]:\)|\1|g'` ;;
+ esac
+ sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+ sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=.so
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+
+
+case $host_os in
+aix3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX 3 has no versioning support, so we append a major version to the name.
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+
+aix[4-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ hardcode_into_libs=yes
+ if test ia64 = "$host_cpu"; then
+ # AIX 5 supports IA64
+ library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line '#! .'. This would cause the generated library to
+ # depend on '.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[01] | aix4.[01].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # Using Import Files as archive members, it is possible to support
+ # filename-based versioning of shared library archives on AIX. While
+ # this would work for both with and without runtime linking, it will
+ # prevent static linking of such archives. So we do filename-based
+ # shared library versioning with .so extension only, which is used
+ # when both runtime linking and shared linking is enabled.
+ # Unfortunately, runtime linking may impact performance, so we do
+ # not want this to be the default eventually. Also, we use the
+ # versioned .so libs for executables only if there is the -brtl
+ # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only.
+ # To allow for filename-based versioning support, we need to create
+ # libNAME.so.V as an archive file, containing:
+ # *) an Import File, referring to the versioned filename of the
+ # archive as well as the shared archive member, telling the
+ # bitwidth (32 or 64) of that shared object, and providing the
+ # list of exported symbols of that shared object, eventually
+ # decorated with the 'weak' keyword
+ # *) the shared object with the F_LOADONLY flag set, to really avoid
+ # it being seen by the linker.
+ # At run time we better use the real file rather than another symlink,
+ # but for link time we create the symlink libNAME.so -> libNAME.so.V
+
+ case $with_aix_soname,$aix_use_runtimelinking in
+ # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct
+ # soname into executable. Probably we can add versioning support to
+ # collect2, so additional links can be useful in future.
+ aix,yes) # traditional libtool
+ dynamic_linker='AIX unversionable lib.so'
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ ;;
+ aix,no) # traditional AIX only
+ dynamic_linker='AIX lib.a(lib.so.V)'
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='$libname$release.a $libname.a'
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+ svr4,*) # full svr4 only
+ dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)"
+ library_names_spec='$libname$release$shared_ext$major $libname$shared_ext'
+ # We do not specify a path in Import Files, so LIBPATH fires.
+ shlibpath_overrides_runpath=yes
+ ;;
+ *,yes) # both, prefer svr4
+ dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)"
+ library_names_spec='$libname$release$shared_ext$major $libname$shared_ext'
+ # unpreferred sharedlib libNAME.a needs extra handling
+ postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"'
+ postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"'
+ # We do not specify a path in Import Files, so LIBPATH fires.
+ shlibpath_overrides_runpath=yes
+ ;;
+ *,no) # both, prefer aix
+ dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)"
+ library_names_spec='$libname$release.a $libname.a'
+ soname_spec='$libname$release$shared_ext$major'
+ # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling
+ postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)'
+ postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"'
+ ;;
+ esac
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ case $host_cpu in
+ powerpc)
+ # Since July 2007 AmigaOS4 officially supports .so libraries.
+ # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ ;;
+ m68k)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+ ;;
+ esac
+ ;;
+
+beos*)
+ library_names_spec='$libname$shared_ext'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi[45]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+ version_type=windows
+ shrext_cmds=.dll
+ need_version=no
+ need_lib_prefix=no
+
+ case $GCC,$cc_basename in
+ yes,*)
+ # gcc
+ library_names_spec='$libname.dll.a'
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+
+ case $host_os in
+ cygwin*)
+ # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+ soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext'
+
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"
+ ;;
+ mingw* | cegcc*)
+ # MinGW DLLs use traditional 'lib' prefix
+ soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext'
+ ;;
+ pw32*)
+ # pw32 DLLs use 'pw' prefix rather than 'lib'
+ library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ ;;
+
+ *,cl* | *,icl*)
+ # Native MSVC or ICC
+ libname_spec='$name'
+ soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext'
+ library_names_spec='$libname.dll.lib'
+
+ case $build_os in
+ mingw*)
+ sys_lib_search_path_spec=
+ lt_save_ifs=$IFS
+ IFS=';'
+ for lt_path in $LIB
+ do
+ IFS=$lt_save_ifs
+ # Let DOS variable expansion print the short 8.3 style file name.
+ lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+ sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+ done
+ IFS=$lt_save_ifs
+ # Convert to MSYS style.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+ ;;
+ cygwin*)
+ # Convert to unix form, then to dos form, then back to unix form
+ # but this time dos style (no spaces!) so that the unix form looks
+ # like /cygdrive/c/PROGRA~1:/cygdr...
+ sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+ sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+ sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ *)
+ sys_lib_search_path_spec=$LIB
+ if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+ # It is most probably a Windows format PATH.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+ else
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ fi
+ # FIXME: find the short name or the path components, as spaces are
+ # common. (e.g. "Program Files" -> "PROGRA~1")
+ ;;
+ esac
+
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+ dynamic_linker='Win32 link.exe'
+ ;;
+
+ *)
+ # Assume MSVC and ICC wrapper
+ library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib'
+ dynamic_linker='Win32 ld.exe'
+ ;;
+ esac
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$major$shared_ext $libname$shared_ext'
+ soname_spec='$libname$release$major$shared_ext'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"
+ sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+ ;;
+
+dgux*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+freebsd* | dragonfly* | midnightbsd*)
+ # DragonFly does not have aout. When/if they implement a new
+ # versioning mechanism, adjust this.
+ if test -x /usr/bin/objformat; then
+ objformat=`/usr/bin/objformat`
+ else
+ case $host_os in
+ freebsd[23].*) objformat=aout ;;
+ *) objformat=elf ;;
+ esac
+ fi
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2.*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ freebsd3.[01]* | freebsdelf3.[01]*)
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ *) # from 4.6 on, and DragonFly
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+haiku*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ dynamic_linker="$host_os runtime_loader"
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ case $host_cpu in
+ ia64*)
+ shrext_cmds='.so'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.so"
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ if test 32 = "$HPUX_IA64_MODE"; then
+ sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+ sys_lib_dlsearch_path_spec=/usr/lib/hpux32
+ else
+ sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+ sys_lib_dlsearch_path_spec=/usr/lib/hpux64
+ fi
+ ;;
+ hppa*64*)
+ shrext_cmds='.sl'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ *)
+ shrext_cmds='.sl'
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ ;;
+ esac
+ # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+ postinstall_cmds='chmod 555 $lib'
+ # or fails outright, so override atomically:
+ install_override_mode=555
+ ;;
+
+interix[3-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $host_os in
+ nonstopux*) version_type=nonstopux ;;
+ *)
+ if test yes = "$lt_cv_prog_gnu_ld"; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ else
+ version_type=irix
+ fi ;;
+ esac
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='$libname$release$shared_ext$major'
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext'
+ case $host_os in
+ irix5* | nonstopux*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+ libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+ libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+ libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff"
+ sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff"
+ hardcode_into_libs=yes
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+ dynamic_linker=no
+ ;;
+
+linux*android*)
+ version_type=none # Android doesn't support versioned libraries.
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext'
+ soname_spec='$libname$release$shared_ext'
+ finish_cmds=
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ dynamic_linker='Android linker'
+ # Don't embed -rpath directories since the linker doesn't support them.
+ hardcode_libdir_flag_spec='-L$libdir'
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+
+ # Some binutils ld are patched to set DT_RUNPATH
+ if test ${lt_cv_shlibpath_overrides_runpath+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ lt_cv_shlibpath_overrides_runpath=no
+ save_LDFLAGS=$LDFLAGS
+ save_libdir=$libdir
+ eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \
+ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main (void)
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null
+then :
+ lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS=$save_LDFLAGS
+ libdir=$save_libdir
+
+fi
+
+ shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # Ideally, we could use ldconfig to report *all* directores which are
+ # searched for libraries, however this is still not possible. Aside from not
+ # being certain /sbin/ldconfig is available, command
+ # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64,
+ # even though it is searched at run-time. Try to do the best guess by
+ # appending ld.so.conf contents (and includes) to the search path.
+ if test -f /etc/ld.so.conf; then
+ lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+ sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+ fi
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsdelf*-gnu)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='NetBSD ld.elf_so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+*nto* | *qnx*)
+ version_type=qnx
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='ldqnx.so'
+ ;;
+
+openbsd* | bitrig*)
+ version_type=sunos
+ sys_lib_dlsearch_path_spec=/usr/lib
+ need_lib_prefix=no
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then
+ need_version=no
+ else
+ need_version=yes
+ fi
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+os2*)
+ libname_spec='$name'
+ version_type=windows
+ shrext_cmds=.dll
+ need_version=no
+ need_lib_prefix=no
+ # OS/2 can only load a DLL with a base name of 8 characters or less.
+ soname_spec='`test -n "$os2dllname" && libname="$os2dllname";
+ v=$($ECHO $release$versuffix | tr -d .-);
+ n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _);
+ $ECHO $n$v`$shared_ext'
+ library_names_spec='${libname}_dll.$libext'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=BEGINLIBPATH
+ sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ postinstall_cmds='base_file=`basename \$file`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='$libname$release$shared_ext$major'
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+
+rdos*)
+ dynamic_linker=no
+ ;;
+
+solaris*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test yes = "$with_gnu_ld"; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ need_lib_prefix=no
+ runpath_var=LD_RUN_PATH
+ ;;
+ siemens)
+ need_lib_prefix=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext'
+ soname_spec='$libname$shared_ext.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ version_type=sco
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ if test yes = "$with_gnu_ld"; then
+ sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+ else
+ sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+ case $host_os in
+ sco3.2v5*)
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+ ;;
+ esac
+ fi
+ sys_lib_dlsearch_path_spec='/usr/lib'
+ ;;
+
+tpf*)
+ # TPF is a cross-target only. Preferred cross-host = GNU/Linux.
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+uts4*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext'
+ soname_spec='$libname$release$shared_ext$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+printf "%s\n" "$dynamic_linker" >&6; }
+test no = "$dynamic_linker" && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test yes = "$GCC"; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then
+ sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec
+fi
+
+if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then
+ sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec
+fi
+
+# remember unaugmented sys_lib_dlsearch_path content for libtool script decls...
+configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec
+
+# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code
+func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH"
+
+# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool
+configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+printf %s "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" ||
+ test -n "$runpath_var" ||
+ test yes = "$hardcode_automatic"; then
+
+ # We can hardcode non-existent directories.
+ if test no != "$hardcode_direct" &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" &&
+ test no != "$hardcode_minus_L"; then
+ # Linking always hardcodes the temporary library directory.
+ hardcode_action=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ hardcode_action=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ hardcode_action=unsupported
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5
+printf "%s\n" "$hardcode_action" >&6; }
+
+if test relink = "$hardcode_action" ||
+ test yes = "$inherit_rpath"; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test yes = "$shlibpath_overrides_runpath" ||
+ test no = "$enable_shared"; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+
+
+
+
+
+
+ if test yes != "$enable_dlopen"; then
+ enable_dlopen=unknown
+ enable_dlopen_self=unknown
+ enable_dlopen_self_static=unknown
+else
+ lt_cv_dlopen=no
+ lt_cv_dlopen_libs=
+
+ case $host_os in
+ beos*)
+ lt_cv_dlopen=load_add_on
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ;;
+
+ mingw* | pw32* | cegcc*)
+ lt_cv_dlopen=LoadLibrary
+ lt_cv_dlopen_libs=
+ ;;
+
+ cygwin*)
+ lt_cv_dlopen=dlopen
+ lt_cv_dlopen_libs=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+printf %s "checking for dlopen in -ldl... " >&6; }
+if test ${ac_cv_lib_dl_dlopen+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char dlopen ();
+int
+main (void)
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_dl_dlopen=yes
+else $as_nop
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes
+then :
+ lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl
+else $as_nop
+
+ lt_cv_dlopen=dyld
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+
+fi
+
+ ;;
+
+ tpf*)
+ # Don't try to run any link tests for TPF. We know it's impossible
+ # because TPF is a cross-compiler, and we know how we open DSOs.
+ lt_cv_dlopen=dlopen
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=no
+ ;;
+
+ *)
+ ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes
+then :
+ lt_cv_dlopen=shl_load
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+printf %s "checking for shl_load in -ldld... " >&6; }
+if test ${ac_cv_lib_dld_shl_load+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char shl_load ();
+int
+main (void)
+{
+return shl_load ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_dld_shl_load=yes
+else $as_nop
+ ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+printf "%s\n" "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes
+then :
+ lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld
+else $as_nop
+ ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes
+then :
+ lt_cv_dlopen=dlopen
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+printf %s "checking for dlopen in -ldl... " >&6; }
+if test ${ac_cv_lib_dl_dlopen+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char dlopen ();
+int
+main (void)
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_dl_dlopen=yes
+else $as_nop
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes
+then :
+ lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+printf %s "checking for dlopen in -lsvld... " >&6; }
+if test ${ac_cv_lib_svld_dlopen+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char dlopen ();
+int
+main (void)
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_svld_dlopen=yes
+else $as_nop
+ ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+printf "%s\n" "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes
+then :
+ lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld
+else $as_nop
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+printf %s "checking for dld_link in -ldld... " >&6; }
+if test ${ac_cv_lib_dld_dld_link+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char dld_link ();
+int
+main (void)
+{
+return dld_link ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_lib_dld_dld_link=yes
+else $as_nop
+ ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+printf "%s\n" "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes
+then :
+ lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+ ;;
+ esac
+
+ if test no = "$lt_cv_dlopen"; then
+ enable_dlopen=no
+ else
+ enable_dlopen=yes
+ fi
+
+ case $lt_cv_dlopen in
+ dlopen)
+ save_CPPFLAGS=$CPPFLAGS
+ test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+ save_LDFLAGS=$LDFLAGS
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+ save_LIBS=$LIBS
+ LIBS="$lt_cv_dlopen_libs $LIBS"
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5
+printf %s "checking whether a program can dlopen itself... " >&6; }
+if test ${lt_cv_dlopen_self+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test yes = "$cross_compiling"; then :
+ lt_cv_dlopen_self=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* When -fvisibility=hidden is used, assume the code has been annotated
+ correspondingly for the symbols needed. */
+#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else
+ {
+ if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ else puts (dlerror ());
+ }
+ /* dlclose (self); */
+ }
+ else
+ puts (dlerror ());
+
+ return status;
+}
+_LT_EOF
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then
+ (./conftest; exit; ) >&5 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5
+printf "%s\n" "$lt_cv_dlopen_self" >&6; }
+
+ if test yes = "$lt_cv_dlopen_self"; then
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5
+printf %s "checking whether a statically linked program can dlopen itself... " >&6; }
+if test ${lt_cv_dlopen_self_static+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if test yes = "$cross_compiling"; then :
+ lt_cv_dlopen_self_static=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* When -fvisibility=hidden is used, assume the code has been annotated
+ correspondingly for the symbols needed. */
+#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else
+ {
+ if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ else puts (dlerror ());
+ }
+ /* dlclose (self); */
+ }
+ else
+ puts (dlerror ());
+
+ return status;
+}
+_LT_EOF
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then
+ (./conftest; exit; ) >&5 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self_static=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5
+printf "%s\n" "$lt_cv_dlopen_self_static" >&6; }
+ fi
+
+ CPPFLAGS=$save_CPPFLAGS
+ LDFLAGS=$save_LDFLAGS
+ LIBS=$save_LIBS
+ ;;
+ esac
+
+ case $lt_cv_dlopen_self in
+ yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+ *) enable_dlopen_self=unknown ;;
+ esac
+
+ case $lt_cv_dlopen_self_static in
+ yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+ *) enable_dlopen_self_static=unknown ;;
+ esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+striplib=
+old_striplib=
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5
+printf %s "checking whether stripping libraries is possible... " >&6; }
+if test -z "$STRIP"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+else
+ if $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+ old_striplib="$STRIP --strip-debug"
+ striplib="$STRIP --strip-unneeded"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ else
+ case $host_os in
+ darwin*)
+ # FIXME - insert some real tests, host_os isn't really good enough
+ striplib="$STRIP -x"
+ old_striplib="$STRIP -S"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ ;;
+ freebsd*)
+ if $STRIP -V 2>&1 | $GREP "elftoolchain" >/dev/null; then
+ old_striplib="$STRIP --strip-debug"
+ striplib="$STRIP --strip-unneeded"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ fi
+ ;;
+ *)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+ ;;
+ esac
+ fi
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+ # Report what library types will actually be built
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5
+printf %s "checking if libtool supports shared libraries... " >&6; }
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5
+printf "%s\n" "$can_build_shared" >&6; }
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5
+printf %s "checking whether to build shared libraries... " >&6; }
+ test no = "$can_build_shared" && enable_shared=no
+
+ # On AIX, shared libraries and static libraries use the same namespace, and
+ # are all built from PIC.
+ case $host_os in
+ aix3*)
+ test yes = "$enable_shared" && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+
+ aix[4-9]*)
+ if test ia64 != "$host_cpu"; then
+ case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in
+ yes,aix,yes) ;; # shared object as lib.so file only
+ yes,svr4,*) ;; # shared object as lib.so archive member only
+ yes,*) enable_static=no ;; # shared object in lib.a archive as well
+ esac
+ fi
+ ;;
+ esac
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5
+printf "%s\n" "$enable_shared" >&6; }
+
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5
+printf %s "checking whether to build static libraries... " >&6; }
+ # Make sure either enable_shared or enable_static is yes.
+ test yes = "$enable_shared" || enable_static=yes
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5
+printf "%s\n" "$enable_static" >&6; }
+
+
+
+
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+CC=$lt_save_CC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ac_config_commands="$ac_config_commands libtool"
+
+
+
+
+# Only expand once:
+
+
+
+# check for headers
+# Autoupdate added the next two lines to ensure that your configure
+# script's behavior did not change. They are probably safe to remove.
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+printf %s "checking for egrep... " >&6; }
+if test ${ac_cv_path_EGREP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ for ac_prog in egrep
+ do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ printf %s 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ printf "%s\n" 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+printf "%s\n" "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+
+ac_fn_c_check_header_compile "$LINENO" "byteswap.h" "ac_cv_header_byteswap_h" "$ac_includes_default"
+if test "x$ac_cv_header_byteswap_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_BYTESWAP_H 1" >>confdefs.h
+
+fi
+ac_fn_c_check_header_compile "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default"
+if test "x$ac_cv_header_stdatomic_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_STDATOMIC_H 1" >>confdefs.h
+
+fi
+
+
+# check for functions
+
+ for ac_func in getopt_long
+do :
+ ac_fn_c_check_func "$LINENO" "getopt_long" "ac_cv_func_getopt_long"
+if test "x$ac_cv_func_getopt_long" = xyes
+then :
+ printf "%s\n" "#define HAVE_GETOPT_LONG 1" >>confdefs.h
+ GETOPT_O_FILES=''
+else $as_nop
+ GETOPT_O_FILES='getopt_long.o'
+fi
+
+done
+ac_fn_c_check_func "$LINENO" "posix_fadvise" "ac_cv_func_posix_fadvise"
+if test "x$ac_cv_func_posix_fadvise" = xyes
+then :
+ printf "%s\n" "#define HAVE_POSIX_FADVISE 1" >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "posix_memalign" "ac_cv_func_posix_memalign"
+if test "x$ac_cv_func_posix_memalign" = xyes
+then :
+ printf "%s\n" "#define HAVE_POSIX_MEMALIGN 1" >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday"
+if test "x$ac_cv_func_gettimeofday" = xyes
+then :
+ printf "%s\n" "#define HAVE_GETTIMEOFDAY 1" >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "sysconf" "ac_cv_func_sysconf"
+if test "x$ac_cv_func_sysconf" = xyes
+then :
+ printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "lseek64" "ac_cv_func_lseek64"
+if test "x$ac_cv_func_lseek64" = xyes
+then :
+ printf "%s\n" "#define HAVE_LSEEK64 1" >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "srand48_r" "ac_cv_func_srand48_r"
+if test "x$ac_cv_func_srand48_r" = xyes
+then :
+ printf "%s\n" "#define HAVE_SRAND48_R 1" >>confdefs.h
+
+fi
+
+SAVED_LIBS=$LIBS
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_create" >&5
+printf %s "checking for library containing pthread_create... " >&6; }
+if test ${ac_cv_search_pthread_create+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char pthread_create ();
+int
+main (void)
+{
+return pthread_create ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' pthread
+do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_search_pthread_create=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext
+ if test ${ac_cv_search_pthread_create+y}
+then :
+ break
+fi
+done
+if test ${ac_cv_search_pthread_create+y}
+then :
+
+else $as_nop
+ ac_cv_search_pthread_create=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_create" >&5
+printf "%s\n" "$ac_cv_search_pthread_create" >&6; }
+ac_res=$ac_cv_search_pthread_create
+if test "$ac_res" != no
+then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+# AC_SEARCH_LIBS adds libraries at the start of $LIBS so remove $SAVED_LIBS
+# from the end of $LIBS.
+pthread_lib=${LIBS%${SAVED_LIBS}}
+ac_fn_c_check_func "$LINENO" "pthread_cancel" "ac_cv_func_pthread_cancel"
+if test "x$ac_cv_func_pthread_cancel" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_CANCEL 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "pthread_kill" "ac_cv_func_pthread_kill"
+if test "x$ac_cv_func_pthread_kill" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_KILL 1" >>confdefs.h
+
+fi
+
+LIBS=$SAVED_LIBS
+PTHREAD_LIB=$pthread_lib
+
+
+SAVED_LIBS=$LIBS
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
+printf %s "checking for library containing clock_gettime... " >&6; }
+if test ${ac_cv_search_clock_gettime+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+char clock_gettime ();
+int
+main (void)
+{
+return clock_gettime ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' rt
+do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"
+then :
+ ac_cv_search_clock_gettime=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+ conftest$ac_exeext
+ if test ${ac_cv_search_clock_gettime+y}
+then :
+ break
+fi
+done
+if test ${ac_cv_search_clock_gettime+y}
+then :
+
+else $as_nop
+ ac_cv_search_clock_gettime=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5
+printf "%s\n" "$ac_cv_search_clock_gettime" >&6; }
+ac_res=$ac_cv_search_clock_gettime
+if test "$ac_res" != no
+then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+rt_lib=${LIBS%${SAVED_LIBS}}
+ac_fn_c_check_func "$LINENO" "clock_gettime" "ac_cv_func_clock_gettime"
+if test "x$ac_cv_func_clock_gettime" = xyes
+then :
+ printf "%s\n" "#define HAVE_CLOCK_GETTIME 1" >>confdefs.h
+
+fi
+
+LIBS=$SAVED_LIBS
+RT_LIB=$rt_lib
+
+
+
+
+
+
+
+
+printf "%s\n" "#define SG_LIB_BUILD_HOST \"${host}\"" >>confdefs.h
+
+
+check_for_getrandom() {
+ for ac_header in sys/random.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "sys/random.h" "ac_cv_header_sys_random_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_random_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_SYS_RANDOM_H 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_GETRANDOM 1" >>confdefs.h
+
+fi
+
+done
+}
+
+check_for_linux_nvme_headers() {
+ for ac_header in linux/nvme_ioctl.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "linux/nvme_ioctl.h" "ac_cv_header_linux_nvme_ioctl_h" "$ac_includes_default"
+if test "x$ac_cv_header_linux_nvme_ioctl_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_LINUX_NVME_IOCTL_H 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_NVME 1" >>confdefs.h
+
+fi
+
+done
+ ac_fn_c_check_header_compile "$LINENO" "linux/types.h" "ac_cv_header_linux_types_h" "#ifdef HAVE_LINUX_TYPES_H
+ # include <linux/types.h>
+ #endif
+
+"
+if test "x$ac_cv_header_linux_types_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_LINUX_TYPES_H 1" >>confdefs.h
+
+fi
+ac_fn_c_check_header_compile "$LINENO" "linux/bsg.h" "ac_cv_header_linux_bsg_h" "#ifdef HAVE_LINUX_TYPES_H
+ # include <linux/types.h>
+ #endif
+
+"
+if test "x$ac_cv_header_linux_bsg_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_LINUX_BSG_H 1" >>confdefs.h
+
+fi
+ac_fn_c_check_header_compile "$LINENO" "linux/kdev_t.h" "ac_cv_header_linux_kdev_t_h" "#ifdef HAVE_LINUX_TYPES_H
+ # include <linux/types.h>
+ #endif
+
+"
+if test "x$ac_cv_header_linux_kdev_t_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_LINUX_KDEV_T_H 1" >>confdefs.h
+
+fi
+
+}
+
+check_for_linux_sg_v4_hdr() {
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+printf %s "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if test ${ac_cv_prog_CPP+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+ # Double quotes because $CC needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <limits.h>
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+ # Broken: success on invalid input.
+continue
+else $as_nop
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok
+then :
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+printf "%s\n" "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <limits.h>
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+ # Broken: success on invalid input.
+continue
+else $as_nop
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok
+then :
+
+else $as_nop
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+ # include <scsi/sg.h>
+ #ifdef SG_IOSUBMIT
+ found
+ #endif
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "found" >/dev/null 2>&1
+then :
+
+printf "%s\n" "#define HAVE_LINUX_SG_V4_HDR 1" >>confdefs.h
+
+fi
+rm -rf conftest*
+
+}
+
+case "${host}" in
+ *-*-android*)
+
+printf "%s\n" "#define SG_LIB_ANDROID 1" >>confdefs.h
+
+
+printf "%s\n" "#define SG_LIB_LINUX 1" >>confdefs.h
+
+ check_for_linux_sg_v4_hdr
+ check_for_getrandom
+ check_for_linux_nvme_headers;;
+ *-*-freebsd*|*-*-kfreebsd*-gnu*)
+
+printf "%s\n" "#define SG_LIB_FREEBSD 1" >>confdefs.h
+
+
+printf "%s\n" "#define HAVE_NVME 1" >>confdefs.h
+
+ check_for_getrandom
+ LIBS="$LIBS -lcam";;
+ *-*-solaris*)
+
+printf "%s\n" "#define SG_LIB_SOLARIS 1" >>confdefs.h
+;;
+ *-*-netbsd*)
+
+printf "%s\n" "#define SG_LIB_NETBSD 1" >>confdefs.h
+;;
+ *-*-openbsd*)
+
+printf "%s\n" "#define SG_LIB_OPENBSD 1" >>confdefs.h
+;;
+ *-*-osf*)
+
+printf "%s\n" "#define SG_LIB_OSF1 1" >>confdefs.h
+;;
+ *-*-cygwin*)
+
+printf "%s\n" "#define SG_LIB_WIN32 1" >>confdefs.h
+
+ # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], [])
+
+printf "%s\n" "#define HAVE_NVME 1" >>confdefs.h
+
+ check_for_getrandom
+ CFLAGS="$CFLAGS -Wno-char-subscripts";;
+ *-*-mingw* | *-*-msys*)
+
+printf "%s\n" "#define SG_LIB_WIN32 1" >>confdefs.h
+
+
+printf "%s\n" "#define SG_LIB_MINGW 1" >>confdefs.h
+
+ # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], [])
+
+printf "%s\n" "#define HAVE_NVME 1" >>confdefs.h
+
+ check_for_getrandom
+ CFLAGS="$CFLAGS -D__USE_MINGW_ANSI_STDIO";;
+ *-*-linux-gnu* | *-*-linux* | *-*-uclinux-gnu* | *-*-uclinux*)
+
+printf "%s\n" "#define SG_LIB_LINUX 1" >>confdefs.h
+
+ check_for_linux_sg_v4_hdr
+ check_for_getrandom
+ check_for_linux_nvme_headers;;
+ *-*-haiku*)
+
+printf "%s\n" "#define SG_LIB_HAIKU 1" >>confdefs.h
+
+ os_cflags=''
+
+ os_libs=''
+ ;;
+ *)
+
+printf "%s\n" "#define SG_LIB_OTHER 1" >>confdefs.h
+
+ isother=yes;;
+esac
+
+# Define platform-specific symbol.
+ if echo $host_os | grep 'freebsd' > /dev/null; then
+ OS_FREEBSD_TRUE=
+ OS_FREEBSD_FALSE='#'
+else
+ OS_FREEBSD_TRUE='#'
+ OS_FREEBSD_FALSE=
+fi
+
+ if echo $host_os | grep -E '^(uc)?linux' > /dev/null; then
+ OS_LINUX_TRUE=
+ OS_LINUX_FALSE='#'
+else
+ OS_LINUX_TRUE='#'
+ OS_LINUX_FALSE=
+fi
+
+ if echo $host_os | grep '^osf' > /dev/null; then
+ OS_OSF_TRUE=
+ OS_OSF_FALSE='#'
+else
+ OS_OSF_TRUE='#'
+ OS_OSF_FALSE=
+fi
+
+ if echo $host_os | grep '^solaris' > /dev/null; then
+ OS_SOLARIS_TRUE=
+ OS_SOLARIS_FALSE='#'
+else
+ OS_SOLARIS_TRUE='#'
+ OS_SOLARIS_FALSE=
+fi
+
+ if echo $host_os | grep '^mingw' > /dev/null; then
+ OS_WIN32_MINGW_TRUE=
+ OS_WIN32_MINGW_FALSE='#'
+else
+ OS_WIN32_MINGW_TRUE='#'
+ OS_WIN32_MINGW_FALSE=
+fi
+
+ if echo $host_os | grep '^cygwin' > /dev/null; then
+ OS_WIN32_CYGWIN_TRUE=
+ OS_WIN32_CYGWIN_FALSE='#'
+else
+ OS_WIN32_CYGWIN_TRUE='#'
+ OS_WIN32_CYGWIN_FALSE=
+fi
+
+ if echo $host_os | grep 'android' > /dev/null; then
+ OS_ANDROID_TRUE=
+ OS_ANDROID_FALSE='#'
+else
+ OS_ANDROID_TRUE='#'
+ OS_ANDROID_FALSE=
+fi
+
+ if echo $host_os | grep 'netbsd' > /dev/null; then
+ OS_NETBSD_TRUE=
+ OS_NETBSD_FALSE='#'
+else
+ OS_NETBSD_TRUE='#'
+ OS_NETBSD_FALSE=
+fi
+
+ if echo $host_os | grep 'openbsd' > /dev/null; then
+ OS_OPENBSD_TRUE=
+ OS_OPENBSD_FALSE='#'
+else
+ OS_OPENBSD_TRUE='#'
+ OS_OPENBSD_FALSE=
+fi
+
+ if echo $host_os | grep '^haiku' > /dev/null; then
+ OS_HAIKU_TRUE=
+ OS_HAIKU_FALSE='#'
+else
+ OS_HAIKU_TRUE='#'
+ OS_HAIKU_FALSE=
+fi
+
+ if test "x$isother" = "xyes"; then
+ OS_OTHER_TRUE=
+ OS_OTHER_FALSE='#'
+else
+ OS_OTHER_TRUE='#'
+ OS_OTHER_FALSE=
+fi
+
+
+# Check whether --enable-debug was given.
+if test ${enable_debug+y}
+then :
+ enableval=$enable_debug; case "${enableval}" in
+ yes) debug=true ;;
+ no) debug=false ;;
+ *) as_fn_error $? "bad value ${enableval} for --enable-debug" "$LINENO" 5 ;;
+ esac
+else $as_nop
+ debug=false
+fi
+
+ if test x$debug = xtrue; then
+ DEBUG_TRUE=
+ DEBUG_FALSE='#'
+else
+ DEBUG_TRUE='#'
+ DEBUG_FALSE=
+fi
+
+
+# Check whether --enable-pt_dummy was given.
+if test ${enable_pt_dummy+y}
+then :
+ enableval=$enable_pt_dummy; case "${enableval}" in
+ yes) pt_dummy=true ;;
+ no) pt_dummy=false ;;
+ *) as_fn_error $? "bad value ${enableval} for --enable-dummy_pt" "$LINENO" 5 ;;
+ esac
+else $as_nop
+ pt_dummy=false
+fi
+
+ if test x$pt_dummy = xtrue; then
+ PT_DUMMY_TRUE=
+ PT_DUMMY_FALSE='#'
+else
+ PT_DUMMY_TRUE='#'
+ PT_DUMMY_FALSE=
+fi
+
+
+# Check whether --enable-linuxbsg was given.
+if test ${enable_linuxbsg+y}
+then :
+ enableval=$enable_linuxbsg;
+printf "%s\n" "#define IGNORE_LINUX_BSG 1" >>confdefs.h
+
+fi
+
+
+# Check whether --enable-win32-spt-direct was given.
+if test ${enable_win32_spt_direct+y}
+then :
+ enableval=$enable_win32_spt_direct;
+printf "%s\n" "#define WIN32_SPT_DIRECT 1" >>confdefs.h
+
+
+fi
+
+
+# Check whether --enable-scsistrings was given.
+if test ${enable_scsistrings+y}
+then :
+ enableval=$enable_scsistrings;
+else $as_nop
+
+printf "%s\n" "#define SG_SCSI_STRINGS 1" >>confdefs.h
+
+fi
+
+
+# Check whether --enable-nvme-supp was given.
+if test ${enable_nvme_supp+y}
+then :
+ enableval=$enable_nvme_supp;
+printf "%s\n" "#define IGNORE_NVME 1" >>confdefs.h
+
+fi
+
+
+# Check whether --enable-fast-lebe was given.
+if test ${enable_fast_lebe+y}
+then :
+ enableval=$enable_fast_lebe;
+printf "%s\n" "#define IGNORE_FAST_LEBE 1" >>confdefs.h
+
+fi
+
+
+# Check whether --enable-linux-sgv4 was given.
+if test ${enable_linux_sgv4+y}
+then :
+ enableval=$enable_linux_sgv4;
+printf "%s\n" "#define IGNORE_LINUX_SGV4 1" >>confdefs.h
+
+fi
+
+
+
+ac_config_files="$ac_config_files Makefile include/Makefile lib/Makefile src/Makefile doc/Makefile scripts/Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+ for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+
+ (set) 2>&1 |
+ case $as_nl`(ac_space=' '; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ # `set' does not quote correctly, so add quotes: double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \.
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;; #(
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+) |
+ sed '
+ /^ac_cv_env_/b end
+ t clear
+ :clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/
+ t end
+ s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+ if test -w "$cache_file"; then
+ if test "x$cache_file" != "x/dev/null"; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+printf "%s\n" "$as_me: updating cache $cache_file" >&6;}
+ if test ! -f "$cache_file" || test -h "$cache_file"; then
+ cat confcache >"$cache_file"
+ else
+ case $cache_file in #(
+ */* | ?:*)
+ mv -f confcache "$cache_file"$$ &&
+ mv -f "$cache_file"$$ "$cache_file" ;; #(
+ *)
+ mv -f confcache "$cache_file" ;;
+ esac
+ fi
+ fi
+ else
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;}
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+ ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"`
+ # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
+ # will be set to the directory where LIBOBJS objects are built.
+ as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+ as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5
+printf %s "checking that generated files are newer than configure... " >&6; }
+ if test -n "$am_sleep_pid"; then
+ # Hide warnings about reused PIDs.
+ wait $am_sleep_pid 2>/dev/null
+ fi
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: done" >&5
+printf "%s\n" "done" >&6; }
+ if test -n "$EXEEXT"; then
+ am__EXEEXT_TRUE=
+ am__EXEEXT_FALSE='#'
+else
+ am__EXEEXT_TRUE='#'
+ am__EXEEXT_FALSE=
+fi
+
+if test -z "${MAINTAINER_MODE_TRUE}" && test -z "${MAINTAINER_MODE_FALSE}"; then
+ as_fn_error $? "conditional \"MAINTAINER_MODE\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+ as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+ as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_FREEBSD_TRUE}" && test -z "${OS_FREEBSD_FALSE}"; then
+ as_fn_error $? "conditional \"OS_FREEBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_LINUX_TRUE}" && test -z "${OS_LINUX_FALSE}"; then
+ as_fn_error $? "conditional \"OS_LINUX\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_OSF_TRUE}" && test -z "${OS_OSF_FALSE}"; then
+ as_fn_error $? "conditional \"OS_OSF\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_SOLARIS_TRUE}" && test -z "${OS_SOLARIS_FALSE}"; then
+ as_fn_error $? "conditional \"OS_SOLARIS\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_WIN32_MINGW_TRUE}" && test -z "${OS_WIN32_MINGW_FALSE}"; then
+ as_fn_error $? "conditional \"OS_WIN32_MINGW\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_WIN32_CYGWIN_TRUE}" && test -z "${OS_WIN32_CYGWIN_FALSE}"; then
+ as_fn_error $? "conditional \"OS_WIN32_CYGWIN\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_ANDROID_TRUE}" && test -z "${OS_ANDROID_FALSE}"; then
+ as_fn_error $? "conditional \"OS_ANDROID\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_NETBSD_TRUE}" && test -z "${OS_NETBSD_FALSE}"; then
+ as_fn_error $? "conditional \"OS_NETBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_OPENBSD_TRUE}" && test -z "${OS_OPENBSD_FALSE}"; then
+ as_fn_error $? "conditional \"OS_OPENBSD\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_HAIKU_TRUE}" && test -z "${OS_HAIKU_FALSE}"; then
+ as_fn_error $? "conditional \"OS_HAIKU\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${OS_OTHER_TRUE}" && test -z "${OS_OTHER_FALSE}"; then
+ as_fn_error $? "conditional \"OS_OTHER\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${DEBUG_TRUE}" && test -z "${DEBUG_FALSE}"; then
+ as_fn_error $? "conditional \"DEBUG\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${PT_DUMMY_TRUE}" && test -z "${PT_DUMMY_FALSE}"; then
+ as_fn_error $? "conditional \"PT_DUMMY\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+as_nop=:
+if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else $as_nop
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+
+# Reset variables that may have inherited troublesome values from
+# the environment.
+
+# IFS needs to be set, to space, tab, and newline, in precisely that order.
+# (If _AS_PATH_WALK were called with IFS unset, it would have the
+# side effect of setting IFS to empty, thus disabling word splitting.)
+# Quoting is to prevent editors from complaining about space-tab.
+as_nl='
+'
+export as_nl
+IFS=" "" $as_nl"
+
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# Ensure predictable behavior from utilities with locale-dependent output.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# We cannot yet rely on "unset" to work, but we need these variables
+# to be unset--not just set to an empty or harmless value--now, to
+# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct
+# also avoids known problems related to "unset" and subshell syntax
+# in other old shells (e.g. bash 2.01 and pdksh 5.2.14).
+for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH
+do eval test \${$as_var+y} \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+
+# Ensure that fds 0, 1, and 2 are open.
+if (exec 3>&0) 2>/dev/null; then :; else exec 0</dev/null; fi
+if (exec 3>&1) 2>/dev/null; then :; else exec 1>/dev/null; fi
+if (exec 3>&2) ; then :; else exec 2>/dev/null; fi
+
+# The user is always right.
+if ${PATH_SEPARATOR+false} :; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ case $as_dir in #(((
+ '') as_dir=./ ;;
+ */) ;;
+ *) as_dir=$as_dir/ ;;
+ esac
+ test -r "$as_dir$0" && as_myself=$as_dir$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ printf "%s\n" "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null
+then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else $as_nop
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null
+then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else $as_nop
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+# Determine whether it's possible to make 'echo' print without a newline.
+# These variables are no longer used directly by Autoconf, but are AC_SUBSTed
+# for compatibility with existing Makefiles.
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+# For backward compatibility with old third-party macros, we provide
+# the shell variables $as_echo and $as_echo_n. New code should use
+# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively.
+as_echo='printf %s\n'
+as_echo_n='printf %s'
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by sg3_utils $as_me 1.48, which was
+generated by GNU Autoconf 2.71. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration. Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number and configuration settings, then exit
+ --config print configuration, then exit
+ -q, --quiet, --silent
+ do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+ --header=FILE[:TEMPLATE]
+ instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to <dgilbert@interlog.com>."
+
+_ACEOF
+ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"`
+ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"`
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config='$ac_cs_config_escaped'
+ac_cs_version="\\
+sg3_utils config.status 1.48
+configured by $0, generated by GNU Autoconf 2.71,
+ with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2021 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=?*)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ --*=)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=
+ ac_shift=:
+ ;;
+ *)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+ printf "%s\n" "$ac_cs_version"; exit ;;
+ --config | --confi | --conf | --con | --co | --c )
+ printf "%s\n" "$ac_cs_config"; exit ;;
+ --debug | --debu | --deb | --de | --d | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ '') as_fn_error $? "missing file argument" ;;
+ esac
+ as_fn_append CONFIG_FILES " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --he | --h)
+ # Conflict between --help and --header
+ as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+ --help | --hel | -h )
+ printf "%s\n" "$ac_cs_usage"; exit ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+ *) as_fn_append ac_config_targets " $1"
+ ac_need_defaults=false ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+ set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+ shift
+ \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6
+ CONFIG_SHELL='$SHELL'
+ export CONFIG_SHELL
+ exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+ printf "%s\n" "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"
+
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`'
+macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`'
+enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`'
+enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`'
+pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`'
+enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`'
+shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`'
+SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`'
+ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`'
+PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`'
+host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`'
+host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`'
+host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`'
+build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`'
+build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`'
+build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`'
+SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`'
+Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`'
+GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`'
+EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`'
+FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`'
+LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`'
+NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`'
+LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`'
+max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`'
+ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`'
+exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`'
+lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`'
+lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`'
+lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`'
+reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`'
+reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`'
+FILECMD='`$ECHO "$FILECMD" | $SED "$delay_single_quote_subst"`'
+OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`'
+deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`'
+file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`'
+file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`'
+want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`'
+DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`'
+sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`'
+AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`'
+lt_ar_flags='`$ECHO "$lt_ar_flags" | $SED "$delay_single_quote_subst"`'
+AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`'
+archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`'
+STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`'
+RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`'
+old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`'
+lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`'
+CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`'
+CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`'
+compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`'
+GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`'
+lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`'
+nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`'
+lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`'
+lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`'
+objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`'
+MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`'
+need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`'
+MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`'
+DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`'
+NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`'
+LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`'
+OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`'
+OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`'
+libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`'
+shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`'
+extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`'
+hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`'
+inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`'
+always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`'
+include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`'
+prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`'
+postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`'
+file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`'
+variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`'
+need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`'
+need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`'
+version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`'
+runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`'
+libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`'
+library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`'
+soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`'
+install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`'
+postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`'
+finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`'
+hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`'
+sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`'
+configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`'
+configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`'
+hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`'
+enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`'
+old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`'
+striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`'
+
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in SHELL \
+ECHO \
+PATH_SEPARATOR \
+SED \
+GREP \
+EGREP \
+FGREP \
+LD \
+NM \
+LN_S \
+lt_SP2NL \
+lt_NL2SP \
+reload_flag \
+FILECMD \
+OBJDUMP \
+deplibs_check_method \
+file_magic_cmd \
+file_magic_glob \
+want_nocaseglob \
+DLLTOOL \
+sharedlib_from_linklib_cmd \
+AR \
+archiver_list_spec \
+STRIP \
+RANLIB \
+CC \
+CFLAGS \
+compiler \
+lt_cv_sys_global_symbol_pipe \
+lt_cv_sys_global_symbol_to_cdecl \
+lt_cv_sys_global_symbol_to_import \
+lt_cv_sys_global_symbol_to_c_name_address \
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \
+lt_cv_nm_interface \
+nm_file_list_spec \
+lt_cv_truncate_bin \
+lt_prog_compiler_no_builtin_flag \
+lt_prog_compiler_pic \
+lt_prog_compiler_wl \
+lt_prog_compiler_static \
+lt_cv_prog_compiler_c_o \
+need_locks \
+MANIFEST_TOOL \
+DSYMUTIL \
+NMEDIT \
+LIPO \
+OTOOL \
+OTOOL64 \
+shrext_cmds \
+export_dynamic_flag_spec \
+whole_archive_flag_spec \
+compiler_needs_object \
+with_gnu_ld \
+allow_undefined_flag \
+no_undefined_flag \
+hardcode_libdir_flag_spec \
+hardcode_libdir_separator \
+exclude_expsyms \
+include_expsyms \
+file_list_spec \
+variables_saved_for_relink \
+libname_spec \
+library_names_spec \
+soname_spec \
+install_override_mode \
+finish_eval \
+old_striplib \
+striplib; do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[\\\\\\\`\\"\\\$]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+# Double-quote double-evaled strings.
+for var in reload_cmds \
+old_postinstall_cmds \
+old_postuninstall_cmds \
+old_archive_cmds \
+extract_expsyms_cmds \
+old_archive_from_new_cmds \
+old_archive_from_expsyms_cmds \
+archive_cmds \
+archive_expsym_cmds \
+module_cmds \
+module_expsym_cmds \
+export_symbols_cmds \
+prelink_cmds \
+postlink_cmds \
+postinstall_cmds \
+postuninstall_cmds \
+finish_cmds \
+sys_lib_search_path_spec \
+configure_time_dlsearch_path \
+configure_time_lt_sys_library_path; do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[\\\\\\\`\\"\\\$]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+ac_aux_dir='$ac_aux_dir'
+
+# See if we are running on zsh, and set the options that allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+fi
+
+
+ PACKAGE='$PACKAGE'
+ VERSION='$VERSION'
+ RM='$RM'
+ ofile='$ofile'
+
+
+
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+ case $ac_config_target in
+ "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+ "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+ "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
+ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
+ "lib/Makefile") CONFIG_FILES="$CONFIG_FILES lib/Makefile" ;;
+ "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
+ "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;;
+ "scripts/Makefile") CONFIG_FILES="$CONFIG_FILES scripts/Makefile" ;;
+
+ *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+ esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files
+ test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers
+ test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+ tmp= ac_tmp=
+ trap 'exit_status=$?
+ : "${ac_tmp:=$tmp}"
+ { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+ trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+ test -d "$tmp"
+} ||
+{
+ tmp=./conf$$-$RANDOM
+ (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+ eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+ ac_cs_awk_cr='\\r'
+else
+ ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+ echo "cat >conf$$subs.awk <<_ACEOF" &&
+ echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+ echo "_ACEOF"
+} >conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+ . ./conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+ ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+ if test $ac_delim_n = $ac_delim_num; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+ N
+ s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+ for (key in S) S_is_set[key] = 1
+ FS = ""
+
+}
+{
+ line = $ 0
+ nfields = split(line, field, "@")
+ substed = 0
+ len = length(field[1])
+ for (i = 2; i < nfields; i++) {
+ key = field[i]
+ keylen = length(key)
+ if (S_is_set[key]) {
+ value = S[key]
+ line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+ len += length(value) + length(field[++i])
+ substed = 1
+ } else
+ len += 1 + keylen
+ }
+
+ print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+ sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+ cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
+h
+s///
+s/^/:/
+s/[ ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[ ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[ ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+ ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+ if test -z "$ac_tt"; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any. Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[ ]*#[ ]*define[ ][ ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ for (key in D) D_is_set[key] = 1
+ FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+ line = \$ 0
+ split(line, arg, " ")
+ if (arg[1] == "#") {
+ defundef = arg[2]
+ mac1 = arg[3]
+ } else {
+ defundef = substr(arg[1], 2)
+ mac1 = arg[2]
+ }
+ split(mac1, mac2, "(") #)
+ macro = mac2[1]
+ prefix = substr(line, 1, index(line, defundef) - 1)
+ if (D_is_set[macro]) {
+ # Preserve the white space surrounding the "#".
+ print prefix "define", macro P[macro] D[macro]
+ next
+ } else {
+ # Replace #undef with comments. This is necessary, for example,
+ # in the case of _POSIX_SOURCE, which is predefined and required
+ # on some systems where configure will not decide to define it.
+ if (defundef == "undef") {
+ print "/*", prefix defundef, macro, "*/"
+ next
+ }
+ }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+ case $ac_tag in
+ :[FHLC]) ac_mode=$ac_tag; continue;;
+ esac
+ case $ac_mode$ac_tag in
+ :[FHL]*:*);;
+ :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+ :[FH]-) ac_tag=-:-;;
+ :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+ esac
+ ac_save_IFS=$IFS
+ IFS=:
+ set x $ac_tag
+ IFS=$ac_save_IFS
+ shift
+ ac_file=$1
+ shift
+
+ case $ac_mode in
+ :L) ac_source=$1;;
+ :[FH])
+ ac_file_inputs=
+ for ac_f
+ do
+ case $ac_f in
+ -) ac_f="$ac_tmp/stdin";;
+ *) # Look for the file first in the build tree, then in the source tree
+ # (if the path is not absolute). The absolute path cannot be DOS-style,
+ # because $ac_f cannot contain `:'.
+ test -f "$ac_f" ||
+ case $ac_f in
+ [\\/$]*) false;;
+ *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+ esac ||
+ as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+ esac
+ case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+ as_fn_append ac_file_inputs " '$ac_f'"
+ done
+
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ configure_input='Generated from '`
+ printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+ `' by configure.'
+ if test x"$ac_file" != x-; then
+ configure_input="$ac_file. $configure_input"
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+printf "%s\n" "$as_me: creating $ac_file" >&6;}
+ fi
+ # Neutralize special characters interpreted by sed in replacement strings.
+ case $configure_input in #(
+ *\&* | *\|* | *\\* )
+ ac_sed_conf_input=`printf "%s\n" "$configure_input" |
+ sed 's/[\\\\&|]/\\\\&/g'`;; #(
+ *) ac_sed_conf_input=$configure_input;;
+ esac
+
+ case $ac_tag in
+ *:-:* | *:-) cat >"$ac_tmp/stdin" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+ esac
+ ;;
+ esac
+
+ ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ as_dir="$ac_dir"; as_fn_mkdir_p
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+ case $ac_mode in
+ :F)
+ #
+ # CONFIG_FILE
+ #
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+ esac
+ ac_MKDIR_P=$MKDIR_P
+ case $MKDIR_P in
+ [\\/$]* | ?:[\\/]* ) ;;
+ */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+ esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+ p
+ q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_datarootdir_hack='
+ s&@datadir@&$datadir&g
+ s&@docdir@&$docdir&g
+ s&@infodir@&$infodir&g
+ s&@localedir@&$localedir&g
+ s&@mandir@&$mandir&g
+ s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+ { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+ { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
+ "$ac_tmp/out"`; test -z "$ac_out"; } &&
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&5
+printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&2;}
+
+ rm -f "$ac_tmp/stdin"
+ case $ac_file in
+ -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+ *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+ esac \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+ :H)
+ #
+ # CONFIG_HEADER
+ #
+ if test x"$ac_file" != x-; then
+ {
+ printf "%s\n" "/* $configure_input */" >&1 \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+ } >"$ac_tmp/config.h" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+printf "%s\n" "$as_me: $ac_file is unchanged" >&6;}
+ else
+ rm -f "$ac_file"
+ mv "$ac_tmp/config.h" "$ac_file" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ fi
+ else
+ printf "%s\n" "/* $configure_input */" >&1 \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+ || as_fn_error $? "could not create -" "$LINENO" 5
+ fi
+# Compute "$ac_file"'s index in $config_headers.
+_am_arg="$ac_file"
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+ case $_am_header in
+ $_am_arg | $_am_arg:* )
+ break ;;
+ * )
+ _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+ esac
+done
+echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" ||
+$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$_am_arg" : 'X\(//\)[^/]' \| \
+ X"$_am_arg" : 'X\(//\)$' \| \
+ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$_am_arg" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`/stamp-h$_am_stamp_count
+ ;;
+
+ :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+printf "%s\n" "$as_me: executing $ac_file commands" >&6;}
+ ;;
+ esac
+
+
+ case $ac_file$ac_mode in
+ "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+ # Older Autoconf quotes --file arguments for eval, but not when files
+ # are listed without --file. Let's play safe and only enable the eval
+ # if we detect the quoting.
+ # TODO: see whether this extra hack can be removed once we start
+ # requiring Autoconf 2.70 or later.
+ case $CONFIG_FILES in #(
+ *\'*) :
+ eval set x "$CONFIG_FILES" ;; #(
+ *) :
+ set x $CONFIG_FILES ;; #(
+ *) :
+ ;;
+esac
+ shift
+ # Used to flag and report bootstrapping failures.
+ am_rc=0
+ for am_mf
+ do
+ # Strip MF so we end up with the name of the file.
+ am_mf=`printf "%s\n" "$am_mf" | sed -e 's/:.*$//'`
+ # Check whether this is an Automake generated Makefile which includes
+ # dependency-tracking related rules and includes.
+ # Grep'ing the whole file directly is not great: AIX grep has a line
+ # limit of 2048, but all sed's we know have understand at least 4000.
+ sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
+ || continue
+ am_dirpart=`$as_dirname -- "$am_mf" ||
+$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$am_mf" : 'X\(//\)[^/]' \| \
+ X"$am_mf" : 'X\(//\)$' \| \
+ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X"$am_mf" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ am_filepart=`$as_basename -- "$am_mf" ||
+$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$am_mf" : 'X\(//\)$' \| \
+ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
+printf "%s\n" X/"$am_mf" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ { echo "$as_me:$LINENO: cd "$am_dirpart" \
+ && sed -e '/# am--include-marker/d' "$am_filepart" \
+ | $MAKE -f - am--depfiles" >&5
+ (cd "$am_dirpart" \
+ && sed -e '/# am--include-marker/d' "$am_filepart" \
+ | $MAKE -f - am--depfiles) >&5 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } || am_rc=$?
+ done
+ if test $am_rc -ne 0; then
+ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "Something went wrong bootstrapping makefile fragments
+ for automatic dependency tracking. If GNU make was not used, consider
+ re-running the configure script with MAKE=\"gmake\" (or whatever is
+ necessary). You can also try re-running configure with the
+ '--disable-dependency-tracking' option to at least be able to build
+ the package (albeit without support for automatic dependency tracking).
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ { am_dirpart=; unset am_dirpart;}
+ { am_filepart=; unset am_filepart;}
+ { am_mf=; unset am_mf;}
+ { am_rc=; unset am_rc;}
+ rm -f conftest-deps.mk
+}
+ ;;
+ "libtool":C)
+
+ # See if we are running on zsh, and set the options that allow our
+ # commands through without removal of \ escapes.
+ if test -n "${ZSH_VERSION+set}"; then
+ setopt NO_GLOB_SUBST
+ fi
+
+ cfgfile=${ofile}T
+ trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+ $RM "$cfgfile"
+
+ cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+# Generated automatically by $as_me ($PACKAGE) $VERSION
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+
+# Provide generalized library-building support services.
+# Written by Gordon Matzigkeit, 1996
+
+# Copyright (C) 2014 Free Software Foundation, Inc.
+# This is free software; see the source for copying conditions. There is NO
+# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# GNU Libtool 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 of of the License, or
+# (at your option) any later version.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program or library that is built
+# using GNU Libtool, you may include this file under the same
+# distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# The names of the tagged configurations supported by this script.
+available_tags=''
+
+# Configured defaults for sys_lib_dlsearch_path munging.
+: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"}
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Which release of libtool.m4 was used?
+macro_version=$macro_version
+macro_revision=$macro_revision
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# What type of objects to build.
+pic_mode=$pic_mode
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# Shared archive member basename,for filename based shared library versioning on AIX.
+shared_archive_member_spec=$shared_archive_member_spec
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# An echo program that protects backslashes.
+ECHO=$lt_ECHO
+
+# The PATH separator for the build system.
+PATH_SEPARATOR=$lt_PATH_SEPARATOR
+
+# The host system.
+host_alias=$host_alias
+host=$host
+host_os=$host_os
+
+# The build system.
+build_alias=$build_alias
+build=$build
+build_os=$build_os
+
+# A sed program that does not truncate output.
+SED=$lt_SED
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="\$SED -e 1s/^X//"
+
+# A grep program that handles long lines.
+GREP=$lt_GREP
+
+# An ERE matcher.
+EGREP=$lt_EGREP
+
+# A literal string matcher.
+FGREP=$lt_FGREP
+
+# A BSD- or MS-compatible name lister.
+NM=$lt_NM
+
+# Whether we need soft or hard links.
+LN_S=$lt_LN_S
+
+# What is the maximum length of a command?
+max_cmd_len=$max_cmd_len
+
+# Object file suffix (normally "o").
+objext=$ac_objext
+
+# Executable file suffix (normally "").
+exeext=$exeext
+
+# whether the shell understands "unset".
+lt_unset=$lt_unset
+
+# turn spaces into newlines.
+SP2NL=$lt_lt_SP2NL
+
+# turn newlines into spaces.
+NL2SP=$lt_lt_NL2SP
+
+# convert \$build file names to \$host format.
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+
+# convert \$build files to toolchain format.
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+
+# A file(cmd) program that detects file types.
+FILECMD=$lt_FILECMD
+
+# An object symbol dumper.
+OBJDUMP=$lt_OBJDUMP
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method = "file_magic".
+file_magic_cmd=$lt_file_magic_cmd
+
+# How to find potential files when deplibs_check_method = "file_magic".
+file_magic_glob=$lt_file_magic_glob
+
+# Find potential files using nocaseglob when deplibs_check_method = "file_magic".
+want_nocaseglob=$lt_want_nocaseglob
+
+# DLL creation program.
+DLLTOOL=$lt_DLLTOOL
+
+# Command to associate shared and link libraries.
+sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd
+
+# The archiver.
+AR=$lt_AR
+
+# Flags to create an archive (by configure).
+lt_ar_flags=$lt_ar_flags
+
+# Flags to create an archive.
+AR_FLAGS=\${ARFLAGS-"\$lt_ar_flags"}
+
+# How to feed a file listing to the archiver.
+archiver_list_spec=$lt_archiver_list_spec
+
+# A symbol stripping program.
+STRIP=$lt_STRIP
+
+# Commands used to install an old-style archive.
+RANLIB=$lt_RANLIB
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Whether to use a lock for old archive extraction.
+lock_old_archive_extraction=$lock_old_archive_extraction
+
+# A C compiler.
+LTCC=$lt_CC
+
+# LTCC compiler flags.
+LTCFLAGS=$lt_CFLAGS
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration.
+global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl
+
+# Transform the output of nm into a list of symbols to manually relocate.
+global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import
+
+# Transform the output of nm in a C name address pair.
+global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address
+
+# Transform the output of nm in a C name address pair when lib prefix is needed.
+global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix
+
+# The name lister interface.
+nm_interface=$lt_lt_cv_nm_interface
+
+# Specify filename containing input files for \$NM.
+nm_file_list_spec=$lt_nm_file_list_spec
+
+# The root where to search for dependent libraries,and where our libraries should be installed.
+lt_sysroot=$lt_sysroot
+
+# Command to truncate a binary pipe.
+lt_truncate_bin=$lt_lt_cv_truncate_bin
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# Used to examine libraries when file_magic_cmd begins with "file".
+MAGIC_CMD=$MAGIC_CMD
+
+# Must we lock files when doing compilation?
+need_locks=$lt_need_locks
+
+# Manifest tool.
+MANIFEST_TOOL=$lt_MANIFEST_TOOL
+
+# Tool to manipulate archived DWARF debug symbol files on Mac OS X.
+DSYMUTIL=$lt_DSYMUTIL
+
+# Tool to change global to local symbols on Mac OS X.
+NMEDIT=$lt_NMEDIT
+
+# Tool to manipulate fat objects and archives on Mac OS X.
+LIPO=$lt_LIPO
+
+# ldd/readelf like tool for Mach-O binaries on Mac OS X.
+OTOOL=$lt_OTOOL
+
+# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4.
+OTOOL64=$lt_OTOOL64
+
+# Old archive suffix (normally "a").
+libext=$libext
+
+# Shared library suffix (normally ".so").
+shrext_cmds=$lt_shrext_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at link time.
+variables_saved_for_relink=$lt_variables_saved_for_relink
+
+# Do we need the "lib" prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Library versioning type.
+version_type=$version_type
+
+# Shared library runtime path variable.
+runpath_var=$runpath_var
+
+# Shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names. First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Permission mode override for installation of shared libraries.
+install_override_mode=$lt_install_override_mode
+
+# Command to use after installation of a shared archive.
+postinstall_cmds=$lt_postinstall_cmds
+
+# Command to use after uninstallation of a shared archive.
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# As "finish_cmds", except a single script fragment to be evaled but
+# not shown.
+finish_eval=$lt_finish_eval
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Compile-time system search path for libraries.
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Detected run-time system search path for libraries.
+sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path
+
+# Explicit LT_SYS_LIBRARY_PATH set during ./configure time.
+configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds
+
+# A language specific compiler.
+CC=$lt_compiler
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds
+module_expsym_cmds=$lt_module_expsym_cmds
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \$shlibpath_var if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+ cat <<'_LT_EOF' >> "$cfgfile"
+
+# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE
+
+# func_munge_path_list VARIABLE PATH
+# -----------------------------------
+# VARIABLE is name of variable containing _space_ separated list of
+# directories to be munged by the contents of PATH, which is string
+# having a format:
+# "DIR[:DIR]:"
+# string "DIR[ DIR]" will be prepended to VARIABLE
+# ":DIR[:DIR]"
+# string "DIR[ DIR]" will be appended to VARIABLE
+# "DIRP[:DIRP]::[DIRA:]DIRA"
+# string "DIRP[ DIRP]" will be prepended to VARIABLE and string
+# "DIRA[ DIRA]" will be appended to VARIABLE
+# "DIR[:DIR]"
+# VARIABLE will be replaced by "DIR[ DIR]"
+func_munge_path_list ()
+{
+ case x$2 in
+ x)
+ ;;
+ *:)
+ eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\"
+ ;;
+ x:*)
+ eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\"
+ ;;
+ *::*)
+ eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\"
+ eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\"
+ ;;
+ *)
+ eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\"
+ ;;
+ esac
+}
+
+
+# Calculate cc_basename. Skip known compiler wrappers and cross-prefix.
+func_cc_basename ()
+{
+ for cc_temp in $*""; do
+ case $cc_temp in
+ compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+ distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+ done
+ func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+}
+
+
+# ### END FUNCTIONS SHARED WITH CONFIGURE
+
+_LT_EOF
+
+ case $host_os in
+ aix3*)
+ cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program. For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test set != "${COLLECT_NAMES+set}"; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+fi
+_LT_EOF
+ ;;
+ esac
+
+
+
+ltmain=$ac_aux_dir/ltmain.sh
+
+
+ # We use sed instead of cat because bash on DJGPP gets confused if
+ # if finds mixed CR/LF and LF-only lines. Since sed operates in
+ # text mode, it properly converts lines to CR/LF. This bash problem
+ # is reportedly fixed, but why not run on old versions too?
+ $SED '$q' "$ltmain" >> "$cfgfile" \
+ || (rm -f "$cfgfile"; exit 1)
+
+ mv -f "$cfgfile" "$ofile" ||
+ (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+ chmod +x "$ofile"
+
+ ;;
+
+ esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+ as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
+
+
+# Borrowed from smartmontools configure.ac
+# Note: Use `...` here as some shells do not properly parse '$(... case $x in X) ...)'
+info=`
+ echo "-----------------------------------------------------------------------------"
+ echo "${PACKAGE}-${VERSION} configuration:"
+ echo "host operating system: $host"
+ echo "default C compiler: $CC"
+
+ case "$host_os" in
+ mingw*)
+ echo "application manifest: ${os_win32_manifest:-built-in}"
+ echo "resource compiler: $WINDRES"
+ echo "message compiler: $WINDMC"
+ echo "NSIS compiler: $MAKENSIS"
+ ;;
+
+ *)
+ echo "binary install path: \`eval eval eval echo $bindir\`"
+ echo "scripts install path: \`eval eval eval echo $bindir\`"
+ echo "man page install path: \`eval eval eval echo $mandir\`"
+ ;;
+ esac
+ echo "-----------------------------------------------------------------------------"
+`
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}:
+$info
+" >&5
+printf "%s\n" "$as_me:
+$info
+" >&6;}
+
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..c6deea8c
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,220 @@
+AC_INIT(sg3_utils, 1.48, dgilbert@interlog.com)
+
+AM_INIT_AUTOMAKE([-Wall -Werror foreign])
+AM_MAINTAINER_MODE
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+# AC_PROG_CXX
+AC_PROG_INSTALL
+
+# AM_PROG_AR is supported and needed since automake v1.12+
+ifdef([AM_PROG_AR], [AM_PROG_AR], [])
+
+# Adding libtools to the build seems to bring in C++ environment
+AC_PROG_LIBTOOL
+
+# check for headers
+AC_HEADER_STDC
+AC_CHECK_HEADERS([byteswap.h stdatomic.h], [], [], [])
+
+# check for functions
+AC_CHECK_FUNCS(getopt_long,
+ GETOPT_O_FILES='',
+ GETOPT_O_FILES='getopt_long.o')
+AC_CHECK_FUNCS(posix_fadvise)
+AC_CHECK_FUNCS(posix_memalign)
+AC_CHECK_FUNCS(gettimeofday)
+AC_CHECK_FUNCS(sysconf)
+AC_CHECK_FUNCS(lseek64)
+AC_CHECK_FUNCS(srand48_r)
+SAVED_LIBS=$LIBS
+AC_SEARCH_LIBS([pthread_create], [pthread])
+# AC_SEARCH_LIBS adds libraries at the start of $LIBS so remove $SAVED_LIBS
+# from the end of $LIBS.
+pthread_lib=${LIBS%${SAVED_LIBS}}
+AC_CHECK_FUNCS([pthread_cancel pthread_kill])
+LIBS=$SAVED_LIBS
+AC_SUBST(PTHREAD_LIB, [$pthread_lib])
+
+SAVED_LIBS=$LIBS
+AC_SEARCH_LIBS([clock_gettime], [rt])
+rt_lib=${LIBS%${SAVED_LIBS}}
+AC_CHECK_FUNCS(clock_gettime)
+LIBS=$SAVED_LIBS
+AC_SUBST(RT_LIB, [$rt_lib])
+
+AC_SUBST(GETOPT_O_FILES)
+
+
+AC_CANONICAL_HOST
+
+AC_DEFINE_UNQUOTED(SG_LIB_BUILD_HOST, "${host}", [sg3_utils Build Host])
+
+check_for_getrandom() {
+ AC_CHECK_HEADERS([sys/random.h], [AC_DEFINE_UNQUOTED(HAVE_GETRANDOM, 1, [Found sys/random.h])], [], [])
+}
+
+check_for_linux_nvme_headers() {
+ AC_CHECK_HEADERS([linux/nvme_ioctl.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], [])
+ AC_CHECK_HEADERS([linux/types.h linux/bsg.h linux/kdev_t.h], [], [],
+ [[#ifdef HAVE_LINUX_TYPES_H
+ # include <linux/types.h>
+ #endif
+ ]])
+}
+
+check_for_linux_sg_v4_hdr() {
+ AC_EGREP_CPP(found,
+ [ # include <scsi/sg.h>
+ #ifdef SG_IOSUBMIT
+ found
+ #endif
+ ],
+ [AC_DEFINE_UNQUOTED(HAVE_LINUX_SG_V4_HDR, 1, [Have Linux sg v4 header]) ])
+}
+
+case "${host}" in
+ *-*-android*)
+ AC_DEFINE_UNQUOTED(SG_LIB_ANDROID, 1, [sg3_utils on android])
+ AC_DEFINE_UNQUOTED(SG_LIB_LINUX, 1, [sg3_utils on linux])
+ check_for_linux_sg_v4_hdr
+ check_for_getrandom
+ check_for_linux_nvme_headers;;
+ *-*-freebsd*|*-*-kfreebsd*-gnu*)
+ AC_DEFINE_UNQUOTED(SG_LIB_FREEBSD, 1, [sg3_utils on FreeBSD])
+ AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])
+ check_for_getrandom
+ LIBS="$LIBS -lcam";;
+ *-*-solaris*)
+ AC_DEFINE_UNQUOTED(SG_LIB_SOLARIS, 1, [sg3_utils on Solaris]);;
+ *-*-netbsd*)
+ AC_DEFINE_UNQUOTED(SG_LIB_NETBSD, 1, [sg3_utils on NetBSD]);;
+ *-*-openbsd*)
+ AC_DEFINE_UNQUOTED(SG_LIB_OPENBSD, 1, [sg3_utils on OpenBSD]);;
+ *-*-osf*)
+ AC_DEFINE_UNQUOTED(SG_LIB_OSF1, 1, [sg3_utils on Tru64 UNIX]);;
+ *-*-cygwin*)
+ AC_DEFINE_UNQUOTED(SG_LIB_WIN32, 1, [sg3_utils on Win32])
+ # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], [])
+ AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])
+ check_for_getrandom
+ CFLAGS="$CFLAGS -Wno-char-subscripts";;
+ *-*-mingw* | *-*-msys*)
+ AC_DEFINE_UNQUOTED(SG_LIB_WIN32, 1, [sg3_utils on Win32])
+ AC_DEFINE_UNQUOTED(SG_LIB_MINGW, 1, [also MinGW environment])
+ # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], [])
+ AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])
+ check_for_getrandom
+ CFLAGS="$CFLAGS -D__USE_MINGW_ANSI_STDIO";;
+ *-*-linux-gnu* | *-*-linux* | *-*-uclinux-gnu* | *-*-uclinux*)
+ AC_DEFINE_UNQUOTED(SG_LIB_LINUX, 1, [sg3_utils on linux])
+ check_for_linux_sg_v4_hdr
+ check_for_getrandom
+ check_for_linux_nvme_headers;;
+ *-*-haiku*)
+ AC_DEFINE_UNQUOTED(SG_LIB_HAIKU, 1, [sg3_utils on Haiku])
+ AC_SUBST([os_cflags], [''])
+ AC_SUBST([os_libs], ['']) ;;
+ *)
+ AC_DEFINE_UNQUOTED(SG_LIB_OTHER, 1, [sg3_utils on other])
+ isother=yes;;
+esac
+
+# Define platform-specific symbol.
+AM_CONDITIONAL(OS_FREEBSD, [echo $host_os | grep 'freebsd' > /dev/null])
+AM_CONDITIONAL(OS_LINUX, [echo $host_os | grep -E '^(uc)?linux' > /dev/null])
+AM_CONDITIONAL(OS_OSF, [echo $host_os | grep '^osf' > /dev/null])
+AM_CONDITIONAL(OS_SOLARIS, [echo $host_os | grep '^solaris' > /dev/null])
+AM_CONDITIONAL(OS_WIN32_MINGW, [echo $host_os | grep '^mingw' > /dev/null])
+AM_CONDITIONAL(OS_WIN32_CYGWIN, [echo $host_os | grep '^cygwin' > /dev/null])
+AM_CONDITIONAL(OS_ANDROID, [echo $host_os | grep 'android' > /dev/null])
+AM_CONDITIONAL(OS_NETBSD, [echo $host_os | grep 'netbsd' > /dev/null])
+AM_CONDITIONAL(OS_OPENBSD, [echo $host_os | grep 'openbsd' > /dev/null])
+AM_CONDITIONAL(OS_HAIKU, [echo $host_os | grep '^haiku' > /dev/null])
+AM_CONDITIONAL(OS_OTHER, [test "x$isother" = "xyes"])
+
+AC_ARG_ENABLE([debug],
+ [ --enable-debug Turn on debugging],
+ [case "${enableval}" in
+ yes) debug=true ;;
+ no) debug=false ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
+ esac],[debug=false])
+AM_CONDITIONAL([DEBUG], [test x$debug = xtrue])
+
+AC_ARG_ENABLE([pt_dummy],
+ [ --enable-pt_dummy pass-through codes compiles, does nothing],
+ [case "${enableval}" in
+ yes) pt_dummy=true ;;
+ no) pt_dummy=false ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-dummy_pt]) ;;
+ esac],[pt_dummy=false])
+AM_CONDITIONAL([PT_DUMMY], [test x$pt_dummy = xtrue])
+
+AC_ARG_ENABLE([linuxbsg],
+ AC_HELP_STRING([--disable-linuxbsg], [option ignored, this is placeholder]),
+ [AC_DEFINE_UNQUOTED(IGNORE_LINUX_BSG, 1, [option ignored], )], [])
+
+AC_ARG_ENABLE([win32-spt-direct],
+ AC_HELP_STRING([--enable-win32-spt-direct], [enable Win32 SPT Direct]),
+ AC_DEFINE_UNQUOTED(WIN32_SPT_DIRECT, 1, [enable Win32 SPT Direct], )
+)
+
+AC_ARG_ENABLE([scsistrings],
+ [AS_HELP_STRING([--disable-scsistrings],
+ [Disable full SCSI sense strings and NVMe status strings])],
+ [], [AC_DEFINE_UNQUOTED(SG_SCSI_STRINGS, 1, [full SCSI sense strings and NVMe status strings], )])
+
+AC_ARG_ENABLE([nvme-supp],
+ AC_HELP_STRING([--disable-nvme-supp], [remove all or most NVMe code]),
+ [AC_DEFINE_UNQUOTED(IGNORE_NVME, 1, [compile out NVMe support], )], [])
+
+AC_ARG_ENABLE([fast-lebe],
+ AC_HELP_STRING([--disable-fast-lebe], [use generic little-endian/big-endian code instead]),
+ [AC_DEFINE_UNQUOTED(IGNORE_FAST_LEBE, 1, [use generic little-endian/big-endian instead], )], [])
+
+AC_ARG_ENABLE([linux-sgv4],
+ AC_HELP_STRING([--disable-linux-sgv4], [for Linux sg driver avoid v4 interface even if available]),
+ [AC_DEFINE_UNQUOTED(IGNORE_LINUX_SGV4, 1, [even if Linux sg v4 available, use v3 instead], )], [])
+
+
+AC_OUTPUT(
+ Makefile
+ include/Makefile
+ lib/Makefile
+ src/Makefile
+ doc/Makefile
+ scripts/Makefile
+)
+
+
+# Borrowed from smartmontools configure.ac
+# Note: Use `...` here as some shells do not properly parse '$(... case $x in X) ...)'
+info=`
+ echo "-----------------------------------------------------------------------------"
+ echo "${PACKAGE}-${VERSION} configuration:"
+ echo "host operating system: $host"
+ echo "default C compiler: $CC"
+
+ case "$host_os" in
+ mingw*)
+ echo "application manifest: ${os_win32_manifest:-built-in}"
+ echo "resource compiler: $WINDRES"
+ echo "message compiler: $WINDMC"
+ echo "NSIS compiler: $MAKENSIS"
+ ;;
+
+ *)
+ echo "binary install path: \`eval eval eval echo $bindir\`"
+ echo "scripts install path: \`eval eval eval echo $bindir\`"
+ echo "man page install path: \`eval eval eval echo $mandir\`"
+ ;;
+ esac
+ echo "-----------------------------------------------------------------------------"
+`
+
+AC_MSG_NOTICE([
+$info
+])
+
diff --git a/debian/README.debian4 b/debian/README.debian4
new file mode 100644
index 00000000..900b04e8
--- /dev/null
+++ b/debian/README.debian4
@@ -0,0 +1,14 @@
+For whatever reason Debian build scripts (e.g. debhelper and dbclean)
+seem to have changed in such a way to be Debian 4.0 ("etch") unfriendly.
+
+So when the ./build_debian.sh script is called on a Debian 4.0 system,
+it fails saying the debhelper is too old. That can be fixed by editing
+the 'control' file, changing this line:
+ Build-Depends: debhelper (>> 7), libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64]
+to:
+ Build-Depends: debhelper, libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64]
+
+The script then dies in dbclean and the hack to get around that is to
+edit the 'compat' file. It contains "7" which needs to be changed to
+"4". Evidently "4" is deprecated and "5" is preferable and should work.
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..9ebae1eb
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,278 @@
+sg3-utils (1.48-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Sat, 12 Nov 2022 21:00:00 -0500
+
+sg3-utils (1.47-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Tue, 09 Nov 2021 12:00:00 -0500
+
+sg3-utils (1.46-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Mon, 29 Mar 2021 01:00:00 -0400
+
+sg3-utils (1.45-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Sat, 29 Feb 2020 20:00:00 -0500
+
+sg3-utils (1.44-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 12 Sep 2018 14:00:00 -0400
+
+sg3-utils (1.43-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Tue, 11 Sep 2018 09:00:00 -0400
+
+sg3-utils (1.42-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 17 Feb 2016 15:00:00 -0500
+
+sg3-utils (1.41-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Tue, 28 Apr 2015 11:00:00 -0400
+
+sg3-utils (1.40-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Mon, 10 Nov 2014 23:00:00 -0500
+
+sg3-utils (1.39-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Thu, 12 Jun 2014 09:00:00 -0400
+
+sg3-utils (1.38-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Tue, 01 Apr 2014 15:00:00 -0400
+
+sg3-utils (1.37-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Mon, 14 Oct 2013 15:00:00 -0400
+
+sg3-utils (1.36-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Fri, 31 May 2013 10:00:00 -0400
+
+sg3-utils (1.35-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Thu, 17 Jan 2013 19:00:00 -0500
+
+sg3-utils (1.34-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Sat, 13 Oct 2012 19:00:00 -0400
+
+sg3-utils (1.33-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 18 Jan 2012 14:00:00 -0500
+
+sg3-utils (1.32-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 22 Jun 2011 16:00:00 -0400
+
+sg3-utils (1.31-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 16 Feb 2011 16:00:00 -0500
+
+sg3-utils (1.30-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Fri, 05 Nov 2010 10:30:00 -0400
+
+sg3-utils (1.29-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Wed, 31 Mar 2010 23:00:00 -0400
+
+sg3-utils (1.28-0.1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Douglas Gilbert <dgilbert@interlog.com> Fri, 02 Oct 2009 00:20:00 -0400
+
+sg3-utils (1.27-0.1) unstable; urgency=low
+
+ [ Martin Pitt ]
+ * Non-maintainer upload; this package blocks DeviceKit, and maintainer is
+ apparently not active any more. Also clean up and modernize the package
+ somewhat while we are at it.
+ * New upstream release (required for current devicekit-disks).
+ (Closes: #532546). Upstream original tarball repacked to not contain
+ debian/ directory.
+ * Rename libsgutils1{,-dev} to libsgutils2-2{,-dev}, upstream bumped SONAME.
+ Also call the library libgsutils2-2 to match SONAME. Add
+ Conflicts/Replaces for "libsgutils2" to provide a clean upgrade from the
+ packages as provided by Upstream and Ubuntu.
+ * debian/rules: Update build rules for upstream Makefile → autotools switch.
+ * debian/rules: Fix cleaning a clean source package.
+ * Demote Recommends to Suggests; the library doesn't actually call the
+ binaries in sg3-utils. (Closes: #532547)
+ * Drop debian/*.dirs, unnecessary with dh_install.
+ * Drop sg3-utils.preinst, not necessary to deal with kernel 2.4 any more.
+ * Drop libsgutils2-0.install, libsgutils2-0-dev.install, these packages
+ don't exist.
+ * Drop libsgutils2.post{inst,rm}: Basically empty, debhelper will create its
+ own.
+ * libsgutils2-dev.install: Drop *.lo.
+ * debian/compat: 4 -> 7. Bump debhelper build-depends accordingly.
+ * debian/control: Bump Standards-Version to 3.8.2.
+ * debian/control: Modernize package description for Linux 2.6.
+ (Closes: #506578)
+ * debian/rules: Drop -k argument from dh_clean (thanks lintian).
+
+ [ Frank Lichtenheld ]
+ * debian/control, debian/rules: Add dependency of libsgutils-dev on
+ libcam-dev on kfreebsd-*. (Closes: #519460)
+
+ -- Martin Pitt <mpitt@debian.org> Mon, 22 Jun 2009 12:04:20 +0200
+
+sg3-utils (1.24-2) unstable; urgency=low
+
+ * Cleaned up package description (Closes: #445920).
+ * Don't make libtool think rpath is necessary (Closes: #451153)
+ * Capitalized Linux in extended description (Closes: #457526).
+ * Completed sentence in libsgutils1 long description (Closes: #421391).
+ * Added patch from Aurelian Jarno to build on kfreebsd-* (Closes: #455430).
+ * Symlinks in examples directory cleaned up (Closes: #372610).
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Sun, 30 Dec 2007 11:52:58 -0700
+
+sg3-utils (1.24-1) unstable; urgency=low
+
+ * New upstream release
+ * Conflicts with upstream libsgutils package libsgutils-1-0 (closes: #391077)
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Tue, 5 Jun 2007 17:04:21 -0600
+
+sg3-utils (1.21-2.1) unstable; urgency=medium
+
+ * Non-maintainer upload.
+ * Fix FTBFS due to old syscall usage (Closes: #395512).
+
+ -- Luk Claes <luk@debian.org> Sun, 5 Nov 2006 17:23:29 +0100
+
+sg3-utils (1.21-2) unstable; urgency=low
+
+ * Added Depends on libsgutils1 to libsgutils1-dev (closes: #387798)
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Fri, 22 Sep 2006 00:20:28 -0600
+
+sg3-utils (1.21-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Wed, 13 Sep 2006 21:54:30 -0600
+
+sg3-utils (1.20-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Wed, 26 Apr 2006 22:31:15 -0600
+
+sg3-utils (1.17-3) unstable; urgency=low
+
+ * Cleaned up sg_read(8) manpage (Closes: #294521)
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Mon, 13 Feb 2006 17:59:46 -0700
+
+sg3-utils (1.17-2) unstable; urgency=low
+
+ * Add libtool to build-depends
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Tue, 4 Oct 2005 19:40:00 -0600
+
+sg3-utils (1.17-1) unstable; urgency=low
+
+ * New upstream version
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Sat, 1 Oct 2005 13:26:16 -0600
+
+sg3-utils (1.08-2) unstable; urgency=low
+
+ * Fix packaging bug that accidentally left off binaries. Sigh.
+ (closes: #271906)
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Wed, 15 Sep 2004 22:40:06 -0600
+
+sg3-utils (1.08-1) unstable; urgency=low
+
+ * New upstream version
+ * Unified package description with list of tools actually installed
+ (closes: #271093)
+
+ -- Eric Schwartz (Skif) <emschwar@debian.org> Sun, 12 Sep 2004 21:22:42 -0600
+
+sg3-utils (1.05-1) unstable; urgency=low
+
+ * New upstream release
+ * updated description to match tools in package (closes: #221143)
+
+ -- Eric Schwartz <emschwar@debian.org> Tue, 18 Nov 2003 22:22:29 -0700
+
+sg3-utils (1.03-1) unstable; urgency=low
+
+ * New upstream release (closes: #181999)
+
+ -- Eric Schwartz <emschwar@debian.org> Tue, 29 Apr 2003 20:18:30 -0600
+
+sg3-utils (0.95-4) unstable; urgency=low
+
+ * Only warns if installed on a kernel version < 2.4 (closes: #136434)
+
+ -- Eric Schwartz <emschwar@debian.org> Tue, 28 May 2002 22:55:29 -0600
+
+sg3-utils (0.95-3) unstable; urgency=low
+
+ * Extended description to include descriptions of all tools included in
+ the package. (closes: #121968)
+
+ -- Eric Schwartz <emschwar@debian.org> Sun, 13 Jan 2002 17:09:27 -0700
+
+sg3-utils (0.95-2) unstable; urgency=low
+
+ * Packaging manpages (closes: #122692)
+ * Conflicts with cdwrite (closes: #123779)
+
+ -- Eric Schwartz <emschwar@debian.org> Wed, 2 Jan 2002 01:05:08 -0700
+
+sg3-utils (0.95-1) unstable; urgency=low
+
+ * Initial Release.
+ * Adjusted Makefile to include $DESTDIR
+
+ -- Eric Schwartz <emschwar@debian.org> Wed, 14 Nov 2001 17:05:56 -0700
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..f599e28b
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..83646432
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,43 @@
+Source: sg3-utils
+Section: admin
+Priority: optional
+Maintainer: Eric Schwartz (Skif) <emschwar@debian.org>
+Build-Depends: debhelper (>> 7), libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64]
+Standards-Version: 3.8.2
+
+Package: sg3-utils
+Architecture: any
+Depends: ${shlibs:Depends}
+Conflicts: sg-utils, cdwrite
+Replaces: sg-utils
+Description: utilities for devices using the SCSI command set.
+ Most OSes have SCSI pass-through interfaces that enable user space programs
+ to send SCSI commands to a device and fetch the response. With SCSI to ATA
+ Translation (SAT) many ATA disks now can process SCSI commands. Typically
+ each utility in this package implements one SCSI command. See the draft
+ standards at www.t10.org for SCSI command definitions plus SAT. ATA
+ commands are defined in the draft standards at www.t13.org . For a mapping
+ between supported SCSI and ATA commands and utility names in this package
+ see the COVERAGE file. Also some support for NVMe devices, especially via
+ sg_ses to NVMe enclsoures.
+
+Package: libsgutils2-2
+Section: libs
+Depends: ${shlibs:Depends}
+Architecture: any
+Conflicts: libsgutils2
+Replaces: libsgutils2
+Suggests: sg3-utils
+Description: utilities for devices using the SCSI command set (shared libraries)
+ Shared library used by the utilities in the sg3-utils package.
+
+Package: libsgutils2-dev
+Section: libdevel
+Architecture: any
+Depends: libsgutils2-2 (= ${binary:Version}), ${shlibs:Depends}, ${kfreebsd:Depends}
+Conflicts: libsgutils1-dev
+Suggests: sg3-utils
+Description: utilities for devices using the SCSI command set (developer files)
+ Developer files (i.e. headers and a static library) which are associated with
+ the utilities in the sg3-utils package.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..7f1906bb
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,24 @@
+
+Copyright (c) 1999-2018, Douglas Gilbert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 00000000..67fe85ab
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,7 @@
+README
+README.sg_start
+AUTHORS
+COVERAGE
+CREDITS
+INSTALL
+ChangeLog
diff --git a/debian/libsgutils2-2.install b/debian/libsgutils2-2.install
new file mode 100644
index 00000000..093956b1
--- /dev/null
+++ b/debian/libsgutils2-2.install
@@ -0,0 +1 @@
+usr/lib/*.so.*
diff --git a/debian/libsgutils2-dev.install b/debian/libsgutils2-dev.install
new file mode 100644
index 00000000..5d09f30f
--- /dev/null
+++ b/debian/libsgutils2-dev.install
@@ -0,0 +1,4 @@
+usr/include/scsi
+usr/lib/*.so
+usr/lib/*.la
+usr/lib/*.a
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..d3a2b0ce
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,83 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 by Joey Hess.
+#
+# This version is for a hypothetical package that builds an
+# architecture-dependant package, as well as an architecture-independent
+# package.
+
+# Uncomment this to turn on verbose mode.
+# export DH_VERBOSE=1
+
+DEB_HOST_ARCH_OS := $(shell dpkg-architecture -qDEB_HOST_ARCH_OS 2>/dev/null)
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+ CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) --bindir=/usr/bin --prefix=/usr --mandir=\$${prefix}/share/man
+ touch configure-stamp
+
+build: configure-stamp build-stamp
+build-stamp:
+ dh_testdir
+
+ # Add here commands to compile the package.
+ PREFIX=/usr MANDIR=/usr/share/man $(MAKE) -e
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) distclean
+
+ rm -f build-stamp configure-stamp debian/substvars
+
+ dh_clean
+
+install: DH_OPTIONS=
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean
+ dh_installdirs
+
+ # Add here commands to install the package into debian/tmp
+ $(MAKE) -e install DESTDIR=$(CURDIR)/debian/tmp PREFIX=/usr
+
+ dh_install --autodest --sourcedir=debian/tmp
+
+ dh_installman
+
+# Build architecture-independent files here.
+# Pass -i to all debhelper commands in this target to reduce clutter.
+binary-indep: build install
+# nothing to do here
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir -a
+ dh_testroot -a
+ dh_installdocs -a
+ dh_installexamples -a
+ dh_installmenu -a
+ dh_installchangelogs ChangeLog -a
+ dh_strip -a
+ dh_link -a
+ dh_compress -a -X archive -X .c -X .h
+ dh_fixperms -a
+ dh_makeshlibs -V -v
+ dh_installdeb -a
+ifeq ($(DEB_HOST_ARCH_OS),kfreebsd)
+ echo kfreebsd:Depends=libcam-dev >>debian/libsgutils2-dev.substvars
+endif
+ dh_shlibdeps -ldebian/tmp/usr/lib -L libsgutils2
+ dh_gencontrol -a
+ dh_md5sums -a
+ dh_builddeb -a
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/debian/sg3-utils.examples b/debian/sg3-utils.examples
new file mode 100644
index 00000000..e39721e2
--- /dev/null
+++ b/debian/sg3-utils.examples
@@ -0,0 +1 @@
+examples/*
diff --git a/debian/sg3-utils.install b/debian/sg3-utils.install
new file mode 100644
index 00000000..6161376a
--- /dev/null
+++ b/debian/sg3-utils.install
@@ -0,0 +1,2 @@
+usr/bin/*
+usr/share/man/man8/*
diff --git a/depcomp b/depcomp
new file mode 100755
index 00000000..715e3431
--- /dev/null
+++ b/depcomp
@@ -0,0 +1,791 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2018-03-07.03; # UTC
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+
+# 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 distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+case $1 in
+ '')
+ echo "$0: No command. Try '$0 --help' for more information." 1>&2
+ exit 1;
+ ;;
+ -h | --h*)
+ cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+ depmode Dependency tracking mode.
+ source Source file read by 'PROGRAMS ARGS'.
+ object Object file output by 'PROGRAMS ARGS'.
+ DEPDIR directory where to store dependencies.
+ depfile Dependency file to output.
+ tmpdepfile Temporary file to use when outputting dependencies.
+ libtool Whether libtool is used (yes/no).
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+ exit $?
+ ;;
+ -v | --v*)
+ echo "depcomp $scriptversion"
+ exit $?
+ ;;
+esac
+
+# Get the directory component of the given path, and save it in the
+# global variables '$dir'. Note that this directory component will
+# be either empty or ending with a '/' character. This is deliberate.
+set_dir_from ()
+{
+ case $1 in
+ */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;;
+ *) dir=;;
+ esac
+}
+
+# Get the suffix-stripped basename of the given path, and save it the
+# global variable '$base'.
+set_base_from ()
+{
+ base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'`
+}
+
+# If no dependency file was actually created by the compiler invocation,
+# we still have to create a dummy depfile, to avoid errors with the
+# Makefile "include basename.Plo" scheme.
+make_dummy_depfile ()
+{
+ echo "#dummy" > "$depfile"
+}
+
+# Factor out some common post-processing of the generated depfile.
+# Requires the auxiliary global variable '$tmpdepfile' to be set.
+aix_post_process_depfile ()
+{
+ # If the compiler actually managed to produce a dependency file,
+ # post-process it.
+ if test -f "$tmpdepfile"; then
+ # Each line is of the form 'foo.o: dependency.h'.
+ # Do two passes, one to just change these to
+ # $object: dependency.h
+ # and one to simply output
+ # dependency.h:
+ # which is needed to avoid the deleted-header problem.
+ { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile"
+ sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile"
+ } > "$depfile"
+ rm -f "$tmpdepfile"
+ else
+ make_dummy_depfile
+ fi
+}
+
+# A tabulation character.
+tab=' '
+# A newline character.
+nl='
+'
+# Character ranges might be problematic outside the C locale.
+# These definitions help.
+upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ
+lower=abcdefghijklmnopqrstuvwxyz
+digits=0123456789
+alpha=${upper}${lower}
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+ echo "depcomp: Variables source, object and depmode must be set" 1>&2
+ exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+ sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Avoid interferences from the environment.
+gccflag= dashmflag=
+
+# Some modes work just like other modes, but use different flags. We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write. Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+ # HP compiler uses -M and no extra arg.
+ gccflag=-M
+ depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+ # This is just like dashmstdout with a different argument.
+ dashmflag=-xM
+ depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+ # This is just like msvisualcpp but w/o cygpath translation.
+ # Just convert the backslash-escaped backslashes to single forward
+ # slashes to satisfy depend.m4
+ cygpath_u='sed s,\\\\,/,g'
+ depmode=msvisualcpp
+fi
+
+if test "$depmode" = msvc7msys; then
+ # This is just like msvc7 but w/o cygpath translation.
+ # Just convert the backslash-escaped backslashes to single forward
+ # slashes to satisfy depend.m4
+ cygpath_u='sed s,\\\\,/,g'
+ depmode=msvc7
+fi
+
+if test "$depmode" = xlc; then
+ # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information.
+ gccflag=-qmakedep=gcc,-MF
+ depmode=gcc
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want. Yay! Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff. Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am. Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+ for arg
+ do
+ case $arg in
+ -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+ *) set fnord "$@" "$arg" ;;
+ esac
+ shift # fnord
+ shift # $arg
+ done
+ "$@"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ mv "$tmpdepfile" "$depfile"
+ ;;
+
+gcc)
+## Note that this doesn't just cater to obsosete pre-3.x GCC compilers.
+## but also to in-use compilers like IMB xlc/xlC and the HP C compiler.
+## (see the conditional assignment to $gccflag above).
+## There are various ways to get dependency output from gcc. Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+## up in a subdir. Having to rename by hand is ugly.
+## (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+## -MM, not -M (despite what the docs say). Also, it might not be
+## supported by the other compilers which use the 'gcc' depmode.
+## - Using -M directly means running the compiler twice (even worse
+## than renaming).
+ if test -z "$gccflag"; then
+ gccflag=-MD,
+ fi
+ "$@" -Wp,"$gccflag$tmpdepfile"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ # The second -e expression handles DOS-style file names with drive
+ # letters.
+ sed -e 's/^[^:]*: / /' \
+ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the "deleted header file" problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header). We avoid this by adding
+## dummy dependencies for each header file. Too bad gcc doesn't do
+## this for us directly.
+## Some versions of gcc put a space before the ':'. On the theory
+## that the space means something, we add a space to the output as
+## well. hp depmode also adds that space, but also prefixes the VPATH
+## to the object. Take care to not repeat it in the output.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly. Breaking it into two sed invocations is a workaround.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+hp)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+sgi)
+ if test "$libtool" = yes; then
+ "$@" "-Wp,-MDupdate,$tmpdepfile"
+ else
+ "$@" -MDupdate "$tmpdepfile"
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+
+ if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files
+ echo "$object : \\" > "$depfile"
+ # Clip off the initial element (the dependent). Don't try to be
+ # clever and replace this with sed code, as IRIX sed won't handle
+ # lines with more than a fixed number of characters (4096 in
+ # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines;
+ # the IRIX cc adds comments like '#:fec' to the end of the
+ # dependency line.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \
+ | tr "$nl" ' ' >> "$depfile"
+ echo >> "$depfile"
+ # The second pass generates a dummy entry for each header file.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+ >> "$depfile"
+ else
+ make_dummy_depfile
+ fi
+ rm -f "$tmpdepfile"
+ ;;
+
+xlc)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+aix)
+ # The C for AIX Compiler uses -M and outputs the dependencies
+ # in a .u file. In older versions, this file always lives in the
+ # current directory. Also, the AIX compiler puts '$object:' at the
+ # start of each line; $object doesn't have directory information.
+ # Version 6 uses the directory in both cases.
+ set_dir_from "$object"
+ set_base_from "$object"
+ if test "$libtool" = yes; then
+ tmpdepfile1=$dir$base.u
+ tmpdepfile2=$base.u
+ tmpdepfile3=$dir.libs/$base.u
+ "$@" -Wc,-M
+ else
+ tmpdepfile1=$dir$base.u
+ tmpdepfile2=$dir$base.u
+ tmpdepfile3=$dir$base.u
+ "$@" -M
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ aix_post_process_depfile
+ ;;
+
+tcc)
+ # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26
+ # FIXME: That version still under development at the moment of writing.
+ # Make that this statement remains true also for stable, released
+ # versions.
+ # It will wrap lines (doesn't matter whether long or short) with a
+ # trailing '\', as in:
+ #
+ # foo.o : \
+ # foo.c \
+ # foo.h \
+ #
+ # It will put a trailing '\' even on the last line, and will use leading
+ # spaces rather than leading tabs (at least since its commit 0394caf7
+ # "Emit spaces for -MD").
+ "$@" -MD -MF "$tmpdepfile"
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'.
+ # We have to change lines of the first kind to '$object: \'.
+ sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile"
+ # And for each line of the second kind, we have to emit a 'dep.h:'
+ # dummy dependency, to avoid the deleted-header problem.
+ sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+## The order of this option in the case statement is important, since the
+## shell code in configure will try each of these formats in the order
+## listed in this file. A plain '-MD' option would be understood by many
+## compilers, so we must ensure this comes after the gcc and icc options.
+pgcc)
+ # Portland's C compiler understands '-MD'.
+ # Will always output deps to 'file.d' where file is the root name of the
+ # source file under compilation, even if file resides in a subdirectory.
+ # The object file name does not affect the name of the '.d' file.
+ # pgcc 10.2 will output
+ # foo.o: sub/foo.c sub/foo.h
+ # and will wrap long lines using '\' :
+ # foo.o: sub/foo.c ... \
+ # sub/foo.h ... \
+ # ...
+ set_dir_from "$object"
+ # Use the source, not the object, to determine the base name, since
+ # that's sadly what pgcc will do too.
+ set_base_from "$source"
+ tmpdepfile=$base.d
+
+ # For projects that build the same source file twice into different object
+ # files, the pgcc approach of using the *source* file root name can cause
+ # problems in parallel builds. Use a locking strategy to avoid stomping on
+ # the same $tmpdepfile.
+ lockdir=$base.d-lock
+ trap "
+ echo '$0: caught signal, cleaning up...' >&2
+ rmdir '$lockdir'
+ exit 1
+ " 1 2 13 15
+ numtries=100
+ i=$numtries
+ while test $i -gt 0; do
+ # mkdir is a portable test-and-set.
+ if mkdir "$lockdir" 2>/dev/null; then
+ # This process acquired the lock.
+ "$@" -MD
+ stat=$?
+ # Release the lock.
+ rmdir "$lockdir"
+ break
+ else
+ # If the lock is being held by a different process, wait
+ # until the winning process is done or we timeout.
+ while test -d "$lockdir" && test $i -gt 0; do
+ sleep 1
+ i=`expr $i - 1`
+ done
+ fi
+ i=`expr $i - 1`
+ done
+ trap - 1 2 13 15
+ if test $i -le 0; then
+ echo "$0: failed to acquire lock after $numtries attempts" >&2
+ echo "$0: check lockdir '$lockdir'" >&2
+ exit 1
+ fi
+
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ # Each line is of the form `foo.o: dependent.h',
+ # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+ # Do two passes, one to just change these to
+ # `$object: dependent.h' and one to simply `dependent.h:'.
+ sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process this invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+hp2)
+ # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+ # compilers, which have integrated preprocessors. The correct option
+ # to use with these is +Maked; it writes dependencies to a file named
+ # 'foo.d', which lands next to the object file, wherever that
+ # happens to be.
+ # Much of this is similar to the tru64 case; see comments there.
+ set_dir_from "$object"
+ set_base_from "$object"
+ if test "$libtool" = yes; then
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir.libs/$base.d
+ "$@" -Wc,+Maked
+ else
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir$base.d
+ "$@" +Maked
+ fi
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ if test -f "$tmpdepfile"; then
+ sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile"
+ # Add 'dependent.h:' lines.
+ sed -ne '2,${
+ s/^ *//
+ s/ \\*$//
+ s/$/:/
+ p
+ }' "$tmpdepfile" >> "$depfile"
+ else
+ make_dummy_depfile
+ fi
+ rm -f "$tmpdepfile" "$tmpdepfile2"
+ ;;
+
+tru64)
+ # The Tru64 compiler uses -MD to generate dependencies as a side
+ # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'.
+ # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+ # dependencies in 'foo.d' instead, so we check for that too.
+ # Subdirectories are respected.
+ set_dir_from "$object"
+ set_base_from "$object"
+
+ if test "$libtool" = yes; then
+ # Libtool generates 2 separate objects for the 2 libraries. These
+ # two compilations output dependencies in $dir.libs/$base.o.d and
+ # in $dir$base.o.d. We have to check for both files, because
+ # one of the two compilations can be disabled. We should prefer
+ # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+ # automatically cleaned when .libs/ is deleted, while ignoring
+ # the former would cause a distcleancheck panic.
+ tmpdepfile1=$dir$base.o.d # libtool 1.5
+ tmpdepfile2=$dir.libs/$base.o.d # Likewise.
+ tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504
+ "$@" -Wc,-MD
+ else
+ tmpdepfile1=$dir$base.d
+ tmpdepfile2=$dir$base.d
+ tmpdepfile3=$dir$base.d
+ "$@" -MD
+ fi
+
+ stat=$?
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ exit $stat
+ fi
+
+ for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+ do
+ test -f "$tmpdepfile" && break
+ done
+ # Same post-processing that is required for AIX mode.
+ aix_post_process_depfile
+ ;;
+
+msvc7)
+ if test "$libtool" = yes; then
+ showIncludes=-Wc,-showIncludes
+ else
+ showIncludes=-showIncludes
+ fi
+ "$@" $showIncludes > "$tmpdepfile"
+ stat=$?
+ grep -v '^Note: including file: ' "$tmpdepfile"
+ if test $stat -ne 0; then
+ rm -f "$tmpdepfile"
+ exit $stat
+ fi
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ # The first sed program below extracts the file names and escapes
+ # backslashes for cygpath. The second sed program outputs the file
+ # name when reading, but also accumulates all include files in the
+ # hold buffer in order to output them again at the end. This only
+ # works with sed implementations that can handle large buffers.
+ sed < "$tmpdepfile" -n '
+/^Note: including file: *\(.*\)/ {
+ s//\1/
+ s/\\/\\\\/g
+ p
+}' | $cygpath_u | sort -u | sed -n '
+s/ /\\ /g
+s/\(.*\)/'"$tab"'\1 \\/p
+s/.\(.*\) \\/\1:/
+H
+$ {
+ s/.*/'"$tab"'/
+ G
+ p
+}' >> "$depfile"
+ echo >> "$depfile" # make sure the fragment doesn't end with a backslash
+ rm -f "$tmpdepfile"
+ ;;
+
+msvc7msys)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+#nosideeffect)
+ # This comment above is used by automake to tell side-effect
+ # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout, regardless of -o.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ # Remove '-o $object'.
+ IFS=" "
+ for arg
+ do
+ case $arg in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift # fnord
+ shift # $arg
+ ;;
+ esac
+ done
+
+ test -z "$dashmflag" && dashmflag=-M
+ # Require at least two characters before searching for ':'
+ # in the target name. This is to cope with DOS-style filenames:
+ # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise.
+ "$@" $dashmflag |
+ sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile"
+ rm -f "$depfile"
+ cat < "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process this sed invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ tr ' ' "$nl" < "$tmpdepfile" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+dashXmstdout)
+ # This case only exists to satisfy depend.m4. It is never actually
+ # run, as this mode is specially recognized in the preamble.
+ exit 1
+ ;;
+
+makedepend)
+ "$@" || exit $?
+ # Remove any Libtool call
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+ # X makedepend
+ shift
+ cleared=no eat=no
+ for arg
+ do
+ case $cleared in
+ no)
+ set ""; shift
+ cleared=yes ;;
+ esac
+ if test $eat = yes; then
+ eat=no
+ continue
+ fi
+ case "$arg" in
+ -D*|-I*)
+ set fnord "$@" "$arg"; shift ;;
+ # Strip any option that makedepend may not understand. Remove
+ # the object too, otherwise makedepend will parse it as a source file.
+ -arch)
+ eat=yes ;;
+ -*|$object)
+ ;;
+ *)
+ set fnord "$@" "$arg"; shift ;;
+ esac
+ done
+ obj_suffix=`echo "$object" | sed 's/^.*\././'`
+ touch "$tmpdepfile"
+ ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+ rm -f "$depfile"
+ # makedepend may prepend the VPATH from the source file name to the object.
+ # No need to regex-escape $object, excess matching of '.' is harmless.
+ sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
+ # Some versions of the HPUX 10.20 sed can't process the last invocation
+ # correctly. Breaking it into two sed invocations is a workaround.
+ sed '1,2d' "$tmpdepfile" \
+ | tr ' ' "$nl" \
+ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+ | sed -e 's/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile" "$tmpdepfile".bak
+ ;;
+
+cpp)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ # Remove '-o $object'.
+ IFS=" "
+ for arg
+ do
+ case $arg in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift # fnord
+ shift # $arg
+ ;;
+ esac
+ done
+
+ "$@" -E \
+ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+ | sed '$ s: \\$::' > "$tmpdepfile"
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ cat < "$tmpdepfile" >> "$depfile"
+ sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+msvisualcpp)
+ # Important note: in order to support this mode, a compiler *must*
+ # always write the preprocessed file to stdout.
+ "$@" || exit $?
+
+ # Remove the call to Libtool.
+ if test "$libtool" = yes; then
+ while test "X$1" != 'X--mode=compile'; do
+ shift
+ done
+ shift
+ fi
+
+ IFS=" "
+ for arg
+ do
+ case "$arg" in
+ -o)
+ shift
+ ;;
+ $object)
+ shift
+ ;;
+ "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+ set fnord "$@"
+ shift
+ shift
+ ;;
+ *)
+ set fnord "$@" "$arg"
+ shift
+ shift
+ ;;
+ esac
+ done
+ "$@" -E 2>/dev/null |
+ sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+ rm -f "$depfile"
+ echo "$object : \\" > "$depfile"
+ sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile"
+ echo "$tab" >> "$depfile"
+ sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+ rm -f "$tmpdepfile"
+ ;;
+
+msvcmsys)
+ # This case exists only to let depend.m4 do its work. It works by
+ # looking at the text of this script. This case will never be run,
+ # since it is checked for above.
+ exit 1
+ ;;
+
+none)
+ exec "$@"
+ ;;
+
+*)
+ echo "Unknown depmode $depmode" 1>&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 00000000..2d5c52ea
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,47 @@
+
+dist_man_MANS = \
+ scsi_mandat.8 scsi_readcap.8 scsi_ready.8 scsi_satl.8 scsi_start.8 \
+ scsi_stop.8 scsi_temperature.8 sg3_utils.8 sg3_utils_json.8 \
+ sg_bg_ctl.8 sg_compare_and_write.8 sg_decode_sense.8 sg_format.8 \
+ sg_get_config.8 sg_get_elem_status.8 sg_get_lba_status.8 sg_ident.8 \
+ sg_inq.8 sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 sg_persist.8 \
+ sg_prevent.8 sg_raw.8 sg_rdac.8 sg_read_attr.8 \
+ sg_read_block_limits.8 sg_read_buffer.8 sg_read_long.8 sg_readcap.8 \
+ sg_reassign.8 sg_referrals.8 sg_rem_rest_elem.8 sg_rep_density.8 \
+ sg_rep_pip.8 sg_rep_zones.8 sg_requests.8 sg_reset_wp.8 sg_rmsn.8 \
+ sg_rtpg.8 sg_safte.8 sg_sanitize.8 sg_sat_identify.8 \
+ sg_sat_phy_event.8 sg_sat_read_gplog.8 sg_sat_set_features.8 \
+ sg_seek.8 sg_senddiag.8 sg_ses.8 sg_ses_microcode.8 sg_start.8 \
+ sg_stpg.8 sg_stream_ctl.8 sg_sync.8 sg_timestamp.8 sg_turs.8 \
+ sg_unmap.8 sg_verify.8 sg_vpd.8 sg_wr_mode.8 sg_write_buffer.8 \
+ sg_write_long.8 sg_write_same.8 sg_write_verify.8 sg_write_x.8 \
+ sg_zone.8 sg_z_act_query.8
+CLEANFILES =
+
+if OS_LINUX
+dist_man_MANS += \
+ rescan-scsi-bus.sh.8 scsi_logging_level.8 sg_copy_results.8 sg_dd.8 \
+ sg_emc_trespass.8 sg_map.8 sg_map26.8 sg_rbuf.8 sg_read.8 sg_reset.8 \
+ sg_scan.8 sg_test_rwbuf.8 sg_xcopy.8 sginfo.8 sgm_dd.8 sgp_dd.8
+CLEANFILES += sg_scan.8
+sg_scan.8: sg_scan.8.linux
+ cp -p $< $@
+endif
+
+if OS_WIN32_MINGW
+dist_man_MANS += sg_scan.8
+CLEANFILES += sg_scan.8
+sg_scan.8: sg_scan.8.win32
+ cp -p $< $@
+endif
+
+if OS_WIN32_CYGWIN
+dist_man_MANS += sg_scan.8
+CLEANFILES += sg_scan.8
+sg_scan.8: sg_scan.8.win32
+ cp -p $< $@
+endif
+
+EXTRA_DIST = \
+ sg_scan.8.linux \
+ sg_scan.8.win32
diff --git a/doc/Makefile.in b/doc/Makefile.in
new file mode 100644
index 00000000..a75d0fdc
--- /dev/null
+++ b/doc/Makefile.in
@@ -0,0 +1,565 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@OS_LINUX_TRUE@am__append_1 = \
+@OS_LINUX_TRUE@ rescan-scsi-bus.sh.8 scsi_logging_level.8 sg_copy_results.8 sg_dd.8 \
+@OS_LINUX_TRUE@ sg_emc_trespass.8 sg_map.8 sg_map26.8 sg_rbuf.8 sg_read.8 sg_reset.8 \
+@OS_LINUX_TRUE@ sg_scan.8 sg_test_rwbuf.8 sg_xcopy.8 sginfo.8 sgm_dd.8 sgp_dd.8
+
+@OS_LINUX_TRUE@am__append_2 = sg_scan.8
+@OS_WIN32_MINGW_TRUE@am__append_3 = sg_scan.8
+@OS_WIN32_MINGW_TRUE@am__append_4 = sg_scan.8
+@OS_WIN32_CYGWIN_TRUE@am__append_5 = sg_scan.8
+@OS_WIN32_CYGWIN_TRUE@am__append_6 = sg_scan.8
+subdir = doc
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man8dir = $(mandir)/man8
+am__installdirs = "$(DESTDIR)$(man8dir)"
+NROFF = nroff
+MANS = $(dist_man_MANS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(dist_man_MANS) $(srcdir)/Makefile.in README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+dist_man_MANS = scsi_mandat.8 scsi_readcap.8 scsi_ready.8 scsi_satl.8 \
+ scsi_start.8 scsi_stop.8 scsi_temperature.8 sg3_utils.8 \
+ sg3_utils_json.8 sg_bg_ctl.8 sg_compare_and_write.8 \
+ sg_decode_sense.8 sg_format.8 sg_get_config.8 \
+ sg_get_elem_status.8 sg_get_lba_status.8 sg_ident.8 sg_inq.8 \
+ sg_logs.8 sg_luns.8 sg_modes.8 sg_opcodes.8 sg_persist.8 \
+ sg_prevent.8 sg_raw.8 sg_rdac.8 sg_read_attr.8 \
+ sg_read_block_limits.8 sg_read_buffer.8 sg_read_long.8 \
+ sg_readcap.8 sg_reassign.8 sg_referrals.8 sg_rem_rest_elem.8 \
+ sg_rep_density.8 sg_rep_pip.8 sg_rep_zones.8 sg_requests.8 \
+ sg_reset_wp.8 sg_rmsn.8 sg_rtpg.8 sg_safte.8 sg_sanitize.8 \
+ sg_sat_identify.8 sg_sat_phy_event.8 sg_sat_read_gplog.8 \
+ sg_sat_set_features.8 sg_seek.8 sg_senddiag.8 sg_ses.8 \
+ sg_ses_microcode.8 sg_start.8 sg_stpg.8 sg_stream_ctl.8 \
+ sg_sync.8 sg_timestamp.8 sg_turs.8 sg_unmap.8 sg_verify.8 \
+ sg_vpd.8 sg_wr_mode.8 sg_write_buffer.8 sg_write_long.8 \
+ sg_write_same.8 sg_write_verify.8 sg_write_x.8 sg_zone.8 \
+ sg_z_act_query.8 $(am__append_1) $(am__append_3) \
+ $(am__append_5)
+CLEANFILES = $(am__append_2) $(am__append_4) $(am__append_6)
+EXTRA_DIST = \
+ sg_scan.8.linux \
+ sg_scan.8.win32
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign doc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign doc/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-man8: $(dist_man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(dist_man_MANS)'; \
+ test -n "$(man8dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.8[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man8:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man8dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(dist_man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.8[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(MANS)
+installdirs:
+ for dir in "$(DESTDIR)$(man8dir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man8
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-man
+
+uninstall-man: uninstall-man8
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-man8 install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags-am uninstall uninstall-am uninstall-man \
+ uninstall-man8
+
+.PRECIOUS: Makefile
+
+@OS_LINUX_TRUE@sg_scan.8: sg_scan.8.linux
+@OS_LINUX_TRUE@ cp -p $< $@
+@OS_WIN32_MINGW_TRUE@sg_scan.8: sg_scan.8.win32
+@OS_WIN32_MINGW_TRUE@ cp -p $< $@
+@OS_WIN32_CYGWIN_TRUE@sg_scan.8: sg_scan.8.win32
+@OS_WIN32_CYGWIN_TRUE@ cp -p $< $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/doc/README b/doc/README
new file mode 100644
index 00000000..6664a32b
--- /dev/null
+++ b/doc/README
@@ -0,0 +1,36 @@
+Various html files used to be included in this directory but they were
+beginning to bloat the size of the source tarball so they have been
+removed in version 1.24 .
+
+Here are the urls of the files that were previously here plus a brief
+summary:
+
+https://sg.danny.cz/sg/sg3_utils.html
+ - overview and examples of this package
+
+https://sg.danny.cz/sg/sg_dd.html
+ - discussion and examples of the sg_dd utility which is a dd variant
+ (also discusses the sgm_dd, sgp_dd and sg_read utilities)
+
+https://sg.danny.cz/sg/sg_ses.html
+ - discussion and examples of the sg_ses utility. SCSI Enclosure
+ Services (SES) devices may contain a lot of information,
+ structured in a non-trivial way.
+
+https://sg.danny.cz/sg/sg_io.html
+ - discussion of Linux SG_IO ioctl (SCSI pass-through)
+
+https://sg.danny.cz/sg/tools.html
+ - overview of around 25 storage and SCSI tools
+
+Many of those pages are also found at: https://doug-gilbert.github.io/
+
+
+There are two versions of sg_scan: one for Linux and the other for Windows.
+They have different man pages: sg_scan.8.linux and sg_scan.8.win32 with
+the Makefile logic (rule in Makefile.am) copying the appropriate one to
+sg_scan.8 . sg_scan is not supported for other ports.
+
+
+Douglas Gilbert
+10th June 2022
diff --git a/doc/rescan-scsi-bus.sh.8 b/doc/rescan-scsi-bus.sh.8
new file mode 100644
index 00000000..51aa00aa
--- /dev/null
+++ b/doc/rescan-scsi-bus.sh.8
@@ -0,0 +1,135 @@
+.TH RESCAN\-SCSI\-BUS.SH "1" "September 2022" "rescan\-scsi\-bus.sh" "User Commands"
+.SH NAME
+rescan-scsi-bus.sh \- script to add and remove SCSI devices without rebooting
+.SH SYNOPSIS
+.B rescan\-scsi\-bus.sh
+[\fI\-\-alltargets\fR] [\fI\-\-attachpq3\fR] [\fI\-c\fR]
+[\fI\-\--channels=CLIST\fR] [\fI\-\-color\fR] [\fI\-d\fR] [\fI\-\-flush\fR]
+[\fI\-f\fR] [\fI\-\-forceremove\fR] [\fI\-\-forcerescan\fR] [\fI\-\-help\fR]
+[\fI\-\-hosts=HLIST\fR] [\fI\-\-ids=TLIST\fR] [\fI\-\-ignore\-rev\fR]
+[\fI\-\-issue\-lip\fR] [\fI\-i\fR] [\fI\-\-issue\-lip\-wait=SECS\fR]
+[\fI\-I SECS\fR] [\fI\-l\fR] [\fI\-L NUM\fR] [\fI\-\-largelun\fR]
+[\fI\-\-luns=LLIST\fR] [\fI\-m\fR] [\fI\-\-multipath\fR]
+[\fI\-\-no\-lip\-scan\fR] [\fI\-\-nooptscan\fR] [\fI\-\-nosync\fR]
+[\fI\-\-remove\fR] [\fI\-\-removelun2\fR] [\fI\-\-resize\fR]
+[\fI\-\-sparselun\fR] [\fI\-\-sync\fR] [\fI\-\-timeout=SECS\fR]
+[\fI\-\-update\fR] [\fI\-\-version\fR] [\fI\-\-wide\fR]
+[\fIHOST1 \fR[\fIHOST2 \fR...]]
+.SH OPTIONS
+Option are ordered by their long name. Those without a long name are ordered
+as if their single letter was a long name.
+.TP
+\fB\-a\fR, \fB\-\-alltargets\fR
+scan all targets, not just currently existing [default: disabled]
+.TP
+\fB\-\-attachpq3\fR
+tell kernel to attach sg to LUN 0 that reports PQ=3
+.TP
+\fB\-c\fR
+enables scanning of channels 0 1 [default: 0 / all detected ones]
+.TP
+\fB\-\-channels\fR=\fICLIST\fR
+scan only channel(s) in \fICLIST\fR
+.TP
+\fB\-\-color\fR
+use coloured prefixes OLD/NEW/DEL
+.TP
+\fB\-d\fR
+enable debug [default: 0]
+.TP
+\fB\-f\fR, \fB\-\-flush\fR
+flush failed multipath devices [default: disabled]
+.TP
+\fB\-\-forceremove\fR
+remove stale devices (DANGEROUS)
+.TP
+\fB\-\-forcerescan\fR
+remove and re\-add existing devices (DANGEROUS)
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print usage message then exit
+.TP
+\fB\-\-hosts\fR=\fIHLIST\fR
+scan only host(s) in \fIHLIST\fR
+.TP
+\fB\-\-ids\fR=\fITLIST\fR
+scan only target ID(s) in \fITLIST\fR
+.TP
+\fB\-\-ignore\-rev\fR
+ignore (firmware) revision change. This is the third text field (4 bytes
+long) in a standard INQUIRY response.
+.TP
+\fB\-i\fR, \fB\-\-issue\-lip\fR
+issue a FibreChannel LIP reset [default: disabled]
+.TP
+\fB\-I SECS\fR, \fB\-\-issue\-lip\-wait=SECS\fR
+issue a FibreChannel LIP reset and then wait SECS seconds.
+.TP
+\fB\-L\fR NUM
+activates scanning for LUNs 0\-\-NUM [default: 0]
+.TP
+\fB\-l\fR
+activates scanning for LUNs 0\-\-7 [default: 0]
+.TP
+\fB\-\-largelun\fR
+tell kernel to support LUNs > 7 even on SCSI2 devs
+.TP
+\fB\-\-luns\fR=\fINLIST\fR
+scan only lun(s) in \fINLIST\fR
+.TP
+\fB\-m\fR, \fB\-\-multipath\fR
+update multipath devices [default: disabled]
+.TP
+\fB\-\-no\-lip\-scan\fR
+don't scan FC Host when the \fI\-\-issue\-lip\fR option is also given.
+.TP
+\fB\-\-nooptscan\fR
+don't stop looking for LUNs is 0 is not found
+.TP
+\fB\-\-nosync\fR
+do not issue a sync [default: sync if remove]
+.TP
+\fB\-r\fR, \fB\-\-remove\fR
+enables removing of devices [default: disabled]
+.TP
+\fB\-\-reportlun2\fR
+tell kernel to try REPORT_LUN even on SCSI2 devices
+.TP
+\fB\-s\fR, \fB\-\-resize\fR
+look for resized disks and reload associated multipath devices, if applicable
+.TP
+\fB\-\-sparselun\fR
+tell kernel to support sparse LUN numbering
+.TP
+\fB\-\-sync\fR
+issue a sync [default: sync if remove]
+.TP
+\fB\-t\fR, \fB\-\-timeout=SECS\fR
+timeout for testing if device is online. Test is skipped if 0 [default:
+30 (seconds)]
+.TP
+\fB\-u\fR, \fB\-\-update\fR
+look for existing disks that have been remapped
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+shows version string then exits. The version string is a numeric datestamp
+of the form YYYYMMDD.
+.TP
+\fB\-w\fR, \fB\-\-wide\fR
+scan for target device IDs 0\-\-15 [default: 0\-\-7]
+.IP
+Host numbers may thus be specified either directly on cmd line (deprecated)
+or with the \fB\-\-hosts\fR=\fILIST\fR parameter (recommended).
+.PP
+Arguments to options that end in \fILIST\fR (e.g. \fITLIST\fR) can have this
+form:
+.br
+ A[\-B][,C[\-D]]...
+.br
+which is a comma separated list of single values and/or ranges (no spaces
+allowed).
+.SH SEE ALSO
+There is a brief descripion here:
+https://fibrevillage.com/storage/585-rescan-scsi-bus-sh-script-for-adding-and-removing-scsi-devices-without-rebooting
+.PP
+\fBsg3_utils\fR Homepage: \fBhttps://sg.danny.cz/sg\fR
diff --git a/doc/scsi_logging_level.8 b/doc/scsi_logging_level.8
new file mode 100644
index 00000000..57a5cf4b
--- /dev/null
+++ b/doc/scsi_logging_level.8
@@ -0,0 +1,128 @@
+.TH SCSI_LOGGING_LEVEL "8" "April 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+scsi_logging_level \- access Linux SCSI logging level information
+.SH SYNOPSIS
+.B scsi_logging_level
+[\fI\-\-all=LEV\fR] [\fI\-\-create\fR] [\fI\-\-error=LEV\fR] [\fI\-\-get\fR]
+[\fI\-\-help\fR] [\fI\-\-highlevel=LEV\fR] [\fI\-\-hlcomplete=LEV\fR]
+[\fI\-\-hlqueue=LEV\fR] [\fI\-\-ioctl=LEV\fR] [\fI\-\-llcomplete=LEV\fR]
+[\fI\-\-llqueue=LEV\fR] [\fI\-\-lowlevel=LEV\fR] [\fI\-\-midlevel=LEV\fR]
+[\fI\-\-mlcomplete=LEV\fR] [\fI\-\-mlqueue=LEV\fR] [\fI\-\-scan=LEV\fR]
+[\fI\-\-set\fR] [\fI\-\-timeout=LEV\fR] [\fI\-\-version\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script accesses the Linux SCSI subsystem logging
+level. The current values can be shown (e.g. with \fI\-\-get\fR)
+or changed (e.g. with \fI\-\-set\fR). Superuser permissions will
+typically be required to set the logging level.
+.PP
+One of these options: \fI\-\-create\fR, \fI\-\-get\fR or \fI\-\-set\fR
+is required. Only one of them can be given.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-all\fR=\fILEV\fR
+\fILEV\fR is used for all SCSI_LOG fields.
+.TP
+\fB\-c\fR, \fB\-\-create\fR
+Options are parsed and placed in internal fields that are displayed but
+no logging levels are changed within the Linux kernel.
+.TP
+\fB\-E\fR, \fB\-\-error\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_ERROR field.
+.TP
+\fB\-g\fR, \fB\-\-get\fR
+Fetches the current SCSI logging levels from the Linux kernel and
+displays them.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-highlevel\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_HLQUEUE and SCSI_LOG_HLCOMPLETE fields.
+.TP
+\fB\-\-hlcomplete\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_HLCOMPLETE field.
+.TP
+\fB\-\-hlqueue\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_HLQUEUE field.
+.TP
+\fB\-I\fR, \fB\-\-ioctl\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_IOCTL field.
+.TP
+\fB\-\-llcomplete\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_LLCOMPLETE field.
+.TP
+\fB\-\-llqueue\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_LLQUEUE field.
+.TP
+\fB\-L\fR, \fB\-\-lowlevel\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_LLQUEUE and SCSI_LOG_LLCOMPLETE fields.
+.TP
+\fB\-M\fR, \fB\-\-midlevel\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_MLQUEUE and SCSI_LOG_MLCOMPLETE fields.
+.TP
+\fB\-\-mlcomplete\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_MLCOMPLETE field.
+.TP
+\fB\-\-mlqueue\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_MLQUEUE field.
+.TP
+\fB\-S\fR, \fB\-\-scan\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_SCAN field.
+.TP
+\fB\-s\fR, \fB\-\-set\fR
+Uses the fields specified in this command's options and attempts to
+apply them to the Linux SCSI subsystem logging levels. Typically superuser
+permissions will be required to do this.
+.TP
+\fB\-T\fR, \fB\-\-timeout\fR=\fILEV\fR
+\fILEV\fR is placed in the SCSI_LOG_TIMEOUT field.
+.TP
+\fB\-v\fR, \fB\-\-version\fR
+Outputs the version information and then exits.
+.SH NOTES
+The \fI\-\-get\fR and \fI\-\-set\fR options access the
+/proc/sys/dev/scsi/logging_level pseudo file.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Any other
+exit status indicates that an error has occurred.
+.SH EXAMPLES
+The following will set SCSI_LOG_ERROR to level 5 in the Linux kernel. It
+requires root permissions:
+.PP
+ scsi_logging_level \-s \-E 5
+.PP
+So as to not interfere with other SCSI subsystem upper level drivers (ULDs)
+which most likely will be active at the same time, the Linux sg driver uses
+SCSI_LOG_TIMEOUT for logging purposes. To see full debugging and trace from
+the sg driver use:
+.PP
+ scsi_logging_level \-s \-T 7
+.PP
+The output from the sg driver caused by this will go to the system
+logs (e.g. /var/log/syslog). To reduce the amount of output use a number
+lower than 7. Using 0 will turn off the tracing and debug.
+.PP
+To turn on maximum SCSI subsystem logging use:
+.PP
+ scsi_logging_level \-s \-a 7
+.PP
+That is probably best done on a system that does not use a SCSI command
+device to hold the root file system, or the file system that holds the
+system log. Note that SATA disks and USB attached storage nearly always
+use the SCSI subsystem.
+.SH AUTHORS
+Written by IBM. Small alterations by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co IBM Corp. 2006
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.PP
+The software was obtained from an IBM package called s390\-tools\-1.6.2
+found on that company's "developerworks" site. The most recent version of
+that package at this time is 1.8.3 .
diff --git a/doc/scsi_mandat.8 b/doc/scsi_mandat.8
new file mode 100644
index 00000000..95a708c6
--- /dev/null
+++ b/doc/scsi_mandat.8
@@ -0,0 +1,44 @@
+.TH SCSI_MANDAT "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_mandat \- check SCSI device support for mandatory commands
+.SH SYNOPSIS
+.B scsi_mandat
+[\fI\-\-help\fR] [\fI\-\-log\fR] [\fI\-\-quiet\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls several SCSI commands on the given
+\fIDEVICE\fR. These SCSI commands are considered mandatory (although
+that varies a little depending on which standard/draft the \fIDEVICE\fR
+complies with). The results of each test and a pass/fail count are
+output.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-L\fR, \fB\-\-log\fR
+the output to stderr (from each SCSI command executed) is appended to
+a file called 'scsi_mandat.err' in the current working directory.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+the amount of output is reduced and typically only the pass/fail
+count is output.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.SH EXIT STATUS
+The exit status of this script is the number of "bad" errors found.
+So an exit status of 0 means all mandatory SCSI commands worked as
+expected.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2011\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq,sg_luns,sg_turs,sg_requests,sg_vpd,sg_senddiag (sg3_utils)
diff --git a/doc/scsi_readcap.8 b/doc/scsi_readcap.8
new file mode 100644
index 00000000..d898ce9c
--- /dev/null
+++ b/doc/scsi_readcap.8
@@ -0,0 +1,51 @@
+.TH SCSI_READCAP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_readcap \- do SCSI READ CAPACITY command on disks
+.SH SYNOPSIS
+.B scsi_readcap
+[\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-long\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR [\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls the sg_readcap utility on each given
+\fIDEVICE\fR. This will send a SCSI READ CAPACITY command to each
+\fIDEVICE\fR.
+.PP
+The default action of this script is to send the 10 byte cdb READ
+CAPACITY(10) command to each \fIDEVICE\fR. If a response indicates
+the number of blocks is greater than or equal to '2**32 \- 1' then
+the READ CAPACITY(16) is sent and its response is output.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+shortens the output to two hexadecimal numbers, both prefixed by '0x'.
+The first number is the number of blocks available and the second is
+the size of each blocks in bytes (e.g. '0x12a19eb0 0x200'). If an error
+is detected '0x0 0x0' is output and the script continues if there are
+more \fIDEVICE\fRs.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-l\fR, \fB\-\-long\fR
+the default is to send the READ CAPACITY(10) command (i.e. the 10 byte
+cdb variant). When this option is given the READ CAPACITY(16) command
+is sent. The latter command yields more information in its response.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Otherwise the
+exit status is that of the last sg_readcap utility called. See
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2009\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_readcap (sg3_utils)
diff --git a/doc/scsi_ready.8 b/doc/scsi_ready.8
new file mode 100644
index 00000000..e0548e06
--- /dev/null
+++ b/doc/scsi_ready.8
@@ -0,0 +1,40 @@
+.TH SCSI_READY "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_ready \- do SCSI TEST UNIT READY on devices
+.SH SYNOPSIS
+.B scsi_ready
+[\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR [\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls the sg_turs utility on each given
+\fIDEVICE\fR. This will send a SCSI TEST UNIT READY command to each
+\fIDEVICE\fR. Disks, tape drives and DVD/BD players amongst others
+may respond to this SCSI command.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+for each \fIDEVICE\fR given output a line containing either ' ready'
+or ' device not ready'. If \fIDEVICE\fR is not found or there is
+another serious error then an error message will appear instead.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Otherwise the
+exit status is that of the last sg_turs utility called. See
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2009\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_turs (sg3_utils)
diff --git a/doc/scsi_satl.8 b/doc/scsi_satl.8
new file mode 100644
index 00000000..bddee9fc
--- /dev/null
+++ b/doc/scsi_satl.8
@@ -0,0 +1,44 @@
+.TH SCSI_SATL "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_satl \- check SCSI to ATA Translation (SAT) device support
+.SH SYNOPSIS
+.B scsi_satl
+[\fI\-\-help\fR] [\fI\-\-log\fR] [\fI\-\-quiet\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls several SCSI commands on the given
+\fIDEVICE\fR that is assumed to be an ATA device behind a SCSI
+to ATA Translation (SAT) layer (SATL). The results of each test
+and a pass/fail count are output.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-L\fR, \fB\-\-log\fR
+the output to stderr (from each SCSI command executed) is appended to
+a file called 'scsi_satl.err' in the current working directory.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+the amount of output is reduced and typically only the pass/fail
+count is output.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.SH EXIT STATUS
+The exit status of this script is the number of "bad" errors found.
+So an exit status of 0 means all mandatory SCSI commands worked as
+expected.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2011\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_luns, sg_turs, sg_requests, sg_vpd, sg_senddiag, sg_modes,
+.B sg_sat_identify (sg3_utils)
diff --git a/doc/scsi_start.8 b/doc/scsi_start.8
new file mode 100644
index 00000000..72682321
--- /dev/null
+++ b/doc/scsi_start.8
@@ -0,0 +1,40 @@
+.TH SCSI_START "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_start \- start one or more SCSI disks
+.SH SYNOPSIS
+.B scsi_start
+[\fI\-\-help\fR] [\fI\-\-verbose\fR] [\fI\-\-wait\fR]
+\fIDEVICE\fR [\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls the sg_start utility on each given
+\fIDEVICE\fR. The purpose is to spin up (start) each given \fIDEVICE\fR.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.TP
+\fB\-w\fR, \fB\-\-wait\fR
+wait for the spin up (start) on each given \fIDEVICE\fR to complete.
+The default action is to do each start in immediate mode.
+.SH NOTES
+If a large number of disks are spun up at the same time (i.e. without
+the \fI\-\-wait\fR option) then the power supply may be overloaded.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Otherwise the
+exit status is that of the last sg_start utility called. See
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2009\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_start (sg3_utils)
diff --git a/doc/scsi_stop.8 b/doc/scsi_stop.8
new file mode 100644
index 00000000..79b7b2fd
--- /dev/null
+++ b/doc/scsi_stop.8
@@ -0,0 +1,41 @@
+.TH SCSI_STOP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_stop \- stop (spin down) one or more SCSI disks
+.SH SYNOPSIS
+.B scsi_stop
+[\fI\-\-help\fR] [\fI\-\-verbose\fR] [\fI\-\-wait\fR]
+\fIDEVICE\fR [\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls the sg_start utility on each given
+\fIDEVICE\fR. The purpose is to spin down (stop) each given \fIDEVICE\fR.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.TP
+\fB\-w\fR, \fB\-\-wait\fR
+wait for the spin down (stop) on each given \fIDEVICE\fR to complete.
+The default action is to do each stop in immediate mode.
+.SH NOTES
+The sg_start utility calls the SCSI START STOP UNIT command and can
+either start (spin up) or stop (spin down) a SCSI disk depending
+on the given command line options.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Otherwise the
+exit status is that of the last sg_start utility called. See
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2009\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_start (sg3_utils)
diff --git a/doc/scsi_temperature.8 b/doc/scsi_temperature.8
new file mode 100644
index 00000000..e3ce56b6
--- /dev/null
+++ b/doc/scsi_temperature.8
@@ -0,0 +1,35 @@
+.TH SCSI_TEMPERATURE "8" "May 2011" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+scsi_temperature \- fetch the temperature of a SCSI device
+.SH SYNOPSIS
+.B scsi_temperature
+[\fI\-\-help\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR [\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This bash shell script calls the sg_logs utility on each given
+\fIDEVICE\fR in order to find the device's temperature. The Temperature
+log page is checked first and if it is not available then the Informational
+Exceptions log page is checked.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.SH EXIT STATUS
+The exit status of this script is 0 when it is successful. Otherwise the
+exit status is that of the last sg_logs utility called. See
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2011\-2013 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_logs (sg3_utils)
diff --git a/doc/sg3_utils.8 b/doc/sg3_utils.8
new file mode 100644
index 00000000..d038cb51
--- /dev/null
+++ b/doc/sg3_utils.8
@@ -0,0 +1,883 @@
+.TH SG3_UTILS "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg3_utils \- a package of utilities for sending SCSI commands
+.SH SYNOPSIS
+.B sg_*
+[\fI\-\-dry\-run\fR] [\fI\-\-enumerate\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-in=FN\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO]\fR]
+[\fI\-\-maxlen=LEN\fR] [\fI\-\-raw\fR] [\fI\-\-timeout=SECS\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fIOTHER_OPTIONS\fR] [\fIDEVICE\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg3_utils is a package of utilities that send SCSI commands to the given
+\fIDEVICE\fR via a SCSI pass through interface provided by the host
+operating system.
+.PP
+The names of all utilities start with "sg" and most start with "sg_" often
+followed by the name, or a shortening of the name, of the SCSI command that
+they send. For example the "sg_verify" utility sends the SCSI VERIFY
+command. A mapping between SCSI commands and the sg3_utils utilities that
+issue them is shown in the COVERAGE file. The sg_raw utility can be used to
+send an arbitrary SCSI command (supplied on the command line) to the
+given \fIDEVICE\fR.
+.PP
+sg_decode_sense can be used to decode SCSI sense data given on the command
+line or in a file. sg_raw \-vvv will output the T10 name of a given SCSI
+CDB which is most often 16 bytes or less in length.
+.PP
+SCSI draft standards can be found at https://www.t10.org . The standards
+themselves can be purchased from ANSI and other standards organizations.
+A good overview of various SCSI standards can be seen in
+https://www.t10.org/scsi\-3.htm with the SCSI command sets in the upper part
+of the diagram. The highest level (i.e. most abstract) document is the SCSI
+Architecture Model (SAM) with SAM\-5 being the most recent standard (ANSI
+INCITS 515\-2016) with the most recent draft being SAM\-6 revision 4 . SCSI
+commands in common with all device types can be found in SCSI Primary
+Commands (SPC) of which SPC\-5 is the most recent standard (ANSI INCITS
+502-2020). The most recent SPC draft is SPC\-6 revision 6. Block device
+specific commands (e.g. as used by disks) are in SBC, those for tape drives
+in SSC, those for SCSI enclosures in SES and those for CD/DVD/BD drives in
+MMC.
+.PP
+It is becoming more common to control ATA disks with the SCSI command set.
+This involves the translation of SCSI commands to their corresponding ATA
+equivalents (and that is an imperfect mapping in some cases). The relevant
+standard is called SCSI to ATA Translation (SAT, SAT\-2 and SAT\-3) are
+now standards at INCITS(ANSI) and ISO while SAT\-4 is at the draft stage.
+The logic to perform the command translation is often called a SAT Layer or
+SATL and may be within an operating system, in host bus adapter firmware or
+in an external device (e.g. associated with a SAS expander). See
+https://www.t10.org for more information.
+.PP
+There is some support for SCSI tape devices but not for their basic
+operation. The reader is referred to the "mt" utility.
+.PP
+There are two generations of command line option usage. The newer
+utilities (written since July 2004) use the getopt_long() function to parse
+command line options. With that function, each option has two representations:
+a short form (e.g. '\-v') and a longer form (e.g. '\-\-verbose'). If an
+argument is required then it follows a space (optionally) in the short form
+and a "=" in the longer form (e.g. in the sg_verify utility '\-l 2a6h'
+and '\-\-lba=2a6h' are equivalent). Note that with getopt_long(), short form
+options can be elided, for example: '\-all' is equivalent to '\-a \-l \-l'.
+The \fIDEVICE\fR argument may appear after, between or prior to any options.
+.PP
+The older utilities, including as sg_inq, sg_logs, sg_modes, sg_opcode,
+sg_rbuff, sg_readcap, sg_senddiag, sg_start and sg_turs had individual
+command line processing code typically based on a single "\-" followed by one
+or more characters. If an argument is needed then it follows a "=" (
+e.g. '\-p=1f' in sg_modes with its older interface). Various options can be
+elided as long as it is not ambiguous (e.g. '\-vv' to increase the verbosity).
+.PP
+Over time the command line interface of these older utilities became messy
+and overloaded with options. So in sg3_utils version 1.23 the command line
+interface of these older utilities was altered to have both a cleaner
+getopt_long() interface and their older interface for backward compatibility.
+By default these older utilities use their getopt_long() based interface.
+The getopt_long() is a GNU extension (i.e. not yet POSIX certified) but
+more recent command line utilities tend to use it. That can be overridden
+by defining the SG3_UTILS_OLD_OPTS environment variable or using '\-O'
+or '\-\-old' as the first command line option. The man pages of the older
+utilities documents the details.
+.PP
+Several sg3_utils utilities are based on the Unix dd command (e.g. sg_dd)
+and permit copying data at the level of SCSI READ and WRITE commands. sg_dd
+is tightly bound to Linux and hence is not ported to other OSes. A more
+generic utility (than sg_dd) called ddpt in a package of the same name has
+been ported to other OSes.
+.SH ENVIRONMENT VARIABLES
+The SG3_UTILS_OLD_OPTS environment variable is explained in the previous
+section. It is only for backward compatibility of the command line options
+for older utilities.
+.PP
+The SG3_UTILS_DSENSE environment variable may be set to a number. It is
+only used by the embedded SNTL within the library used by the utilities in
+this library. SNTL is a SCSI to NVMe Translation Layer. This environment
+variable defaults to 0 which will lead to any utility that issues a SCSI
+command that is translated to a NVMe command (by the embedded SNTL) that
+fails at the NVMe device, to return SCSI sense in 'fixed' format. If this
+variable is non\-zero then then the returned SCSI sense will be in 'descriptor'
+format.
+.PP
+Several utilities have their own environment variable setting (e.g.
+sg_persist has SG_PERSIST_IN_RDONLY). See individual utility man pages
+for more information.
+.PP
+There is a Linux specific environment variable called SG3_UTILS_LINUX_NANO
+that if defined and the sg driver in the system is 4.0.30 or later, will
+show command durations in nanoseconds rather than the default milliseconds.
+Command durations are typically only shown if \-\-verbose is used 3 or more
+times. Due to an interface problem (a 32 bit integer that should be 64 bits
+with the benefit of hindsight) the maximum duration that can be represented
+in nanoseconds is about 4.2 seconds. If longer durations may occur then
+don't define this environment variable (or undefine it).
+.SH LINUX DEVICE NAMING
+Most disk block devices have names like /dev/sda, /dev/sdb, /dev/sdc, etc.
+SCSI disks in Linux have always had names like that but in recent Linux
+kernels it has become more common for many other disks (including SATA
+disks and USB storage devices) to be named like that. Partitions within a
+disk are specified by a number appended to the device name, starting at
+1 (e.g. /dev/sda1 ).
+.PP
+Tape drives are named /dev/st<num> or /dev/nst<num> where <num> starts
+at zero. Additionally one letter from this list: "lma" may be appended to
+the name. CD, DVD and BD readers (and writers) are named /dev/sr<num>
+where <num> start at zero. There are less used SCSI device type names,
+the dmesg and the lsscsi commands may help to find if any are attached to
+a running system.
+.PP
+There is also a SCSI device driver which offers alternate generic access
+to SCSI devices. It uses names of the form /dev/sg<num> where <num> starts
+at zero. The "lsscsi \-g" command may be useful in finding these and which
+generic name corresponds to a device type name (e.g. /dev/sg2 may
+correspond to /dev/sda). In the lk 2.6 series a block SCSI generic
+driver was introduced and its names are of the form
+/dev/bsg/<h:c:t:l> where h, c, t and l are numbers. Again see the lsscsi
+command to find the correspondence between that SCSI tuple (i.e. <h:c:t:l>)
+and alternate device names.
+.PP
+Prior to the Linux kernel 2.6 series these utilities could only use
+generic device names (e.g. /dev/sg1 ). In almost all cases in the Linux
+kernel 2.6 series, any device name can be used by these utilities.
+.PP
+Very little has changed in Linux device naming in the Linux kernel 3
+and 4 series.
+.SH WINDOWS DEVICE NAMING
+Storage and related devices can have several device names in Windows.
+Probably the most common in the volume name (e.g. "D:"). There are also
+a "class" device names such as "PhysicalDrive<n>", "CDROM<n>"
+and "TAPE<n>". <n> is an integer starting at 0 allocated in ascending
+order as devices are discovered (and sometimes rediscovered).
+.PP
+Some storage devices have a SCSI lower level device name which starts
+with a SCSI (pseudo) adapter name of the form "SCSI<n>:". To this is added
+sub\-addressing in the form of a "bus" number, a "target" identifier and
+a LUN (Logical Unit Number). The "bus" number is also known as a "PathId".
+These are assembled to form a device name of the
+form: "SCSI<n>:<bus>,<target>,<lun>". The trailing ",<lun>" may be omitted
+in which case a LUN of zero is assumed. This lower level device name cannot
+often be used directly since Windows blocks attempts to use it if a class
+driver has "claimed" the device. There are SCSI device types (e.g.
+Automation/Drive interface type) for which there is no class driver. At
+least two transports ("bus types" in Windows jargon): USB and IEEE 1394 do
+not have a "scsi" device names of this form.
+.PP
+In keeping with DOS file system conventions, the various device names
+can be given in upper, lower or mixed case. Since "PhysicalDrive<n>" is
+tedious to write, a shortened form of "PD<n>" is permitted by all
+utilities in this package.
+.PP
+A single device (e.g. a disk) can have many device names. For
+example: "PD0" can also be "C:", "D:" and "SCSI0:0,1,0". The two volume names
+reflect that the disk has two partitions on it. Disk partitions that are
+not recognized by Windows are not usually given a volume name. However
+Vista does show a volume name for a disk which has no partitions recognized
+by it and when selected invites the user to format it (which may be rather
+unfriendly to other OSes).
+.PP
+These utilities assume a given device name is in the Win32 device namespace.
+To make that explicit "\\\\.\\" can be prepended to the device names mentioned
+in this section. Beware that backslash is an escape character in Unix like
+shells and the C programming language. In a shell like Msys (from MinGW)
+each backslash may need to be typed twice.
+.PP
+The sg_scan utility within this package lists out Windows device names in
+a form that is suitable for other utilities in this package to use.
+.SH FREEBSD DEVICE NAMING
+SCSI disks have block names of the form /dev/da<num> where <num> is an
+integer starting at zero. The "da" is replaced by "sa" for SCSI tape
+drives and "cd" for SCSI CD/DVD/BD drives. Each SCSI device has a
+corresponding pass\-through device name of the form /dev/pass<num>
+where <num> is an integer starting at zero. The "camcontrol devlist"
+command may be useful for finding out which SCSI device names are
+available and the correspondence between class and pass\-through names.
+.PP
+FreeBSD allows device names to be given without the leading "/dev/" (e.g.
+da0 instead of /dev/da0). That worked in this package up until version
+1.43 when the unadorned device name (e.g. "da0") gave an error. The
+original action (i.e. allowing unadorned device names) has been restored
+in version 1.46 . Also note that symlinks (to device names) are followed
+before prepending "/dev/" if the resultant name doesn't start with a "/".
+.PP
+FreeBSD's NVMe naming has been evolving. The controller naming is the
+same as Linux: "/dev/nvme<n>" but the namespaces have an
+extra "s" (e.g. "/dev/nvme0ns1"). The latter is not a block (GEOM)
+device (strictly speaking FreeBSD does not have block devices). Initially
+FreeBSD had "/dev/nvd<m>" GEOM devices that were not based on the CAM
+subsystem. Then in FreeBSD release 12 a new nda driver was added that is
+CAM (and GEOM) based for NVMe namespaces; it has names like "/dev/nda0".
+The preferred device nodes for this package are "/dev/nvme0" for NVMe
+controllers and "/dev/nda0" for NVMe namespaces.
+.SH SOLARIS DEVICE NAMING
+SCSI device names below the /dev directory have a form like: c5t4d3s2
+where the number following "c" is the controller (HBA) number, the number
+following "t" is the target number (from the SCSI parallel interface days)
+and the number following "d" is the LUN. Following the "s" is the slice
+number which is related to a partition and by convention "s2" is the whole
+disk.
+.PP
+OpenSolaris also has a c5t4d3p2 form where the number following the "p" is
+the partition number apart from "p0" which is the whole disk. So a whole
+disk may be referred to as either c5t4d3, c5t4d3s2 or c5t4d3p0 .
+.PP
+And these device names are duplicated in the /dev/dsk and /dev/rdsk
+directories. The former is the block device name and the latter is
+for "raw" (or char device) access which is what sg3_utils needs. So in
+OpenSolaris something of the form 'sg_inq /dev/rdsk/c5t4d3p0' should work.
+If it doesn't work then add a '\-vvv' option for more debug information.
+Trying this form 'sg_inq /dev/dsk/c5t4d3p0' (note "rdsk" changed to "dsk")
+will result in an "inappropriate ioctl for device" error.
+.PP
+The device names within the /dev directory are typically symbolic links to
+much longer topological names in the /device directory. In Solaris cd/dvd/bd
+drives seem to be treated as disks and so are found in the /dev/rdsk
+directory. Tape drives appear in the /dev/rmt directory.
+.PP
+There is also a sgen (SCSI generic) driver which by default does not attach
+to any device. See the /kernel/drv/sgen.conf file to control what is
+attached. Any attached device will have a device name of the
+form /dev/scsi/c5t4d3 .
+.PP
+Listing available SCSI devices in Solaris seems to be a challenge. "Use
+the 'format' command" advice works but seems a very dangerous way to list
+devices. [It does prompt again before doing any damage.] 'devfsadm \-Cv'
+cleans out the clutter in the /dev/rdsk directory, only leaving what
+is "live". The "cfgadm \-v" command looks promising.
+.SH NVME SUPPORT
+NVMe (or NVM Express) is a relatively new storage transport and command
+set. The level of abstraction of the NVMe command set is somewhat lower
+the SCSI command sets, closer to the level of abstraction of ATA (and SATA)
+command sets. NVMe claims to be designed with flash and modern "solid
+state" storage in mind, something unheard of when SCSI was originally
+developed in the 1980s.
+.PP
+The SCSI command sets' advantage is the length of time they have been in
+place and the existing tools (like these) to support it. Plus SCSI command
+sets level of abstraction is both and advantage and disadvantage. Recently
+the NVME\-MI (Management Interface) designers decide to use the SCSI
+Enclosure Services (SES\-3) standard "as is" with the addition of two
+tunnelling NVME\-MI commands: SES Send and SES Receive. This means after the
+OS interface differences are taken into account, the sg_ses, sg_ses_microcode
+and sg_senddiag utilities can be used on a NVMe device that supports a newer
+version of NVME\-MI.
+.PP
+The NVME\-MI SES Send and SES Receive commands correspond to the SCSI
+SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS commands respectively.
+There are however a few other commands that need to be translated, the
+most important of which is the SCSI INQUIRY command to the NVMe Identify
+controller/namespace. Starting in version 1.43 these utilities contain a
+small SNTL (SCSI to NVMe Translation Layer) to take care of these details.
+.PP
+As a side effect of this "juggling" if the sg_inq utility is used (without
+the \-\-page= option) on a NVMe \fIDEVICE\fR then the actual NVMe
+Identifier (controller and possibly namespace) responses are decoded and
+output. However if 'sg_inq \-\-page=sinq <device>' is given for the
+same \fIDEVICE\fR then parts of the NVMe Identify controller and namespace
+response are translated to a SCSI standard INQUIRY response which is then
+decoded and output.
+.PP
+Apart from the special case with the sg_inq, all other utilities in the
+package assume they are talking to a SCSI device and decode any response
+accordingly. One easy way for users to see the underlying device is a
+NVMe device is the standard INQUIRY response Vendor Identification field
+of "NVMe " (an 8 character long string with 4 spaces to the right).
+.PP
+The following SCSI commands are currently supported by the SNTL library:
+INQUIRY, MODE SELECT(10), MODE SENSE(10), READ(10,16), READ CAPACITY(10,16),
+RECEIVE DIAGNOSTIC RESULTS, REQUEST SENSE, REPORT LUNS, REPORT SUPPORTED
+OPERATION CODES, REPORT SUPPORTED TASK MANAGEMENT FUNCTIONS, SEND
+DIAGNOSTICS, START STOP UNIT, SYNCHRONIZE CACHE(10,16), TEST UNIT READY,
+VERIFY(10,16), WRITE(10,16) and WRITE SAME(10,16).
+.SH EXIT STATUS
+To aid scripts that call these utilities, the exit status is set to indicate
+success (0) or failure (1 or more). Note that some of the lower values
+correspond to the SCSI sense key values.
+.PP
+The exit status values listed below can be given to the sg_decode_sense
+utility (which is found in this package) as follows:
+.br
+ sg_decode_sense \-\-err=<exit_status>
+.br
+and a short explanatory string will be output to stdout.
+.PP
+The exit status values are:
+.TP
+.B 0
+success. Also used for some utilities that wish to return a boolean value
+for the "true" case (and that no error has occurred). The false case is
+conveyed by exit status 36.
+.TP
+.B 1
+syntax error. Either illegal command line options, options with bad
+arguments or a combination of options that is not permitted.
+.TP
+.B 2
+the \fIDEVICE\fR reports that it is not ready for the operation requested.
+The \fIDEVICE\fR may be in the process of becoming ready (e.g. spinning up
+but not at speed) so the utility may work after a wait. In Linux the
+\fIDEVICE\fR may be temporarily blocked while error recovery is taking place.
+See exit status values 12 and 13 below which refine this exit value.
+.TP
+.B 3
+the \fIDEVICE\fR reports a medium or hardware error (or a blank check). For
+example an attempt to read a corrupted block on a disk will yield this value.
+.TP
+.B 5
+the \fIDEVICE\fR reports an "illegal request" with an additional sense code
+other than "invalid command operation code". This is often a supported
+command with a field set requesting an unsupported capability. For commands
+that require a "service action" field this value can indicate that the
+command with that service action value is not supported.
+.TP
+.B 6
+the \fIDEVICE\fR reports a "unit attention" condition. This usually indicates
+that something unrelated to the requested command has occurred (e.g. a device
+reset) potentially before the current SCSI command was sent. The requested
+command has not been executed by the device. Note that unit attention
+conditions are usually only reported once by a device.
+.TP
+.B 7
+the \fIDEVICE\fR reports a "data protect" sense key. This implies some
+mechanism has blocked writes (or possibly all access to the media).
+.TP
+.B 9
+the \fIDEVICE\fR reports an illegal request with an additional sense code
+of "invalid command operation code" which means that it doesn't support the
+requested command.
+.TP
+.B 10
+the \fIDEVICE\fR reports a "copy aborted". This implies another command or
+device problem has stopped a copy operation. The EXTENDED COPY family of
+commands (including WRITE USING TOKEN) may return this sense key.
+.TP
+.B 11
+the \fIDEVICE\fR reports an aborted command. In some cases aborted
+commands can be retried immediately (e.g. if the transport aborted
+the command due to congestion).
+.TP
+.B 12
+the \fIDEVICE\fR reports a sense key of not ready together with an
+additional sense code of "target port in standby state".
+.TP
+.B 13
+the \fIDEVICE\fR reports a sense key of not ready together with an
+additional sense code of "target port in unavailable state".
+.TP
+.B 14
+the \fIDEVICE\fR reports a miscompare sense key. VERIFY and COMPARE AND
+WRITE commands may report this.
+.TP
+.B 15
+the utility is unable to open, close or use the given \fIDEVICE\fR or some
+other file. The given file name could be incorrect or there may be
+permission problems. Adding the '\-v' option may give more information.
+.TP
+.B 17
+a SCSI "Illegal request" sense code received with a flag indicating the
+Info field is valid. This is often a LBA but its meaning is command specific.
+.TP
+.B 18
+the \fIDEVICE\fR reports a medium or hardware error (or a blank check)
+with a flag indicating the Info field is valid. This is often a LBA (of
+the first encountered error) but its meaning is command specific.
+.TP
+.B 20
+the \fIDEVICE\fR reports it has a check condition but "no sense"
+and non\-zero information in its additional sense codes. Some polling
+commands (e.g. REQUEST SENSE) can receive this response. There may
+be useful information in the sense data such as a progress indication.
+.TP
+.B 21
+the \fIDEVICE\fR reports a "recovered error". The requested command
+was successful. Most likely a utility will report a recovered error
+to stderr and continue, probably leaving the utility with an exit
+status of 0 .
+.TP
+.B 22
+the \fIDEVICE\fR reports that the current command or its parameters imply
+a logical block address (LBA) that is out of range. This happens surprisingly
+often when trying to access the last block on a storage device; either a
+classic "off by one" logic error or a misreading of the response from READ
+CAPACITY(10 or 16) in which the address of the last block rather than the
+number of blocks on the \fIDEVICE\fR is returned. Since LBAs are origin zero
+they range from 0 to n\-1 where n is the number of blocks on the \fIDEVICE\fR,
+so the LBA of the last block is one less than the total number of blocks.
+.TP
+.B 24
+the \fIDEVICE\fR reports a SCSI status of "reservation conflict". This
+means access to the \fIDEVICE\fR with the current command has been blocked
+because another machine (HBA or SCSI "initiator") holds a reservation on
+this \fIDEVICE\fR. On modern SCSI systems this is related to the use of
+the PERSISTENT RESERVATION family of commands.
+.TP
+.B 25
+the \fIDEVICE\fR reports a SCSI status of "condition met". Currently only
+the PRE\-FETCH command (see SBC\-4) yields this status.
+.TP
+.B 26
+the \fIDEVICE\fR reports a SCSI status of "busy". SAM\-6 defines this status
+as the logical unit is temporarily unable to process a command. It is
+recommended to re\-issue the command.
+.TP
+.B 27
+the \fIDEVICE\fR reports a SCSI status of "task set full".
+.TP
+.B 28
+the \fIDEVICE\fR reports a SCSI status of "ACA active". ACA is "auto
+contingent allegiance" and is seldom used.
+.TP
+.B 29
+the \fIDEVICE\fR reports a SCSI status of "task aborted". SAM\-5 says:
+"This status shall be returned if a command is aborted by a command or task
+management function on another I_T nexus and the Control mode page TAS bit
+is set to one".
+.TP
+.B 31
+error involving two or more command line options. They may be contradicting,
+select an unsupported mode, or a required option (given the context) is
+missing.
+.TP
+.B 32
+there is a logic error in the utility. It corresponds to code comments
+like "shouldn't/can't get here". Perhaps the author should be informed.
+.TP
+.B 33
+the command sent to \fIDEVICE\fR has timed out.
+.TP
+.B 34
+this is a Windows only exit status and indicates that the Windows error
+number (32 bits) cannot meaningfully be mapped to an equivalent Unix error
+number returned as the exit status (7 bits).
+.TP
+.B 35
+a transport error has occurred. This will either be in the driver (e.g. HBA
+driver) or in the interconnect between the host (initiator) and the
+device (target). For example in SAS an expander can run out of paths and
+thus be unable to return the user data from a READ command.
+.TP
+.B 36
+no error has occurred plus the utility wants to convey a boolean value
+of false. The corresponding true value is conveyed by a 0 exit status.
+.TP
+.B 40
+the command sent to \fIDEVICE\fR has received an "aborted command" sense
+key with an additional sense code of 0x10. This value is related to
+problems with protection information (PI or DIF). For example this error
+may occur when reading a block on a drive that has never been written (or
+is unmapped) if that drive was formatted with type 1, 2 or 3 protection.
+.TP
+.B 41
+the command sent to \fIDEVICE\fR has received an "aborted command" sense
+key with an additional sense code of 0x10 (as with error code) plus a flag
+indicating the Info field is valid.
+.TP
+.B 48
+this is an internal message indicating a NVMe status field (SF) is other
+than zero after a command has been executed (i.e. something went wrong).
+Work in this area is currently experimental.
+.TP
+.B 49
+low level driver reports a response's residual count (i.e. number of bytes
+actually received by HBA is 'requested_bytes \- residual_count') that is
+nonsensical.
+.TP
+.B 50
+OS system calls that fail often return a small integer number to help. In
+Unix these are called "errno" values where 0 implies no error. These error
+codes set aside 51 to 96 for mapping these errno values but that may not be
+sufficient. Higher errno values that cannot be mapped are all mapped to
+this value (i.e. 50).
+.br
+Note that an errno value of 0 is mapped to error code 0.
+.TP
+.B 50 + <os_error_number>
+OS system calls that fail often return a small integer number to help
+indicate what the error is. For example in Unix the inability of a system
+call to allocate memory returns (in 'errno') ENOMEM which often is
+associated with the integer 12. So 62 (i.e. '50 + 12') may be returned
+by a utility in this case. It is also possible that a utility in this
+package reports 50+ENOMEM when it can't allocate memory, not necessarily
+from an OS system call. In recent versions of Linux the file showing the
+mapping between symbolic constants (e.g. ENOMEM) and the corresponding
+integer is in the kernel source code file:
+include/uapi/asm\-generic/errno\-base.h
+.br
+Note that errno values that are greater than or equal to 47 cannot fit in
+range provided. Instead they are all mapped to 50 as discussed in the
+previous entry.
+.TP
+.B 97
+a SCSI command response failed sanity checks.
+.TP
+.B 98
+the \fIDEVICE\fR reports it has a check condition but the error
+doesn't fit into any of the above categories.
+.TP
+.B 99
+any errors that can't be categorized into values 1 to 98 may yield
+this value. This includes transport and operating system errors
+after the command has been sent to the device.
+.TP
+.B 100\-125
+these error codes are used by the ddpt utility which uses the sg3_utils
+library. They are mainly specialized error codes associated with offloaded
+copies.
+.TP
+.B 126
+the utility was found but could not be executed. That might occur if the
+executable does not have execute permissions.
+.TP
+.B 127
+This is the exit status for utility not found. That might occur when a
+script calls a utility in this package but the PATH environment variable
+has not been properly set up, so the script cannot find the executable.
+.TP
+.B 128 + <signum>
+If a signal kills a utility then the exit status is 128 plus the signal
+number. For example if a segmentation fault occurs then a utility is
+typically killed by SIGSEGV which according to 'man 7 signal' has an
+associated signal number of 11; so the exit status will be 139 .
+.TP
+.B 255
+the utility tried to yield an exit status of 255 or larger. That should
+not happen; given here for completeness.
+.PP
+Most of the error conditions reported above will be repeatable (an example
+of one that is not is "unit attention") so the utility can be run again with
+the '\-v' option (or several) to obtain more information.
+.SH COMMON OPTIONS
+Arguments to long options are mandatory for short options as well. In the
+short form an argument to an option uses zero or more spaces as a
+separator (i.e. the short form does not use "=" as a separator).
+.PP
+If an option takes a numeric argument then that argument is assumed to
+be decimal unless otherwise indicated (e.g. with a leading "0x", a
+trailing "h" or as noted in the usage message).
+.PP
+Some options are used uniformly in most of the utilities in this
+package. Those options are listed below. Note that there are some
+exceptions.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+utilities that can cause lots of user data to be lost or overwritten
+sometimes have a \fI\-\-dry\-run\fR option. Device modifying actions are
+typically bypassed (or skipped) to implement a policy of "do no harm".
+This allows complex command line invocations to be tested before the
+action required (e.g. format a disk) is performed. The \fI\-\-dry\-run\fR
+option has become a common feature of many command line utilities (e.g.
+the Unix 'patch' command), not just those from this package.
+.br
+Note that most hyphenated option names in this package also can be given
+with an underscore rather than a hyphen (e.g. \fI\-\-dry_run\fR).
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+some utilities (e.g. sg_ses and sg_vpd) store a lot of information in
+internal tables. This option will output that information in some readable
+form (e.g. sorted by an acronym or by page number) then exit. Note that
+with this option \fIDEVICE\fR is ignored (as are most other options) and no
+SCSI IO takes place, so the invoker does not need any elevated permissions.
+.TP
+\fB\-h\fR, \fB\-?\fR, \fB\-\-help\fR
+output the usage message then exit. In a few older utilities the '\-h'
+option requests hexadecimal output. In these cases the '\-?' option will
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+for SCSI commands that yield a non\-trivial response, print out that response
+in ASCII hexadecimal. When used once, 16 bytes are printed on each line,
+prefixed by an relative address, starting at 0 (hex). When used twice, an
+ASCII rendering of the 16 bytes is appended to each line, with non printable
+characters replaced by a '.' . When used three times only the 16 hex bytes
+are printed on each line (hence no address prefix nor ASCII appended). To
+produce hexadecimal that can be parsed by other utilities use this option
+three or four times.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR
+many SCSI commands fetch a significant amount of data (returned in the
+data\-in buffer) which several of these utilities decode (e.g. sg_vpd and
+sg_logs). To separate the two steps of fetching the data from a SCSI device
+and then decoding it, this option has been added. The first step (fetching
+the data) can be done using the \fI\-\-hex\fR or \fI\-\-raw\fR option and
+redirecting the command line output to a file (often done with ">" in Unix
+based operating systems). The difference between \fI\-\-hex\fR and
+\fI\-\-raw\fR is that the former produces output in ASCII hexadecimal
+while \fI\-\-raw\fR produces its output in "raw" binary.
+.br
+The second step (i.e. decoding the SCSI response data now held in a file)
+can be done using this \fI\-\-in=FN\fR option where the file name is
+\fIFN\fR. If "\-" is used for \fIFN\fR then stdin is assumed, again this
+allows for command line redirection (or piping). That file (or stdin)
+is assumed to contain ASCII hexadecimal unless the \fI\-\-raw\fR option is
+also given in which case it is assumed to be binary. Notice that the meaning
+of the \fI\-\-raw\fR option is "flipped" when used with \fI\-\-in=FN\fR to
+act on the input, typically it acts on the output data.
+.br
+Since the structure of the data returned by SCSI commands varies
+considerably then the usage information or the manpage of the utility being
+used should be checked. In some cases \fI\-\-hex\fR may need to be used
+multiple times (and is more conveniently given as '\-HH' or '\-HHH).
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+This option has the same or similar functionality as \fI\-\-in=FN\fR. And
+perhaps 'inhex' is more descriptive since by default, ASCII hexadecimal is
+expected in the contents of file: \fIFN\fR. Alternatively the short form
+option may be \fI\-I\fR or \fI\-X\fR. See the "FORMAT OF FILES CONTAINING
+ASCII HEX" section below for more information.
+.TP
+\fB\-\-json\fR[=\fIJO\fR]
+The default output of most utilities that decode information returned from
+SCSI devices is designed for human readability. Sometimes a more parseable
+form of output is required and JSON is a popular way to do this. Only
+utilities that decode a significant amount of SCSI data support this option.
+.br
+The corresponding short option is usually \fI\-j[JO]\fR but maybe
+\fI\-J[JO]\fR if \fI\-j\fR is already in use. Note that in all cases \fIJO\fR
+argument is itself optional. See the sg3_utils_json manpage for more
+information.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+several important SCSI commands (e.g. INQUIRY and MODE SENSE) have response
+lengths that vary depending on many factors, only some of which these
+utilities take into account. The maximum response length is typically
+specified in the 'allocation length' field of the cdb. In the absence of
+this option, several utilities use a default allocation length (sometimes
+recommended in the SCSI draft standards) or a "double fetch" strategy.
+See sg_logs(8) for its description of a "double fetch" strategy. These
+techniques are imperfect and in the presence of faulty SCSI targets can
+cause problems (e.g. some USB mass storage devices freeze if they receive
+an INQUIRY allocation length other than 36). Also use of this option
+disables any "double fetch" strategy that may have otherwise been used.
+.br
+To head off a class of degenerate bugs, if \fILEN\fR is less than 16 then
+it is ignored (usually with a warning message) and the default value is
+used instead. Some utilities use 4 (bytes), rather than 16, as the cutoff
+value.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+for SCSI commands that yield a non\-trivial response, output that response
+in binary to stdout. If any error messages or warning are produced they are
+usually sent to stderr so as to not interfere with the output from this
+option.
+.br
+Some utilities that consume data to send to the \fIDEVICE\fR along with the
+SCSI command, use this option. Alternatively the \fI\-\-in=FN\fR option causes
+\fIDEVICE\fR to be ignored and the response data (to be decoded) fetched
+from a file named \fIFN\fR. In these cases this option may indicate that
+binary data can be read from stdin or from a nominated file (e.g. \fIFN\fR).
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR
+utilities that issue potentially long\-running SCSI commands often have a
+\fI\-\-timeout=SECS\fR option. This typically instructs the operating system
+to abort the SCSI command in question once the timeout expires. Aborting
+SCSI commands is typically a messy business and in the case of format like
+commands may leave the device in a "format corrupt" state requiring another
+long\-running re\-initialization command to be sent. The argument, \fISECS\fR,
+is usually in seconds and the short form of the option may be something
+other than \fI\-t\fR since the timeout option was typically added later as
+storage devices grew in size and initialization commands took longer. Since
+many utilities had relatively long internal command timeouts before this
+option was introduced, the actual command timeout given to the operating
+systems is the higher of the internal timeout and \fISECS\fR.
+.br
+Many long running SCSI commands have an IMMED bit which causes the command
+to finish relatively quickly but the initialization process to continue. In
+such cases the REQUEST SENSE command can be used to monitor progress with
+its progress indication field (see the sg_requests and sg_turs utilities).
+Utilities that send such SCSI command either have an \fI\-\-immed\fR option
+or a \fI\-\-wait\fR option which is the logical inverse of the "immediate"
+action.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output). Can be used multiple
+times to further increase verbosity. The additional output caused by this
+option is almost always sent to stderr.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit. Each utility has its own version
+number and date of last code change.
+.SH NUMERIC ARGUMENTS
+Many utilities have command line options that take numeric arguments. These
+numeric arguments can be large values (e.g. a logical block address (LBA) on
+a disk) and can be inconvenient to enter in the default decimal
+representation. So various other representations are permitted.
+.PP
+Multiplicative suffixes are accepted. They are one, two or three letter
+strings appended directly after the number to which they apply:
+.PP
+ c C *1
+.br
+ w W *2
+.br
+ b B *512
+.br
+ k K KiB *1024
+.br
+ KB kB *1000
+.br
+ m M MiB *1048576
+.br
+ MB mB *1000000
+.br
+ g G GiB *(2^30)
+.br
+ GB gB *(10^9)
+.br
+ t T TiB *(2^40)
+.br
+ TB *(10^12)
+.br
+ p P PiB *(2^50)
+.br
+ PB *(10^15)
+.PP
+An example is "2k" for 2048. The large tera and peta suffixes are only
+available for numeric arguments that might require 64 bits to represent
+internally.
+.PP
+These multiplicative suffixes are compatible with GNU's dd command (since
+2002) which claims compliance with SI and with IEC 60027\-2.
+.PP
+A suffix of the form "x<n>" multiplies the preceding number by <n>. An
+example is "2x33" for "66". The left argument cannot be '0' as '0x' will
+be interpreted as hexadecimal number prefix (see below). The left
+argument to the multiplication must end in a hexadecimal digit (i.e.
+0 to f) and the whole expression cannot have any embedded whitespace (e.g.
+spaces). An ugly example: "0xfx0x2" for 30.
+.PP
+A suffix of the form "+<n>" adds the preceding number to <n>. An example
+is "3+1k" for "1027". The left argument to the addition must end in a
+hexadecimal digit (i.e. 0 to f) and the whole expression cannot have any
+embedded whitespace (e.g. spaces). Another example: "0xf+0x2" for 17.
+.PP
+Alternatively numerical arguments can be given in hexadecimal. There are
+two syntaxes. The number can be preceded by either "0x" or "0X" as found
+in the C programming language. The second hexadecimal representation is a
+trailing "h" or "H" as found in (storage) standards. When hex numbers are
+given, multipliers cannot be used. For example the decimal value "256" can
+be given as "0x100" or "100h".
+.SH FORMAT OF FILES CONTAINING ASCII HEX
+Such a file is assumed to contain a sequence of one or two digit ASCII
+hexadecimal values separated by whitespace. "Whitespace consists of either
+spaces, tabs, blank lines, or any combination thereof". Hyphens (e.g. '\-')
+are also allowed as separators. Each one or two digit ASCII hex pair is
+decoded into a byte (i.e. 8 bits). The following will be decoded to
+valid (ascending valued) bytes: '0', '01', '3', 'c', 'F', '4a', 'cC'
+and 'ff'. Lines containing only whitespace are ignored. The contents of any
+line containing a hash mark ('#') are ignored from that point until the end
+of that line. Users are encouraged to use hash marks to introduce comments
+in hex files. The author uses the extension '.hex' on such files. Examples
+can be found in the 'inhex' directory. Note that this format does _not_
+have an index (counter) value at the beginning of each line (like, for
+example, the hexdump utility outputs).
+.PP
+The hexadecimal format described in the previous paragraph can be converted
+to binary using the sg_decode_sense utility with these
+options: "\fI\-\-inhex=HFN \-\-nodecode \-\-write=WFN\fR". The input (in
+hex) is in the \fIHFN\fR file while the output is placed in the \fIWFN\fR
+file.
+.PP
+To convert a binary file into a hexadecimal form that can be given as input
+to various sg3_utils utilities, the sg_decode_sense utility can also be
+used with these options: "\fI\-\-binary=BFN \-\-nodecode \-HHH\fR" and the
+hex output will be sent to the console (stdout).
+.SH MICROCODE AND FIRMWARE
+There are two standardized methods for downloading microcode (i.e. device
+firmware) to a SCSI device. The more general way is with the SCSI WRITE
+BUFFER command, see the sg_write_buffer utility. SCSI enclosures have
+their own method based on the Download microcode control/status diagnostic
+page, see the sg_ses_microcode utility.
+.SH SCRIPTS, EXAMPLES and UTILS
+There are several bash shell scripts in the 'scripts' subdirectory that
+invoke compiled utilities (e.g. sg_readcap). Several of the scripts start
+with 'scsi_' rather than 'sg_'. One purpose of these scripts is to call the
+same utility (e.g. sg_readcap) on multiple devices. Most of the basic
+compiled utilities only allow one device as an argument. Some distributions
+install these scripts in a more visible directory (e.g. /usr/bin). Some of
+these scripts have man page entries. See the README file in the 'scripts'
+subdirectory.
+.PP
+There is some example C code plus examples of complex invocations in
+the 'examples' subdirectory. There is also a README file. The example C
+may be a simpler example of how to use a SCSI pass\-through in Linux
+than the main utilities (found in the 'src' subdirectory). This is due
+to the fewer abstraction layers (e.g. they don't worry the MinGW in
+Windows may open a file in text rather than binary mode).
+.PP
+Some utilities that the author has found useful have been placed in
+the 'utils' subdirectory.
+.SH DEBUGGING
+Each utility and most scripts have a \fI\-\-verbose\fR option (short
+form: \fI\-v\fR) that can be used multiple times to increase the verbosity
+of the output to aid debugging. Normal output (if any) is sent to stdout
+while verbose output (and error output) is sent to stderr. This may be
+important when the (normal output) of a utility is being piped to another
+command (e.g. the grep command to find a particular field in the output).
+.PP
+The Linux SCSI subsystem has a pseudo file for getting and changing the SCSI
+logging level: /proc/sys/dev/scsi/logging_level . The scsi_logging_level
+script in this package can be used to manipulate the logging level in a
+command line friendly way. See its manpage.
+.PP
+The logging level runs from 0 (no logging and the default) to 7 (lots of
+logging) and applies to all storage devices that use the SCSI subsystem.
+The logging output goes to "the log" which is often the /var/log/syslog
+file.
+.PP
+The Linux SCSI generic (sg) driver is often used under the utilities in
+this package. It uses a seldom (otherwise) used logging type of
+SCSI_LOG_TIMEOUT. An example of its use to turn on full debugging is:
+.PP
+ scsi_logging_level \-s \-T 7
+.PP
+To reduce the amount of output to only error paths, the following is
+suggested:
+.PP
+ scsi_logging_level \-s \-T 3
+.PP
+And to turn off logging in the sg driver:
+.PP
+ scsi_logging_level \-s \-T 0
+.PP
+For analyzing machine crashes associated with a SCSI command, nothing beats
+a real serial port. By "real" means that it is _not_ a USB serial port.
+The reason is that like SCSI, USB needs a functioning software stack within
+the OS kernel, the very thing that may be crippled during a machine crash.
+.PP
+Modern laptops do not have real serial ports and many server machines
+don't either (or it is an optional extra). In Linux the netconsole module
+does a pretty good job by sending log entries to another machine (on the
+same sub\-net)) using the UDP ("fire and forget") network protocol .
+.SH WEB SITE
+There is a web page discussing this package at
+https://sg.danny.cz/sg/sg3_utils.html . The device naming used by this
+package on various operating systems is discussed at:
+https://sg.danny.cz/sg/device_name.html . There is a git code mirror at
+https://github.com/hreinecke/sg3_utils . The principle code repository
+uses subversion and is on the author's equipment. The author keeps track
+of this via the subversion revision number which is an ascending integer
+(currently at 922 for this package). The github mirror gets updated
+periodically from the author's repository. Depending on the time of
+update, the above Downloads section at sg.danny.cz may be more up to
+date than the github mirror.
+.SH AUTHORS
+Written by Douglas Gilbert. Some utilities have been contributed, see the
+CREDITS file and individual source files (in the 'src' directory).
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 1999\-2022 Douglas Gilbert
+.br
+Some utilities are distributed under a GPL version 2 license while others,
+usually more recent ones, are under a BSD\-2\-Clause license. The files
+that are common to almost all utilities and thus contain the most reusable
+code, namely sg_lib.[hc], sg_cmds_basic.[hc] and sg_cmds_extra.[hc] are
+under a BSD\-2\-Clause license. There is NO warranty; not even for
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils_json,sg_decode_sense(sg3_utils), sdparm(sdparm), ddpt(ddpt),
+.B lsscsi(lsscsi), dmesg(1), mt(1)
+.br
+The format of this section is: <utility_name>(<package_containing_utility>)
+or <utility_name>(<manpage_section_number_containing_utility>) .
diff --git a/doc/sg3_utils_json.8 b/doc/sg3_utils_json.8
new file mode 100644
index 00000000..e734b4a1
--- /dev/null
+++ b/doc/sg3_utils_json.8
@@ -0,0 +1,296 @@
+.TH SG3_UTILS_JSON "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg3_utils_json \- JSON output for some sg3_utils utilities
+.SH SYNOPSIS
+.B sg_*
+\fI\-\-json[=JO]\fR [\fIOTHER_OPTIONS\fR] [\fIDEVICE\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg3_utils is a package of utilities that send SCSI commands to the given
+\fIDEVICE\fR via a SCSI pass through interface provided by the host
+operating system. Some utilities, mainly those decoding structured data
+returned by SCSI commands (e.g. sg_vpd) can optionally provide JSON
+output, rather than simple, human-readable output. The default remains
+human-readable output.
+.PP
+JavaScript Object Notation (JSON) is an open standard file format that can be
+used for data exchange between programs including across a network. See
+https://en.wikipedia.org/wiki/JSON . JSON comes in many flavours and this one
+uses the json-builder C implementation found at
+https://github.com/json-parser/json-builder which implements four simple JSON
+data types: string, integer, boolean and null. Its other data types are JSON
+object and JSON array.
+.PP
+This project uses the "snake_case" convention for JSON object names: all in
+lower case letters or numerals with individual words joined with a single
+underscore (e.g. "starting_lba"). There should be no leading or trailing
+underscore characters. The json-builder library uses the
+SPDX\-License\-Identifier: BSD\-2\-Clause which is the same license as the
+bulk of the utilities in the sg3_utils package.
+.PP
+The json-builder library is relatively lightweight (700 lines of C code) and
+is "hidden" fully within the sg3_utils library so that its function interface
+and data types are not available (directly) to the utilities in the sg3_utils
+package. That is why the json-builder interface (a file named
+sg_json_builder.h) is in the lib directory and not in the include directory.
+As presented on github, json-builder shares some header files with its
+companion json-parser. The author has modified the json-builder header to
+include what is needed from the json-parser header so that only the builder
+and not the parser are built. The parser could be added later, but currently
+there seems to be no need for it.
+.PP
+The user interface to JSON functionality in the sg3_utils package is heavily
+based on what has been done by Christian Franke and others in smartctl, a
+utility in the smartmontools package for getting S.M.A.R.T. information
+from disks (and other storage devices).
+.PP
+This manpage discusses the \fI\-\-json\fR option which may or may not itself
+have an optional argument. In its shorter form it may either be \fI\-j\fR or
+\fI\-J\fR (lower case preferred if not already in use). The shorter form may
+also take an argument but there must not be a space (or whitespace) between
+\fI\-j\fR and that argument.
+.SH ENVIRONMENT VARIABLES
+The SG3_UTILS_JSON_OPTS environment variable allows the user to override the
+default values of the \fIJO\fR settings. Those settings can again be overridden
+by the command line \fI\-\-json[=JO]\fR option. If the string associated with
+SG3_UTILS_JSON_OPTS cannot be parsed this error message is sent to
+stderr: "error parsing SG3_UTILS_JSON_OPTS environment variable, ignore".
+.SH OPTIONS
+Since the argument to \fI\-\-json[=JO]\fR is optional, in the shorter form
+there can be no space(s) between the option and its argument.
+.TP
+\fB\-j[JO]\fR, \fB\-\-json\fR\fI[=JO]\fR
+\fIJO\fR is a string of 0 or more characters whose order is not significant
+apart from the negation characters ('\-' is preferred). The negation character
+must appear immediately before the (boolean) feature it is toggling.
+.br
+In the short form the option letter may be other than \fI\-j\fR if that letter
+has already been used (\fI\-J\fR is preferred next). For example the sg_ses
+utility uses \fI\-j\fR for its "join" operation. Also since the argument to
+the short form option is itself optional, there can be no space between the
+short form option and \fIJO\fR, if it is given. To make this read a little
+better on the command line, "=" may be first character of \fIJO\fR, so for
+example, this is valid '\-j=av'.
+.SH JSON CONTROL CHARACTERS
+Each \fIJO\fR string is made up of zero or more of the following JSON control
+characters.
+.TP
+\fB0\fR
+If pretty printing JSON output, tab to 2 spaces.
+.TP
+\fB2\fR
+If pretty printing JSON output, tab to 2 spaces.
+.TP
+\fB4\fR
+If pretty printing JSON output, tab to 4 spaces.
+.br
+This is the default tab setting for pretty printing JSON.
+.TP
+\fB8\fR
+If pretty printing JSON output, tab to 8 spaces.
+.TP
+\fB=\fR
+does nothing. May appear as first character of \fIJO\fR. This character is
+defined to make the short option form look better (e.g. '\-j=av').
+.TP
+\fB\-\fR
+negation character. Toggles the (boolean) sense of the following control
+character.
+.TP
+\fBe\fR
+this is a boolean control character for "exit status". If active an "exit
+status" field is placed at the end of the JSON output. The integer value
+of this field is the Unix exit status value that is return to the operating
+system when this utility exits. The value of 0 is a good exit (i.e. no
+errors detected).
+.br
+This boolean control character is default on (true).
+.TP
+\fBh\fR
+this is a boolean control character for "hexadecimal". Many values associated
+with SCSI are best (or at least historically) viewed in hexadecimal while
+JSON output prefers decimal integers (assumed to have a maximum size of 64
+bits, signed). The maximum size of most SCSI fields is 64 bit _unsigned_ .
+Also some SCSI fields are masks which are best viewed in hex. When this
+control character is active most (non\-trivial) fields that have an integer
+value instead receive a a sub\-object containing at least a "i" field with
+the integer value and a "hex" field with the corresponding hex value in a
+JSON string. That hex string has no hex decorations (i.e. no leading '0x'
+nor trailing 'h').
+.br
+This boolean control character is default off (false).
+.TP
+\fBk\fR
+this is a boolean control character for finer control of non\-pretty printed
+JSON output. If the 'p' control character is set on (true) then this option
+has no effect.
+.br
+If the 'p' control character is set off (false) and this control character is
+set off (false) then the single line JSON output contains some spaces for
+readability. If the 'p' control character is set off (false) and this control
+character is set on (true) then the JSON single line JSON output is "packed"
+removing all unnecessary spaces.
+.br
+This boolean control character is default off (false).
+.TP
+\fBl\fR
+this is a boolean control character to control whether lead\-in fields are
+output. Lead\-in fields are at the start of the JSON output and include
+json_format_version and utility_invoked sub\-objects. The utility_invoked
+sub\-object includes name, version_date string fields and an JSON array
+named argv with an entry for each command line argument. If the \fIo\fR
+control character is also active, then if available, the non_JSON output
+is placed in and array called output with one element per line
+of 'normal' output.
+.br
+This boolean control character is default on (true).
+.TP
+\fBn\fR
+this is a boolean control character for "name_extra". It is used to provide
+additional information about the name it is a sub\-object of. The most
+common usage is to spell out an abbreviated name (e.g. a T10 name like "SKSV"
+to "Sense Key Specific Valid"). Another use is to note that a T10 field is
+obsolete and in which T10 standard it first became obsolete. Also if the
+named field's value is a physical quantity where the unit is unclear (e.g. a
+timeout) then "name_extra" can state that (e.g. "unit: millisecond").
+Only some fields have associated "name_extra" data.
+.br
+This boolean control character is default off (false).
+.TP
+\fBo\fR
+this is a boolean control character to control whether normal (i.e.
+non\-JSON) lines of output are placed in a JSON array (one element per
+line of normal output) within the utility_invoked subject (see control
+character \fIl\fR). This control character is active even if the
+\fIl\fR control character is negated).
+.br
+This boolean control character is default off (false).
+.TP
+\fBp\fR
+this boolean control character controls whether the JSON output
+is "pretty printed" or sent in a relatively compact stream suitable
+for more efficient transmission over a communications channel.
+.br
+The pretty printed form of output has one JSON name with its associated
+integer, string or boolean value per line; and one array element per line.
+JSON objects and arrays that have an associated JSON object as their value,
+have their name on a separate line. These lines are indented with the
+current tab setting to indicate the level of nesting. Basically the pretty
+printed form is for human consumption.
+.br
+There are two forms of non\-pretty printed output, see the "packed" control
+character ['k'].
+.br
+This boolean control character is default on (true).
+.TP
+\fBs\fR
+this boolean control character controls whether T10 field values that have
+a defined meaning are broken out with an added JSON sub\-object usually
+named "meaning". When active the field name has a sub\-object that contains
+at least an "i" field with the integer value of the field and a JSON string
+object, usually named "meaning", with a string that corresponds to the T10
+defined meaning of the value in the "i" field.
+.br
+This boolean control character is default on (true).
+.TP
+\fBv\fR
+this is an integer control character that controls the amount of debug output.
+It can be given multiple times to increase the level of JSON debug
+verbosity in the output.
+.br
+Note that this verbose control character is JSON specific while the
+\fI\-\-verbose\fR option (short form: fI\-v\fR often repeated: fI\-vvv\fR) that
+most utilities support is more general.
+.br
+This integer control character is set to 0 by default.
+.SH OUTPUT PROCESSING
+The default remains the same for all utilities that support the
+\fI\-\-json\fR option, namely the decoded information is sent to stdout in
+human readable form. Errors are reported to stderr and may cause the early
+termination of a utility (e.g. command line option syntax error).
+.PP
+When the \fI\-\-json\fR option is given and no errors are detected, then
+only JSON is normally sent to stdout. As the SCSI response is parsed, a JSON
+representation is built as a tree in memory. After all other actions (perhaps
+apart from the final exit status report) that JSON tree is "dumped" to
+stdout. This means if there is any non-JSON output sent to stdout that
+it will appear _before_ the JSON output.
+.PP
+If the 'o' control character is in the \fIJO\fR argument to the
+\fI\-\-json\fR option, then the former "human readable" output is placed in
+a JSON array named "output" within a JSON object named "utility_invoked".
+Each line of the former "human readable" output is placed in its own
+element of the JSON array named "output".
+.PP
+A JSON tree is built in memory as the utility parses the data returned
+from the SCSI device (e.g. sg_vpd parsing a VPD page returned from a
+SCSI INQUIRY command). SCSI "list"s become JSON named arrays (e.g. in
+the Device Identification VPD page there is a "Designation descriptor
+list" that becomes a JSON array named "designation_descriptor_list").
+.PP
+At the completion of the utility that JSON tree is "measured" taking into
+account the form of output (i.e. pretty-printed, single line or packed single
+line). For the pretty-printed JSON output, the size of each indentation in
+spaces is also given (i.e. the tab width). The JSON is then output to a
+single C string, then sent to stdout. If a NULL character (ASCII zero and C
+string terminator) somehow finds its way into a field that should (according
+to the spec) be space padded, then the JSON output may appear truncated.
+.PP
+Note that this JSON processing means that if a utility is aborted for whatever
+reason then no JSON output will appear. With the normal, human readable output
+processing, some output may appear before the utility aborts in such bad
+situations.
+.SH INTERACTION WITH OTHER OPTIONS
+As stated above, the default output is in human readable form using 7 bit
+ASCII. The \fI\-\-json[=JO]\fR option is designed to be an alternative to that
+human readable form. There are other alternative output formats such as the
+response output as a hexadecimal sequence of bytes or in "raw" binary output;
+both of those take precedence over the \fI\-\-json[=JO]\fR option. Other
+specialized output format (e.g. 'sg_inq \-\-export') will usually take
+precedence over JSON output.
+.PP
+When the \fI\-\-raw\fR option is used together with the \fI\-\-inhex=FN\fR
+option only the data input to the utility is interpreted as binary. So the
+output format defaults to human readable form and thus can be changed to
+JSON if the \fI\-\-json[=JO]\fR option is also used.
+.PP
+There is typically only one form of JSON output so options like
+\fI\-\-brief\fR and \fI\-\-quiet\fR are ignored in the JSON output. In some
+cases (i.e 'sg_inq \-\-descriptors') the JSON output is expanded.
+.SH ERRORS
+No attempts have been made to translate errors into JSON form, apart from the
+final "exit_status" JSON object where a value of 0 means "no errors". Exit
+status values indicating a problem range from 1 to 255.
+.PP
+The sg_decode_sense utility will parse SCSI sense data into JSON form if
+requested. So if another utility is failing with a sense data report (most
+often seen when the \fI\-\-verbose\fR option is used). That sense data (in
+hex bytes) could be cut\-and\-paste onto the command line
+following 'sg_decode_sense \-j ' which should then render that sense data
+in JSON.
+.PP
+Otherwise, when a error is detected while JSON output is selected, the error
+message is sent to stderr in human readable form. Typically once an error is
+detected the utility will exit, first dumping the JSON in\-memory tree as
+discussed above and a non\-zero exit status will be set. The JSON output will
+be well formed but missing any fields or list elements following the point
+that the error was detected.
+.PP
+The summary is that when JSON output is selected and an error occurs each
+utility will process the error the same way as it would if JSON output had
+not been selected. In all cases error messages, in human readable form,
+are sent to stderr.
+.SH AUTHORS
+Written by Douglas Gilbert. Some utilities have been contributed, see the
+CREDITS file and individual source files (in the 'src' directory).
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2 or the BSD\-2\-Clause
+license. There is NO warranty; not even for MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils(sg3_utils), smartctl(smartmontools)
diff --git a/doc/sg_bg_ctl.8 b/doc/sg_bg_ctl.8
new file mode 100644
index 00000000..98d56b44
--- /dev/null
+++ b/doc/sg_bg_ctl.8
@@ -0,0 +1,72 @@
+.TH SG_BG_CTL "8" "May 2016" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_bg_ctl \- send SCSI BACKGROUND CONTROL command
+.SH SYNOPSIS
+.B sg_bg_ctl
+[\fI\-\-ctl=CTL\fR] [\fI\-\-help\fR] [\fI\-\-time=TN\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI BACKGROUND CONTROL command to the \fIDEVICE\fR. This command
+was first found in the SBC\-4 draft standard revision 8 (sbc4r08.pdf). It can
+be used to start and stop 'advanced background operations' on the
+\fIDEVICE\fR. Only resource or thin provisioned devices (logical units which
+are typically (solid state) disks) support this command. Those advanced
+background operations often include garbage collection type operations which
+may degrade the disk's performance while they are being performed.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-ctl\fR=\fICTL\fR
+\fICTL\fR is the value placed in the BO_CTL field of the BACKGROUND CONTROL
+command (cdb). It is a two bit field so has 4 variants: 0 does not change
+the host initiated advanced background operations; 1 starts these operations;
+2 stops these operations and 3 is reserved. The default value is 0.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-t\fR, \fB\-\-time\fR=\fITN\fR
+\fITN\fR is a maximum time (with a unit of 100 ms or 1/10 second) that
+advanced background operations can occur. This value is ignored if the
+\fICTL\fR argument is other than 1. The default value is 0 which means there
+is no maximum time limit. Only values 0 to 255 (which is 25.5 seconds) can
+be given. This value is place in the BO_TIME field of the BACKGROUND CONTROL
+command.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+According to T10, support for 'background control operations' is indicated by
+the BOCS bit being set in the Block device characteristics VPD page [0xb1].
+The setting of the BOCS bit can be checked with the sg_vpd and sdparm
+utilities (and it is read only). There is a Background operations control
+mode page [0xa, 0x6] with a BO_MODE field for modifying the action of this
+operation. The BO_MODE field can be accessed and possibly modified with the
+sdparm utility. The BO_STATUS field can be found in the Background operation
+log page [0x15, 0x2] and that can be viewed with the sg_logs utility.
+.PP
+The current draft describing this area is SBC\-4 revision 10 (sbc4r10.pdf)
+in clause 4.33 . That contains the following example of a background
+operation: "Advanced background operation may include NAND block erase
+operations, media read operations, and media write operations (e.g.,
+garbage collection), which may impact response time for normal read requests
+or write requests from the application client."
+.SH EXIT STATUS
+The exit status of sg_bg_ctl is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2016 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd,sg_logs(sg3_utils); sdparm(sdparm)
diff --git a/doc/sg_compare_and_write.8 b/doc/sg_compare_and_write.8
new file mode 100644
index 00000000..83ea2ba5
--- /dev/null
+++ b/doc/sg_compare_and_write.8
@@ -0,0 +1,244 @@
+.TH "COMPARE AND WRITE" "8" "April 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_compare_and_write \- send the SCSI COMPARE AND WRITE command
+.SH SYNOPSIS
+.B sg_compare_and_write
+[\fI\-\-dpo\fR] [\fI\-\-fua\fR] [\fI\-\-fua_nv\fR] [\fI\-\-grpnum=GN\fR]
+[\fI\-\-help\fR] \fI\-\-in=IF\fR [\fI\-\-inw=WF\fR] \fI\-\-lba=LBA\fR
+[\fI\-\-num=NUM\fR] [\fI\-\-quiet\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-wrprotect=WP\fR]
+[\fI\-\-xferlen=LEN\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+Send the SCSI COMPARE AND WRITE command to \fIDEVICE\fR. This utility fetches
+a compare buffer and a write buffer from either one or two files. If the
+\fI\-\-inw=WF\fR option is given then the compare buffer is fetched from the
+file indicated by the \fI\-\-in=IF\fR while the write buffer is fetched from
+the file indicated by the \fI\-\-inw=WF\fR. If the \fI\-\-inw=WF\fR option is
+not given then the concatenated compare and write buffers are fetched from the
+file indicated by the \fI\-\-in=IF\fR option.
+.PP
+Those buffers are expected to each contain \fINUM\fR blocks of data. The
+compare starts at logical block address \fILBA\fR on the \fIDEVICE\fR and if
+the comparison fails (i.e. the provided compare buffer does not equal the data
+at \fILBA\fR on the \fIDEVICE\fR) then the COMPARE AND WRITE command finishes
+with a sense key of MISCOMPARE. In this case this utility will complete and
+set an exit status of 14 (which happens to be the sense key value of
+MISCOMPARE).
+.PP
+If the comparison succeeds then the provided write buffer is stored starting
+at \fILBA\fR for \fINUM\fR blocks on the \fIDEVICE\fR.
+.PP
+The actual number of bytes transferred in the data\-out buffer of the COMPARE
+AND WRITE command may need to be given by the user with the
+\fI\-\-xferlen=LEN\fR option. \fILEN\fR defaults to (2 * \fINUM\fR * 512)
+which is 1024 for the default \fINUM\fR of 1. If the block size is other than
+512 then the user will need to use \fI\-\-xferlen=LEN\fR option. If
+protection information is given (indicated by a value of \fIWP\fR other than
+0 (the default)) then for a \fINUM\fR of 1 \fILEN\fR should be 1040 . Note
+that the SCSI READ CAPACITY command is not performed by this utility (e.g.
+to find the block size).
+.PP
+The T10 definition of the SCSI COMPARE AND WRITE command requires that the
+\fIDEVICE\fR implement the compare and optional write as an uninterrupted
+series of actions. Depending on some other \fIDEVICE\fR settings a
+verify operation may occur prior to the compare.
+.PP
+When a mismatch occurs between the compare buffer and the blocks starting
+at \fILBA\fR read from the \fIDEVICE\fR the sense buffer containing the
+MISCOMPARE sense key causes several messages to be sent to stderr (including
+the offset of the first byte mismatch). To suppress these messages use the
+\fI\-\-quiet\fR option. With or without the \fI\-\-quiet\fR option the exit
+status will be set to 14.
+.PP
+This command is defined in SBC\-3 whose most recent revision is 36. SBC\-3
+and other SCSI documents can be found at https://www.t10.org .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long option name.
+.TP
+\fB\-d\fR, \fB\-\-dpo\fR
+Set the DPO bit in the COMPARE AND WRITE CDB
+.TP
+\fB\-f\fR, \fB\-\-fua\fR
+Set the FUA bit in the COMPARE AND WRITE CDB
+.TP
+\fB\-F\fR, \fB\-\-fua_nv\fR
+Set the FUA_NV bit in the COMPARE AND WRITE CDB. This bit was removed in
+SBC\-3 revision 35d and its position marked as "reserved".
+.TP
+\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
+where \fIGN\fR is the value to be placed in the group number field in the
+COMPARE AND WRITE CDB.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+read data (binary) from file named \fIIF\fR. This will either be the combined
+compare and write buffers (when the \fI\-\-inw=WF\fR option is not given) or
+just the compare buffer (when the \fI\-\-inw=WF\fR option is given). If
+\fIIF\fR is '\-' then stdin (e.g. a pipe) is read.
+.TP
+\fB\-C\fR, \fB\-\-inc\fR=\fIIF\fR
+The same as the \fB\-\-in\fR option.
+.TP
+\fB\-D\fR, \fB\-\-inw\fR=\fIWF\fR
+read data (binary) from file named \fIWF\fR. This will the write buffer
+that will become the second half of the data\-out buffer sent to the
+\fIDEVICE\fR associated with the COMPARE AND WRITE command. Note that
+when this option is given then the \fI\-\-in=IF\fR is expected to hold
+the associated compare buffer.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the logical block address to start the COMPARE AND WRITE
+command. Assumed to be in decimal unless prefixed with '0x' or has a
+trailing 'h'.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to read
+and compare with the verify instance. And given a match, the \fINUM\fR of
+blocks to write starting \fILBA\fR. The default value for \fINUM\fR is 1.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+suppress the sense buffer messages associated with a MISCOMPARE sense key
+that would otherwise be sent to stderr. Still set the exit status to 14
+which is the sense key value indicating a MISCOMPARE.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+where \fITO\fR is the command timeout value in seconds. The default value is
+60 seconds. If \fINUM\fR is large (or zero) a WRITE SAME command may require
+considerably more time than 60 seconds to complete.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWP\fR
+set the WRPROTECT field in the cdb to \fIWP\fR. The default value is 0 which
+implies no protection information is sent (along with the user data) by this
+utility.
+.TP
+\fB\-x\fR, \fB\-\-xferlen\fR=\fILEN\fR
+where \fILEN\fR is the data out buffer length in byte. It defaults to (2 *
+\fINUM\fR * 512) bytes. If the \fIDEVICE\fR block size is other than 512
+bytes or \fIWP\fR is non\-zero (implying additional protection information)
+then this default will be incorrect; the use must supply the correct value
+for \fILEN\fR
+.SH NOTES
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.SH EXAMPLES
+Before overwriting the first two blocks of whatever (SCSI) storage device
+that is chosen, take a small backup. The logical block size is assumed to
+be 512 bytes. Take a copy (in backup01.bin) of the first two blocks::
+.PP
+ # sg_dd if=/dev/sg1 bs=512 of=backup01.bin count=2
+ 2+0 records in
+ 2+0 records out
+.PP
+.B WARNING:
+if /dev/sg1 corresponds to a disk on your system that contains currently
+mounted file systems, do _not_ continue. If you can, unmount all file
+systems on that disk. If that is not possible, use another disk with no
+mounted file systems on it. In Linux the scsi_debug driver is a good
+candidate for experimentation.
+.PP
+Now fill the first block with 0xff bytes:
+.PP
+ # sg_dd iflag=ff bs=512 of=/dev/sg1 count=1
+ 1+0 records in
+ 1+0 records out
+.PP
+and the second block with 0x0 bytes:
+.PP
+ # sg_dd iflag=00 bs=512 seek=1 of=/dev/sg1 count=1
+ 1+0 records in
+ 1+0 records out
+.PP
+Now copy those two blocks into a file:
+.PP
+ # sg_dd if=/dev/sg1 bs=512 of=ff00.bin count=2
+ 2+0 records in
+ 2+0 records out
+.PP
+Now we can do a compare and write command. It is told to compare the first
+block (i.e. LBA 0) with the first block in the given file (i.e. ff00.bin).
+If they are equal (they should be both full of 0xff bytes). Since the
+compare succeeds, it will write the second block in ff00.bin over LBA 0:
+.PP
+ # sg_compare_and_write \-\-in=ff00.bin \-\-lba=0 \-\-num=1 /dev/sg1
+
+.PP
+No news is good news. Now if we do that command again:
+.PP
+ # sg_compare_and_write \-\-in=ff00.bin \-\-lba=0 \-\-num=1 /dev/sg1
+ Miscompare at byte offset: 0 [0x0]
+ sg_compare_and_write failed: Miscompare
+.PP
+This is expected. The first sg_compare_and_write ended up writing 0x0 bytes
+over LBA 0x0. The second sg_compare_and_write command compares LBA 0x0 with
+0xff bytes and fails immediately (i.e. at byte offset: 0). Now we will
+overwrite the first 3 bytes of ff00.bin with 0x0:
+.PP
+ # sg_dd bs=1 iflag=00 of=ff00.bin count=3
+ 3+0 records in
+ 3+0 records out
+.PP
+Notice the 'bs=1' operand. The dd utility (and thus sg_dd) is very useful for
+doing small binary edits on a file. Now if we do that sg_compare_and_write
+again, it still fails but with a small difference:
+.PP
+ # sg_compare_and_write \-\-in=ff00.bin \-\-lba=0 \-\-num=1 /dev/sg1
+ Miscompare at byte offset: 3 [0x3]
+ sg_compare_and_write failed: Miscompare
+.PP
+So the bytes at offset 0, 1, and 2 compared equal but not the byte at
+offset 3. The SCSI COMPARE AND WRITE will stop on the first micompared
+byte.
+.SH EXIT STATUS
+The exit status of sg_compare_and_write is 0 when it is successful. If the
+compare step fails then the exit status is 14. For other exit status values
+see the EXIT STATUS section in the sg3_utils(8) man page.
+.PP
+Earlier versions of this utility set an exit status of 98 when there was a
+MISCOMPARE.
+.SH AUTHORS
+Written by Shahar Salzman. Maintained by Douglas Gilbert. Additions by
+Eric Seppanen.
+.SH "REPORTING BUGS"
+Report bugs to shahar.salzman@kaminario.com or dgilbert@interlog.com
+.SH COPYRIGHT
+Copyright \(co 2012\-2020 Kaminario Technologies LTD
+.br
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+.br
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+.br
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+.br
+* Neither the name of the <organization> nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+.br
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Kaminario Technologies LTD BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+.SH "SEE ALSO"
+.B sg_xcopy, sg_receive_copy_results(sg3_utils)
diff --git a/doc/sg_copy_results.8 b/doc/sg_copy_results.8
new file mode 100644
index 00000000..2e2203bf
--- /dev/null
+++ b/doc/sg_copy_results.8
@@ -0,0 +1,126 @@
+.TH SG_COPY_RESULTS "8" "September 2014" "sg3_utils\-1.40" SG3_UTILS
+.SH NAME
+sg_copy_results \- send SCSI RECEIVE COPY RESULTS command (XCOPY related)
+.SH SYNOPSIS
+.B sg_copy_results
+[\fI\-\-failed\fR|\fI\-\-params\fR|\fI\-\-receive\fR|\fI\-\-status\fR]
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-list_id=ID\fR] [\fI\-\-readonly\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-xfer_len=BTL\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility is designed to query the status of the SCSI Extended
+Copy (XCOPY) facility (see SPC\-3 revision 23 sections 6.3 and 6.17), present
+in some modern storage arrays. This utility sends a SCSI RECEIVE COPY
+RESULTS command to the given \fIDEVICE\fR and displays the response.
+.PP
+During the draft stages of SPC\-4 the T10 committee has expanded the XCOPY
+command so that it now has two variants: "LID1" (for a List Identifier
+length of 1 byte) and "LID4" (for a List Identifier length of 4 bytes).
+This utility supports the older, LID1 variant which is also found in SPC\-3
+and earlier. While the LID1 variant in SPC\-4 is command level (binary)
+compatible with XCOPY as defined in SPC\-3, some of the command naming has
+changed. This utility uses the older, SPC\-3 XCOPY names.
+.PP
+The command has four distinct modes of operation, distinguished by
+the service action field:
+.TP
+\fBCOPY STATUS [SPC\-4: RECEIVE COPY STATUS(LID1)]\fR
+Displays the current status of the EXTENDED COPY command identified by
+the list id field.
+.TP
+\fBRECEIVE DATA [SPC\-4: RECEIVE COPY DATA(LID1)]\fR
+Return the held data read by the EXTENDED COPY command identified by
+the list id field. This option is only meaningful if the respective
+segment descriptor are supported.
+.TP
+\fBOPERATING PARAMETERS [SPC\-4: RECEIVE COPY OPERATING PARAMETERS]\fR
+Return copy manager operating parameters. This option is also useful
+to determine if the SCSI Extended Copy facility is supported.
+.TP
+\fBFAILED SEGMENT DETAILS [SPC\-4: RECEIVE COPY FAILURE DETAILS(LID1)]\fR
+Return copy target device sense data and other information about any
+failed segments.
+
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-f\fR, \fB\-\-failed\fR
+sets the service action field to FAILED SEGMENT DETAILS [4].
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+prints out the response buffer in hex.
+.TP
+\fB\-l\fR, \fB\-\-list_id\fR=\fIID\fR
+sets the list identifier field to \fIID\fR (default: 0).
+.TP
+\fB\-p\fR, \fB\-\-params\fR
+sets the service action field to OPERATING PARAMETERS [3].
+This is the default.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-r\fR, \fB\-\-receive\fR
+sets the service action field to RECEIVE DATA [1].
+.TP
+\fB\-s\fR, \fB\-\-status\fR
+sets the service action field to COPY STATUS [0].
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR
+sets the allocation length field to \fIBTL\fR. It is the byte transfer
+length and is the maximum (byte) size of the response. \fIBTL\fR must be
+less than 10000 and defaults to 520.
+.SH NOTES
+Decoding of \fIRECEIVE DATA\fR service action is not implemented.
+.PP
+In a similar way the functionality of sg_xcopy has been ported to the
+more general ddpt utility (and package), the functionality of this utility
+has been ported to the ddptctl utility.
+.SH EXAMPLES
+Query the operating parameters for a device:
+.PP
+# sg_copy_results \-p /dev/sdo
+.br
+Receive copy results (report operating parameters):
+ Supports no list identifier: no
+ Maximum target descriptor count: 2
+ Maximum segment descriptor count: 1
+ Maximum descriptor list length: 92 bytes
+ Maximum segment length: 33553920 bytes
+ Inline data not supported
+ Held data limit: 0 bytes
+ Maximum stream device transfer size: 0 bytes
+ Total concurrent copies: 0
+ Maximum concurrent copies: 255
+ Data segment granularity: 512 bytes
+ Inline data granularity: 1 bytes
+ Held data granularity: 1 bytes
+ Implemented descriptor list:
+ Segment descriptor 0x02: Copy from block device to block device
+ Target descriptor 0xe4: Identification descriptor
+
+.SH EXIT STATUS
+The exit status of sg_copy_results is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2012\-2014 Hannes Reinecke and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_xcopy(sg3_utils), ddpt,ddptctl(ddpt)
diff --git a/doc/sg_dd.8 b/doc/sg_dd.8
new file mode 100644
index 00000000..df4e94c8
--- /dev/null
+++ b/doc/sg_dd.8
@@ -0,0 +1,614 @@
+.TH SG_DD "8" "August 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_dd \- copy data to and from files and devices, especially SCSI
+devices
+.SH SYNOPSIS
+.B sg_dd
+[\fIbs=BS\fR] [\fIconv=CONV\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR]
+[\fIif=IFILE\fR] [\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR]
+[\fIoflag=FLAGS\fR] [\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR]
+.PP
+[\fIblk_sgio=\fR{0|1}] [\fIbpt=BPT\fR] [\fIcdbsz=\fR{6|10|12|16}]
+[\fIcdl=CDL\fR] [\fIcoe=\fR{0|1|2|3}] [\fIcoe_limit=CL\fR]
+[\fIdio=\fR{0|1}] [\fIodir=\fR{0|1}] [\fIof2=OFILE2\fR]
+[\fIretries=RETR\fR] [\fIsync=\fR{0|1}] [\fItime=\fR{0|1}[,TO]]
+[\fIverbose=VERB\fR] [\fI\-\-dry\-run\fR] [\fI\-\-progress\fR]
+[\fI\-\-verify\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Copy data to and from any files. Specialized for "files" that are Linux SCSI
+generic (sg) devices, raw devices or other devices that support the SG_IO
+ioctl (which are only found in the lk 2.6 series). Similar syntax and
+semantics to
+.B dd(1)
+command.
+.PP
+The first group in the synopsis above are "standard" Unix
+.B dd(1)
+operands. The second group are extra options added by this utility.
+Both groups are defined below.
+.PP
+When the \fI\-\-verify\fR option is given, then the read side is the
+same but the on the write side, the WRITE SCSI command is replaced by
+the VERIFY SCSI command. If any VERIFY commands yields a sense key of
+MISCOMPARE then the verify operation will stop. The \fI\-\-verify\fR
+option can only be used when \fIOFILE\fR is either a sg device or
+a block device with oflag=sgio also given. When the \fI\-\-verify\fR
+option is used, this utility works in a similar fashion to the Unix
+cmp(1) command.
+.PP
+This utility is only supported on Linux whereas most other utilities in the
+sg3_utils package have been ported to other operating systems. A utility
+called "ddpt" has similar syntax and functionality to sg_dd. ddpt drops some
+Linux specific features while adding some other generic features. This allows
+ddpt to be ported to other operating systems.
+.SH OPTIONS
+.TP
+\fBblk_sgio\fR={0|1}
+when set to 0, block devices (e.g. /dev/sda) are treated like normal
+files (i.e.
+.B read(2)
+and
+.B write(2)
+are used for IO). When set to 1, block devices are assumed to accept the
+SG_IO ioctl and SCSI commands are issued for IO. This is only supported
+for 2.6 series kernels. Note that ATAPI devices (e.g. cd/dvd players) use
+the SCSI command set but ATA disks do not (unless there is a protocol
+conversion as often occurs in the USB mass storage class). If the input
+or output device is a block device partition (e.g. /dev/sda3) then setting
+this option causes the partition information to be ignored (since access
+is directly to the underlying device). Default is 0. See the 'sgio' flag.
+.TP
+\fBbpt\fR=\fIBPT\fR
+each IO transaction will be made using \fIBPT\fR blocks (or less if near
+the end of the copy). Default is 128 for logical block sizes less that 2048
+bytes, otherwise the default is 32. So for bs=512 the reads and writes
+will each convey 64 KiB of data by default (less if near the end of the
+transfer or memory restrictions). When cd/dvd drives are accessed, the
+logical block size is typically 2048 bytes and bpt defaults to 32 which
+again implies 64 KiB transfers. The block layer when the blk_sgio=1 option
+is used has relatively low upper limits for transfer sizes (compared
+to sg device nodes, see /sys/block/<dev_name>/queue/max_sectors_kb ).
+.TP
+\fBbs\fR=\fIBS\fR
+where \fIBS\fR
+.B must
+be the logical block size of the physical device (if either the input or
+output files are accessed via SCSI commands). Note that this differs from
+.B dd(1)
+which permits \fIBS\fR to be an integral multiple. Default is 512 which
+is usually correct for disks but incorrect for cdroms (which normally
+have 2048 byte blocks). For this utility the maximum size of each individual
+IO operation is \fIBS\fR * \fIBPT\fR bytes.
+.TP
+\fBcdbsz\fR={6|10|12|16}
+size of SCSI READ and/or WRITE commands issued on sg device
+names (or block devices when 'iflag=sgio' and/or 'oflag=sgio' is given).
+Default is 10 byte SCSI command blocks (unless calculations indicate
+that a 4 byte block number may be exceeded or \fIBPT\fR is greater than
+16 bits (65535), in which case it defaults to 16 byte SCSI commands).
+.TP
+\fBcdl\fR=\fICDL\fR
+allows setting of command duration limits. \fICDL\fR is either a single value
+or two values separated by a comma. If one value is given, it applies to both
+\fIIFILE\fR and \fIOFILE\fR (if they are pass\-through devices). If two
+values are given, the first applies to \fIIFILE\fR while the second applies
+to \fIOFILE\fR. The value may be from 0 to 7 where 0 is the default and means
+there are no command duration limits. Command duration limits are only
+supported by 16 byte READ and WRITE commands (plus READ(32), WRITE(32) and
+the WRITE SCATTERED command, bit they are used by this utility). If the
+cdbsz operand is not given and would have a value less than 16, then if
+\fICDL\fR is greater than 0, the cdbsz is increased to 16.
+.br
+Command duration limits can be accesses and changed in the Command duration
+limit A and B mode pages, plus the Command duration limit T2A and T2B mode
+pages. The sdparm utility may be used to access and change these mode pages.
+.TP
+\fBcoe\fR={0|1|2|3}
+set to 1 or more for continue on error ('coe'). Only applies to errors on sg
+devices or block devices with the 'sgio' flag set. Thus errors on other
+files will stop sg_dd. Default is 0 which implies stop on any error. See
+the 'coe' flag for more information.
+.TP
+\fBcoe_limit\fR=\fICL\fR
+where \fICL\fR is the maximum number of consecutive bad blocks stepped
+over (due to "coe>0") on reads before the copy terminates. This only
+applies when \fIIFILE\fR is accessed via the SG_IO ioctl. The default
+is 0 which is interpreted as no limit. This option is meant to stop
+the copy soon after unrecorded media is detected while still
+offering "continue on error" capability.
+.TP
+\fBconv\fR=\fBsparse\fR
+see the CONVERSIONS section below.
+.TP
+\fBcount\fR=\fICOUNT\fR
+copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the
+minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices
+report from SCSI READ CAPACITY commands or that block devices (or their
+partitions) report. Normal files are not probed for their size. If
+\fIskip=SKIP\fR or \fIseek=SEEK\fR are given and the count is derived (i.e.
+not explicitly given) then the derived count is scaled back so that the
+copy will not overrun the device. If the file name is a block device
+partition and \fICOUNT\fR is not given then the size of the partition
+rather than the size of the whole device is used. If \fICOUNT\fR is not
+given (or \fIcount=\-1\fR) and cannot be derived then an error message is
+issued and no copy takes place.
+.TP
+\fBdio\fR={0|1}
+default is 0 which selects indirect (buffered) IO on sg devices. Value of 1
+attempts direct IO which, if not available, falls back to indirect IO and
+notes this at completion. If direct IO is selected and
+/sys/module/sg/parameters/allow_dio has the value of 0 then a warning is
+issued (and indirect IO is performed). For finer grain control
+use 'iflag=dio' or 'oflag=dio'.
+.TP
+\fBibs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBif\fR=\fIIFILE\fR
+read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin
+is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR
+is given.
+.TP
+\fBiflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIIFILE\fR and are ignored when
+\fIIFILE\fR is stdin.
+.TP
+\fBobs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBodir\fR={0|1}
+when set to one opens block devices (e.g. /dev/sda) with the O_DIRECT
+flag. User memory buffers are aligned to the page size when set. The
+default is 0 (i.e. the O_DIRECT flag is not used). Has no effect on sg,
+normal or raw files. If blk_sgio is also set then both are honoured:
+block devices are opened with the O_DIRECT flag and SCSI commands are
+issued via the SG_IO ioctl.
+.TP
+\fBof\fR=\fIOFILE\fR
+write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes
+to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed.
+If \fIOFILE\fR is '.' (period) then it is treated the same way as
+/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it
+is _not_ truncated; it is overwritten from the start of \fIOFILE\fR
+unless 'oflag=append' or \fISEEK\fR is given.
+.TP
+\fBof2\fR=\fIOFILE2\fR
+write output to \fIOFILE2\fR. The default action is not to do this additional
+write (i.e. when this option is not given). \fIOFILE2\fR is assumed to be
+a normal file or a fifo (i.e. a named pipe). \fIOFILE2\fR is opened for
+writing, created if necessary, and closed at the end of the transfer. If
+\fIOFILE2\fR is a fifo (named pipe) then some other command should be
+consuming that data (e.g. 'md5sum OFILE2'), otherwise this utility will block.
+.TP
+\fBoflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIOFILE\fR and are ignored when
+\fIOFILE\fR is /dev/null, '.' (period), or stdout.
+.TP
+\fBretries\fR=\fIRETR\fR
+sometimes retries at the host are useful, for example when there is a
+transport error. When \fIRETR\fR is greater than zero then SCSI READs and
+WRITEs are retried on error, \fIRETR\fR times. Default value is zero.
+.TP
+\fBseek\fR=\fISEEK\fR
+start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBskip\fR=\fISKIP\fR
+start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBsync\fR={0|1}
+when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the
+transfer. Only active when \fIOFILE\fR is a sg device file name or a block
+device and 'blk_sgio=1' is given.
+.TP
+\fBtime\fR={0|1}[,\fITO\fR]
+when 1, times transfer and does throughput calculation, outputting the
+results (to stderr) at completion. When 0 (default) doesn't perform timing.
+.br
+If that value is followed by a comma, then \fITO\fR is the command timeout
+in seconds for SCSI READ, WRITE or VERIFY commands issued by this utility.
+The default is 60 seconds.
+.TP
+\fBverbose\fR=\fIVERB\fR
+as \fIVERB\fR increases so does the amount of debug output sent to stderr.
+Default value is zero which yields the minimum amount of debug output.
+A value of 1 reports extra information that is not repetitive. A value
+2 reports cdbs and responses for SCSI commands that are not repetitive
+(i.e. other that READ and WRITE). Error processing is not considered
+repetitive. Values of 3 and 4 yield output for all SCSI commands (and
+Unix read() and write() calls) so there can be a lot of output.
+This only occurs for scsi generic (sg) devices and block devices when
+the 'blk_sgio=1' option is set.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+does all the command line parsing and preparation but bypasses the actual
+copy or read. That preparation may include opening \fIIFILE\fR or
+\fIOFILE\fR to determine their lengths. This option may be useful for
+testing the syntax of complex command line invocations in advance of
+executing them.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs usage message and exits.
+.TP
+\fB\-p\fR, \fB\-\-progress\fR
+this option causes a progress report to be output every two minutes until
+the copy is complete. After the copy is complete a line with "completed"
+is printed to distinguish the final report from the prior progress reports.
+When used twice the progress report is every minute, when used three times
+the progress report is every 30 seconds.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+when used once, this is equivalent to \fIverbose=1\fR. When used
+twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc.
+.TP
+\fB\-x\fR, \fB\-\-verify\fR
+do a verify operation (like Unix command cmp(1)) rather than a copy. Cannot
+be used with "oflag=sparse". \fIof=OFILE\fR must be given and \fIOFILE\fR
+must be an sg device or a block device with "oflag=sgio" also given. Uses the
+SCSI VERIFY command with the BYTCHK field set to 1. The VERIFY command is
+used instead of WRITE when this option is given. There is no VERIFY(6)
+command. Stops on the first miscompare unless \fIoflag=coe\fR is given.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+outputs version number information and exits.
+.SH CONVERSIONS
+One or more conversions can be given to the "conv=" option. If more than one
+is given, they should be comma separated. sg_dd does not perform the
+traditional dd conversions (e.g. ASCII to EBCDIC). Recently added
+conversions overlap somewhat with the flags so some conversions are
+now supported by sg_dd.
+.TP
+nocreat
+this conversion has the same effect as "oflag=nocreat", namely: \fIOFILE\fR
+must exist, it will not be created.
+.TP
+noerror
+this conversion is very close to "iflag=coe" and is treated as such. See
+the "coe" flag. Note that an error on \fIOFILE\fR will stop the copy.
+.TP
+notrunc
+this conversion is accepted for compatibility with dd and ignored since
+the default action of this utility is not to truncate \fIOFILE\fR.
+.TP
+null
+has no affect, just a placeholder.
+.TP
+sparse
+FreeBSD supports "conv=sparse" so the same syntax is supported in sg_dd.
+See "sparse" in the FLAGS sections for more information.
+.TP
+sync
+is ignored by sg_dd. With dd it means supply zero fill (rather than skip)
+and is typically used like this "conv=noerror,sync" to have the same
+functionality as sg_dd's "iflag=coe".
+.SH FLAGS
+Here is a list of flags and their meanings:
+.TP
+00
+this flag is only active with \fIiflag=\fR and when given replaces
+\fIif=IFILE\fR. If both are given an error is generated. The input will
+be a stream of zeros, similar to using "if=/dev/zero" alone (but a little
+quicker), apart from the following case.
+.br
+If 'iflag=00,ff' is given then the block address (lower 32 bits, in 4
+bytes, big endian) is placed, multiple times, in each block. The block
+address takes into account the \fIskip=SKIP\fR setting. The
+.B sgp_dd
+utility has a \fI\-\-chkaddr\fR option that complements this option.
+.TP
+append
+causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For regular
+files this will lead to data appended to the end of any existing data. Cannot
+be used together with the \fIseek=SEEK\fR option as they conflict. The default
+action of this utility is to overwrite any existing data from the beginning
+of the file or, if \fISEEK\fR is given, starting at block \fISEEK\fR. Note
+that attempting to 'append' to a device file (e.g. a disk) will usually be
+ignored or may cause an error to be reported.
+.TP
+coe
+continue on error. Only active for sg devices and block devices that have
+the 'sgio' flag set. 'iflag=coe oflag=coe' and 'coe=1' are equivalent. Use
+this flag twice (e.g. 'iflag=coe,coe') to have the same action as the 'coe=2'.
+A medium, hardware or blank check error while reading will re\-read blocks
+prior to the bad block, then try to recover the bad block, supplying zeros
+if that fails, and finally re\-read the blocks after the bad block. A medium,
+hardware or blank check error while writing is noted and ignored. A miscompare
+sense key during a VERIFY command (i.e. \fI\-\-verify\fR given) is noted and
+ignored when 'oflag=coe'. The recovery of the bad block when reading uses the
+SCSI READ LONG command if 'coe' given twice or more (also with the command
+line option 'coe=2'). Further, the READ LONG will set its CORRCT bit if 'coe'
+given thrice. SCSI disks may automatically try and remap faulty sectors (see
+the AWRE and ARRE in the read write error recovery mode page (the sdparm
+utility can access and possibly change these attributes)). Errors occurring on
+other files types will stop sg_dd. Error messages are sent to stderr. This
+flag is similar to 'conv=noerror,sync' in the
+.B dd(1)
+utility. See note about READ LONG below.
+.TP
+dio
+request the sg device node associated with this flag does direct IO. If direct
+IO is not available, falls back to indirect IO and notes this at completion.
+If direct IO is selected and /sys/module/sg/parameters/allow_dio has the
+value of 0 then a warning is issued (and indirect IO is performed).
+.TP
+direct
+causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user
+memory buffers are aligned to the page size. Has no effect on sg, normal
+or raw files. If 'iflag=sgio' and/or 'oflag=sgio' is also set then both
+are honoured: block devices are opened with the O_DIRECT flag and SCSI
+commands are issued via the SG_IO ioctl.
+.TP
+dpo
+set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not
+supported for 6 byte cdb variants of READ and WRITE. Indicates that data is
+unlikely to be required to stay in device (e.g. disk) cache. May speed media
+copy and/or cause a media copy to have less impact on other device users.
+.TP
+dsync
+causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. The 'd' is prepended to lower confusion with the 'sync=0|1'
+option which has another action (i.e. a synchronisation to media at the
+end of the transfer).
+.TP
+excl
+causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR.
+.TP
+ff
+this flag is only active with \fIiflag=\fR and when given replaces
+\fIif=IFILE\fR. If both are given an error is generated. The input will be
+a stream of 0xff bytes (or all bits set), apart from the following case.
+.br
+If 'iflag=00,ff' is given then the block address (lower 32 bits, in 4
+bytes, big endian) is placed, multiple times, in each block. The block
+address takes into account the \fIskip=SKIP\fR setting.
+.TP
+flock
+after opening the associated file (i.e. \fIIFILE\fR and/or \fIOFILE\fR)
+an attempt is made to get an advisory exclusive lock with the flock()
+system call. The flock arguments are "FLOCK_EX | FLOCK_NB" which will
+cause the lock to be taken if available else a "temporarily unavailable"
+error is generated. An exit status of 90 is produced in the latter case
+and no copy is done.
+.TP
+fua
+causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE
+commands. This only has an effect with sg devices or block devices
+that have the 'sgio' flag set. The 6 byte variants of the SCSI READ and
+WRITE commands do not support the FUA bit.
+.TP
+nocache
+use posix_fadvise() to advise corresponding file there is no need to fill
+the file buffer with recently read or written blocks.
+.TP
+nocreat
+this flag is only active in \fIoflag=FLAGS\fR. If present then \fIOFILE\fR
+will be opened if it exists. If \fIOFILE\fR doesn't exist then an error
+is generated. Without this flag a regular (empty) file named \fIOFILE\fR
+will be created (and then filled). For production quality scripts where
+\fIOFILE\fR is a device node (e.g. '/dev/sdc') this flag is recommended.
+It guards against the remote possibility of 'dev/sdc' disappearing
+temporarily (e.g. a USB memory key removed) resulting in a large regular
+file called '/dev/sdc' being created.
+.TP
+null
+has no affect, just a placeholder.
+.TP
+random
+this flag is only active with \fIiflag=\fR and when given replaces
+\fIif=IFILE\fR. If both are given an error is generated. The input will
+be a stream of pseudo random bytes. The Linux getrandom(2) system call is
+used to create a seed and there after mrand48(3) is used to generate a
+pseudo random sequence, 4 bytes at a time. The quality of the randomness
+can be viewed with the ent(1) utility. This is not a high quality random
+number generator, it is built for speed, not quality. One application is
+checking the correctness of the copy and verify operations of this utility.
+.TP
+sgio
+causes block devices to be accessed via the SG_IO ioctl rather than
+standard UNIX read() and write() commands. When the SG_IO ioctl is
+used the SCSI READ and WRITE commands are used directly to move
+data. sg devices always use the SG_IO ioctl. This flag offers finer
+grain control compared to the otherwise identical 'blk_sgio=1' option.
+.TP
+sparse
+after each \fIBS\fR * \fIBPT\fR byte segment is read from the input,
+it is checked for being all zeros. If so, nothing is written to the output
+file unless this is the last segment of the transfer. This flag is only
+active with the oflag option. It cannot be used when the output is not
+seekable (e.g. stdout). It is ignored if the output file is /dev/null .
+Note that this utility does not remove the \fIOFILE\fR prior to starting
+to write to it. Hence it may be advantageous to manually remove the
+\fIOFILE\fR if it is large prior to using oflag=sparse. The last segment
+is always written so regular files will show the same length and so
+programs like md5sum and sha1sum will generate the same value regardless
+of whether oflag=sparse is given or not. This option may be used when the
+\fIOFILE\fR is a raw device but is probably only useful if the device is
+known to contain zeros (e.g. a SCSI disk after a FORMAT command).
+.SH RETIRED OPTIONS
+Here are some retired options that are still present:
+.TP
+append=0 | 1
+when set, equivalent to 'oflag=append'. When clear the action is
+to overwrite the existing file (if it exists); this is the default.
+See the 'append' flag.
+.TP
+fua=0 | 1 | 2 | 3
+force unit access bit. When 3, fua is set on both \fIIFILE\fR and
+\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR;, when 1, fua is set on
+\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag.
+.SH NOTES
+Block devices (e.g. /dev/sda and /dev/hda) can be given for \fIIFILE\fR.
+If neither '\-iflag=direct', 'iflag=sgio' nor 'blk_sgio=1' is given then
+normal block IO involving buffering and caching is performed. If
+only '\-iflag=direct' is given then the buffering and caching is
+bypassed (this is applicable to both SCSI devices and ATA disks).
+If 'iflag=sgio' or 'blk_sgio=1' is given then the SG_IO ioctl is used on
+the given file causing SCSI commands to be sent to the device and that also
+bypasses most of the actions performed by the block layer (this is only
+applicable to SCSI devices, not ATA disks). The same applies for block
+devices given for \fIOFILE\fR.
+.PP
+Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit
+values (i.e. very big numbers). Other values are limited to what can fit in
+a signed 32 bit number.
+.PP
+Data usually gets to the user space in a 2 stage process: first the
+SCSI adapter DMAs into kernel buffers and then the sg driver copies
+this data into user memory (write operations reverse this sequence).
+This is called "indirect IO" and there is a 'dio' option to
+select "direct IO" which will DMA directly into user memory. Due to some
+issues "direct IO" is disabled in the sg driver and needs a
+configuration change to activate it. This is typically done
+with 'echo 1 > /sys/module/sg/parameters/allow_dio'.
+.PP
+All informative, warning and error output is sent to stderr so that
+dd's output file can be stdout and remain unpolluted. If no options
+are given, then the usage message is output and nothing else happens.
+.PP
+Even if READ LONG succeeds on a "bad" block when 'coe=2' (or 'coe=3')
+is given, the recovered data may not be useful. There are no guarantees
+that the user data will appear "as is" in the first 512 bytes.
+.PP
+A raw device must be bound to a block device prior to using sg_dd.
+See
+.B raw(8)
+for more information about binding raw devices. To be safe, the sg device
+mapping to SCSI block devices should be checked with sg_map before use.
+.PP
+Disk partition information can often be found with
+.B fdisk(8)
+[the "\-ul" argument is useful in this respect].
+.PP
+For sg devices (and block devices when blk_sgio=1 is given) this utility
+issues SCSI READ and WRITE (SBC) commands which are appropriate for disks and
+reading from CD/DVD/HD\-DVD/BD drives. Those commands
+are not formatted correctly for tape devices so sg_dd should not be used on
+tape devices. If the largest block address of the requested transfer
+exceeds a 32 bit block number (i.e 0xffff) then a warning is issued and
+the sg device is accessed via SCSI READ(16) and WRITE(16) commands.
+.PP
+The attributes of a block device (partition) are ignored when 'blk_sgio=1'
+is used. Hence the whole device is read (rather than just the second
+partition) by this invocation:
+.PP
+ sg_dd if=/dev/sdb2 blk_sgio=1 of=t bs=512
+.SH EXAMPLES
+.PP
+Looks quite similar in usage to dd:
+.PP
+ sg_dd if=/dev/sg0 of=t bs=512 count=1MB
+.PP
+This will copy 1 million 512 byte blocks from the device associated with
+/dev/sg0 (which should have 512 byte blocks) to a file called t.
+Assuming /dev/sda and /dev/sg0 are the same device then the above is
+equivalent to:
+.PP
+ dd if=/dev/sda iflag=direct of=t bs=512 count=1000000
+.PP
+although dd's speed may improve if bs was larger and count was suitably
+reduced. The use of the 'iflag=direct' option bypasses the buffering and
+caching that is usually done on a block device.
+.PP
+Using a raw device to do something similar on a ATA disk:
+.PP
+ raw /dev/raw/raw1 /dev/hda
+.br
+ sg_dd if=/dev/raw/raw1 of=t bs=512 count=1MB
+.PP
+To copy a SCSI disk partition to an ATA disk partition:
+.PP
+ raw /dev/raw/raw2 /dev/hda3
+.br
+ sg_dd if=/dev/sg0 skip=10123456 of=/dev/raw/raw2 bs=512
+.PP
+This assumes a valid partition is found on the SCSI disk at the given
+skip block address (past the 5 GB point of that disk) and that
+the partition goes to the end of the SCSI disk. An explicit count
+is probably a safer option. The partition is copied to /dev/hda3 which
+is an offset into the ATA disk /dev/hda . The exact number of blocks
+read from /dev/sg0 are written to /dev/hda (i.e. no padding).
+.PP
+To time a streaming read of the first 1 GB (2 ** 30 bytes) on a disk
+this utility could be used:
+.PP
+ sg_dd if=/dev/sg0 of=/dev/null bs=512 count=2m time=1
+.PP
+On completion this will output a line like:
+"time to transfer data was 18.779506 secs, 57.18 MB/sec". The "MB/sec"
+in this case is 1,000,000 bytes per second.
+.PP
+The 'of2=' option can be used to copy data and take a md5sum of it
+without needing to re\-read the data:
+.PP
+ mkfifo fif
+.br
+ md5sum fif &
+.br
+ sg_dd if=/dev/sg3 iflag=coe of=sg3.img oflag=sparse of2=fif bs=512
+.PP
+This will image /dev/sg3 (e.g. an unmounted disk) and place the contents
+in the (sparse) file sg3.img . Without re\-reading the data it will also
+perform a md5sum calculation on the image.
+.SH SIGNALS
+The signal handling has been borrowed from dd: SIGINT, SIGQUIT and
+SIGPIPE output the number of remaining blocks to be transferred and
+the records in + out counts; then they have their default action.
+SIGUSR1 causes the same information to be output yet the copy continues.
+All output caused by signals is sent to stderr.
+.SH EXIT STATUS
+The exit status of sg_dd is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page. Since this utility works at a higher level
+than individual commands, and there are 'coe' and 'retries' flags,
+individual SCSI command failures do not necessary cause the process
+to exit.
+.PP
+An additional exit status of 90 is generated if the flock flag is given
+and some other process holds the advisory exclusive lock.
+.SH AUTHORS
+Written by Douglas Gilbert and Peter Allworth.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+cmp(1)
+.PP
+There is a web page discussing sg_dd at https://sg.danny.cz/sg/sg_dd.html
+.PP
+A POSIX threads version of this utility called
+.B sgp_dd
+is in the sg3_utils package. Another version from that package is called
+.B sgm_dd
+and it uses memory mapped IO to speed transfers from sg devices.
+.PP
+The lmbench package contains
+.B lmdd
+which is also interesting. For moving data to and from tapes see
+.B dt
+which is found at https://www.scsifaq.org/RMiller_Tools/index.html
+.PP
+To change mode parameters that effect a SCSI device's caching and error
+recovery see
+.B sdparm(sdparm)
+.PP
+To verify the data on the media or to verify it against some other
+copy of the data see
+.B sg_verify(sg3_utils)
+.PP
+See also
+.B raw(8), dd(1), ddrescue(GNU), ddpt
diff --git a/doc/sg_decode_sense.8 b/doc/sg_decode_sense.8
new file mode 100644
index 00000000..e91d6892
--- /dev/null
+++ b/doc/sg_decode_sense.8
@@ -0,0 +1,219 @@
+.TH SG_DECODE_SENSE "8" "August 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_decode_sense \- decode SCSI sense and related data
+.SH SYNOPSIS
+.B sg_decode_sense
+[\fI\-\-binary=BFN\fR] [\fI\-\-cdb\fR] [\fI\-\-err=ES\fR] [\fI\-\-file=HFN\fR]
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-inhex=HFN\fR]
+[\fI\-\-ignore\-first\fR] [\fI\-\-json[=JO]\fR] [\fI\-\-nodecode\fR]
+[\fI\-\-nospace\fR] [\fI\-\-status=SS\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-write=WFN\fR] [H1 H2 H3 ...]
+.SH DESCRIPTION
+.\" Add any additional description here
+This utility takes SCSI sense data in binary or as a sequence of ASCII
+hexadecimal bytes and decodes it. The primary reference for the
+decoding is SPC\-5 ANSI INCITS 502\-2020 and the most recent draft
+SPC\-6 revision 6 which can be found at https://www.t10.org and other
+locations on the internet.
+.PP
+SCSI sense data is often found in kernel log files as a result of
+something going wrong or may be an informative warning. It is often shown
+as a sequence of hexadecimal bytes, starting with 70, 71, 72, 73, f0 or f1.
+Sense data could be up to 252 bytes long but typically is much shorter
+than that, 18 bytes long is often seen and is usually associated with
+the older "fixed" format sense data.
+.PP
+The sense data can be provided on the command line or in a file. If given
+on the command line the sense data should be a sequence of hexadecimal bytes
+separated by space. Alternatively a file can be given with the contents in
+binary or ASCII hexadecimal bytes. The latter form can contain several lines
+each with none, one or more ASCII hexadecimal bytes separated by
+space (comma or tab). The hash symbol may appear and it and the rest of the
+line is ignored making it useful for comments.
+.PP
+If the \fI\-\-cdb\fR option is given then rather than viewing the given hex
+arguments as sense data, it is viewed as a SCSI command descriptor
+block (CDB). In this case the command name is printed out. That name is
+based on the first hex byte given (know as the opcode) and optionally on
+another field called the "service action".
+.PP
+Another alternate action is when the \fI\-\-err=ES\fR is given. \fIES\fR
+is assumed to be an "exit status" value between 0 and 255 from one of the
+utilities in this package. A descriptive string is printed. Other options
+are ignored apart from \fI\-\-verbose\fR.
+.PP
+When the \fI\-\-nodecode\fR option is given, this utility may be used to
+convert a binary file to hexadecimal or vice versa. The data converted does
+not need to be sense data.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-binary\fR=\fIBFN\fR
+the data is read in binary from a file called \fIBFN\fR. The option
+cannot be given with \fI\-\-file=HFN\fR or \fI\-\-inhex=HFN\fR as they
+contradict. The data is assumed to be sense data unless the
+fI\-\-nodecode\fR is given.
+.TP
+\fB\-c\fR, \fB\-\-cdb\fR
+treat the given string of hex arguments as bytes in a SCSI CDB and
+decode the command name.
+.TP
+\fB\-e\fR, \fB\-\-err\fR=\fIES\fR
+\fIES\fR should be an "exit status" value between 0 and 255 that is
+available from the shell (i.e. the utility's execution context) after the
+utility is finished. By default an indicative error message is printed to
+stdout; and if the \fI\-\-verbose\fR option is given once (or an odd number
+of times) then the message is instead printed to stderr. If \fI\-\-verbose\fR
+is given two or more times a longer form of the message is output. In all
+cases the message is less than 128 characters long with one trailing line
+feed. All other command line options and arguments are ignored.
+.TP
+\fB\-f\fR, \fB\-\-file\fR=\fIHFN\fR
+the sense data is read in ASCII hexadecimal from a file called \fIHFN\fR.
+The sense data should appear as a sequence of bytes separated by space,
+comma, tab, hyphen or newline. Everything from and including a hash symbol
+to the end of that line is ignored. If \fI\-\-nospace\fR is set then no
+separator is required between the ASCII hexadecimal digits in \fIHFN\fR
+with bytes decoded from pairs of ASCII hexadecimal digits.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+this option is used once in conjunction with \fI\-\-write=WFN\fR in order to
+change the output written to \fIWFN\fR to lines of ASCII hex bytes suitable
+for a C language compiler. Each line contains up to 16 bytes (e.g. a line
+starting with "0x3b,0x07,0x00,0xff").
+.br
+In other cases (i.e. when \fI\-\-write=WFN\fR is not given, or this option
+is given more than once) then the output is as described in the sg3_utils(8)
+manpage.
+.br
+The combination of \fI\-\-inhex=HFN\fR and this option used three times
+can be useful to converting hexadecimal bytes (e.g. hyphen separated) into
+a more regular form. The short option form is more convenient for invoking
+this option three times (e.g. '\-HHH').
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIHFN\fR
+same action as \fI\-\-file=HFN\fR. This option was added for compatibility
+with other utilities in this package that have a \fI\-\-inhex=\fR option.
+.TP
+\fB\-I\fR, \fB\-\-ignore\-first\fR
+many programs that output hex bytes (e.g. 'hexdump \-C') have a running
+count (or index) in the first column of each line. This option ignores the
+first hexadecimal value on each line. This option has no effect if
+\fI\-\-binary=BFN\fR or \fI\-\-nospace\fR are given. Blank lines and any
+character from and after "#" on a line are ignored. Useful with the
+\fI\-\-file=HFN\fR and \fI\-\-nodecode\fR options.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.br
+This option is designed to parse sense data into JSON output.
+.TP
+\fB\-N\fR, \fB\-\-nodecode\fR
+Do not decode the given data as sense or a cdb. Useful when arbitrary data
+is given (e.g. when converting hex to binary or vice versa).
+.TP
+\fB\-n\fR, \fB\-\-nospace\fR
+expect ASCII hexadecimal to be a string of hexadecimal digits with no
+spaces between them. Bytes are decoded by taking two hexadecimal digits
+at a time, so an even number of digits is expected. The string of
+hexadecimal digits may be on the command line (replacing "H1 H2 H3")
+or spread across multiple lines the \fIHFN\fR given to \fI\-\-file=\fR.
+On the command line, spaces (or other whitespace characters) between
+sequences of hexadecimal digits are ignored; the maximum command line
+hex string is 1023 characters long.
+.TP
+\fB\-s\fR, \fB\-\-status\fR=\fISS\fR
+where \fISS\fR is a SCSI status byte value, given in hexadecimal. The
+SCSI status byte is related to, but distinct from, sense data.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-write\fR=\fIWFN\fR
+writes the sense data out to a file called \fIWFN\fR. If necessary \fIWFN\fR
+is created. If \fIWFN\fR exists then it is truncated prior to writing the
+sense data to it. If the \fI\-\-hex\fR option is also given then ASCII hex
+is written to \fIWFN\fR (see the \fI\-\-hex\fR option description);
+otherwise binary is written to \fIWFN\fR. This option is a convenience and
+may be helpful in converting the ASCII hexadecimal representation of sense
+data (or anything else) into the equivalent binary or a compilable ASCII
+hex form.
+.SH NOTES
+Unlike most utilities in this package, this utility does not access a
+SCSI device (logical unit). This utility accesses a library associated
+with this package. Amongst other things the library decodes SCSI sense
+data.
+.PP
+The sg_raw utility takes a ASCII hexadecimal sequence representing a SCSI
+CDB. When sg_raw is given the '\-vvv' option, it will attempt to decode the
+CDB name.
+.PP
+Using the option combination: "\fI\-\-inhex=HFN \-\-nodecode \-\-write=WFN\fR"
+may be used to convert hexadecimal (as produced by this and other utilities
+in this package) to binary where the output file is \fIWFN\fR.
+.PP
+Unlike many other utilities there is no \fI\-\-raw\fR option. However binary
+data can be input using the \fI\-\-binary=BFN\fR option while binary data
+can be output using the \fI\-\-write=WFN\fR option (in the absence of the
+\fI\-\-hex\fR option).
+.SH EXAMPLES
+Sense data is often printed out in kernel logs and sometimes on the
+command line when verbose or debug flags are given. It will be at least
+8 bytes long, often 18 bytes long but may be longer. A sense data string
+might look like this:
+.PP
+f0 00 03 00 00 12 34 0a 00 00 00 00 11 00 00 00
+.br
+00 00
+.PP
+Cut and paste it after the sg_decode_sense command:
+.PP
+ sg_decode_sense f0 00 03 00 00 12 34 0a 00 00 00 00 11 00 00 00 00 00
+.PP
+and for this sense data the output should look like this:
+.PP
+ Fixed format, current; Sense key: Medium Error
+.br
+ Additional sense: Unrecovered read error
+.br
+ Info fld=0x1234 [4660]
+.PP
+For a medium error the Info field is the logical block address (LBA)
+of the lowest numbered block that the associated SCSI command was not
+able to read (verify or write).
+.PP
+To convert arbitrary binary data to hex, suitable to be parsed by other
+sg3_utils utilities. The \fI\-\-nodecode\fR option is used in this case:
+.PP
+ sg_decode_sense \-N \-i vpd_zbdc.hex \-w vpd_zbdc.bin
+.PP
+The '\-HHH' will output hex to the console (stdout) in a form suitable for
+other utilities in this package to parse as input. And sg_decode_sense can
+also be used to convert from arbitrary hex to binary with:
+.PP
+ sg_decode_sense \-N \-b vpd_zbdc.raw \-HHH
+.PP
+Note that tools like hexdump and od place a counter (i.e. an index starting
+at 0) at the beginning of each line which is a pain when parsing hex.
+The '/-HHH' option(s) does not output that leading counter on each line.
+.SH EXIT STATUS
+The exit status of sg_decode_sense is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2010\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_requests,sg_raw(sg3_utils)
diff --git a/doc/sg_emc_trespass.8 b/doc/sg_emc_trespass.8
new file mode 100644
index 00000000..94b592bd
--- /dev/null
+++ b/doc/sg_emc_trespass.8
@@ -0,0 +1,52 @@
+.TH SG_EMC_TRESPASS "8" "December 2012" "sg3_utils\-1.35" SG3_UTILS
+.SH NAME
+sg_emc_trespass \- change ownership of SCSI LUN from another
+Service\-Processor to this one
+.SH SYNOPSIS
+.B sg_emc_trespass
+[\fI\-d\fR] [\fI\-hr\fR] [\fI\-s\fR]
+[\fI\-V\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_emc_trespass sends an EMC\-specific Trespass Command to the \fIDEVICE\fR
+with the selected options. This Mode Select changes the ownership of the LUN
+of the device from another Service\-Processor to the one the command was
+received on.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR
+outputs some extra debug information associated with executing this command
+.TP
+\fB\-hr\fR
+Sets the 'Honor Reservation' bit in the command. If set, the trespass
+will only succeed to change the ownership from the Peer SP if the Peer
+SP does not have an outstanding SCSI reservation for the LUN. By
+default, the reservation state will be ignored.
+.TP
+\fB\-s\fR
+Send the short version of the trespass command instead of the long
+version. The short version is supported on the EMC FC5300, FC4500 and
+FC4700. The long version (default) is supported on the CLARiiON CX and
+AX family arrays.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI
+generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks
+and DVD drives) can also be specified. For example "sg_start 0 /dev/sda"
+will work in the 2.6 series kernels.
+.SH EXIT STATUS
+The exit status of sg_emc_trespass is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Lars Marowsky\-Bree, based on sg_start.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2012 Lars Marowsky\-Bree, Douglas Gilbert.
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/sg_format.8 b/doc/sg_format.8
new file mode 100644
index 00000000..7c00cc50
--- /dev/null
+++ b/doc/sg_format.8
@@ -0,0 +1,725 @@
+.TH SG_FORMAT "8" "February 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_format \- format, format with preset, resize SCSI disk; format tape
+.SH SYNOPSIS
+.B sg_format
+[\fI\-\-cmplst=\fR{0|1}] [\fI\-\-count=COUNT\fR] [\fI\-\-dcrt\fR]
+[\fI\-\-dry\-run\fR] [\fI\-\-early\fR] [\fI\-\-ffmt=FFMT\fR]
+[\fI\-\-fmtmaxlba\R] [\fI\-\-fmtpinfo=FPI\fR] [\fI\-\-format\fR]
+[\fI\-\-help\fR] [\fI\-\-ip\-def\fR] [\fI\-\-long\fR] [\fI\-\-mode=MP\fR]
+[\fI\-\-pfu=PFU\fR] [\fI\-\-pie=PIE\fR] [\fI\-\-pinfo\fR] [\fI\-\-poll=PT\fR]
+[\fI\-\-preset=ID\fR] [\fI\-\-quick\fR] [\fI\-\-resize\fR] [\fI\-\-rto_req\fR]
+[\fI\-\-security\fR] [\fI\-\-six\fR] [\fI\-\-size=LB_SZ\fR]
+[\fI\-\-tape=FM\fR] [\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR]
+[\fI\-\-verify\fR] [\fI\-\-version\fR] [\fI\-\-wait\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Not all SCSI direct access devices need to be formatted and some have vendor
+specific formatting procedures. SCSI disks with rotating media are probably
+the largest group that do support a 'standard' format operation. They are
+typically factory formatted to a block size of 512 bytes with the largest
+number of blocks that the manufacturer recommends. The manufacturer's
+recommendation typically leaves aside a certain number of tracks, spread
+across the media, for reassignment of blocks to logical block addresses
+during the life of the disk.
+.PP
+This utility issues one of three SCSI format commands: FORMAT UNIT, FORMAT
+MEDIUM or FORMAT WITH PRESET. In the following description, unqualified
+sections will usually be referring to the SCSI FORMAT UNIT command. Both
+FORMAT UNIT and FORMAT WITH PRESET apply to disks (or disk\-like devices).
+The FORMAT MEDIUM command is for tapes. A SCSI INQUIRY response categorizes
+the 'Peripheral Device Type' (PDT) of each SCSI device. This utility uses
+the PDT to check if there is a conflict between the \fIDEVICE\fR and the
+given option (e.g. giving the \fI\-\-tape=FM\fR option when \fIDEVICE\fR is
+a normal disk). If there is a conflict, this utility will not continue.
+.PP
+This utility can format modern SCSI disks and potentially change their block
+size (if permitted) and the block count (i.e. number of accessible blocks on
+the media also known as "resizing"). Resizing a disk to less than the
+manufacturer's recommended block count is sometimes called "short
+stroking" (see NOTES section). Resizing the block count while not changing
+the block size may not require a format operation. The SBC\-2 standard (see
+www.t10.org) has obsoleted the "format device" mode page. Many of the low
+level details found in that mode page are now left up to the discretion of
+the manufacturer. There is a Format Status log page which reports on the
+previous successful format operation(s).
+.PP
+When this utility is used without options (i.e. it is only given a
+\fIDEVICE\fR argument) it prints out the existing block size and block count
+derived from two sources. These two sources are a block descriptor in the
+response to a MODE SENSE command and the response to a READ CAPACITY command.
+The reason for this double check is to detect a "format corrupt" state (see
+the NOTES section). This usage will not modify the disk.
+.PP
+When this utility is used with either \fI\-\-format\fR, \fI\-\-preset=ID\fR
+or \fI\-\-tape=FM\fR, it will attempt to format the given DEVICE. In the
+absence of the \fI\-\-quick\fR option there is a 15 second pause during which
+time the user is invited thrice (5 seconds apart) to abort sg_format. This
+occurs just prior the SCSI FORMAT UNIT, FORMAT WITH PRESET or FORMAT MEDIUM
+command being issued. See the NOTES section for more information.
+.PP
+Protection information (PI) is optional and is made up of one or more
+protection intervals, each made up of 8 bytes associated with a logical
+block. When PI is active each logical block will have 1, 2, 4, 8, etc
+protection intervals (i.e. a power of two), interleaved with (and following)
+the user data to which they refer. Four protection types are defined with
+protection type 0 being no protection intervals. See the PROTECTION
+INFORMATION section below for more information.
+.PP
+When the \fI\-\-tape=FM\fR option is given then the SCSI FORMAT MEDIUM
+command is sent to the \fIDEVICE\fR. FORMAT MEDIUM is defined in the SSC
+documents at T10 and prepares a volume for use. That may include partitioning
+the medium. See the section below on TAPE for more information.
+.PP
+The FORMAT WITH PRESET was added in draft SBC\-4 revision 18. A preset
+pattern, selected by the PRESET IDENTIFIER field (\fI\-\-id=FWPID\fR),
+is written to the disk. See the FORMAT PRESETS VPD page (0xb8) for a list
+of available Format preset identifiers and their associated data.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-C\fR, \fB\-\-cmplst\fR={0|1}
+sets the CMPLST ("complete list") bit in the FORMAT UNIT cdb to 0 or 1.
+If the value is 0 then the existing GLIST (grown list) is taken into account.
+If the value is 1 then the existing GLIST is ignored. CMPLST defaults to 1
+apart from when the \fI\-\-ffmt=FFMT\fR option's value is non\-zero in which
+case CMPLST defaults to 0. See the LISTS section below. In most cases this
+bit should be left at its default value.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR
+where \fICOUNT\fR is the number of blocks to be formatted or media to be
+resized to. Can be used with either \fI\-\-format\fR or \fI\-\-resize\fR.
+With \fI\-\-format\fR this option need not be given in which case it is
+assumed to be zero.
+.br
+With \fI\-\-format\fR the interpretation of \fICOUNT\fR is:
+.br
+ (\fICOUNT\fR > 0) : only format the first \fICOUNT\fR blocks and READ
+CAPACITY will report \fICOUNT\fR blocks after format
+.br
+ (\fICOUNT\fR = 0) and block size unchanged : use existing block count
+.br
+ (\fICOUNT\fR = 0) and block size changed : recommended maximum block
+count for new block size
+.br
+ (\fICOUNT\fR = \-1) : use recommended maximum block count
+.br
+ (\fICOUNT\fR < \-1) : illegal
+.br
+With \fI\-\-resize\fR this option must be given and \fICOUNT\fR has this
+interpretation:
+.br
+ (\fICOUNT\fR > 0) : after resize READ CAPACITY will report \fICOUNT\fR
+blocks
+.br
+ (\fICOUNT\fR = 0) : after resize READ CAPACITY will report 0 blocks
+.br
+ (\fICOUNT\fR = \-1) : after resize READ CAPACITY will report its
+maximum number of blocks
+.br
+ (\fICOUNT\fR < \-1) : illegal
+.br
+In both cases if the given \fICOUNT\fR exceeds the maximum number of
+blocks (for the block size) then the disk reports an error.
+See NOTES section below.
+.TP
+\fB\-D\fR, \fB\-\-dcrt\fR
+this option sets the DCRT bit in the FORMAT UNIT command's parameter list
+header. It will "disable certification". Certification verifies that blocks
+are usable during the format process. Using this option may speed the format
+but \fI\-\-ffmt=FFMT\fR, if available, would probably be better. The default
+action of this utility (i.e. when this option is not given) is to clear the
+DCRT bit thereby requesting "media certification" (also unless another
+option needs it, the FOV bit will be cleared). When the DCRT bit is set, the
+FOV bit must also be set hence sg_format does that.
+.br
+If this option is given twice then certification is enabled by clearing the
+DCRT bit and setting the FOV bit. Both these bits are found in the parameter
+list associated with the FORMAT UNIT cdb.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+this option will parse the command line, do all the preparation but bypass
+the actual FORMAT UNIT, FORMAT WITH PRESET or FORMAT MEDIUM command. Also if
+the options would otherwise cause the logical block size to change, then the
+MODE SELECT command that would do that is also bypassed when the dry
+run option is given.
+.TP
+\fB\-e\fR, \fB\-\-early\fR
+during a format operation, The default action of this utility is to poll the
+disk every 60 seconds (or every 10 seconds if \fIFFMT\fR is non\-zero) to
+determine the progress of the format operation until it is finished. When this
+option is given this utility will exit "early", that is as soon as the format
+operation has commenced. Then the user can monitor the progress of the ongoing
+format operation with other utilities (e.g. sg_turs(8) or sg_requests(8)).
+This option and \fI\-\-wait\fR are mutually exclusive.
+.TP
+\fB\-t\fR, \fB\-\-ffmt\fR=\fIFFMT\fR
+\fIFFMT\fR (fast format) is placed in a field of the same name in the FORMAT
+UNIT cdb. The field was introduced in SBC\-4 revision 10. The default value
+is 0 which implies the former action which is typically to overwrite all
+blocks on the \fIDEVICE\fR. That can take a long time (e.g. with hard disks
+over 10 TB in size that can be days). With \fIFFMT\fR set that time may be
+reduced to minutes or less. So it is worth trying if it is available.
+.br
+\fIFFMT\fR has values 1 and 2 for fast format with 3 being reserved
+currently. These two values include this description: "The device server
+initializes the medium ... without overwriting the medium (i.e. resources
+for managing medium access are initialized and the medium is not written)".
+The difference between 1 and 2 concerns read operations on LBAs to which no
+data has been written to, after the fast format. When \fIFFMT\fR is 1 the
+read operation should return "unspecified logical block data" and complete
+without error. When \fIFFMT\fR is 2 the read operation may yield check
+condition status with a sense key set to hardware error, medium error or
+command aborted. See draft SBC\-4 revision 16 section 4.34 for more details.
+.TP
+\fB\-b\fR, \fB\-\-fmtmaxlba\fR
+This option is only active if it is given together with the
+\fI\-\-preset=ID\fR option. If so it sets the FMTMAXLBA field in the FORMAT
+WITH PRESET command.
+.TP
+\fB\-f\fR, \fB\-\-fmtpinfo\fR=\fIFPI\fR
+sets the FMTPINFO field in the FORMAT UNIT cdb to a value between 0 and 3.
+The default value is 0. The FMTPINFO field from SBC\-3 revision 16 is a 2
+bit field (bits 7 and 6 of byte 1 in the cdb). Prior to that revision it was
+a single bit field (bit 7 of byte 1 in the cdb) and there was an accompanying
+bit called RTO_REQ (bit 6 of byte 1 in the cdb). The deprecated
+options "\-\-pinfo" and "\-\-rto\-req" represent the older usage. This
+option should be used in their place. See the PROTECTION INFORMATION section
+below for more information.
+.TP
+\fB\-F\fR, \fB\-\-format\fR
+issue one of the three SCSI "format" commands. In the absence of the
+\fI\-\-preset=ID\fR and \fI\-\-tape=FM\fR options, the SCSI FORMAT UNIT
+command is issued.
+.B These commands will destroy all the data held on the media.
+This option is required to change the block size of a disk. In the absence
+of the \fI\-\-quick\fR option, the user is given a 15 second count down to
+ponder the wisdom of doing this, during which time control\-C (amongst other
+Unix commands) can be used to kill this process before it does any damage.
+.br
+When used three times (or more) the preliminary MODE SENSE and SELECT
+commands are bypassed, leaving only the initial INQUIRY and FORMAT UNIT
+commands. This is for emergency use (e.g. when the MODE SENSE/SELECT
+commands are not working) and cannot change the logical block size.
+.br
+Host managed zoned devices (e.g. many zoned disks) have a different PDT
+compared to other disks but can still be formatted as if they were 'normal'
+disks.
+.br
+See NOTES section for implementation details and EXAMPLES section for typical
+use.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage information then exit.
+.TP
+\fB\-I\fR, \fB\-\-ip\-def\fR
+sets the default Initialization Pattern. Some disks (SSDs) use this to flag
+that a format should fully provision (i.e. associate a physical block with
+every logical block). The same disks (SSDs) might thin provision if this
+option is not given. If this option is given then the \fI\-\-security\fR
+option cannot be given. Also accepts \fI\-\-ip_def\fR for this option.
+.TP
+\fB\-l\fR, \fB\-\-long\fR
+the default action of this utility is to assume 32 bit logical block
+addresses. With 512 byte block size this permits more than 2
+terabytes (almost 2 ** 41 bytes) on a single disk. This option selects
+commands and parameters that allow for 64 bit logical block addresses.
+Specifically this option sets the "longlba" flag in the MODE SENSE (10)
+command and uses READ CAPACITY (16) rather than READ CAPACITY (10). If this
+option is not given and READ CAPACITY (10) or MODE SELECT detects a disk
+the needs more than 32 bits to represent its logical blocks then it is
+set internally. This option does not set the LONGLIST bit in the FORMAT UNIT
+command. The LONGLIST bit is set as required depending other
+parameters (e.g. when '\-\-pie=PIE' is greater than zero).
+.TP
+\fB\-M\fR, \fB\-\-mode\fR=\fIMP\fR
+\fIMP\fR is a mode page number (0 to 62 inclusive) that will be used for
+reading and perhaps changing the device logical block size. The default
+is 1 which is the Read\-Write Error Recovery mode page.
+.br
+Preferably the chosen (or default) mode page should be saveable (i.e.
+accept the SP bit set in the MODE SELECT command used when the logical
+block size is being changed). Recent version of this utility will retry a
+MODE SELECT if the SP=1 variant fails with a sense key of ILLEGAL REQUEST.
+That retry will use the same MODE SELECT command but with SP=0 .
+.TP
+\fB\-P\fR, \fB\-\-pfu\fR=\fIPFU\fR
+sets the "Protection Field Usage" field in the parameter block associated
+with a FORMAT UNIT command to \fIPFU\fR. The default value is 0, the only
+other defined value currently is 1. See the PROTECTION INFORMATION section
+below for more information.
+.TP
+\fB\-q\fR, \fB\-\-pie\fR=\fIPIE\fR
+sets the "Protection Interval Exponent" field in the parameter block
+associated with a FORMAT UNIT command to \fIPIE\fR. The default value is 0.
+\fIPIE\fR can only be non\-zero with protection types 2 and 3.
+The value of 0 is typical for 512 byte blocks; with 4096 byte blocks a value
+of 3 may be appropriate (i.e. 8 protection intervals interleaved with 4096
+bytes of user data). A device may not support any non\-zero values. This
+field first appeared in SBC\-3 revision 18.
+.TP
+\fB\-p\fR, \fB\-\-pinfo\fR
+this option is deprecated, use the \fI\-\-fmtpinfo=FPI\fR option instead.
+If used, then it sets bit 7 of byte 1 in the FORMAT UNIT cdb and that
+is equivalent to setting \fI\-\-fmtpinfo=2\fR. [So if \fI\-\-pinfo\fR is
+used (plus \fI\-\-fmtpinfo=FPI\fR and \fI\-\-pfu=PFU\fR are not given or
+their arguments are 0) then protection type 1 is selected.]
+.TP
+\fB\-x\fR, \fB\-\-poll\fR=\fIPT\fR
+where \fIPT\fR is the type of poll used. If \fIPT\fR is 0 then a TEST UNIT
+READY command is used, otherwise a REQUEST SENSE command is used. The
+default is currently 0 but this will change to 1 in the near future. See
+the NOTES sections below.
+.TP
+\fB\-E\fR, \fB\-\-preset\fR=\fIID\fR
+this option instructs this utility to issue a SCSI FORMAT WITH PRESET
+command. The PRESET IDENTIFIER field in that cdb is set to \fIID\fR. The
+IMMED field in that cdb is also set unless the \fI\-\-wait\fR option is
+also given, in which case it is cleared.
+.TP
+\fB\-Q\fR, \fB\-\-quick\fR
+the default action (i.e. when the option is not given) is to give the user
+15 seconds to reconsider doing a format operation on the \fIDEVICE\fR.
+When this option is given that step (i.e. the 15 second warning period)
+is skipped.
+.TP
+\fB\-r\fR, \fB\-\-resize\fR
+rather than format the disk, it can be resized. This means changing the
+number of blocks on the device reported by the READ CAPACITY command.
+This option should be used with the \fI\-\-count=COUNT\fR option.
+The contents of all logical blocks on the media remain unchanged when
+this option is used. This means that any resize operation can be
+reversed. This option cannot be used together with either \fI\-\-format\fR
+or a \fI\-\-size=LB_SZ\fR whose argument is different to the existing block
+size.
+.TP
+\fB\-R\fR, \fB\-\-rto_req\fR
+The option is deprecated, use the \fI\-\-fmtpinfo=FPI\fR option instead.
+If used, then it sets bit 6 of byte 1 in the FORMAT UNIT cdb.
+.TP
+\fB\-S\fR, \fB\-\-security\fR
+sets the "Security Initialization" (SI) bit in the FORMAT UNIT command's
+initialization pattern descriptor within the parameter list. According
+to SBC\-3 the default initialization pattern "shall be written using a
+security erasure write technique". See the NOTES section on the SCSI
+SANITIZE command. If this option is given then the \fI\-\-ip_def\fR option
+cannot be given.
+.TP
+\fB\-6\fR, \fB\-\-six\fR
+Use 6 byte variants of MODE SENSE and MODE SELECT. The default action
+is to use the 10 byte variants. Some MO drives need this option set
+when doing a format.
+.TP
+\fB\-s\fR, \fB\-\-size\fR=\fILB_SZ\fR
+where \fILB_SZ\fR is the logical block size (i.e. number of user bytes in each
+block) to format the device to. The default value is whatever is currently
+reported by the block descriptor in a MODE SENSE command. If the block size
+given by this option is different from the current value then a MODE SELECT
+command is used to change it prior to the FORMAT UNIT command being
+started (as recommended in the SBC standards). Some SCSI disks have 512 byte
+logical blocks by default and allow an alternate logical block size of 4096
+bytes. If the given size in unacceptable to the disk, most likely an "Invalid
+field in parameter list" message will appear in sense data (requires the
+use of '\-v' to decode sense data).
+.br
+Note that formatting a disk to add or remove protection information is not
+regarded as a change to its logical block size so this option should not
+be used.
+.TP
+\fB\-T\fR, \fB\-\-tape\fR=\fIFM\fR
+will send a FORMAT MEDIUM command to the \fIDEVICE\fR with its FORMAT field
+set to \fIFM\fR. This option is used to prepare a tape (i.e. the "medium")
+in a tape drive for use. Values for \fIFM\fR include 0 to do the "default"
+format; 1 to partition a volume and 2 to do a default format then partition.
+.TP
+\fB\-m\fR, \fB\-\-timeout\fR=\fISECS\fR
+where \fISECS\fR is the FORMAT UNIT, FORMAT WITH PRESET or FORMAT MEDIUM
+command timeout in seconds. \fISECS\fR will only be used if it exceeds the
+internal timeout which is 20 seconds if the IMMED bit is set and 72000
+seconds (20 hours) or higher if the IMMED bit is not set. If the disk size
+exceeds 4 TB then the timeout value is increased to 144000 seconds (40 hours).
+And if it is greater than 8 TB then the timeout value is increased to
+288000 seconds (80 hours). If the timeout is exceeded then the operating
+system will typically abort the command. Aborting a command may escalate to
+a LUN reset (or worse). A timeout may also leave the disk or tape format
+operation incomplete. And that may result in the disk or tape being in
+a "format corrupt" state requiring another format to remedy the situation.
+So for various reasons command timeouts are best avoided.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output). "\-vvv" gives
+a lot more debug output.
+.TP
+\fB\-y\fR, \fB\-\-verify\fR
+set the VERIFY bit in the FORMAT MEDIUM cdb. The default is that the VERIFY
+bit is clear. This option is only appropriate for tapes.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-w\fR, \fB\-\-wait\fR
+the default format action is to set the "IMMED" bit in the FORMAT UNIT
+command's (short) parameter header. If this option (i.e. \fI\-\-wait\fR) is
+given then the "IMMED" bit is not set. If \fI\-\-wait\fR is given then the
+FORMAT UNIT, FORMAT WITH PRESET or FORMAT MEDIUM command waits until the
+format operation completes before returning its response. This can be many
+hours on large disks. See the \fI\-\-timeout=SECS\fR option.
+.br
+Alternatively this option may be useful when used together with
+\fI\-\-ffmt=FFMT\fR (and \fIFFMT\fR greater than 0) since the fast format
+may only be a matter of seconds.
+.SH LISTS
+The SBC\-3 draft (revision 20) defines PLIST, CLIST, DLIST and GLIST in
+section 4.10 on "Medium defects". Briefly, the PLIST is the "primary"
+list of manufacturer detected defects, the CLIST ("certification" list)
+contains those detected during the format operation, the DLIST is a list of
+defects that can be given to the format operation. The GLIST is the grown
+list which starts in the format process as CLIST+DLIST and can "grow" later
+due to automatic reallocation (see the ARRE and AWRE bits in the
+Read\-Write Error Recovery mode page (see sdparm(8))) and use of the
+SCSI REASSIGN BLOCKS command (see sg_reassign(8)).
+.PP
+By the SBC\-3 standard (following draft revision 36) the CLIST and DLIST
+had been removed, leaving PLIST and GLIST. Only PLIST and GLIST are found
+in the SBC\-4 drafts.
+.PP
+The CMPLST bit (controlled by the \fI\-\-cmplst=\fR0|1 option) determines
+whether the existing GLIST, when the format operation is invoked,
+is taken into account. The sg_format utility sets the FOV bit to zero
+which causes DPRY=0, so the PLIST is taken into account, and DCRT=0, so
+the CLIST is generated and used during the format process.
+.PP
+The sg_format utility does not permit a user to provide a defect
+list (i.e. DLIST).
+.SH PROTECTION INFORMATION
+Protection Information (PI) is additional information held with logical
+blocks so that an application and/or host bus adapter can check the
+correctness of those logical blocks. PI is placed in one or more
+protection intervals interleaved in each logical block. Each protection
+interval follows the user data to which it refers. A protection interval
+contains 8 bytes made up of a 2 byte "logical block guard" (CRC), a 2
+byte "logical block application guard", and a 4 byte "logical block
+reference tag". Devices with 512 byte logical block size typically have
+one protection interval appended, making its logical block data 520 bytes
+long. Devices with 4096 byte logical block size often have 8 protection
+intervals spread across its logical block data for a total size of 4160
+bytes. Note that for all other purposes the logical block size is considered
+to be 512 and 4096 bytes respectively.
+.PP
+The SBC\-3 standard have added several "protection types" to the PI
+introduced in the SBC\-2 standard. SBC\-3 defines 4 protection types (types
+0 to 3) with protection type 0 meaning no PI is maintained. While a device
+may support one or more protection types, it can only be formatted with 1
+of the 4. To change a device's protection type, it must be re\-formatted.
+For more information see the Protection Information in section 4.21 of
+draft SBC\-4 revision 16.
+.PP
+A device that supports PI information (i.e. supports one or more protection
+types 1, 2 and 3) sets the "PROTECT" bit in its standard INQUIRY response. It
+also sets the SPT field in the EXTENDED INQUIRY VPD page response to indicate
+which protection types it supports. Given PROTECT=1 then SPT=0 implies the
+device supports PI type 1 only, SPT=1 implies the device supports PI types 1
+and 2, and various other non\-obvious mappings up to SPT=7 which implies
+protection types 1, 2 and 3 are supported. The
+.B current
+protection type of a disk can be found in the "P_TYPE" and "PROT_EN"
+fields in the response of a READ CAPACITY (16) command (e.g. with
+the 'sg_readcap \-\-long' utility).
+.PP
+Given that a device supports a particular protection type, a user can
+then choose to format that disk with that protection type by setting
+the "FMTPINFO" and "Protection Field Usage" fields in the FORMAT UNIT
+command. Those fields correspond to the \fI\-\-fmtpinfo=FPI\fR and the
+\fI\-\-pfu=PFU\fR options in this utility. The list below shows the four
+protection types followed by the options of this utility needed to select
+them:
+.br
+ \fB0\fR : \-\-fmtpinfo=0 \-\-pfu=0
+.br
+ \fB1\fR : \-\-fmtpinfo=2 \-\-pfu=0
+.br
+ \fB2\fR : \-\-fmtpinfo=3 \-\-pfu=0
+.br
+ \fB3\fR : \-\-fmtpinfo=3 \-\-pfu=1
+.br
+The default value of \fIFPI\fR (in \fI\-\-fmtpinfo=FPI\fR) is 0 and the
+default value of \fIPFU\fR (in \fI\-\-pfu=PFU\fR) is 0. So if neither
+\fI\-\-fmtpinfo=FPI\fR nor \fI\-\-pfu=PFU\fR are given then protection
+type 0 (i.e. no protection information) is chosen.
+.SH NOTES
+After a format that changes the logical block size or the number of logical
+blocks on a disk, the operating system may need to be told to re\-initialize
+its setting for that disk. In Linux that can be done with:
+.br
+ echo 1 > /sys/block/sd{letter(s)}/device/rescan
+.br
+where "letter(s)" will be between 'a' and 'zzz'. The lsscsi utility in Linux
+can be used to check the various namings of a disk.
+.PP
+The SBC\-2 standard states that the REQUEST SENSE command should be used
+for obtaining progress indication when the format command is underway.
+However, tests on a selection of disks shows that TEST UNIT READY
+commands yield progress indications (but not REQUEST SENSE commands). So
+the current version of this utility defaults to using TEST UNIT READY
+commands to poll the disk to find out the progress of the format. The
+\fI\-\-poll=PT\fR option has been added to control this.
+.PP
+When the \fI\-\-format\fR, \fI\-\-preset=ID\fR or \fI\-\-tape=FM\fR option
+is given without the \fI\-\-wait\fR option then the corresponding SCSI
+command is issued with the IMMED bit set which causes the SCSI command to
+return after it has started the format operation. The \fI\-\-early\fR option
+will cause sg_format to exit at that point. Otherwise the \fIDEVICE\fR is
+polled every 60 seconds or every 10 seconds if \fIFFMT\fR is non\-zero. The
+poll is with TEST UNIT READY or REQUEST SENSE commands until one reports
+an "all clear" (i.e. the format operation has completed). Normally these
+polling commands will result in a progress indicator (expressed as a
+percentage) being output to the screen. If the user gets bored watching the
+progress report then sg_format process can be terminated (e.g. with
+control\-C) without affecting the format operation which continues. However
+a target or device reset (or a power cycle) will probably cause the format
+to cease and the \fIDEVICE\fR to become "format corrupt".
+.PP
+When the \fI\-\-format\fR (\fI\-\-preset=ID\fR or \fI\-\-tape\fR) and
+\fI\-\-wait\fR options are both given then this utility may take a long time
+to return. In this case care should be taken not to send any other SCSI
+commands to the disk as it may not respond leaving those commands queued
+behind the active format command. This may cause a timeout in the OS
+driver (in a lot shorter period than 20 hours applicable to some format
+operations). This may result in the OS resetting the disk leaving the format
+operation incomplete. This may leave the disk in a "format corrupt" state
+requiring another format to remedy the situation. Modern SCSI devices should
+yield a "not ready" sense key with an additional sense indicating a format
+is in progress. With older devices the user should take precautions that
+nothing attempts to access a device while it is being formatted. Unmounting
+in mounted file systems on a \fIDEVICE\fR prior to calling this utility
+is strongly advised.
+.PP
+When the block size (i.e. the number of bytes in each block) is changed
+on a disk two SCSI commands must be sent: a MODE SELECT to change the block
+size followed by a FORMAT command. If the MODE SELECT command succeeds and
+the FORMAT fails then the disk may be in a state that the standard
+calls "format corrupt". A block descriptor in a subsequent MODE SENSE
+will report the requested new block size while a READ CAPACITY command
+will report the existing (i.e. previous) block size. Alternatively
+the READ CAPACITY command may fail, reporting the device is not ready,
+potentially requiring a format. The solution to this situation is to
+do a format again (and this time the new block size does not have to
+be given) or change the block size back to the original size.
+.PP
+The SBC\-2 standard states that the block count can be set back to the
+manufacturer's maximum recommended value in a format or resize operation.
+This can be done by placing an address of 0xffffffff (or the 64 bit
+equivalent) in the appropriate block descriptor field to a MODE SELECT
+command. In signed (two's complement) arithmetic that value corresponds
+to '\-1'. So a \-\-count=\-1 causes the block count to be set back to
+the manufacturer's maximum recommended value. To see exactly which SCSI
+commands are being executed and parameters passed add the "\-vvv" option to
+the sg_format command line.
+.PP
+The FMTDATA field shown in the FORMAT UNIT cdb does not have a corresponding
+option in this utility. When set in the cdb it indicates an additional
+parameter list will be sent to the \fIDEVICE\fR along with the cdb. It is set
+as required, basically when any field in the parameter list header is set.
+.PP
+Short stroking is a technique to trade off capacity for performance on
+hard disks. "Hard" disk is often used to mean a storage device with
+spinning platters which contain the user data. Solid State Disk (SSD) is
+the newer form of storage device that contains no moving parts. Hard disk
+performance is usually highest on the outer tracks (usually the lower logical
+block addresses) so by resizing or reformatting a disk to a smaller capacity,
+average performance will usually be increased.
+.PP
+Other utilities may be useful in finding information associated with
+formatting. These include sg_inq(8) to fetch standard INQUIRY
+information (e.g. the PROTECT bit) and to fetch the EXTENDED INQUIRY
+VPD page (e.g. RTO and GRD_CHK bits). The sdparm(8) utility can be
+used to access and potentially change the now obsolete format mode page.
+.PP
+scsiformat is another utility available for formatting SCSI disks
+with Linux. It dates from 1997 (most recent update) and may be useful for
+disks whose firmware is of that vintage.
+.PP
+The \fICOUNT\fR numeric argument may include a multiplicative suffix or be
+given in hexadecimal. See the "NUMERIC ARGUMENTS" section in the
+sg3_utils(8) man page.
+.PP
+The SCSI SANITIZE command was introduced in SBC\-3 revision 27. It is closely
+related to the ATA sanitize disk feature set and can be used to remove all
+existing data from a disk. Sanitize is more likely to be implemented on
+modern disks (including SSDs) than FORMAT UNIT's security initialization
+feature (see the \fI\-\-security\fR option) and in some cases much faster.
+.PP
+SSDs that support thin provisioning will typically unmap all logical blocks
+during a format. The reason is to improve the SSD's endurance. Also thin
+provisioned formats typically complete faster than fully provisioned ones
+on the same disk (see the \fI\-\-ip_def\fR option). In either case format
+operations on SSDs tend to be a lot faster than they are on hard disks with
+spinning media.
+.PP
+Host managed zoned devices (aka zoned disks) have a different Peripheral
+Device Type [PDT=20 or 0x14] from normal disks. They can be considered
+as a superset of normal disks (e.g. SSDs and hard disks) at least from
+the perspective of the number of SCSI commands they support. Typically
+they can be formatted just like other SCSI disks. They have their own
+T10 standards: ZBC standard (INCITS 536\-2016) and draft ZBC\-2.
+.br
+Two other zoned disk variants ("host aware" and "Domains and Realms") use
+the same PDT as other disks (i.e. PDT=0) and can be formatted by this
+utility as if they were normal disks.
+.SH TAPE
+Tape system use a variant of the FORMAT UNIT command used on disks. Tape
+systems use the FORMAT MEDIUM command which is simpler with only three
+fields in the cdb typically used. Apart from sharing the same opcode the
+cdbs of FORMAT UNIT and FORMAT MEDIUM are quite different. FORMAT MEDIUM's
+fields are VERIFY, IMMED and FORMAT (with TRANSFER LENGTH always set to 0).
+The VERIFY bit field is set with the \fI\-\-verify\fR option. The IMMED bit
+is manipulated by the \fI\-\-wait\fR option in the same way it is for disks;
+one difference is that if the \fI\-\-poll=PT\fR option is not given then it
+defaults to \fIPT\fR of 1 which means the poll is done with REQUEST SENSE
+commands.
+.PP
+The argument given to the \fI\-\-tape=FM\fR option is used to set the FORMAT
+field. \fIFM\fR can take values from "\-1" to "15" where "\-1" (the default)
+means don't do a tape format; value "8" to "15" are for vendor specific
+formats. The \fI\-\-early\fR option may also be used to set the IMMED
+bit and then exit this utility (rather than poll periodically until it is
+finished). In this case the tape drive will still be busy doing the format
+for some time but, according to T10, should still respond in full to the
+INQUIRY and REPORT LUNS commands. Other commands (including REQUEST SENSE)
+should yield a "not ready" sense key with an additional sense code
+of "Logical unit not ready, format in progress". Additionally REQUEST SENSE
+should contain a progress indication in its sense data.
+.PP
+When \fIFM\fR is 1 or 2 then the settings in the Medium partition mode page
+control the partitioning. That mode page can be viewed and modified with the
+sdparm utility.
+.PP
+Prior to invoking this utility the tape may need to be positioned to the
+beginning of partition 0. In Linux that can typically be done with the mt
+utility (e.g. 'mt \-f /dev/st0 rewind').
+.SH EXAMPLES
+These examples use Linux device names. For suitable device names in
+other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+In the first example below simply find out the existing block count and
+size derived from two sources: a block descriptor in a MODE SELECT command
+response and from the response of a READ CAPACITY commands. No changes
+are made:
+.PP
+ # sg_format /dev/sdm
+.PP
+Now a simple format, leaving the block count and size as they were previously.
+The FORMAT UNIT command is executed in IMMED mode and the device is polled
+every 60 seconds to print out a progress indication:
+.PP
+ # sg_format \-\-format /dev/sdm
+.PP
+Now the same format, but waiting (passively) until the format operation is
+complete:
+.PP
+ # sg_format \-\-format \-\-wait /dev/sdm
+.PP
+Next is a format in which the block size is changed to 520 bytes and the block
+count is set to the manufacturer's maximum value (for that block size). Note,
+not all disks support changing the block size:
+.PP
+ # sg_format \-\-format \-\-size=520 /dev/sdm
+.PP
+Now a resize operation so that only the first 0x10000 (65536) blocks on a disk
+are accessible. The remaining blocks remain unaltered.
+.PP
+ # sg_format \-\-resize \-\-count=0x10000 /dev/sdm
+.PP
+Now resize the disk back to its normal (maximum) block count:
+.PP
+ # sg_format \-\-resize \-\-count=\-1 /dev/sdm
+.PP
+One reason to format a SCSI disk is to add protection information. First
+check which protection types are supported by a disk (by checking the SPT
+field in the Extended inquiry VPD page together with the Protect bit in the
+standard inquiry response):
+.PP
+ # sg_vpd \-p ei \-l /dev/sdb
+.br
+ extended INQUIRY data VPD page:
+.br
+ ACTIVATE_MICROCODE=0
+.br
+ SPT=1 [protection types 1 and 2 supported]
+.br
+ ....
+.PP
+Format with type 1 protection:
+.PP
+ # sg_format \-\-format \-\-fmtpinfo=2 /dev/sdm
+.PP
+After a successful format with type 1 protection, READ CAPACITY(16)
+should show something like this:
+.PP
+ # sg_readcap \-l /dev/sdm
+.br
+ Read Capacity results:
+.br
+ Protection: prot_en=1, p_type=0, p_i_exponent=0 [type 1 protection]
+.br
+ Logical block provisioning: lbpme=0, lbprz=0
+.br
+ ....
+.PP
+To format with type 3 protection:
+.PP
+ # sg_format \-\-format \-\-fmtpinfo=3 \-\-pfu=1 /dev/sdm
+.PP
+For the disk shown above this will probably fail because the Extended inquiry
+VPD page showed only types 1 and 2 protection are supported.
+.PP
+Here are examples of using fast format (FFMT field in FORMAT UNIT cdb) to
+quickly switch between 512 and 4096 byte logical block size. Assume disk
+starts with 4096 byte logical block size and all important data has been
+backed up.
+.PP
+ # sg_format \-\-format \-\-ffmt=1 \-\-size=512 /dev/sdd
+.PP
+Now /dev/sdd should have 512 byte logical block size. And to switch it back:
+.PP
+ # sg_format \-\-format \-\-ffmt=1 \-\-size=4096 /dev/sdd
+.PP
+Since fast formats can be very quick (a matter of seconds) using the
+\-\-wait option may be appropriate.
+.PP
+And to use the Format with preset command this invocation could be used:
+.PP
+ # sg_format \-\-preset=1 \-\-fmtmaxlba /dev/sdd
+.PP
+The FORMAT PRESETS VPD page (0xb8) should be consulted to check that Preset
+identifier 0x1 is there and has the expected format (i.e. "default host aware
+zoned block device model with 512 bytes of user data in each logical block").
+That VPD page can be viewed with the sg_vpd utility.
+.SH EXIT STATUS
+The exit status of sg_format is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page. Unless the \fI\-\-wait\fR option is given, the
+exit status may not reflect the success of otherwise of the format.
+Using sg_turs(8) and sg_readcap(8) after the format operation may be wise.
+.PP
+The Unix convention is that "no news is good news" but that can be a bit
+unnerving after an operation like format, especially if it finishes
+quickly (i.e. before the first progress poll is sent). Giving the
+\fI\-\-verbose\fR option once should supply enough additional output to
+settle those nerves.
+.SH AUTHORS
+Written by Grant Grundler, James Bottomley and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2005\-2022 Grant Grundler, James Bottomley and Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_turs(8), sg_requests(8), sg_inq(8), sg_modes(8), sg_vpd(8),
+.B sg_reassign(8), sg_readcap(8), sg3_utils(8),
+.B sg_sanitize(8) [all in sg3_utils],
+.B lsscsi(8), mt(mt\-st), sdparm(8), scsiformat (old), hdparm(8)
diff --git a/doc/sg_get_config.8 b/doc/sg_get_config.8
new file mode 100644
index 00000000..3716ed60
--- /dev/null
+++ b/doc/sg_get_config.8
@@ -0,0 +1,143 @@
+.TH SG_GET_CONFIG "8" "December 2012" "sg3_utils\-1.35" SG3_UTILS
+.SH NAME
+sg_get_config \- send SCSI GET CONFIGURATION command (MMC\-4 +)
+.SH SYNOPSIS
+.B sg_get_config
+[\fI\-\-brief\fR] [\fI\-\-current\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-inner\-hex\fR] [\fI\-\-list\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR]
+[\fI\-\-rt=RT\fR] [\fI\-\-starting=FC\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI GET CONFIGURATION command to \fIDEVICE\fR and decodes the
+response. The response includes the features and profiles of the device.
+Typically these devices are CD, DVD, HD\-DVD and BD players that may (but
+not necessarily) have media in them. These devices may well be connected via
+ATAPI, USB or IEEE 1394 transports. In such cases they are "SCSI" devices
+only in the sense that they use the "Multi\-Media command" set (MMC).
+MMC is a specialized SCSI command set whose definition can be found
+at https://www.t10.org .
+.PP
+This utility is based on the MMC\-4 and later draft standards. See section
+5 on "Features and Profile for Multi_Media devices" for more information on
+specific feature parameters and profiles. The manufacturer's product manual
+may also be useful.
+.PP
+Since modern DVD and BD writers support many features and profiles, the
+decoded output from this utility can be large. There are various ways to cut
+down the output. If the \fI\-\-brief\fR option is used only the feature names
+are shown and the feature parameters are not decoded. Alternatively if only
+one feature is of interest then this combination of options is
+appropriate: "\-\-rt=2 \-\-starting=\fIFC\fR". Another possibility is to show
+only the features that are relevant to the media in the drive (i.e. "current")
+with the "\-\-rt=1" option.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+show the feature names but don't decode the parameters of those features.
+When used with \fI\-\-list\fR outputs known feature names but not known
+profile names.
+.TP
+\fB\-c\fR, \fB\-\-current\fR
+output features marked as current. This option is equivalent to '\-\-rt=1'.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hex (don't decode response).
+.TP
+\fB\-i\fR, \fB\-\-inner\-hex\fR
+decode to the feature name level then output each feature's data in hex.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+list all known feature and profile names. Ignore the device name (if given).
+Simply lists the feature names and profiles (followed by their hex values)
+that this utility knows about. If \fI\-\-brief\fR is also given then only
+feature names are listed.
+.TP
+\fB\-q\fR, \fB\-\-readonly\fR
+opens the DEVICE read\-only rather than read\-write which is the
+default. The Linux sg driver needs read\-write access for the SCSI
+GET CONFIGURATION command but other access methods may require
+read\-only access.
+.TP
+\fB\-r\fR, \fB\-\-rt\fR=\fIRT\fR
+where \fIRT\fR is the field of that name in the GET CONFIGURATION cdb.
+Allowable values are 0, 1, 2, or 3 . The command's action also depends on
+the value given to the \fI\-\-starting=FC\fR option. The default value is 0.
+When \fIRT\fR is 0 then all features, regardless of currency, are
+returned (whose feature code is greater than or equal to \fIFC\fR given
+to \fI\-\-starting=\fR). When \fIRT\fR is 1 then all current features are
+returned (whose feature code is greater than or equal to \fIFC\fR). When
+\fIRT\fR is 2 then the feature whose feature code is equal to \fIFC\fR,
+if any, is returned. When \fIRT\fR is 3 the response is reserved (probably
+yields an "illegal field in cdb" error). To simplify the meanings of the
+\fIRT\fR values are:
+.br
+ \fB0\fR : all features, current on not
+.br
+ \fB1\fR : only current features
+.br
+ \fB2\fR : only feature whose code is \fIFC\fR
+.br
+ \fB3\fR : reserved
+.br
+.TP
+\fB\-R\fR, \fB\-\-raw\fR
+output response in binary (to stdout). Note that the short form is \fI\-R\fR
+unlike most other utilities in this package that use \fI\-r\fR for this
+action.
+.TP
+\fB\-s\fR, \fB\-\-starting\fR=\fIFC\fR
+where \fIFC\fR is the feature code value. This option works closely with
+the \fI\-\-rt=RT\fR option. The \fIFC\fR value is in the range 0 to
+65535 (0xffff) inclusive. Its default value is 0. A value prefixed
+with "0x" (or a trailing 'h') is interpreted as hexadecimal.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+There are multiple versions of the MMC (draft) standards: MMC [1997],
+MMC\-2 [2000], MMC\-3 [2002], MMC\-4 and MMC\-5. The first three are now
+ANSI INCITS standards with the year they became standards shown in
+brackets. The draft immediately prior to standardization can
+be found at https://www.t10.org . In the initial MMC standard there
+was no GET CONFIGURATION command and the relevant information was
+obtained from the "CD capabilities and mechanical status mode
+page" (mode page 0x2a). It was later renamed the "MM capabilities and
+mechanical status mode page" and has been made obsolete in MMC\-4 and
+MMC\-5. The GET CONFIGURATION command was introduced in MMC\-2 and has
+become a replacement for that mode page. New features such as support
+for "BD" (blue ray) media type can only be found by using the
+GET CONFIGURATION command. Hence older CD players may not support
+the GET CONFIGURATION command in which case the "MM capabilities ..."
+mode page can be checked with sdparm(8), sginfo(8) or sg_modes(8).
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be
+a SCSI generic (sg) device. In the 2.6 series block devices
+can also be specified. For example "sg_get_config /dev/hdc"
+will work in the 2.6 series kernels as long as /dev/hdc is
+an ATAPI device. In the 2.6 series external DVD writers attached
+via USB could be queried with "sg_get_config /dev/scd1" for example.
+.SH EXIT STATUS
+The exit status of sg_get_config is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2012 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sginfo(8), sg_modes(8), sg_inq(8), sg_prevent(8),
+.B sg_start(8) [all in sg3_utils],
+.B sdparm(8)
diff --git a/doc/sg_get_elem_status.8 b/doc/sg_get_elem_status.8
new file mode 100644
index 00000000..b45ae131
--- /dev/null
+++ b/doc/sg_get_elem_status.8
@@ -0,0 +1,137 @@
+.TH SG_GET_ELEM_STATUS "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_get_elem_status \- send SCSI GET PHYSICAL ELEMENT STATUS command
+.SH SYNOPSIS
+.B sg_get_elem_status
+[\fI\-\-brief\fR] [\fI\-\-filter=FLT\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO\fR]] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-report\-type=RT\fR]
+[\fI\-\-starting=ELEM\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send the SCSI GET PHYSICAL ELEMENT STATUS command to the \fIDEVICE\fR and
+output the response. That command was introduced in (draft) SBC\-4 revision
+16.
+.PP
+T10 drafts now speak of both 'physical' and 'storage' elements. The latter
+term is more specific (i.e. storage elements are a sub\-set of physical
+elements) and refers to disk resources that control user data storage. An
+example of a storage element is the user data associated with a head on a
+spinning hard disk. When a storage element has been "depopulated" its former
+storage accessed via LBAs is no longer available. Physical elements are more
+general and includes storage elements and might include disk resources used
+for "saved" mode page settings amongst other things.
+.PP
+The default action of this utility is to decode the response into a header
+and up to 32 physical element status descriptors. The status descriptors are
+output one per line. The amount of output can be reduced by the
+\fI\-\-brief\fR option.
+.PP
+Rather than send this SCSI command to \fIDEVICE\fR, if the \fI\-\-inhex=FN\fR
+option is given, then the contents of the file named \fIFN\fR are decoded
+as ASCII hex (or binary if \fI\-\-raw\fR is also given) and then processed
+as if it was the response of the GET PHYSICAL ELEMENT STATUS command.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+when used once, the output of each physical element status descriptor is
+reduced to: <element_id>: <element_type>,<element_health> . All three are
+output as decimal integers. When used twice the "Element descriptors:"
+line introducing the status descriptors is not output. When used three
+or more times only the response header is output.
+.TP
+\fB\-f\fR, \fB\-\-filter\fR=\fIFLT\fR
+where \fIFLT\fR is placed in a two bit field called FILTER in the GET
+PHYSICAL ELEMENT STATUS command. Only two values are defined for that
+field: 0 for all element descriptors; 1 for those element descriptors that
+are outside 'spec' or have depopulation information to report. In both cases
+the REPORT TYPE and STARTING ELEMENT fields may further restrict (reduce)
+the number of element descriptors returned. The default value is zero.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response to this command in ASCII hex. Each line of 16 bytes is
+preceded by an address or index, starting at 0 and the address is also in
+hex. If given twice then an ASCII rendering of each byte is appended to the
+line output. If given three or more times then only the ASCII hex of each
+byte is output, 16 bytes per line (i.e. so no leading address nor trailing
+ASCII rendering). This latter form is suitable for placing in a file and
+being used with the \fI\-\-inhex=FN\fR option in a later invocation.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a file name whose contents are assumed to be ASCII
+hexadecimal. If \fIDEVICE\fR is also given then \fIDEVICE\fR is ignored,
+a warning is issued and the utility continues, decoding the file named
+\fIFN\fR. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information. If the \fI\-\-raw\fR option is
+also given then the contents of \fIFN\fR are treated as binary.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given then 1056 is used. 1056 is
+enough space for the response header plus 32 physical element status
+descriptors. \fILEN\fR should be a multiple of 32 (e.g. 32, 64, and 96 are
+suitable).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout) unless the \fI\-\-inhex=FN\fR option
+is also given. In that case the input file name (\fIFN\fR) is decoded as
+binary (and the output is _not_ in binary).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-t\fR, \fB\-\-report\-type\fR=\fIRT\fR
+where \fIRT\fR will be placed in the REPORT TYPE field of the GET PHYSICAL
+ELEMENT STATUS command. Currently only two values are defined: 0
+for 'physical element' and 1: for 'storage element'. The default value
+is 0 .
+.TP
+\fB\-s\fR, \fB\-\-starting\fR=\fIELEM\fR
+where \fIELEM\fR is placed in the STARTING ELEMENT field of the GET PHYSICAL
+ELEMENT STATUS command. Only physical elements with identifiers greater
+than, or equal to \fIELEM\fR are returned. The default value is zero
+which, while it isn't a valid element identifier (since they must be
+non\-zero), is given in an example in Annex L of SBC\-4 revision 17. So
+an \fIELEM\fR of zero is assumed to be valid in this context.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output). Additional output
+caused by this option is sent to stderr.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+The "Warning - physical element status change" additional sense code [0xb,
+0x14] is special and should prompt an application client to call the GET
+PHYSICAL ELEMENT STATUS command. How this warning is triggered depends on
+the settings in the Informational Exceptions Control mode page [0xc, 0x0].
+.PP
+After detecting one or more out\-of\-spec storage elements the disk in
+question should either be decommissioned or have the REMOVE ELEMENT AND
+TRUNCATE (or ... AND MODIFY ZONES) command invoked to repair (and reduce
+the storage capacity) of the disk.
+.SH EXIT STATUS
+The exit status of sg_get_elem_status is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2019\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_get_lba_status,sg3_utils,sg3_utils_json(sg3_utils)
diff --git a/doc/sg_get_lba_status.8 b/doc/sg_get_lba_status.8
new file mode 100644
index 00000000..0ccc70e4
--- /dev/null
+++ b/doc/sg_get_lba_status.8
@@ -0,0 +1,161 @@
+.TH SG_GET_LBA_STATUS "8" "August 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_get_lba_status \- send SCSI GET LBA STATUS(16 or 32) command
+.SH SYNOPSIS
+.B sg_get_lba_status
+[\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-blockhex\fR] [\fI\-\-brief\fR]
+[\fI\-\-element-id=EI\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO\fR]] [\fI\-\-lba=LBA\fR]
+[\fI\-\-maxlen=LEN\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR]
+[\fI\-\-report\-type=RT\fR] [\fI\-\-scan-len=SL\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send the SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command to the
+\fIDEVICE\fR and output the response. The 16 byte command variant was
+introduced in (draft) SBC\-3 revision 20 and devices that support logical
+block provisioning should support this command. The GET LBA STATUS(32)
+command was added in (draft) SBC\-4 revision 14.
+.PP
+The default action is to decode the response into one LBA status descriptor
+per line then output a header and the status descriptors to stdout. The
+descriptor LBA is output in hex (prefixed by '0x') and the number of blocks
+is output in decimal followed by the provisioning status and additional status
+in decimal. The provisioning status can be in the range 0 to 15 of which only
+0 (mapped or unknown), 1 (unmapped), 2 (anchored), 3 (mapped) and 4 (unknown)
+are used currently. The amount of output can be reduced by the
+\fI\-\-brief\fR option.
+.PP
+Rather than send this SCSI command to \fIDEVICE\fR, if the \fI\-\-inhex=FN\fR
+option is given, then the contents of the file named \fIFN\fR are decoded
+as ASCII hex and then processed if it was the response of this command.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+send SCSI GET LBA STATUS(16) command which is the 16 byte variant. In the
+absence of the \fI\-\-16\fR or the \fI\-\-32\fR options the SCSI GET LBA
+STATUS(16) command is sent. If both \fI\-\-16\fR and the \fI\-\-32\fR options
+are given then the GET LBA STATUS(16) command is sent.
+.TP
+\fB\-T\fR, \fB\-\-32\fR
+send SCSI GET LBA STATUS(32) command which is the 32 byte variant. When
+given together with the \fI\-\-16\fR option then this option is ignored (so
+the GET LBA STATUS(16) command is sent).
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+when use once then one LBA status descriptor per line is output to stdout.
+Each line has this
+format: "0x<descriptor_LBA> 0x<blocks> <provisioning_status>
+<additional_status>". So the descriptor's starting LBA and number of blocks
+are output in hex while the provisioning status and additional status are
+in decimal. When used twice (e.g. '\-bb' or '\-\-brief \-\-brief') then the
+provisioning status of the given \fILBA\fR (or LBA 0 if the \fI\-\-lba\fR
+option is not given) is output to stdout. A check is made that the given
+\fILBA\fR lies in the range of the first returned LBA status descriptor (as
+it should according to SBC\-3 revision 20) and warnings are sent to stderr
+if it doesn't.
+.TP
+\fB\-B\fR, \fB\-\-blockhex\fR
+the number of blocks in each LBA status descriptor is usually displayed in
+decimal. An exception is when the \fI\-\-brief\fR option is given in which
+case it is shown in hexadecimal. When the option is given once, both cases
+are output in hexadecimal. When the option is given twice, both cases are
+output in decimal.
+.TP
+\fB\-e\fR, \fB\-\-element\-id\fR=\fIEI\fR
+where \fIEI\fR is the element identifier of the physical element for which
+the LBAs shall be reported based on the value in the report type field (i.e.
+\fIRT\fR). This option is only active with the SCSI GET LBA STATUS(32)
+command (i.e. it is ignored if the GET LBA STATUS(16) command is sent).
+.br
+Valid element identifiers are non\-zero. The default value of \fIEI\fR is 0
+which means in the context that no element identifier is specified.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response to this command in ASCII hex.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a filename whose contents are assumed to be ASCII
+hexadecimal bytes. See the "FORMAT OF FILES CONTAINING ASCII HEX" section
+in the sg3_utils manpage for more information. If \fIDEVICE\fR is also
+given then it is ignored. If the \fI\-\-raw\fR option is also given then
+the contents of \fIFN\fR are treated as binary.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the starting Logical Block Address (LBA) to check the
+provisioning status for. Note that the \fIDEVICE\fR chooses how many
+following blocks that it will return provisioning status for.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given then 24 is used. 24 is
+enough space for the response header and one LBA status descriptor.
+\fILEN\fR should be 8 plus a multiple of 16 (e.g. 24, 40, and 56 are suitable).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout) unless the \fI\-\-inhex=FN\fR option
+is also given. In that case the input file name (\fIFN\fR) is decoded as
+binary (and the output is _not_ in binary).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-t\fR, \fB\-\-report\-type\fR=\fIRT\fR
+where \fIRT\fR is 0 for report all LBAs; 1 for report LBAs using non\-zero
+provisioning status; 2 for report LBAs that are mapped; 3 for report LBAs
+that are de\-allocated; 4 for report LBAs that are anchored; 16 for report
+LBAs that may return an unrecovered error. The REPORT TYPE field was added
+to the GET LBA STATUS cdb in sbc4r12.
+.br
+Since the REPORT TYPE field is newer than the command, the response contains
+the RTP bit to indicate whether or not the \fIDEVICE\fR acts on the REPORT
+TYE field (set when it does act on it, clear otherwise).
+.TP
+\fB\-s\fR, \fB\-\-scan\-len\fR=\fISL\fR
+where \fISL\fR is the scan length which is the maximum number of contiguous
+logical blocks to be scanned for logical blocks that meet the given report
+type (i.e. \fIRT\fR). This option is only active with the SCSI GET LBA
+STATUS(32) command (i.e. it is ignored if the GET LBA STATUS(16) command is
+sent).
+.br
+The default value of \fISL\fR is 0 which should be interpreted by the
+\fIDEVICE\fR as there is no limits to the number of LBAs that shall be
+scanned.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output). Additional output
+caused by this option is sent to stderr.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+In SBC\-3 revision 25 the calculation associated with the Parameter Data
+Length field in the response was modified. Prior to that the byte offset
+was 8 and in revision 25 it was changed to 4.
+.PP
+For a discussion of logical block provisioning see section 4.7 of sbc4r14.pdf
+at https://www.t10.org (or the corresponding section of a later draft).
+.SH EXIT STATUS
+The exit status of sg_get_lba_status is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2009\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_write_same,sg_unmap,sg3_utils,sg3_utils_json(sg3_utils)
diff --git a/doc/sg_ident.8 b/doc/sg_ident.8
new file mode 100644
index 00000000..d36a176c
--- /dev/null
+++ b/doc/sg_ident.8
@@ -0,0 +1,119 @@
+.TH SG_IDENT "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_ident \- send SCSI REPORT/SET IDENTIFYING INFORMATION command
+.SH SYNOPSIS
+.B sg_ident
+[\fI\-\-ascii\fR] [\fI\-\-clear\fR] [\fI\-\-help\fR] [\fI\-\-itype=IT\fR]
+[\fI\-\-raw\fR] [\fI\-\-set\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI REPORT IDENTIFYING INFORMATION or SET IDENTIFYING INFORMATION
+command to \fIDEVICE\fR. Prior to SPC\-4 (revision 7) these
+commands were called REPORT DEVICE IDENTIFIER and SET DEVICE IDENTIFIER
+respectively. SCSI devices that support these two commands allow users
+to write (set) identifying information and report it back at some
+later time. The information is persistent (i.e. stored on some
+non\-volatile medium within the SCSI device that will survive a power
+outage).
+.PP
+Typically the space allocated for the information is limited:
+SPC\-4 (revision 7) states that for information type 0, the minimum
+length is 64 bytes and the maximum is 512 bytes. For other information
+types (1 to 126 inclusive) the maximum length is 256 bytes. Also
+information types 1 to 126 (inclusive) should contain a null
+terminated UTF\-8 string. The author has seen older disks that only
+support 16 bytes.
+.PP
+The default action when no options are given is to invoke the
+Report Identifying Information command with the information type defaulting
+to zero. Error reports are sent to stderr. By default the information is
+shown in ASCII\-HEX (up to 16 bytes per line) with an ASCII representation
+to the right with dots replacing non printable characters.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-A\fR, \fB\-\-ascii\fR
+invokes the Report Identifying Information command and if anything is
+found interprets it as ASCII (or UTF\-8 which is locale dependent) and
+prints the information to stdout.
+.TP
+\fB\-C\fR, \fB\-\-clear\fR
+invokes the Set Identifying Information command with an information length
+of zero. This has the effect of clearing the existing information.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-itype\fR=\fIIT\fR
+where \fIIT\fR is the information type. Defaults to zero. The maximum value
+is 127 which is special and cannot be used with \fI\-\-set\fR or
+\fI\-\-clear\fR. The information type of 127 (if supported) causes the REPORT
+IDENTIFYING INFORMATION command to respond with a list of available
+information types and their maximum lengths in bytes. The odd numbered
+information types between 3 and 125 (inclusive) are not to be used (as they
+clash with the SCC\-2 standard).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+invokes the Report Identifying information command and if anything
+is found sends the information (which may be binary) to stdout. Nothing else
+is sent to stdout however error reports, if any, are sent to stderr.
+.TP
+\fB\-S\fR, \fB\-\-set\fR
+first reads stdin until an EOF is detected then invokes the Set Identifying
+Information command to set what has been fetched from stdin as the
+information. The amount of data read must be between 1 and 512 bytes
+length (inclusive).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.PP
+This utility permits users to write their own identifying information to
+their SCSI devices. There are several other types of descriptors (or
+designators) that the user cannot change. These include the SCSI INQUIRY
+command with its standard vendor and product identification strings and the
+product revision level; plus the large amount of information provided by
+the "Device Identification" VPD page (see sg_vpd). There is also the READ
+MEDIA SERIAL NUMBER command (see sg_rmsn). The MMC\-4 command set for CD
+and DVDs has a "media serial number" feature (0x109) [and a "logical unit
+serial number" feature]. These can be viewed with the sg_get_config utility.
+.SH EXAMPLES
+First, to see if there is an existing information whose format
+is unknown (for information type 0), use no options:
+.PP
+ # sg_ident /dev/sdb
+.br
+ 00 31 32 33 34 35 36 37 38 39 30 1234567890
+.PP
+If it is ASCII then it can printed as such:
+.PP
+ # sg_ident \-\-ascii /dev/sdb
+.br
+ 1234567890
+.PP
+The information can be copied to a file, cleared and then
+re\-asserted with this sequence:
+.PP
+ # sg_ident \-\-raw /dev/sdb > t
+.br
+ # sg_ident \-\-clear /dev/sdb
+.br
+ # cat t | sg_ident \-\-set /dev/sdb
+.SH EXIT STATUS
+The exit status of sg_ident is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2005\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd(sg3_utils), sg_rmsn(sg3_utils), sg_get_config(sg3_utils)
diff --git a/doc/sg_inq.8 b/doc/sg_inq.8
new file mode 100644
index 00000000..d01d35c7
--- /dev/null
+++ b/doc/sg_inq.8
@@ -0,0 +1,562 @@
+.TH SG_INQ "8" "August 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_inq \- issue SCSI INQUIRY command and/or decode its response
+.SH SYNOPSIS
+.B sg_inq
+[\fI\-\-ata\fR] [\fI\-\-block=0|1\fR] [\fI\-\-cmddt\fR]
+[\fI\-\-descriptors\fR] [\fI\-\-export\fR] [\fI\-\-extended\fR]
+[\fI\-\-force\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-id\fR]
+[\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO]\fR] [\fI\-\-len=LEN\fR]
+[\fI\-\-long\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-only\fR] [\fI\-\-page=PG\fR]
+[\fI\-\-raw\fR] [\fI\-\-vendor\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-vpd\fR] \fIDEVICE\fR
+.PP
+.B sg_inq
+[\fI\-36\fR] [\fI\-a\fR] [\fI\-A\fR] [\fI\-b\fR] [\fI\-\-B=0|1\fR]
+[\fI\-c\fR] [\fI\-cl\fR] [\fI\-d\fR] [\fI\-e\fR] [\fI\-f\fR] [\fI\-h\fR]
+[\fI\-H\fR] [\fI\-i\fR] [\fI\-I=FN\fR] [\fI\-j[=LEN]\fR] [\fI\-l=LEN\fR]
+[\fI\-L\fR] [\fI\-m\fR] [\fI\-M\fR] [\fI\-o\fR] [\fI\-p=VPD_PG\fR]
+[\fI\-P\fR] [\fI\-r\fR] [\fI\-s\fR] [\fI\-u\fR] [\fI\-v\fR]
+[\fI\-V\fR] [\fI\-x\fR] [\fI\-36\fR] [\fI\-?\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility, when \fIDEVICE\fR is given, sends a SCSI INQUIRY command to it
+then outputs the response. All SCSI devices are meant to respond to
+a "standard" INQUIRY command with at least a 36 byte response (in SCSI 2 and
+higher). An INQUIRY is termed as "standard" when both the EVPD and CmdDt (now
+obsolete) bits are clear. Formally (i.e. as per SPC stardards) the name of
+a standard INQUIRY response is the "standard INQUIRY data format" but here
+the "standard INQUIRY response" is used as it is shorter and more descriptive.
+.PP
+Alternatively the \fI\-\-inhex=FN\fR option can be given. In this case
+\fIFN\fR is assumed to be a file name ('\-' for stdin) containing ASCII
+hexadecimal representing an INQUIRY response.
+.PP
+This utility supports two command line syntaxes. The preferred one is shown
+first in the synopsis and is described in the main OPTIONS section. A later
+section titled OLDER COMMAND LINE OPTIONS describes the second group of
+options.
+.PP
+An important "non\-standard" INQUIRY page is the Device Identification
+Vital Product Data (VPD) page [0x83]. Since SPC\-3, support for this page
+is mandatory. The \fI\-\-id\fR option decodes this page. New VPD page
+information is no longer being added to this utility. The sg_vpd(8) utility
+is specialized for decoding VPD pages and shares code with this utility for
+that purpose. The sdparm(8) utility which is in a package of that name also
+decodes VPD pages although its major purpose is to access and modify SCSI
+mode pages.
+.PP
+In Linux, if the \fIDEVICE\fR exists and the SCSI INQUIRY fails (e.g. because
+the SG_IO ioctl is not supported) then an ATA IDENTIFY (PACKET) DEVICE is
+tried. If it succeeds then device identification strings are output. The
+\fI\-\-raw\fR and \fI\-\-hex\fR options can be used to manipulate the output.
+If the \fI\-\-ata\fR option is given then the SCSI INQUIRY is not performed
+and the \fIDEVICE\fR is assumed to be ATA (or ATAPI). For more information
+see the ATA DEVICES section below.
+.PP
+In some operating systems a NVMe device (e.g. SSD) may be given as the
+\fIDEVICE\fR. For more information see the NVME DEVICES section below.
+.PP
+The reference document used for interpreting an INQUIRY is T10/BSR INCITS
+566 Revision 6 which is draft SPC\-6 dated 22 October 2021. It can be found
+at https://www.t10.org . Obsolete and reserved items in the standard
+INQUIRY response output are displayed in square brackets.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-a\fR, \fB\-\-ata\fR
+Assume given \fIDEVICE\fR is an ATA or ATAPI device which can receive ATA
+commands from the host operating system. Skip the SCSI INQUIRY command and
+use either the ATA IDENTIFY DEVICE command (for non\-packet devices) or the
+ATA IDENTIFY PACKET DEVICE command. To show the response in hex, add
+a '\-\-verbose' option. This option is only available in Linux.
+.TP
+\fB\-B\fR, \fB\-\-block\fR=\fI0|1\fR
+this option controls how the file handle to the \fIDEVICE\fR is opened. If
+this argument is 0 then the open is non\-blocking. If the argument is 1 then
+the open is blocking. In Unix a non\-blocking open is indicated by a
+O_NONBLOCK flag while a blocking open is indicated by the absence of that
+flag. The default value depends on the operating system and the type of
+\fIDEVICE\fR node. For Linux pass\-throughs (i.e. the sg and bsg drivers)
+the default is 0.
+.TP
+\fB\-c\fR, \fB\-\-cmddt\fR
+set the Command Support Data (CmdDt) bit (defaults to clear(0)). Used in
+conjunction with the \fI\-\-page=PG\fR option where \fIPG\fR specifies the
+SCSI command opcode to query. When used twice (e.g. '\-cc') this utility
+forms a list by looping over all 256 opcodes (0 to 255 inclusive) only
+outputting a line for commands that are found. The CmdDt bit is now
+obsolete; it has been replaced by the REPORT SUPPORTED OPERATION CODES
+command, see the sg_opcodes(8) utility.
+.TP
+\fB\-d\fR, \fB\-\-descriptors\fR
+decodes and prints the version descriptors found in a standard INQUIRY
+response. There are up to 8 of them. Version descriptors indicate which
+versions of standards and/or drafts the \fIDEVICE\fR complies with. The
+normal components of a standard INQUIRY are output (typically from
+the first 36 bytes of the response) followed by the version descriptors
+if any.
+.TP
+\fB\-e\fR
+see entry below for \fI\-\-vpd\fR.
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+As a sanity check, the normal action when fetching VPD pages other than
+page 0x0 (the "Supported VPD pages" VPD page), is to first fetch page 0x0
+and only if the requested page is one of the supported pages, to go ahead
+and fetch the requested page.
+.br
+When this option is given, skip checking of VPD page 0x0 before accessing
+the requested VPD page. The prior check of VPD page 0x0 is known to
+crash certain USB devices, so use with care.
+.TP
+\fB\-u\fR, \fB\-\-export\fR
+prints out information obtained from the device. The output can be
+modified by selecting a VPD page with \fIPG\fR (from
+\fI\-\-page=PG\fR). If the device identification VPD page 0x83 is
+given it prints out information in the form:
+"SCSI_IDENT_<assoc>_<type>=<ident>" to stdout. If the device serial
+number VPD page 0x80 is given it prints out information in the form:
+"SCSI_SERIAL=<ident>". Other VPD pages are not supported. If no VPD
+page is given it prints out information in the form:
+"SCSI_VENDOR=<vendor>", "SCSI_MODEL=<model>", and
+"SCSI_REVISION=<rev>", taken from the standard inquiry. This may be
+useful for tools like udev(7) in Linux.
+.TP
+\fB\-E\fR, \fB\-x\fR, \fB\-\-extended\fR
+prints the extended INQUIRY VPD page [0x86]. It has the same effect
+as giving the \fI\-\-page=ei\fR option.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit. When used twice, after the
+usage message, there is a list of available abbreviations than can be
+given to the \fI\-\-page=PG\fR option.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+rather than decode a standard INQUIRY response, a VPD page or command
+support data; print out the response in hex and send the output to stdout.
+Error messages and warnings are typically output to stderr. When used twice
+with the ATA Information VPD page [0x89] decodes the start of the response
+then outputs the ATA IDENTIFY (PACKET) DEVICE response in hexadecimal
+bytes (not 16 bit words). When used three times with the ATA Information VPD
+page [0x89] or the \fI\-\-ata\fR option, this utility outputs the ATA
+IDENTIFY (PACKET) DEVICE response in hexadecimal words suitable for input
+to 'hdparm \-\-Istdin'. See note below.
+.br
+To generate output suitable for placing in a file that can be used by a
+later invocation with the \fI\-\-inhex=FN\fR option, use the '\-HHHH'
+option (e.g. 'sg_inq \-p di \-HHHH /dev/sg3 > dev_id.hex').
+.TP
+\fB\-i\fR, \fB\-\-id\fR
+prints the device identification VPD page [0x83]. It has the same effect
+as giving the \fI\-\-page=di\fR option.
+.TP
+\fB\-I\fR, \fB\-\-inhex\fR=\fIFN\fR
+\fIFN\fR is expected to be a file name (or '\-' for stdin) which contains
+ASCII hexadecimal or binary representing an INQUIRY (including VPD page)
+response. This utility will then decode that response. It is preferable to
+also supply the \fI\-\-page=PG\fR option, if not this utility will attempt
+to guess which VPD page (or standard INQUIRY) that the response is associated
+with. The hexadecimal should be arranged as 1 or 2 digits representing a
+byte each of which is whitespace or comma separated. Anything from and
+including a hash mark to the end of a line is ignored. If the \fI\-\-raw\fR
+option is also given then \fIFN\fR is treated as binary.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-l\fR, \fB\-\-len\fR=\fILEN\fR
+the number \fILEN\fR is the "allocation length" field in the INQUIRY cdb.
+This is the (maximum) length of the response returned by the device. The
+default value of \fILEN\fR is 0 which is interpreted as: first request is
+for 36 bytes and if necessary execute another INQUIRY if the "additional
+length" field in the response indicates that more than 36 bytes is available.
+.br
+If \fILEN\fR is greater than 0 then only one INQUIRY command is performed.
+This means that the Serial Number (obtained from the Serial Number VPD
+pgae (0x80)) is not fetched and therefore not printed.
+See the NOTES section below about "36 byte INQUIRYs".
+.TP
+\fB\-L\fR, \fB\-\-long\fR
+this option causes more information to be decoded from the Identify command
+sent to a NVMe \fIDEVICE\fR.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+this option has the same action as the \fI\-\-len=LEN\fR option above. It has
+been added for compatibility with the sg_vpd, sg_modes and sg_logs utilities.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option on the command line.
+.TP
+\fB\-o\fR, \fB\-\-only\fR
+Do not attempt to additionally retrieve the serial number VPD page (0x80) to
+enhance the output of a standard INQUIRY. So with this option given and no
+others, this utility will send a standard INQUIRY SCSI command and decode
+its response. No other SCSI commands will be sent to the \fIDEVICE\fR.
+Without this option an additional SCSI command is sent: a (non\-standard)
+SCSI INQUIRY to fetch the Serial Number VPD page. However the Serial
+Number VPD page is not mandatory (while the Device Identification page is
+mandatory but a billion USB keys ignore that) and may cause nuisance error
+reports.
+.br
+For NVMe devices only the Identify controller is performed, even if the
+\fIDEVICE\fR includes a namespace identifier. For example in FreeBSD
+given a \fIDEVICE\fR named /dev/nvme0ns1 then an Identify controller is
+sent to /dev/nvme0 and nothing is sent to its "ns1" (first namespace).
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR
+the \fIPG\fR argument can be either a number of an abbreviation for a VPD
+page. To enumerate the available abbreviations for VPD pages use '\-hh' or
+a bad abbreviation (e.g, '\-\-page=xxx'). When the \fI\-\-cmddt\fR option is
+given (once) then \fIPG\fR is interpreted as an opcode number (so VPD page
+abbreviations make little sense).
+.br
+If \fIPG\fR is a negative number, then a standard INQUIRY is performed. This
+can be used to override some guessing logic associated with the
+\fI\-\-inhex=FN\fR option.
+.br
+If \fIPG\fR is not found in the 'Supported VPD pages' VPD page (0x0) then
+EDOM is returned. To bypass this check use the \fI\-\-force\fR option.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+in the absence of \fI\-\-inhex=FN\fR then the output response is in binary.
+The output should be piped to a file or another utility when this option is
+used. The binary is sent to stdout, and errors are sent to stderr.
+.br
+If used with \fI\-\-inhex=FN\fR then the contents of \fIFN\fR is treated as
+binary.
+.TP
+\fB\-s\fR, \fB\-\-vendor\fR
+output a standard INQUIRY response's vendor specific fields from offset 36
+to 55 in ASCII. When used twice (i.e. '\-ss') also output the vendor
+specific field from offset 96 in ASCII. This is only done if the data
+passes some simple sanity checks.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.TP
+\fB\-e\fR, \fB\-\-vpd\fR
+set the Enable Vital Product Data (EVPD) bit (defaults to clear(0)). Used in
+conjunction with the \fI\-\-page=PG\fR option where \fIPG\fR specifies the
+VPD page number to query. If the \fI\-\-page=PG\fR is not given then \fIPG\fR
+defaults to zero which is the "Supported VPD pages" VPD page.
+.SH NOTES
+Some devices with weak SCSI command set implementations lock up when they
+receive commands they don't understand (and some lock up if they receive
+response lengths that they don't expect). Such devices need to be treated
+carefully, use the '\-\-len=36' option. Without this option this utility will
+issue an initial standard INQUIRY requesting 36 bytes of response data. If
+the device indicates it could have supplied more data then a second INQUIRY
+is issued to fetch the longer response. That second command may lock up
+faulty devices.
+.PP
+ATA or ATAPI devices that use a SCSI to ATA Translation layer (see
+SAT at www.t10.org) may support the SCSI ATA INFORMATION VPD page. This
+returns the IDENTIFY (PACKET) DEVICE response amongst other things.
+The ATA Information VPD page can be fetched with '\-\-page=ai'.
+.PP
+In the INQUIRY standard response there is a 'MultiP' flag which is set
+when the device has 2 or more ports. Some vendors use the preceding
+vendor specific ('VS') bit to indicate which port is being accessed by
+the INQUIRY command (0 \-> relative port 1 (port "a"), 1 \-> relative
+port 2 (port "b")). When the 'MultiP' flag is set, the preceding vendor
+specific bit is shown in parentheses. SPC\-3 compliant devices should
+use the device identification VPD page (0x83) to show which port is
+being used for access and the SCSI ports VPD page (0x88) to show all
+available ports on the device.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be
+a SCSI generic (sg) device. In the 2.6 series and later block devices (e.g.
+disks and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda"
+will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char"
+device names may be used as well (e.g. "/dev/st0m").
+.PP
+The number of bytes output by \fI\-\-hex\fR and \fI\-\-raw\fR is 36 bytes
+or the number given to \fI\-\-len=LEN\fR (or \fI\-\-maxlen=LEN\fR). That
+number is reduced if the "resid" returned by the HBA indicates less bytes
+were sent back from \fIDEVICE\fR.
+.PP
+The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the
+O_RDONLY flag).
+.SH ATA DEVICES
+There are two major types of ATA devices: non\-packet devices (e.g. ATA
+disks) and packet devices (ATAPI). The majority of ATAPI devices are
+CD/DVD/BD drives in which the ATAPI transport carries the MMC set (i.e.
+a SCSI command set). Further, both types of ATA devices can be connected
+to a host computer via a "SCSI" (or some other) transport. When an
+ATA disk is controlled via a SCSI (or non\-ATA) transport then two
+approaches are commonly used: tunnelling (e.g. STP in Serial Attached
+SCSI (SAS)) or by emulating a SCSI device (e.g. with a SCSI to
+ATA translation layer, see SAT at www.t10.org ). Even when the
+physical transport to the host computer is ATA (especially in the
+case of SATA) the operating system may choose to put a SAT
+layer in the driver "stack" (e.g. libata in Linux).
+.PP
+The main identifying command for any SCSI device is an INQUIRY. The
+corresponding command for an ATA non\-packet device is IDENTIFY DEVICE
+while for an ATA packet device it is IDENTIFY PACKET DEVICE.
+.PP
+When this utility is invoked for an ATAPI device (e.g. a CD/DVD/BD
+drive with "sg_inq /dev/hdc") then a SCSI INQUIRY is sent to the
+device and if it responds then the response to decoded and output and
+this utility exits. To see the response for an ATA IDENTIFY PACKET
+DEVICE command add the \fI\-\-ata\fR option (e.g. "sg_inq \-\-ata /dev/hdc).
+.PP
+This utility doesn't decode the response to an ATA IDENTIFY (PACKET)
+DEVICE command, hdparm does a good job at that. The '\-HHH' option has
+been added for use with either the '\-\-ata' or '\-\-page=ai'
+option to produce a format acceptable to "hdparm \-\-Istdin".
+An example: 'sg_inq \-\-ata \-HHH /dev/hdc | hdparm \-\-Istdin'. See hdparm.
+.SH NVME DEVICES
+Currently these device are typically SSDs (Solid State Disks) directly
+connected to a PCIe connector or via a specialized connector such as a M2
+connector. Linux and FreeBSD treat NVMe storage devices as separate from
+SCSI storage with device names like /dev/nvme0n1 (in Linux) and
+/dev/nvme0ns1 (in FreeBSD). The NVM Express group has a document titled "NVM
+Express: SCSI Translation Reference" which defines a partial "SCSI to NVMe
+Translation Layer" often known by its acronym: SNTL.
+.PP
+On operating systems where it is supported by this package, this utility
+will detect NVMe storage devices directly connected and send an Identify
+controller NVMe Admin command and decode its response. A NVMe controller
+is architecturally similar to a SCSI target device. If the NVMe \fIDEVICE\fR
+indicates a namespace then an Identify namespace NVMe Admin command is sent
+to that namespace and its response is decoded. Namespaces are numbered
+sequentially starting from 1. Namespaces are similar to SCSI Logical Units
+and their identifiers (nsid_s) can be thought of as SCSI LUNs. In the
+Linux and FreeBSD example device names above the "n1" and the "ns1" parts
+indicate nsid 1 . If no namespace is given in the \fIDEVICE\fR then all
+namespaces found in the controller are sent Identify namespace commands and
+the responses are decoded.
+.PP
+To get more details in the response use the \fI\-\-long\fR option. To only
+get the controller's Identify decoded use the \fI\-\-only\fR option.
+.PP
+It is possible that even though the \fIDEVICE\fR presents as a NVMe device,
+it has a SNTL and accepts SCSI commands. In this case to send a SCSI INQUIRY
+command (and fetch its VPD pages) use the sg_vpd(8) utility.
+.SH SG_INQ and SG_VPD
+Both these utilities have much in common since VPD pages are fetched with the
+SCSI INQUIRY command. As more VPD pages have been added (and existing pages
+expanded) the newer ones were only decoded with sg_vpd. Recently with the
+optional JSON output work, it was decided to place all VPD page decoding in
+a source file called sg_vpd_common.c that is linked in by both sg_inq and
+sg_vpd.
+.PP
+This means that the VPD page decoding capabilities of both sg_inq and sg_vpd
+will be the same. Their default actions remain as they were, namely when
+sg_inq is used without command line options, it decodes the "standard INQUIRY
+data format" (usually called the "standard INQUIRY response") response while
+when sg_vpd is used without options, it decodes the "Supported VPD pages" VPD
+page.
+.SH EXIT STATUS
+The exit status of sg_inq is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-36\fR
+only requests 36 bytes of response data for an INQUIRY. Furthermore even
+if the device indicates in its response it can supply more data, a
+second (longer) INQUIRY is not performed. This is a paranoid setting.
+Equivalent to '\-\-len=36' in the OPTIONS section.
+.TP
+\fB\-a\fR
+fetch the ATA Information VPD page [0x89]. Equivalent to '\-\-page=ai' in
+the OPTIONS section. This page is defined in SAT (see at www.t10.org).
+.TP
+\fB\-A\fR
+Assume given \fIDEVICE\fR is an ATA or ATAPI device.
+Equivalent to \fI\-\-ata\fR in the OPTIONS section.
+.TP
+\fB\-b\fR
+decodes the Block Limits VPD page [0xb0]. Equivalent to '\-\-page=bl' in
+the OPTIONS section. This page is defined in SBC\-2 (see www.t10.org) and
+later.
+.TP
+\fB\-B\fR=\fI0|1\fR
+equivalent to \fI\-\-block=0|1\fR in OPTIONS section.
+.TP
+\fB\-c\fR
+set the Command Support Data (CmdDt) bit (defaults to clear(0)). Used in
+conjunction with the \fI\-p=VPD_PG\fR option to specify the SCSI command
+opcode to query. Equivalent to \fI\-\-cmddt\fR in the OPTIONS section.
+.TP
+\fB\-cl\fR
+lists the command data for all supported commands (followed by the command
+name) by looping through all 256 opcodes. This option uses the CmdDt bit
+which is now obsolete. See the sg_opcodes(8) utility.
+Equivalent to '\-\-cmddt \-\-cmddt' in the OPTIONS section.
+.TP
+\fB\-d\fR
+decodes depending on context. If \fI\-e\fR option is given, or any option
+that implies \fI\-e\fR (e.g. '\-i' or '\-p=80'), then this utility attempts
+to decode the indicated VPD page. Otherwise the version descriptors (if any)
+are listed following a standard INQUIRY response. In the version descriptors
+sense, equivalent to \fI\-\-descriptors\fR in the OPTIONS section.
+.TP
+\fB\-e\fR
+enable (i.e. sets) the Vital Product Data (EVPD) bit (defaults to clear(0)).
+Used in conjunction with the \fI\-p=VPD_PG\fR option to specify the VPD page
+to fetch. If \fI\-p=VPD_PG\fR is not given then VPD page 0 (list supported
+VPD pages) is assumed.
+.TP
+\fB\-f\fR
+Equivalent to \fI\-\-force\fR in the OPTIONS section.
+.TP
+\fB\-h\fR
+outputs INQUIRY response in hex rather than trying to decode it.
+Equivalent to \fI\-\-hex\fR in the OPTIONS section.
+.TP
+\fB\-H\fR
+same action as \fI\-h\fR.
+Equivalent to \fI\-\-hex\fR in the OPTIONS section.
+.TP
+\fB\-i\fR
+decodes the Device Identification VPD page [0x83]. Equivalent to \fI\-\-id\fR
+in the OPTIONS section. This page is made up of several "designation
+descriptors". If \fI\-h\fR is given then each descriptor header is decoded
+and the identifier itself is output in hex. To see the whole VPD 0x83 page
+response in hex use '\-p=83 \-h'.
+.TP
+\fB\-I\fR=\fIFN\fR
+equivalent to \fI\-\-inhex=FN\fR in the OPTIONS section.
+.TP
+\fB\-j[\fR=\fIJO]\fR
+equivalent to \fI\-\-json[=JO]\fR in the OPTIONS section.
+.TP
+\fB\-l\fR=\fILEN\fR
+equivalent to \fI\-\-len=LEN\fR in the OPTIONS section.
+.TP
+\fB\-L\fR
+equivalent to \fI\-\-long\fR in the OPTIONS section.
+.TP
+\fB\-m\fR
+decodes the Management network addresses VPD page [0x85]. Equivalent
+to '\-\-page=mna' in the OPTIONS section.
+.TP
+\fB\-M\fR
+decodes the Mode page policy VPD page [0x87]. Equivalent to '\-\-page=mpp'
+in the OPTIONS section.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-o\fR
+equivalent to \fI\-\-only\fR in the OPTIONS section.
+.TP
+\fB\-p\fR=\fIVPD_PG\fR
+used in conjunction with the \fI\-e\fR or \fI\-c\fR option. If neither given
+then the \fI\-e\fR option assumed. When the \fI\-e\fR option is also
+given (or assumed) then the argument to this option is the VPD page number.
+The argument is interpreted as hexadecimal and is expected to be in the range
+0 to ff inclusive. Only VPD page 0 is decoded and it lists supported VPD pages
+and their names (if known). To decode the mandatory device identification
+page (0x83) use the \fI\-i\fR option. A now obsolete usage is when the
+\fI\-c\fR option is given in which case the argument to this option is assumed
+to be a command opcode number. Recent SCSI draft standards have moved this
+facility to a separate command (see sg_opcodes(8)). Defaults to 0 so if
+\fI\-e\fR is given without this option then VPD page 0 is output.
+.TP
+\fB\-P\fR
+decodes the Unit Path Report VPD page [0xc0] which is EMC specific.
+Equivalent to '\-\-page=upr' in the OPTIONS section.
+.TP
+\fB\-r\fR
+outputs the response in binary to stdout. Equivalent to \fI\-\-raw\fR in
+the OPTIONS section. Can be used twice (i.e. '\-rr' (and '\-HHH' has
+same effect)) and if used with the \fI\-A\fR or \fI\-a\fR option yields
+output with the same format as "cat /proc/ide/hd<x>/identify" so that it
+can then be piped to "hdparm \-\-Istdin".
+.TP
+\fB\-s\fR
+decodes the SCSI Ports VPD page [0x88].
+Equivalent to '\-\-page=sp' in the OPTIONS section.
+.TP
+\fB\-u\fR
+equivalent to '\-\-export' in the OPTIONS section.
+.TP
+\fB\-v\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.TP
+\fB\-x\fR
+decodes the Extended INQUIRY data VPD [0x86] page.
+Equivalent to '\-\-page=ei' in the OPTIONS section.
+.TP
+\fB\-?\fR
+output usage message and exit. Ignore all other parameters.
+.SH EXAMPLES
+The examples in this page use Linux device names. For suitable device
+names in other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+To view the standard inquiry response use without options:
+.PP
+ sg_inq /dev/sda
+.PP
+Some SCSI devices include version descriptors indicating the various
+SCSI standards and drafts they support. They can be viewed with:
+.PP
+ sg_inq \-d /dev/sda
+.PP
+Modern SCSI devices include Vital Product Data (VPD)pages which can be
+viewed with the SCSI INQUIRY command. To list the supported VPD
+pages (but not their contents) try:
+.PP
+ sg_inq \-e /dev/sda
+.PP
+In Linux, binary images of some important VPD page responses (e.g. 0, 80h
+and 83h) are cached in files within the sysfs pseudo file system. Since
+VPD pages hardly ever change their contents, decoding those files will
+give the same output as probing the device with the added benefit that
+decoding those files doesn't need root permissions. If /dev/sg3 is a disk
+at 2:0:0:0 , then these three invocations should result in the same output:
+.PP
+ sg_inq \-\-raw \-\-inhex=/sys/class/scsi_generic/sg3/device/vpd_pg83
+.PP
+ sg_inq \-rI /sys/class/scsi_generic/sg3/device/vpd_pg83
+.PP
+ sg_inq \-r \-I /sys/class/scsi_disk/2:0:0:0/device/vpd_pg83
+.PP
+Without the \fI\-\-raw\fR option, the \fI\-\-inhex=FN\fR option would
+expect the contents of those files to be hexadecimal. vpd_pg83 contains
+the response (in binary) to the Device Identification VPD page whose page
+number is 83h (i.e. hexadecimal).
+.PP
+Some VPD pages can be read with the sg_inq utility but a newer utility
+called sg_vpd specializes in showing their contents. The sdparm utility
+can also be used to show the contents of VPD pages.
+.PP
+Further examples of sg_inq together with some typical output can be found
+on https://sg.danny.cz/sg/sg3_utils.html web page.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2001\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2 or the BSD\-2\-Clause
+license. There is NO warranty; not even for MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_opcodes(8), sg_vpd(8), sg_logs(8), sg_modes(8), sdparm(8), hdparm(8),
+.B sgdiag(scsirastools)
diff --git a/doc/sg_logs.8 b/doc/sg_logs.8
new file mode 100644
index 00000000..2de64cde
--- /dev/null
+++ b/doc/sg_logs.8
@@ -0,0 +1,571 @@
+.TH SG_LOGS "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_logs \- access log pages with SCSI LOG SENSE command
+.SH SYNOPSIS
+.B sg_logs
+[\fI\-\-ALL\fR] [\fI\-\-all\fR] [\fI\-\-brief\fR] [\fI\-\-exclude\fR]
+[\fI\-\-filter=FL\fR] [\fI\-\-full\fR] [\fI\-\-hex\fR] [\fI\-\-json[=JO]\fR]
+[\fI\-\-list\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-name\fR] [\fI\-\-no_inq\fR]
+[\fI\-\-page=PG\fR] [\fI\-\-paramp=PP\fR] [\fI\-\-pcb\fR] [\fI\-\-ppc\fR]
+[\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-sp\fR]
+[\fI\-\-temperature\fR] [\fI\-\-transport\fR] [\fI\-\-undefined\fR]
+[\fI\-\-vendor=VP\fR] [\fI\-\-verbose\fR] \fIDEVICE\fR
+.PP
+.B sg_logs
+\fI\-\-in=FN\fR [\fI\-\-brief\fR] [\fI\-\-exclude\fR] [\fI\-\-filter=FL\fR]
+[\fI\-\-full\fR] [\fI\-\-hex\fR] [\fI\-\-json[=JO]\fR] [\fI\-\-name\fR]
+[\fI\-\-page=PG\fR] [\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-undefined\fR]
+[\fI\-\-vendor=VP\fR]
+.PP
+.B sg_logs
+\fI\-\-select\fR [\fI\-\-control=PC\fR] [\fI\-\-page=PG\fR] [\fI\-\-raw\fR]
+[\fI\-\-reset\fR] [\fI\-\-sp\fR] [\fI\-\-verbose\fR] \fIDEVICE\fR
+.PP
+.B sg_logs
+\fI\-\-enumerate\fR [\fI\-\-filter=FL\fR] [\fI\-\-help\fR]
+[\fI\-\-vendor=VP\fR] [\fI\-\-version\fR]
+.PP
+.B sg_logs
+[\fI\-a\fR] [\fI\-A\fR] [\fI\-b\fR] [\fI\-c=PC\fR] [\fI\-D=DT\fR] [\fI\-e\fR]
+[\fI\-E\fR] [\fI\-f=FL\fR] [\fI\-F\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-i=FN\fR]
+[\fI\-l\fR] [\fI\-L\fR] [\fI\-m=LEN\fR] [\fI\-M=VP\fR] [\fI\-n\fR]
+[\fI\-p=PG\fR] [\fI\-paramp=PP\fR] [\fI\-pcb\fR] [\fI\-ppc\fR] [\fI\-r\fR]
+[\fI\-R\fR] [\fI\-select\fR] [\fI\-sp\fR] [\fI\-t\fR] [\fI\-T\fR] [\fI\-u\fR]
+[\fI\-v\fR] [\fI\-V\fR] [\fI\-?\fR] [\fI\-x\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends a SCSI LOG SENSE command to the \fIDEVICE\fR and then
+outputs the response. The LOG SENSE command is used to fetch log pages which,
+if known, are decoded by default. When the \fI\-\-reset\fR and/or
+\fI\-\-select\fR option is given then a SCSI LOG SELECT command is issued
+to the \fIDEVICE\fR. Alternatively one or more log page responses can be in
+a file read using the \fI\-\-in=FN\fR option; in this case those responses
+are decoded and the \fIDEVICE\fR argument, if given, is ignored.
+.PP
+In SPC\-4 revision 5 the subpage code was introduced to both the LOG SENSE and
+LOG SELECT command. At the same time a page code field was introduced to the
+to the LOG SELECT command. The log subpage code can range from 0 to 255 (0xff)
+inclusive. The subpage code value 255 can be thought of as a wildcard.
+.PP
+The SYNOPSIS section above is divided into five forms. The first form
+shows the options that can be used to send a LOG SENSE command to the
+\fIDEVICE\fR and decode its response. The second form fetches data from a
+file (named \fIFN\fR) and decodes it as if it were a response from a LOG
+SENSE command. The third form shows the options that can be used to send a
+LOG SELECT command. The fourth form groups various management options.
+The last form shows the older, deprecated command line interface which is
+maintained for backward compatibility.
+.PP
+When no options are given, just a \fIDEVICE\fR, that is equivalent to calling
+this utility with the \fI\-\-list\fR option. In that case the names of the
+supported log pages (but not subpages) are listed out.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well. The options
+are arranged in alphabetical order based on the long option name.
+.TP
+\fB\-A\fR, \fB\-\-ALL\fR
+fetch and decode all the log pages and subpages supported by the \fIDEVICE\fR.
+This requires a two stage process: first the "supported log pages and
+subpages" log page is fetched, then for each entry in its response, the
+corresponding log page (or subpage) is fetched and displayed. Note that there
+are many SCSI devices that do not support LOG SENSE subpages and respond
+to this option with an illegal request sense key.
+.br
+Since some vendors don't list all log pages in the "supported log pages and
+subpages" log page, the '\-lll' option can be given in addition. This will
+merge both "supported ..." log pages then, from that resultant merged list,
+fetch page contents.
+.br
+The long option may also appear as \fB\-\-All\fR.
+.br
+This option overrides the \fI\-\-page=PG\fR if the latter is also given.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+outputs all the log pages supported by the \fIDEVICE\fR. This requires a two
+stage process: first the "supported log pages" log page is fetched, then for
+each entry in its response, the corresponding log page is fetched and
+displayed. When used twice (e.g. '\-aa') all log pages and subpages are
+fetched.
+.br
+This option overrides the \fI\-\-page=PG\fR if the latter is also given.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+shorten the amount of output for some log pages. For example the Tape
+Alert log page only outputs parameters whose flags are set when
+\fI\-\-brief\fR is given.
+.TP
+\fB\-c\fR, \fB\-\-control\fR=\fIPC\fR
+accepts 0, 1, 2 or 3 for the \fIPC\fR argument:
+.br
+ \fB0\fR : current threshold values
+.br
+ \fB1\fR : current cumulative values
+.br
+ \fB2\fR : default threshold values
+.br
+ \fB3\fR : default cumulative values
+.br
+The default value is 1 (i.e. current cumulative values).
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+this option is used to output information held in this utility's internal
+tables about known log pages including their name, acronym and fields. If
+given, the \fIDEVICE\fR argument is ignored. When given once (e.g. '\-e')
+all known pages are listed, sorted in ascending alphabetical acronym order.
+.br
+When given twice, vendor pages are excluded. When given three times, all
+known pages are listed, sorted in ascending numeric order listed; when given
+four times, vendor pages are excluded from the numeric order.
+.br
+The \fI\-\-filter=FL\fR and \fI\-\-verbose\fR options reduce the output
+of the enumeration.
+.TP
+\fB\-E\fR, \fB\-\-exclude\fR
+this option excludes vendor specific pages and parameters from the output.
+Trying to decode vendor specific pages and parameters does not necessarily
+work well for many reasons. This option limits the output to pages and
+parameters defined by T10.
+.br
+Only parameter fields identified in the drafts as 'vendor specific' are
+excluded. So parameters codes identified as 'reserved' are shown.
+.TP
+\fB\-f\fR, \fB\-\-filter\fR=\fIFL\fR
+\fIFL\fR is either a parameter code when \fIDEVICE\fR is given, or a
+peripheral device type (pdt) (or other) if \fI\-\-enumerate\fR is given.
+.br
+In the parameter code case \fIFL\fR is a value between 0 and 65535 (0xffff)
+and only the parameter section matching that code is output. If the
+\fB\-\-hex\fR option is given the log parameter is output in hexadecimal
+rather than decoding it. If the \fB\-\-hex\fR option is used twice then the
+leading address on each line of hex is removed. If the \fB\-\-raw\fR option
+is given then the log parameter is output in binary. Most log pages contain
+one or more log parameters. Examples of those that don't follow that
+convention are those pages that list supported log pages (and subpages).
+.br
+In the \fI\-\-enumerate\fR case, when \fIFL\fR >= zero it is taken as a
+pdt value and only log pages associated with that pdt plus generic pages
+listed in SPC are enumerated. If \fIFL\fR is \-1 then the filter does
+nothing which is the same as not giving this option; when \fIFL\fR is \-2
+then only generic pages listed in SPC are enumerated. If \fIFL\fR is \-10
+then only generic direct access like (e.g. disk) pages are enumerated. If
+\fIFL\fR is \-11 then only generic tape like pages (e.g. includes ADC)
+are enumerated.
+.TP
+\fB\-F\fR, \fB\-\-full\fR
+this option only applies to the Application client log page. Typically that
+log page has more than 16,000 bytes of user supplied data. Rather than
+print it all out, the default is to print out the first 64 bytes of data.
+When this option is given, the application client log pages is fully
+decoded.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+The default action is to decode known log page numbers (and subpage numbers)
+into text. When this option is used once, the response is output in
+hexadecimal. When used twice, each line of hex has the ASCII equivalent shown
+to the right. When used three times, the hex has no leading address nor
+trailing ASCII making it suitable to be placed in a file (or piped). That
+file might later be used by another invocation using the \fI\-\-in=FN\fR
+option.
+.br
+A weaker form of this option, called \fI\-\-undefined\fR, handles the
+formatting of hexadecimal output for fields that this utility is unable to
+decode.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR
+This option may be used in two different contexts. One is with the
+\fI\-\-select\fR to send a LOG SELECT command to the given \fIDEVICE\fR;
+see the LOG SELECT section below.
+.br
+The other context is with no \fIDEVICE\fR argument given in which case
+the contents of \fIFN\fR are decoded as if it were the response of a LOG
+SENSE command (i.e. a log page). For decoding the page and subpage numbers
+are taken from \fIFN\fR while the peripheral device type is either
+generic (i.e. from SPC) or the value given by \fI\-\-pdt=DT\fR.
+.br
+\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII
+hexadecimal or binary representing a log page. The hexadecimal should be
+arranged as 1 or 2 digits representing a byte each of which is whitespace or
+comma separated. Anything from and including a hash mark to the end of line
+is ignored. If the \fI\-\-raw\fR option is also given then \fIFN\fR is
+treated as binary.
+.br
+For compatibility with other utilities in this package "inhex" may also be
+used as this (long) option name.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR in order to have a summary printed to stderr.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+lists the names of the logs sense pages supported by this device. This is
+done by reading the "supported log pages" log page. When used once only
+log pages, but not subpages, are listed. When used twice the "supported
+log pages and subpages" log page is output. Some vendors do not list some
+log pages (e.g. those without any subpages) in the "supported log pages
+and subpages" log page. To get a full inventory, this option can be used
+three times (e.g. '\-lll') and the output of the two log pages is merged.
+Even if the "supported log pages and subpages" log page is not supported
+using this option three times will yield a list from the "supported log
+pages" log page. In the absence of other options, the page/subpage names,
+but not their contents, are shown when this option is given.
+.br
+The '\-lll' form may be useful with the \fI\-\-ALL\fR option to show the
+contents of all pages referred to in either the "supported log page" or
+the "supported log page and subpage" log pages.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+sets the "allocation length" field in the LOG SENSE cdb. The is the maximum
+length in bytes that the response will be. Without this option (or \fILEN\fR
+equal to 0) this utility first fetches the 4 byte response then does a second
+access with the length indicated in the first (4 byte) response. Negative
+values and 1 for \fILEN\fR are not accepted. Responses can be quite
+large (e.g. the background scan results log page) and this option can be used
+to limit the amount of information returned.
+.br
+The default \fILEN\fR is 65532 unless the \fI\-\-in=FN\fR option is given;
+in that case the default is 262144 .
+.TP
+\fB\-n\fR, \fB\-\-name\fR
+decode some log pages into 'name=value' entries, one per line. The name
+contains no space and may be abbreviated and the value is decimal unless
+prefixed by '0x'. Nesting is indicated by leading spaces. This form
+is meant to be relatively easy to parse.
+.br
+This option is superseded by the \fI\-\-json[=JO]\fR option. If both are
+given then this option is ignored.
+.TP
+\fB\-x\fR, \fB\-\-no_inq\fR
+suppresses the output of information obtained from an initial call to the
+INQUIRY command for the standard response. The default (assuming some other
+options that suppress this output are also not given) is to output several
+device identification strings.
+.br
+If this option is given twice (or more) then no INQUIRY command is sent
+hence there will be no device identification string output either. Also the
+peripheral device type (PDT) field will not be obtained so this utility will
+not be able to differentiate between some log pages that are device
+dependent. It will assume a PDT of 0 (i.e. a disk).
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR
+log page name/number to access. \fIPG\fR is either an acronym, a page number,
+or a page, subpage number pair. Available acronyms can be listed with the
+\fI\-\-enumerate\fR option. Page (0 to 63) and subpage (0 to 255) numbers
+are comma separated. They are decimal unless a hexadecimal indication is
+given. A hexadecimal number can be specified by a leading "0x" or a
+trailing "h".
+.br
+A few acronyms specify a range of subpage values in which case the acronym
+may be followed by a comma then a subpage number. This method can also be
+used to fetch the Supported subpages log page (e.g. \-\-page=temp,0xff).
+.TP
+\fB\-P\fR, \fB\-\-paramp\fR=\fIPP\fR
+\fIPP\fR is the parameter pointer value to place in a field of that name in
+the LOG SENSE cdb. A number in the range 0 to 65535 (0x0 to 0xffff) is
+expected. When a value greater than 0 is given the \fI\-\-ppc\fR option
+should be selected. The default value is 0.
+.br
+For log pages that have parameter codes, the \fIDEVICE\fR should return
+only parameters code equal to \fIPP\fR or higher in its response.
+.TP
+\fB\-q\fR, \fB\-\-pcb\fR
+show Parameter Control Byte settings (only relevant when log parameters
+being output in ASCII). This byte includes the DU and TSD bits plus
+the 'Format and linking' field (2 bits wide).
+.TP
+\fB\-D\fR, \fB\-\-pdt\fR=\fIDT\fR
+\fIDT\fR is the peripheral device type (PDT) that is used when it is not
+available from the \fIDEVICE\fR. There are two main cases of this: with
+the \fI\-\-pdt=DT\fR without a \fIDEVICE\fR (e.g. when \fI\-\-in=FN\fR
+is used) and when \fI\-\-no_inq\fR is used with a \fIDEVICE\fR.
+.br
+\fIDT\fR may be -1 which is the default value. This value may select any
+device type but favours the lower numbers (e.g. the PDT of disks is 0).
+.TP
+\fB\-Q\fR, \fB\-\-ppc\fR
+sets the Parameter Pointer Control (PPC) bit in the LOG SENSE cdb. Default
+is 0 (i.e. cleared). This bit was made obsolete in SPC\-4 revision 18.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the response in binary to stdout. Error messages and warnings are
+output to stderr.
+.br
+This option may also be given together with \fI\-\-in=FN\fR in which case
+the contents of \fIFN\fR are interpreted as binary data (and the response is
+decoded as normal, not dumped as binary).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). The
+default action is to try and open \fIDEVICE\fR read\-write then if that
+fails try to open again with read\-only. However when a read\-write open
+succeeds there may still be unwanted actions on the close (e.g. some OSes
+try to do a SYNCHRONIZE CACHE command). So this option forces a read\-only
+open on \fIDEVICE\fR and if it fails, this utility will exit. Note that
+options like \fI\-\-select\fR most likely need a read\-write open.
+.TP
+\fB\-R\fR, \fB\-\-reset\fR
+use SCSI LOG SELECT command (with the PCR bit set) to reset the all log
+pages (or the given page). Exactly what is reset depends on the accompanying
+SP bit (i.e. \fI\-\-sp\fR option which defaults to 0) and the
+\fIPC\fR ("page control") value (which defaults to 1). Supplying this option
+implies the \fI\-\-select\fR option as well. This option seems to clear error
+counter log pages but leaves pages like self\-test results, start\-stop cycle
+counter and temperature log pages unaffected. This option may be required to
+clear log pages if a counter reaches its maximum value since the log page in
+which the counter is found will remain "stuck" at its maximum value until
+some user interaction (e.g. calling sg_logs with this option).
+.TP
+\fB\-S\fR, \fB\-\-select\fR
+use a LOG SELECT command. The default action (i.e. when neither this option
+nor \fI\-\-reset\fR is given) is to do a LOG SENSE command. See the LOG
+SELECT section.
+.TP
+\fB\-s\fR, \fB\-\-sp\fR
+sets the Saving Parameters (SP) bit. Default is 0 (i.e. cleared). When set
+this instructs the device to store the current log page parameters (as
+indicated by the DS and TSD parameter codes) in some non\-volatile location.
+Hence the log parameters will be preserved across power cycles. This option
+is typically not needed, especially if the GLTSD flag is clear in the
+control mode page which causes the \fIDEVICE\fR to periodically save all
+saveable log parameters to non\-volatile storage.
+.TP
+\fB\-t\fR, \fB\-\-temperature\fR
+outputs the temperature. First looks in the temperature log page and if
+that is not available tries the Informational Exceptions log page which
+may also have the current temperature (especially on older disks).
+.TP
+\fB\-T\fR, \fB\-\-transport\fR
+outputs the transport ('Protocol specific port') log page. Equivalent to
+setting '\-\-page=18h'.
+.TP
+\fB\-u\fR, \fB\-\-undefined\fR
+to see fields decoded, the \fI\-\-hex\fR option cannot be used. However some
+fields are not defined in the T10 documents and in that case they are output
+in hex. This option controls the format of 'undefined' fields when they
+output in hex. Multiple uses of this option has the same sense as the
+\fI\-\-hex\fR option. For example '\-uu' will output undefined fields in
+hexadecimal with an ASCII rendering to the right of each line.
+.TP
+\fB\-M\fR, \fB\-\-vendor\fR=\fIVP\fR
+where \fIVP\fR is a vendor/manufacturer (e.g. "sea" for Seagate) or
+product (group) acronym (e.g. "lto5" for the 5th generation LTO (tape)
+consortium). Either the whole log page is vendor specific (e.g. page
+numbers 0x30 to 0x3f) or part of a T10 defined log page is vendor specific.
+For example SPC\-5 defines parameter code 0x0 of page 0x2f (the Informational
+Exceptions log page) and states that the remaining parameter codes (i.e. 0x1
+to 0xffff) are vendor specific. Using a \fIVP\fR of "xxx" will list the
+available acronyms.
+.br
+If this option is used with \fI\-\-page=PG\fR and \fIPG\fR is an acronym
+then this option is ignored. If \fIPG\fR is a number (e.g. 0xc0) then
+\fIVP\fR is used to choose the which vendor specific page (e.g. sharing
+page number 0xc0) to decode.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. When used with \fI\-\-enumerate\fR, in the
+list of known log page names, those that have no associated decode logic
+are followed by "[hex only]".
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH LOG SELECT
+The SCSI LOG SELECT command can be used to reset certain parameters to vendor
+specific defaults, save them to non\-volatile storage (i.e. the media), or
+supply new page contents. This command has changed between SPC\-3 and SPC\-4
+with the addition of the Page and Subpage Code fields which can only be
+non zero when the Parameter list length is zero.
+.PP
+The \fI\-\-select\fR (or \fI\-\-reset\fR) option is required to issue a LOG
+SELECT command. If the \fI\-\-in=FN\fR option is not given (or \fIFN\fR is
+effectively empty) then the Parameter list length field is set to zero. If
+the \fI\-\-in=FN\fR option is is given then its decoded data is placed in
+the data\-out buffer and its length in bytes is placed in the Parameter list
+length field.
+.PP
+Other options that are active with the LOG SELECT command are
+\fI\-\-control=PC\fR, \fI\-\-reset\fR (which sets the PCR bit) and
+\fI\-\-sp\fR.
+.SH
+APPLICATION CLIENT
+This is the name of a log page that acts as a container for data provided
+by the user. An application client is a SCSI term for the program that issues
+commands to a SCSI initiator (often known as a Host Bus Adapter (HBA)). So,
+for example, this utility is a SCSI application client.
+.PP
+The Application Client log page has 64 log parameters with parameters codes
+0 to 63. Each can hold 252 bytes of user binary data. That 252 bytes (or
+less) of user data, with a 4 byte prefix (for a total of 256 bytes) can be
+provided with the \fI\-\-in=FN\fR option. A typical prefix would
+be '0,n,83,fc'. The "n" is the parameter code in hex so the last log
+parameter would be '0,3f,83,fc'. That log parameter could be read back at
+some later time with '\-\-page=0xf \-\-filter=0x<n>'.
+.SH NOTES
+This utility will usually do a double fetch of log pages with the SCSI LOG
+SENSE command. The first fetch requests a 4 byte response (i.e. place 4 in
+the "allocation length" field in the cdb). From that response it can
+calculate the actual length of the response which is what it asks for
+on the second fetch. This is typical practice in SCSI and guaranteed to
+work in the standards. However some older devices don't comply. For
+those devices using the \fI\-\-maxlen=LEN\fR option will do a single fetch.
+A value of 252 should be a safe starting point.
+.PP
+Various log pages hold information error rates, device temperature, start
+stop cycles since the device was produced and the results of the last
+20 self tests. Self tests can be initiated by the sg_senddiag(8) utility.
+The smartmontools package provides much of the information found with
+sg_logs in a form suitable for monitoring the health of SCSI disks and
+tape drives.
+.PP
+The simplest way to find which log pages can be decoded by this utility is
+to use the \fI\-\-enumerate\fR option. Some page names are known but there
+is no decode logic; such cases have "[hex only]" after the log page name
+when the \fI\-\-verbose\fR option is given with \fI\-\-enumerate\fR.
+.PP
+Vendors are specifically permitted by the SPC\-6 to _not_ report all pages
+and subpages supported by a device. That weakens the usefulness of the pages
+that report a list of supported pages and subpages. One guarantee which is
+given is that the pages reported shall be in ascending order.
+.SH EXIT STATUS
+The exit status of sg_logs is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.PP
+Options with arguments or with two or more letters can have an extra '\-'
+prepended. For example: both '\-pcb' and '\-\-pcb' are acceptable.
+.TP
+\fB\-a\fR
+outputs all the log pages supported by the \fIDEVICE\fR.
+Equivalent to \fI\-\-all\fR in the main description.
+.TP
+\fB\-A\fR
+outputs all the log pages and subpages supported by the \fIDEVICE\fR.
+Equivalent to \fI\-\-ALL\fR in the main description.
+.TP
+\fB\-c\fR=\fIPC\fR
+Equivalent to \fI\-\-control=PC\fR in the main description.
+.TP
+\fB\-D\fR=\fIDT\fR
+Equivalent to \fI\-\-pdt=DT\fR in the main description.
+.TP
+\fB\-e\fR
+enumerate internal tables to show information about known log pages.
+Equivalent to \fI\-\-enumerate\fR in the main description.
+.TP
+\fB\-E\fR
+Equivalent to \fI\-\-exclude\fR in the main description.
+.TP
+\fB\-h\fR
+suppresses decoding of known log sense pages and prints out the
+response in hex instead.
+.TP
+\fB\-i\fR=\fIFN\fR
+\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII
+hexadecimal representing a log page that will be sent as parameter data of a
+LOG SELECT command. See the LOG SELECT section.
+.TP
+\fB\-H\fR
+same action as '\-h' in this section and equivalent to \fI\-\-hex\fR in
+the main description.
+.TP
+\fB\-l\fR
+lists the names of all logs sense pages supported by this \fIDEVICE\fR.
+Equivalent to \fI\-\-list\fR in the main description.
+.TP
+\fB\-L\fR
+lists the names of all logs sense pages and subpages supported by this
+\fIDEVICE\fR. Equivalent to '\-\-list \-\-list' in the main description.
+.TP
+\fB\-m\fR=\fILEN\fR
+request only \fILEN\fR bytes of response data. Default is 0 which is
+interpreted as all that is available. \fILEN\fR is decimal unless it has
+a leading '0x' or trailing 'h'. Equivalent to \fI\-\-maxlen=LEN\fR in
+the main description.
+.TP
+\fB\-M\fR=\fIVP\fR
+Equivalent to \fI\-\-vendor=VP\fR in the main description.
+.TP
+\fB\-n\fR
+Equivalent to \fI\-\-name\fR in the main description.
+.TP
+\fB\-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-p\fR=\fIPG\fR
+log page code to access. \fIPG\fR is either an acronym, a page number, or
+a page, subpage pair. Available acronyms can be listed with the
+\fI\-\-enumerate\fR option. Page (0 to 3f) and subpage (0 to ff) numbers
+are comma separated. The numbers are assumed to be hexadecimal.
+.TP
+\fB\-paramp\fR=\fIPP\fR
+\fIPP\fR is the parameter pointer value (in hex) to place in command.
+Should be a number between 0 and ffff inclusive.
+.TP
+\fB\-pcb\fR
+show Parameter Control Byte settings (only relevant when log parameters
+being output in ASCII).
+.TP
+\fB\-ppc\fR
+sets the Parameter Pointer Control (PPC) bit. Default is 0 (i.e. cleared).
+.TP
+\fB\-r\fR
+use SCSI LOG SELECT command (PCR bit set) to reset the all log pages (or
+the given page). Equivalent to \fI\-\-reset\fR in the main description.
+.TP
+\fB\-R\fR
+Equivalent to \fI\-\-readonly\fR in the main description.
+.TP
+\fB\-select\fR
+use a LOG SELECT command. Equivalent to \fI\-\-select\fR in the main
+description.
+.TP
+\fB\-sp\fR
+sets the Saving Parameters (SP) bit. Default is 0 (i.e. cleared).
+Equivalent to \fI\-\-sp\fR in the main description.
+.TP
+\fB\-t\fR
+outputs the temperature. Equivalent to \fI\-\-temperature\fR in the main
+description.
+.TP
+\fB\-T\fR
+outputs the transport ('Protocol specific port') log page. Equivalent
+to \fI\-\-transport\fR in the main description.
+.TP
+\fB\-v\fR
+increase level of verbosity.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.TP
+\fB\-x\fR
+suppress the INQUIRY command. Equivalent to \fI\-\-no_inq\fR in the main
+description.
+.TP
+\fB\-?\fR
+output usage message then exit.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2002\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B smartctl(smartmontools), sg_senddiag(8)
diff --git a/doc/sg_luns.8 b/doc/sg_luns.8
new file mode 100644
index 00000000..3ededa9f
--- /dev/null
+++ b/doc/sg_luns.8
@@ -0,0 +1,319 @@
+.TH SG_LUNS "8" "January 2020" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_luns \- send SCSI REPORT LUNS command or decode given LUN
+.SH SYNOPSIS
+.B sg_luns
+[\fI\-\-decode\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-linux\fR]
+[\fI\-\-lu_cong\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-quiet\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-select=SR\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_luns
+\fI\-\-test=ALUN\fR [\fI\-\-decode\fR] [\fI\-\-hex\fR] [\fI\-\-lu_cong\fR]
+[\fI\-\-verbose\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+In the first form shown in the SYNOPSIS this utility sends the SCSI REPORT
+LUNS command to the \fIDEVICE\fR and outputs the response. The response
+should be a list of LUNs ("a LUN inventory") for the I_T nexus associated
+with the \fIDEVICE\fR. Roughly speaking that is all LUNs that share the
+target device that the REPORT LUNS command is sent through. This command
+is defined in the SPC\-3 and SPC\-4 SCSI standards and its support is
+mandatory. The most recent draft if SPC\-6 revision 1.
+.PP
+When the \fI\-\-test=ALUN\fR option is given (the second form in the
+SYNOPSIS), then the \fIALUN\fR value is decoded as outlined in various
+SCSI Architecture Model (SAM) standards and recent drafts (e.g. SAM\-6
+revision 2, section 4.7) .
+.PP
+Where required below the first form shown in the SYNOPSIS is called "device
+mode" and the second form is called "test mode".
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-decode\fR
+decode LUNs into their component parts, as described in the LUN section
+of SAM\-3, SAM\-4 and SAM\-5.
+.br
+[test mode] \fIALUN\fR is decoded irrespective of whether this option is
+given or not. If this option is given once then the given \fIALUN\fR is
+output in T10 preferred format (which is 8 pairs of hex digits, each
+separated by a space). If given twice then the given \fIALUN\fR is output
+in an alternate T10 format made up of four quads of hex digits with each
+quad separated by a "-" (e.g. C101\-0000\-0000\-0000).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+[device mode] when given once this utility will output the SCSI
+response (i.e. the data\-out buffer) to the REPORT LUNS command in ASCII
+hex then exit. When given twice it causes \fI\-\-decode\fR to output
+component fields in hex rather than decimal.
+.br
+[test mode] when this option is given, then decoded component fields of
+\fIALUN\fR are output in hex.
+.TP
+\fB\-l\fR, \fB\-\-linux\fR
+this option is only available in Linux. After the T10 representation of
+each 64 bit LUN (in 16 hexadecimal digits), if this option is given then
+to the right, in square brackets, is the Linux LUN integer in decimal.
+If the \fI\-\-hex\fR option is given twice (e.g. \-HH) as well then the
+Linux LUN integer is output in hexadecimal.
+.TP
+\fB\-L\fR, \fB\-\-lu_cong\fR
+this option is only considered with \fI\-\-decode\fR. When given once
+then the list of LUNs is decoded as if the LU_CONG bit was set in
+each LU's corresponding INQUIRY response. When given twice the list of
+LUNs is decoded as if the LU_CONG bit was clear in each LU's corresponding
+INQUIRY response. When this option is not given and \fI\-\-decode\fR is
+given then an INQUIRY is sent to the \fIDEVICE\fR and the setting of
+its LU_CONG bit is used to decode the list of LUNs.
+.br
+[test mode] decode \fIALUN\fR as if the LU_CONG bit is set in its
+corresponding standard INQUIRY response. In other words treat \fIALUN\fR
+as if it is a conglomerate LUN. If not given (or given twice) then decode
+\fIALUN\fR as if the LU_CONG bit is clear.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+output only the ASCII hex rendering of each report LUN, one per line.
+Without the \fI\-\-quiet\fR option, there is header information printed
+before the LUN listing.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the SCSI response (i.e. the data\-out buffer) in binary (to stdout).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-s\fR, \fB\-\-select\fR=\fISR\fR
+\fISR\fR is placed in the SELECT REPORT field of the SCSI REPORT LUNS
+command. The default value is 0. Hexadecimal values may be given with
+a leading "0x" or a trailing "h". For detailed information see the
+REPORT LUNS command in SPC (most recent is SPC\-4 revision 37 in section
+6.33). To simplify, for the I_T nexus associated with the \fIDEVICE\fR, the
+meanings of the \fISR\fR values defined to date for SPC\-4 are:
+.br
+ \fB0\fR : most luns excluding well known logical unit numbers
+.br
+ \fB1\fR : well known logical unit numbers
+.br
+ \fB2\fR : all luns accessible to this I_T nexus
+.br
+ \fB0x10\fR : only accessible administrative luns
+.br
+ \fB0x11\fR : administrative luns plus non-conglomerate luns (see SPC\-4)
+.br
+ \fB0x12\fR : if \fIDEVICE\fR is an administrative LU, then report its
+.br
+ lun plus its subsidiary luns
+.PP
+For \fISR\fR values 0x10 and 0x11, the \fIDEVICE\fR must be either LUN 0 or
+the REPORT LUNS well known logical unit. Values between 0xf8 and
+0xff (inclusive) are vendor specific, other values are reserved. This
+utility will accept any value between 0 and 255 (0xff) for \fISR\fR .
+.TP
+\fB\-t\fR, \fB\-\-test\fR=\fIALUN\fR
+\fIALUN\fR is assumed to be a hexadecimal number in ASCII hex or the
+letter 'L' followed by a decimal number (see below). The hexadecimal number
+can be up to 64 bits in size (i.e. 16 hexadecimal digits) and is padded to
+the right if less than 16 hexadecimal digits are given (e.g.
+\fI\-\-test=0122003a\fR represents T10 LUN: 01 22 00 3a 00 00 00 00).
+\fIALUN\fR may be prefixed by '0x' or '0X' (e.g. the previous example could
+have been \fI\-\-test=0x0122003a\fR). \fIALUN\fR may also be given with
+spaces, tabs, or a '\-' between each byte (or other grouping (e.g.
+c101\-0000\-0000\-0000)). However in the case of space or tab separators
+the \fIALUN\fR would need to be surrounded by single or double quotes.
+.br
+In the leading 'L' case the, following decimal number (hex if preceded
+by '0x') is assumed to be a Linux "word flipped" LUN which is converted
+into a T10 LUN representation and printed. In both cases the number is
+interpreted as a LUN and decoded as if the \fI\-\-decode\fR option had been
+given. Also when \fIALUN\fR is a hexadecimal number it can have a
+trailing 'L' in which case the corresponding Linux "word flipped" LUN value
+is output. The LUN is decoded in all cases.
+.br
+The action when used with \fI\-\-decode\fR is explained under that option.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+The SCSI REPORT LUNS command is important for Logical Unit (LU) discovery.
+After a target device is discovered (usually via some transport specific
+mechanism) and after sending an INQUIRY command (to determine the LU_CONG
+setting), a REPORT LUNS command should either be sent to LUN 0 (which
+is Peripheral device addressing method with bus_id=0 and target/lun=0)
+or to the REPORT LUNS well known LUN (i.e. 0xc101000000000000). SAM\-5
+requires that one of these responds with an inventory of LUNS that are
+contained in this target device.
+.PP
+In test mode, if the \fI\-\-hex\fR option is given once then in the decoded
+output, some of the component fields are printed in hex with leading zeros.
+The leading zeros are to indicate the size of the component field. For
+example: in the Peripheral device addressing method (16 bits overall), the
+bus ID is 6 bits wide and the target/LUN field is 8 bits wide; so both are
+shown with two hex digits (e.g. bus_id=0x02, target=0x3a).
+.SH EXAMPLES
+Typically by the time user space programs get to run, SCSI LUs have been
+discovered. In Linux the lsscsi utility lists the LUs that are currently
+present. The LUN of a device (LU) is the fourth element in the tuple at the
+beginning of each line. Below we see a target (or "I_T Nexus": "6:0:0") has
+two LUNS: 1 and 49409. If 49409 is converted into T10 LUN format it is
+0xc101000000000000 which is the REPORT LUNS well known LUN.
+.PP
+ # lsscsi \-g
+.br
+ [6:0:0:1] disk Linux scsi_debug 0004 /dev/sdb /dev/sg1
+.br
+ [6:0:0:2] disk Linux scsi_debug 0004 /dev/sdc /dev/sg2
+.br
+ [6:0:0:49409]wlun Linux scsi_debug 0004 \- /dev/sg3
+.PP
+We could send a REPORT LUNS command (with \fISR\fR 0x0, 0x1 or 0x2) to any
+of those file device nodes and get the same result. Below we use /dev/sg1 :
+.PP
+ # sg_luns /dev/sg1
+.br
+ Lun list length = 16 which imples 2 lun entry
+.br
+ Report luns [select_report=0x0]:
+.br
+ 0001000000000000
+.br
+ 0002000000000000
+.PP
+That is a bit noisy so cut down the clutter with \fI\-\-quiet\fR:
+.PP
+ # sg_luns \-q /dev/sg1
+.br
+ 0001000000000000
+.br
+ 0002000000000000
+.PP
+Now decode that LUN into its component parts:
+.PP
+ # sg_luns \-d \-q /dev/sg1
+.br
+ 0001000000000000
+.br
+ Peripheral device addressing: lun=1
+.br
+ 0002000000000000
+.br
+ Peripheral device addressing: lun=2
+.PP
+Now use \fI\-\-select=1\fR to find out if there are any well known
+LUNs:
+.PP
+ # sg_luns \-q \-s 1 /dev/sg1
+.br
+ c101000000000000
+.PP
+So how many LUNs do we have all together (associated with the current
+I_T Nexus):
+.PP
+ # sg_luns \-q \-s 2 /dev/sg1
+.br
+ 0001000000000000
+.br
+ 0002000000000000
+.br
+ c101000000000000
+.PP
+ # sg_luns \-q \-s 2 \-d /dev/sg1
+.br
+ 0001000000000000
+.br
+ Peripheral device addressing: lun=1
+.br
+ 0002000000000000
+.br
+ Peripheral device addressing: lun=1
+.br
+ c101000000000000
+.br
+ REPORT LUNS well known logical unit
+.PP
+The following example uses the \fI\-\-linux\fR option and is not available
+in other operating systems. The extra number in square brackets is the
+Linux version of T10 LUN shown at the start of the line.
+.PP
+ # sg_luns \-q \-s 2 \-l /dev/sg1
+.br
+ 0001000000000000 [1]
+.br
+ 0002000000000000 [2]
+.br
+ c101000000000000 [49409]
+.PP
+Now we use the \fI\-\-test=\fR option to decode LUNS input on the command
+line (rather than send a REPORT LUNS command and act on the response):
+.PP
+ # sg_luns \-\-test=0002000000000000
+.br
+ Decoded LUN:
+.br
+ Peripheral device addressing: lun=2
+.PP
+ # sg_luns \-\-test="c1 01"
+.br
+ Decoded LUN:
+.br
+ REPORT LUNS well known logical unit
+.PP
+ # sg_luns \-t 0x023a004b \-H
+.br
+ Decoded LUN:
+.br
+ Peripheral device addressing: bus_id=0x02, target=0x3a
+.br
+ >>Second level addressing:
+.br
+ Peripheral device addressing: lun=0x4b
+.PP
+The next example is Linux specific as we try to find out what the
+Linux LUN 49409 translates to in the T10 world:
+.PP
+ # sg_luns \-\-test=L49409
+.br
+ 64 bit LUN in T10 preferred (hex) format: c1 01 00 00 00 00 00 00
+.br
+ Decoded LUN:
+.br
+ REPORT LUNS well known logical unit
+.PP
+And the mapping between T10 and Linux LUN representations can be done the
+other way:
+.PP
+ # sg_luns \-t c101L
+.br
+ Linux 'word flipped' integer LUN representation: 49409
+.br
+ Decoded LUN:
+.br
+ REPORT LUNS well known logical unit
+.br
+.SH EXIT STATUS
+The exit status of sg_luns is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq(8)
diff --git a/doc/sg_map.8 b/doc/sg_map.8
new file mode 100644
index 00000000..abe0935d
--- /dev/null
+++ b/doc/sg_map.8
@@ -0,0 +1,182 @@
+.TH SG_MAP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+sg_map \- displays mapping between Linux sg and other SCSI devices
+.SH SYNOPSIS
+.B sg_map
+[\fI\-a\fR] [\fI-h\fR] [\fI\-i\fR] [\fI\-n\fR] [\fI\-scd\fR] [\fI\-sd\fR]
+[\fI\-sr\fR] [\fI\-st\fR] [\fI\-V\fR] [\fI\-x\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sometimes it is difficult to determine which SCSI device a sg device
+name (e.g. /dev/sg0) refers to. This command loops through the
+sg devices and finds the corresponding SCSI disk, cdrom or tape
+device name (if any). Scanners are an example of SCSI devices
+that have no alternate SCSI device name apart from their sg device
+name.
+.PP
+This utility is deprecated and has not been updated for years, only very
+obvious bugs will be fixed. Unless a very old version of Linux is being
+used (e.g. 2.4 series or earlier), then please use a utility like lsscsi(8)
+or the facilities offered by udev(8).
+.SH OPTIONS
+.TP
+\fB\-a\fR
+assume the sg devices have alphabetical device names and loop
+through /dev/sga, /dev/sgb, etc. Default is numeric scan.
+Note that sg device nodes with an alphabetical index have been
+deprecated since the Linux kernel 2.2 series.
+.TP
+\fB\-h\fR
+print usage message then exit.
+.TP
+\fB\-i\fR
+in addition do a standard INQUIRY and output vendor, product and revision
+strings for devices that are found.
+.TP
+\fB\-n\fR
+assume the sg devices have numeric device names and loop
+through /dev/sg0, /dev/sg1, etc. Default is numeric scan
+.TP
+\fB\-scd\fR
+display mappings to SCSI cdrom device names of the form
+/dev/scd0, /dev/scd1 etc
+.TP
+\fB\-sd\fR
+display mappings to SCSI disk device names
+.TP
+\fB\-sr\fR
+display mappings to SCSI cdrom device names of the form
+/dev/sr0, /dev/sr1 etc
+.TP
+\fB\-st\fR
+display mappings to SCSI tape device names
+.TP
+\fB\-V\fR
+print out version string then exit (without further ado).
+.TP
+\fB\-x\fR
+after each active sg device name is displayed there are
+five digits: <host_number> <bus> <scsi_id> <lun> <scsi_type>
+.SH NOTES
+If no options starting with "\-s" are given then the mapping to
+all SCSI disk, cdrom and tape device names is shown.
+.PP
+If the device file system (devfs) is present a line noting
+this is output. The "native" devfs scsi hierarchy makes the
+relationship between a sg device name and any corresponding
+disk, cdrom or tape device name easy to establish. This
+replaces the need for this command. However many applications
+will continue to look for Linux SCSI device names in their
+traditional places. [Devfs supplies a compatibility daemon
+called devfsd whose default configuration adds back the
+Linux device names in their traditional positions.
+.PP
+Quite often the mapping information can be derived by
+observing the output of the command: "sg_map".
+However if devices have been added since boot this can
+be deceptive.
+.PP
+In the Linux kernel 2.6 series something close to the mapping
+shown by this utility can be found by analysing sysfs. The
+main difference is that sysfs analysis will show the mapping
+between sg nodes and other SCSI device nodes in terms of
+major and minor numbers. While major 8, minor 16 will usually
+be /dev/sdb this is not necessarily so. Facilities associated
+with udev may assign major 8, minor 16 some other device node
+name. This version of sg_map has been extended to cope with
+sparse disk device node names of the form "/dev/sd<str>"
+where <str> can be one of [a\-z,aa\-zz,aaa\-zzz]. See the sg_map26
+utility for a more precise way (i.e. less directory scanning)
+for mapping between sg device names and higher level names;
+including finding user defined names.
+.PP
+This utility was written at a time when hotplugging of SCSI devices
+was not supported in Linux. It used a simple algorithm to scan sg
+device nodes in ascending numeric or alphabetical order, stopping
+after there were 5 consecutive errors.
+.PP
+In the Linux kernel 2.6 series, this utility uses sysfs to find which
+sg device nodes are active and only checks those. Hence there can be
+large "holes" in the numbering of sg device nodes (e.g. after an
+adapter has been removed) and still all active sg device nodes will
+be listed. This utility assumes that sg device nodes are named using
+the normal conventions and searches from /dev/sg0 to /dev/sg4095
+inclusive.
+.SH EXAMPLES
+.PP
+My system has a SCSI disk, a cd writer and a dvd player:
+.br
+ $ sg_map
+.br
+ # Note: the devfs pseudo file system is present
+.br
+ /dev/sg0 /dev/sda
+.br
+ /dev/sg1 /dev/sr0
+.br
+ /dev/sg2 /dev/sr1
+.PP
+In order to find which sg device name corresponds to the disk:
+.br
+ $ sg_map \-sd
+.br
+ # Note: the devfs pseudo file system is present
+.br
+ /dev/sg0 /dev/sda
+.br
+ /dev/sg1
+.br
+ /dev/sg2
+.PP
+The "\-x" option gives the following output:
+.br
+ sg_map \-x
+.br
+ # Note: the devfs pseudo file system is present
+.br
+ /dev/sg0 1 0 1 0 0 /dev/sda
+.br
+ /dev/sg1 2 0 4 0 5 /dev/sr0
+.br
+ /dev/sg2 2 0 6 0 5 /dev/sr1
+.PP
+When a SCSI scanner is added the output becomes:
+.br
+ $ sg_map
+.br
+ # Note: the devfs pseudo file system is present
+.br
+ /dev/sg0 /dev/sda
+.br
+ /dev/sg1 /dev/sr0
+.br
+ /dev/sg2 /dev/sr1
+.br
+ /dev/sg3
+.PP
+By process of elimination /dev/sg3 must be the scanner.
+.SH EXIT STATUS
+The exit status of sg_map is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2013 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_map26(8)
+,
+.B scsi_info(8)
+,
+.B scsidev(8)
+,
+.B devfsd(8)
+,
+.B lsscsi(8)
+,
+.B udev(7)
diff --git a/doc/sg_map26.8 b/doc/sg_map26.8
new file mode 100644
index 00000000..7ad013bf
--- /dev/null
+++ b/doc/sg_map26.8
@@ -0,0 +1,161 @@
+.TH SG_MAP26 "8" "November 2012" "sg3_utils\-1.35" SG3_UTILS
+.SH NAME
+sg_map26 \- map SCSI generic (sg) device to corresponding device names
+.SH SYNOPSIS
+.B sg_map26
+[\fI\-\-dev_dir=DIR\fR] [\fI\-\-given_is=\fR0|1] [\fI\-\-help\fR]
+[\fI\-\-result=\fR0|1|2|3] [\fI\-\-symlink\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Maps a special file (block or char) associated with a SCSI device
+to the corresponding SCSI generic (sg) device, or vice versa.
+Can also be given a sysfs file, for example '/sys/block/sda'
+or '/sys/block/sda/dev'.
+.PP
+Rather than map to or from a sg device, the sysfs file name
+matching a given device special file (or vice versa) can be
+requested. This is done with '\-\-result=2' and '\-\-result=3'.
+This feature works on ATA devices (e.g. 'dev/hdc') as well
+as SCSI devices.
+.PP
+In this utility, "mapped" refers to finding the relationship between
+a SCSI generic (sg) node and the higher level SCSI device name; or
+vice versa. For example '/dev/sg0' may "map" to '/dev/sda'.
+Mappings may not exist, if a relevant module is not loaded, for
+example. Also there are SCSI devices that can only be accessed via a sg
+node (e.g. SAF\-TE and some SES devices).
+.PP
+In this utility, "matching" refers to different representations of
+the same device accessed via the same driver. For example, '/dev/hdc'
+and '/sys/block/hdc' usually refer to the same device and thus would
+be considered matching. A related example is that '/dev/cdrom'
+and '/dev/hdc' are also considered matching if '/dev/cdrom' is a
+symlink to '/dev/hdc'.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-dev_dir\fR=\fIDIR\fR
+where \fIDIR\fR is the directory to search for resultant device special
+files in (or symlinks to same). Only active when '\-\-result=0' (the
+default) or '\-\-result=2'. If this option is not given and \fIDEVICE\fR is
+a device special file then the directory part of \fIDEVICE\fR is assumed.
+If this option is not given and \fIDEVICE\fR is a sysfs name, then if
+necessary '/dev' is assumed as the directory.
+.TP
+\fB\-g\fR, \fB\-\-given_is\fR=0 | 1
+specifies the \fIDEVICE\fR is either a device special file (when the
+argument is 0), or a sysfs 'dev' file (when the argument is 1). The parent
+directory of a sysfs 'dev' file is also accepted (e.g.
+either '/sys/block/sda/dev' or '/sys/block/sda' are accepted). Usually
+there is no need to give this option since this utility first checks for
+special files (or symlinks to special files) and if not, assumes it
+has been given a sysfs 'dev' file (or its parent). Generates an error
+if given and disagrees with variety of \fIDEVICE\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-r\fR, \fB\-\-result\fR=0 | 1 | 2 | 3
+specifies what variety of file (or files) that this utility tries to find.
+The default is a "mapped" device special file, when the argument is 0.
+When the argument is 1, this utility tries to find the "mapped" sysfs node
+name. When the argument is 2, this utility tries to find the "matching"
+device special file. When the argument is 3, this utility tries to find
+the "matching" sysfs node name.
+.TP
+\fB\-s\fR, \fB\-\-symlink\fR
+when a device special file is being sought (i.e. when '\-\-result=0' (the
+default) or '\-\-result=2') then also look for symlinks to that device
+special file in the same directory.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+This utility is designed for the Linux 2.6 (and later) kernel series.
+It uses special file major and minor numbers (and whether the special
+is block or character) together with sysfs to do its mapping or
+matching. In the absence of any other information, device special
+files are assumed to be in the '/dev' directory while sysfs is
+assumed to be mounted at '/sys'. Device names in sysfs are
+predictable, given the corresponding major and minor number of
+the device. However, due to udev rules, the name of device
+special files can be anything the user desires (e.g. '/dev/sda'
+could be named '/dev/my_boot_disk'). When trying to
+find a resultant device special file, this utility uses the major
+and minor numbers (and whether a block or char device is sought)
+to search the device directory.
+.PP
+This utility only shows one relationship at a time. To get an
+overview of all SCSI devices, with special file names and optionally
+the "mapped" sg device name, see the lsscsi utility.
+.SH EXAMPLES
+Assume sg2 maps to sdb while dvd, cdrom and hdc are all matching.
+.PP
+ # sg_map26 /dev/sg2
+.br
+ /dev/sdb
+.PP
+ # sg_map26 /dev/sdb
+.br
+ /dev/sg2
+.PP
+ # sg_map26 \-\-result=0 /dev/sdb
+.br
+ /dev/sg2
+.PP
+ # sg_map26 \-\-result=3 /dev/sdb
+.br
+ /sys/block/sda
+.PP
+ # sg_map26 \-\-result=1 /dev/sdb
+.br
+ /sys/class/scsi_generic/sg0
+.PP
+Now look at '/dev/hdc' and friends
+.PP
+ # sg_map26 /dev/hdc
+.br
+ <error: a hd device does not map to a sg device>
+.PP
+ # sg_map26 \-\-result=3 /dev/hdc
+.br
+ /sys/block/hdc
+.PP
+ # sg_map26 \-\-result=2 /dev/hdc
+.br
+ /dev/hdc
+.PP
+ # sg_map26 \-\-result=2 \-\-symlink /dev/hdc
+.br
+ /dev/cdrom
+.br
+ /dev/dvd
+.br
+ /dev/hdc
+.PP
+ # sg_map26 \-\-result=2 \-\-symlink /sys/block/hdc
+.br
+ /dev/cdrom
+.br
+ /dev/dvd
+.br
+ /dev/hdc
+.SH EXIT STATUS
+The exit status of sg_map26 is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2005\-2012 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B udev(7), lsscsi(lsscsi)
diff --git a/doc/sg_modes.8 b/doc/sg_modes.8
new file mode 100644
index 00000000..4fef24ad
--- /dev/null
+++ b/doc/sg_modes.8
@@ -0,0 +1,314 @@
+.TH SG_MODES "8" "July 2022" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_modes \- reads mode pages with SCSI MODE SENSE command
+.SH SYNOPSIS
+.B sg_modes
+[\fI\-\-all\fR] [\fI\-\-control=PC\fR] [\fI\-\-dbd\fR] [\fI\-\-dbout\fR]
+[\fI\-\-examine\fR] [\fI\-\-flexible\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-list\fR] [\fI\-\-llbaa\fR] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-page=PG[,SPG]\fR] [\fI\-\-raw\fR] [\fI\-R\fR] [\fI\-\-readwrite\fR]
+[\fI\-\-six\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fIDEVICE\fR]
+.PP
+.B sg_modes
+[\fI\-6\fR] [\fI\-a\fR] [\fI\-A\fR] [\fI\-c=PC\fR] [\fI\-d\fR] [\fI\-D\fR]
+[\fI\-e\fR] [\fI\-f\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-l\fR] [\fI\-L\fR]
+[\fI\-m=LEN\fR] [\fI\-p=PG[,SPG]\fR] [\fI\-r\fR] [\fI\-subp=SPG\fR]
+[\fI\-v\fR] [\fI\-V\fR] [\fI\-w\fR] [\fI\-?\fR] [\fIDEVICE\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends a MODE SENSE SCSI command to the \fIDEVICE\fR and
+outputs the response. There is a 6 byte and 10 byte (cdb) variant of the
+MODE SENSE command, this utility defaults to the 10 byte variant. The SPC\-4
+standard (and the SPC\-5 standard) include a note stating that implementers
+should migrate away from the SCSI MODE SELECT(6) and MODE SENSE(6) commands
+in favour of the 10 byte variants (e.g. MODE SENSE(10)).
+.PP
+This utility decodes mode page headers and block descriptors but outputs
+the contents of each mode page in hex. It also has no facility to change
+the mode page contents or block descriptor data. Mode page contents are
+decoded and can be changed by the
+.B sdparm
+utility.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later
+section on the old command line syntax outlines the second group of
+options.
+.PP
+If no page is given (and \fI\-\-list\fR is not selected) then \fI\-\-all\fR
+is assumed. The \fI\-\-all\fR option requests all mode pages (but not
+subpages) in a single response.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+output all the mode pages reported by the \fIDEVICE\fR. This is what the
+page code 63 (0x3f) is defined to do. When used once, mode subpages are
+not fetched. When used twice (e.g. '\-aa'), all mode pages and subpages
+are requested which is equivalent to '\-\-page=63,255'.
+.TP
+\fB\-c\fR, \fB\-\-control\fR=\fIPC\fR
+\fIPC\fR is the page control value. Up to four different versions of each
+page are held by the device:
+.br
+ \fB0\fR : current values (i.e. those active at present)
+.br
+ \fB1\fR : changeable values
+.br
+ \fB2\fR : default values (i.e. the manufacturer's settings)
+.br
+ \fB3\fR : saved values
+.br
+The changeable values are bit masks showing which fields could be changed
+with a MODE SELECT. The saved values will be re\-instated the next time
+the device is power cycled or reset. If this option is not given then
+current values [0] are assumed.
+.TP
+\fB\-d\fR, \fB\-\-dbd\fR
+disable block descriptors. By default, block descriptors (usually
+one (for disks) or none) are returned in a MODE SENSE response. This option
+sets the "disable block descriptors" (DBD) bit in the cdb which instructs
+the device not to return any block descriptors in its response. Older
+devices may not support this setting and may return an "illegal request"
+sense key; alternatively they may ignore it. Oddly the Reduced Block Command
+set (RBC) requires this bit set.
+.TP
+\fB\-D\fR, \fB\-\-dbout\fR
+disable outputting block descriptors. Irrespective of whether block
+descriptors are present in the response or not, they are not output.
+.TP
+\fB\-e\fR, \fB\-\-examine\fR
+examine each mode page in the range 0 through to 62 (inclusive).
+If some response is given then print out the mode page name or
+number (in hex) if the name is not known.
+.br
+The sdparm utility which lists mode and VPD pages also has a \fB\-\-examine\fR
+option will similar functionility.
+.TP
+\fB\-f\fR, \fB\-\-flexible\fR
+Some devices, bridges and/or drivers attempt crude translations between
+MODE SENSE 6 and 10 byte commands without correcting the response. This
+will cause the response to be mis\-interpreted (usually with an error saying
+the response is malformed). With this option, the length of the response
+is checked, and if it looks wrong, the response is then decoded as if the
+other mode sense (cdb length) was sent.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+The default action is to decode known mode page numbers (and subpage
+numbers) into text. When this option is used once, the response is output
+in hexadecimal to stdout. When this option is used twice, mode page numbers
+and page control values are output in hex.
+.br
+When this option is used three times, the full response to the MODE SENSE
+command is output in hex to stdout without any decoding. This form can
+be redirected to a file (or piped) and then used 'sdparm \-\-inhex=' to
+decode.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+lists all common page and subpage codes and their names that are found in
+the command set that matches the peripheral type of the given \fIDEVICE\fR.
+If no \fIDEVICE\fR and no \fI\-\-page=PG\fR is given then the common page and
+subpage codes and their names are listed for SBC (e.g. a disk). If no
+\fIDEVICE\fR is given and a \fI\-\-page=PG\fR is given then the
+common page and subpage codes and their names are listed for the command set
+whose peripheral device type matches the value given to \fIPG\fR. For
+example 'sg_mode \-\-list \-\-page=1' lists the command mode pages and
+subpages for tape devices. Additionally if a sub_page_code is given then it
+is interpreted as a transport identifier and command transport specific mode
+page codes and their names are listed following the main mode page list.
+Other options are ignored.
+.TP
+\fB\-L\fR, \fB\-\-llbaa\fR
+set the Long LBA Accepted (LLBAA) bit in the MODE SENSE (10) cdb. This
+bit is not defined in the MODE SENSE (6) cdb so setting the '\-L'
+and '\-\-six' options is reported as an error. When set the \fIDEVICE\fR
+may respond with 16 byte block descriptors as indicated by
+the 'LongLBA' field in the response. In most cases setting this option
+is not needed.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+The \fILEN\fR argument is the maximum response length in bytes. It is
+the 'allocation length' field in the cdb. When not given (or \fILEN\fR is
+zero) then the allocation length field is set to 4096 for MODE SENSE (10)
+or 252 for MODE SENSE (6). The \fILEN\fR argument must be non\-negative
+and no greater than 65535 for MODE SENSE (10) and not greater than 255
+for MODE SENSE (6).
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR
+page code to fetch. The \fIPG\fR is assumed to be a decimal value unless
+prefixed by '0x' or has a trailing 'h'. It should be a value between 0
+and 63 (inclusive). When not given and a default is required then
+a value of 63 (0x3f), which fetches all mode pages, is used.
+.br
+Alternatively an acronym for the mode page can be given. The available
+acronyms can be listed out with the \fI\-\-page=xxx\fR option. They are
+almost the same as the acronyms used for mode pages in the sdparm utility.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG,SPG\fR
+page code and subpage code values to fetch. Both arguments are assumed
+to be decimal unless flagged as hexadecimal. The page code should be
+between 0 and 63 inclusive. The subpage code should be between 0 and 255
+inclusive. The default value for the subpage code is 0.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the response in binary to stdout. Error messages and warnings, if
+any, are sent to stderr. When this option is used twice (e.g. '\-rr')
+then has the same action as '\-R'
+.TP
+\fB\-R\fR
+output the selected mode page to stdout a byte per line. Each line contains
+two hexadecimal digits (e.g. "3e"). Useful as input (after editing) to
+the sg_wr_mode(8) utility.
+.TP
+\fB\-w\fR, \fB\-\-readwrite\fR
+open \fIDEVICE\fR in "read\-write" mode. Default is to open it in read\-only
+mode.
+.TP
+\fB\-6\fR, \fB\-s\fR, \fB\-\-six\fR
+by default this utility sends a 10 byte MODE SENSE command to the
+\fIDEVICE\fR. However some SCSI devices only support 6 byte MODE SENSE
+commands (e.g. SCSI\-2 tape drives). This parameter forces the use of 6
+byte MODE SENSE commands.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH NOTES
+If the normal sg_modes utility fails with "illegal command
+operation code" then try the '\-\-six' (or '\-6') option.
+.PP
+This utility performs a SCSI INQUIRY command to determine the peripheral
+type of the device (e.g. 0 \-> Direct Access Device (disk)) prior to
+sending a MODE SENSE command. This helps in decoding the block
+descriptor and mode pages.
+.PP
+This utility opens \fIDEVICE\fR in read\-only mode (e.g. in Unix, with
+the O_RDONLY flag) by default. It will open \fIDEVICE\fR in read\-write
+mode if the \fI\-\-readwrite\fR option is given.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI
+generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks
+and DVD drives) can also be specified. For example "sg_modes \-a /dev/sda"
+will work in the 2.6 series kernels.
+.SH EXIT STATUS
+The exit status of sg_modes is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-6\fR
+by default this utility sends a 10 byte MODE SENSE command to
+the \fIDEVICE\fR. This parameter forces the use of 6 byte MODE SENSE commands.
+See \fI\-\-six\fR in the main description.
+.TP
+\fB\-a\fR
+see \fI\-\-all\fR in the main description.
+.TP
+\fB\-A\fR
+output all the mode pages and subpages supported by the \fIDEVICE\fR. Same
+as '\-\-all \-\-all' in the new syntax.
+.TP
+\fB\-c\fR=\fIPC\fR
+\fIPC\fR is the page control value. See \fB\-\-control\fR=\fIPC\fR in
+the main description.
+.TP
+\fB\-d\fR
+see \fB\-\-dbd\fR in the main description.
+.TP
+\fB\-D\fR
+see \fB\-\-dbout\fR in the main description.
+.TP
+\fB\-e\fR
+see \fB\-\-examine\fR in the main description.
+.TP
+\fB\-f\fR
+see \fB\-\-flexible\fR in the main description.
+.TP
+\fB\-h\fR
+The default action is to decode known mode page numbers (and subpage
+numbers) into text. With this option mode page numbers (and subpage
+numbers) are output in hexadecimal.
+.TP
+\fB\-H\fR
+same action as the '\-h' option.
+.TP
+\fB\-l\fR
+see \fB\-\-list\fR in the main description.
+.TP
+\fB\-L\fR
+see \fB\-\-llbaa\fR in the main description.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-m\fR=\fILEN\fR
+see \fB\-\-maxlen\fR=\fILEN\fR in the main description.
+.TP
+\fB\-p\fR=\fIPG\fR
+\fIPG\fR is page code to fetch. Should be a hexadecimal number between 0
+and 3f inclusive (0 to 63 decimal). The default value when required is
+3f (fetch all mode pages). Note that an acronym for the page and/or
+subpage values is not accepted in this older format (because any acronym
+starting with the letters 'a' to 'f' is ambiguous; it could either be a hex
+number or an acronym).
+.TP
+\fB\-p\fR=\fIPG,SPG\fR
+page code and subpage code values to fetch. The page code should be a
+hexadecimal number between 0 and 3f inclusive. The subpage code should
+be a hexadecimal number between 0 and ff inclusive. The default value
+for the subpage code is 0.
+.TP
+\fB\-r\fR
+output the selected mode page to stdout a byte per line. Each line contains
+two hexadecimal digits (e.g. "3e"). Useful as input (after editing) to
+the sg_wr_mode(8) utility.
+.TP
+\fB\-subp\fR=\fISPG\fR
+sub page code to fetch. Should be a hexadecimal number between 0 and
+0xff inclusive. The default value is 0.
+.TP
+\fB\-v\fR
+increase verbosity of output.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.TP
+\fB\-w\fR
+see \fB\-\-readwrite\fR in the main description.
+.TP
+\fB\-?\fR
+output usage message then exit. Ignore all other parameters.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sdparm(8), sg_wr_mode(8), sginfo(8),
+.B sgmode(scsirastools), scsiinfo(net), scu(net),
+.B seatools(seagate)
+.PP
+All these utilities offer some facility to change mode page (or block
+descriptor) parameters.
diff --git a/doc/sg_opcodes.8 b/doc/sg_opcodes.8
new file mode 100644
index 00000000..21d148be
--- /dev/null
+++ b/doc/sg_opcodes.8
@@ -0,0 +1,339 @@
+.TH SG_OPCODES "8" "September 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_opcodes \- report supported SCSI commands or task management functions
+.SH SYNOPSIS
+.B sg_opcodes
+[\fI\-\-alpha\fR] [\fI\-\-compact\fR] [\fI\-\-enumerate\fR] [\fI\-\-help\fR]
+[\fI\-\-hex\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO]\fR] [\fI\-\-mask\fR]
+[\fI\-\-mlu\fR] [\fI\-\-no-inquiry\fR] [\fI\-\-opcode=OP[,SA]\fR]
+[\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-rctd\fR] [\fI\-\-repd\fR]
+[\fI\-\-sa=SA\fR] [\fI\-\-tmf\fR] [\fI\-\-unsorted\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_opcodes
+[\fI\-a\fR] [\fI\-c\fR] [\fI\-e\fR] [\fI\-H\fR] [\fI\-i=FN\fR] [\fI\-j\fR]
+[\fI\-m\fR] [\fI\-M\fR] [\fI\-n\fR] [\fI\-o=OP\fR] [\fI\-p=DT\fR] [\fI\-q\fR]
+[\fI\-R\fR] [\fI\-s=SA\fR] [\fI\-t\fR] [\fI\-u\fR] [\fI\-v\fR] [\fI\-V\fR]
+[\fI\-?\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends a SCSI REPORT SUPPORTED OPERATION CODES or a REPORT
+SUPPORTED TASK MANAGEMENT FUNCTIONS command to the \fIDEVICE\fR and then
+outputs the response. The default action is to report supported operation
+codes. In this mode it will either list all supported commands or give
+detailed information on a specific command identified by the
+\fI\-\-opcode=OP\fR option (perhaps with additional information from the
+\fI\-\-sa=SA\fR option).
+.PP
+The name of a SCSI command depends on its peripheral device type (e.g. a
+disk). The REPORT SUPPORTED OPERATION CODES and REPORT SUPPORTED TASK
+MANAGEMENT FUNCTIONS commands are not supported in the MMC command set for
+CD and DVD devices. This utility does an INQUIRY to obtain the peripheral
+device type and prints out the vendor, product and revision strings.
+.PP
+A similar facility to query supported operation codes previously was available
+via the CmdDt bit in the SCSI INQUIRY command (see sg_inq(8)). However that
+facility was made obsolete and replaced by the REPORT SUPPORTED OPERATION
+CODES command in SPC\-3 (revision 4) during February 2002.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later section
+on the old command line syntax outlines the second group of options.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-alpha\fR
+when all supported commands are being listed there is no requirement for
+the device server (i.e. the \fIDEVICE\fR) to sort the list of commands. When
+this option is given the list of supported commands is sorted by
+name (alphabetically). When this option and the \fB\-\-unsorted\fR option are
+both _not_ given then the list of supported commands is sorted
+numerically (first by operation code and then by service action).
+.TP
+\fB\-c\fR, \fB\-\-compact\fR
+some command names, especially those associated with some service actions,
+are getting longer. This may cause line wrap in the one line per command
+mode on some terminals. When this option is given the opcode and service
+action fields are combined into a single field with the service action,
+prefixed by a comma shown directly after the opcode. If there is no service
+action associated with the command, then the comma and the service action
+are not shown after the opcode. The CDB size field is not shown when this
+option is given.
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+this option prints the name of the SCSI command based on the given opcode,
+peripheral device type and optionally the service action. If given,
+\fIDEVICE\fR is ignored. The opcode, peripheral device type and service
+action default to zero if not given. Thus if this option is the only option
+given then "Test Unit ready" is output since its opcode is 0, it has no
+service action and it is common to all peripheral device types since it is
+defined in the SCSI Primary Commands (SPC) standard(s).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options
+then exits. Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+outputs the response in ASCII hexadecimal to stdout. When used once or
+twice, each line starts with a relative (hex) address starting at 0
+on the first hex line output. The difference is when used twice the
+hexadecimal bytes are rendered in ASCII at the right of each line;
+non\-printable characters are replaced by "." .
+.br
+When used three or more times, there is no leading relative address
+on each line. This output is suitable for being redirected to a file
+which can later by given to the \fI\-\-inhex=FN\fR option.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a file name whose contents are assumed to be ASCII
+hexadecimal. If \fIDEVICE\fR is also given then \fIDEVICE\fR is ignored,
+a warning is issued and the utility continues, decoding the file named
+\fIFN\fR. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information. If the \fI\-\-raw\fR option is
+also given then the contents of \fIFN\fR are treated as binary.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-m\fR, \fB\-\-mask\fR
+additionally prints out the cdb mask in hex. So a 12 byte cdb will have
+a 12 byte hexadecimal mask. If the hexadecimal is expanded (mentally)
+to binary then a "1" means the corresponding position in the cdb may
+be set. And "0" means the corresponding position in the cdb must not
+be set. For "0" mask positions that a user tries to set in a cdb, the
+device may either ignore it or report an error, typically with a
+sense key of "illegal request".
+.TP
+\fB\-M\fR, \fB\-\-mlu\fR
+additionally prints out an indication (0 or 1) whether the command
+effects all logical units in the containing target. MLU (Multiple Logical
+Units) is a bit in the REPORT SUPPORTED OPERATION CODES response
+introduced by proposal 18\-045r1 (and possibly in spc5r20). Without
+the option, the default output format which lists all opcodes, does
+not include a MLU indication.
+.TP
+\fB\-n\fR, \fB\-\-no-inquiry\fR
+Prior to calling a SCSI REPORT SUPPORTED OPERATION CODES or a REPORT
+SUPPORTED TASK MANAGEMENT FUNCTIONS command, a SCSI INQUIRY command
+is performed. The reason is to determine the peripheral device type (pdt)
+of the \fIDEVICE\fR as this is helpful in translating operation codes
+to the command names. By default this utility prints a summary of INQUIRY
+command response on stdout. If this option (or the \fI\-\-raw\fR option)
+is given then that summary is not printed on stdout.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-o\fR, \fB\-\-opcode\fR=\fIOP[,SA]\fR
+the \fIDEVICE\fR will be queried for the given operation code (i.e. the
+\fIOP\fR value) which is the first byte of a SCSI command. Optionally, if
+a \fISA\fR value is given, it will be used as that SCSI command's service
+action. Note that \fIOP\fR and \fIOP,0\fR are not the same thing, as SCSI
+does allow the service action to be 0 (but not in this command). \fIOP\fR
+and \fISA\fR are decimal unless prefixed by "0x" or they have a
+trailing "h". \fIOP\fR should be in the range 0 to 255 (0xff) inclusive.
+\fISA\fR should be in the range 0 to 65535 (0xffff) inclusive. When this
+option is not given then all available SCSI commands supported by the
+\fIDEVICE\fR are listed.
+.TP
+\fB\-p\fR, \fB\-\-pdt\fR=\fIDT\fR
+where \fIDT\fR is the peripheral device type. This is used together with
+the \fI\-\-enumerate\fR to differentiate when a command opcode (and perhaps
+service action) is shared by multiple device types.
+.br
+This option may also be used with the \fI\-\-no-inquiry\fR option to
+suppress this utility doing an INQUIRY command since the main reason
+for doing that is to find the peripheral device type of the \fIDEVICE\fR.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout) unless the \fI\-\-inhex=FN\fR option
+is also given. In that case the input file name (\fIFN\fR) is decoded as
+binary (and the output is _not_ in binary (but may be hex)).
+.TP
+\fB\-R\fR, \fB\-\-rctd\fR
+set report command timeout descriptor (RCTD) bit in the cdb. The response
+may or may not contain command timeout descriptors. If available they are
+output. If supported there are two values: a nominal command timeout
+and a recommended command timeout. Both have units of seconds. A value
+of zero means that no timeout is indicated and this is shown in
+the corresponding decoded output as "\-".
+.TP
+\fB\-q\fR, \fB\-\-repd\fR
+set read extended parameter data (REPD) bit in the report task management
+functions cdb. 16 bytes rather than the default 4 bytes expected in the
+response. This was added in SPC\-4 (revision 26).
+.TP
+\fB\-s\fR, \fB\-\-sa\fR=\fISA\fR
+the \fIDEVICE\fR will be queried for a command with the given service
+action (i.e. the \fISA\fR value). Used in conjunction with the
+\fI\-\-opcode=OP\fR option. If this option is not given, \fI\-\-opcode=OP\fR
+is given and the command in question does have a service action then a value
+of 0 will be assumed. \fISA\fR is decimal and expected to be in the range 0
+to 65535 (0xffff) inclusive.
+.TP
+\fB\-t\fR, \fB\-\-tmf\fR
+list supported task management functions. This is done with the SCSI REPORT
+SUPPORTED TASK MANAGEMENT FUNCTIONS command. When this option is chosen
+the \fI\-\-alpha\fR, \fI\-\-opcode=OP\fR, \fI\-\-rctd\fR, \fI\-\-sa=SA\fR
+and \fI\-\-unsorted\fR options are ignored.
+.TP
+\fB\-u\fR, \fB\-\-unsorted\fR
+when all supported commands are being listed there is no requirement for
+the device server (i.e. the \fIDEVICE\fR) to sort the list of commands. When
+this option is given the list of supported commands is in the order given by
+the \fIDEVICE\fR. When this option is not given the supported commands
+are sorted numerically (first by operation code and then by service action).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH NOTES
+As of SPC\-5 revision 8 the recognized task management functions are:
+abort set, abort task set, clear ACA, clear task set, logical unit reset,
+query task, query asynchronous event, query task set, and I_T nexus reset.
+In SPC\-4 revision 26 target reset and wakeup task management functions
+were made obsolete.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI
+generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks
+and DVD drives) can also be specified. For example "sg_opcodes /dev/sda"
+will work in the 2.6 series kernels.
+.SH EXIT STATUS
+The exit status of sg_opcodes is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-a\fR
+sort command alphabetically. Equivalent to \fI\-\-alpha\fR in main
+description.
+.TP
+\fB\-c\fR
+see the \fI\-\-compact\fR option above.
+.TP
+\fB\-e\fR
+see the \fI\-\-enumerate\fR option above.
+.TP
+\fB\-H\fR
+see the \fI\-\-hex\fR option above.
+.TP
+\fB\-m\fR
+see the \fI\-\-mask\fR option above.
+.TP
+\fB\-n\fR
+don't print a summary of the SCSI INQUIRY response on stdout.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-o\fR=\fIOP\fR
+the \fIDEVICE\fR will be queried for the given operation code (i.e.
+\fIOP\fR) which is the first byte of a SCSI command. \fIOP\fR is
+hexadecimal and expected to be in the range 0 to ff inclusive.
+When this option is not given then all available SCSI commands supported
+by the \fIDEVICE\fR are listed.
+.TP
+\fB\-p\fR=\fIDT\fR
+see the \fI\-\-pdt=DT\fR option above.
+.TP
+\fB\-q\fR
+set the read extended parameter data (REPD) bit in report TMF cdb.
+Equivalent to \fI\-\-repd\fR in main description.
+.TP
+\fB\-R\fR
+set the report command timeout descriptor (RCTD) bit in cdb. Equivalent
+to \fI\-\-rctd\fR in main description.
+.TP
+\fB\-s\fR=\fISA\fR
+the \fIDEVICE\fR will be queried for a command with the given service
+action (i.e. \fISA\fR). Used in conjunction with the \fI\-o=OP\fR
+option. If this option is not given, \fI\-o=OP\fR is given and the command
+in question does have a service action then a value of 0 will be assumed.
+\fISA\fR is hexadecimal and expected to be in the range 0 to ffff inclusive.
+.TP
+\fB\-t\fR
+list supported task management functions. Equivalent to \fI\-\-tmf\fR in
+the main description.
+.TP
+\fB\-u\fR
+output all supported commands in the order given by \fIDEVICE\fR.
+Equivalent to \fI\-\-unsorted\fR in main description.
+.TP
+\fB\-v\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.TP
+\fB\-?\fR
+output usage message. Ignore all other parameters.
+.SH EXAMPLES
+The examples in this page use Linux device names. For suitable device
+names in other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+To see the information about a specific command give its operation
+code to the '\-\-op=' option. A command line invocation is shown first
+followed by a typical response:
+.PP
+ # sg_opcodes \-\-op=93h /dev/sdb
+.PP
+ Opcode=0x93
+.br
+ Command_name: Write same(16)
+.br
+ Command supported [conforming to SCSI standard]
+.br
+ Usage data: 93 e2 00 00 00 00 ff ff ff ff 00 00 ff ff 00 00
+.PP
+The next example shows the supported task management functions:
+.PP
+ # sg_opcodes \-\-tmf \-n /dev/sdb
+.PP
+Task Management Functions supported by device:
+.br
+ Abort task
+.br
+ Abort task set
+.br
+ Clear ACA
+.br
+ Clear task set
+.br
+ Logical unit reset
+.br
+ Query task
+.PP
+Enumerate can be used to look up a SCSI command name in the absence of a
+device that supports that command. The opcode and service action (if
+required) should be supplied:
+.PP
+ # sg_opcodes \-\-enumerate \-\-op=0x9b,0xa
+.PP
+ SCSI command:
+.br
+ Read buffer(16), read data from echo buffer
+.br
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq,sg3_utils_json(sg3_utils)
diff --git a/doc/sg_persist.8 b/doc/sg_persist.8
new file mode 100644
index 00000000..23a0c97a
--- /dev/null
+++ b/doc/sg_persist.8
@@ -0,0 +1,435 @@
+.TH SG_PERSIST "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_persist \- use SCSI PERSISTENT RESERVE command to access registrations
+and reservations
+.SH SYNOPSIS
+.B sg_persist
+[\fIOPTIONS\fR] \fIDEVICE\fR
+.PP
+.B sg_persist
+[\fIOPTIONS\fR] \fI\-\-device=DEVICE\fR
+.PP
+.B sg_persist
+\fI\-\-help\fR | \fI\-\-version\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility allows Persistent reservations and registrations to be
+queried and changed. Persistent reservations and registrations are
+queried by sub\-commands (called "service actions" in SPC\-4) of the
+SCSI PERSISTENT RESERVE IN (PRIN) command. Persistent reservations and
+registrations are changed by sub\-commands of the SCSI PERSISTENT RESERVE
+OUT (PROUT) command.
+.PP
+There is a two stage process to obtain a persistent reservation. First an
+application (an I_T nexus in standard's jargon) must register a reservation
+key. If that is accepted (and it should be unless some other I_T nexus has
+registered that key) then the application can try and reserve the device.
+The reserve operation must specify the reservation key and a "type" (see
+the \fI\-\-prout\-type=TYPE\fR option).
+.PP
+It is relatively safe to query the state of Persistent reservations and
+registrations. With no options this utility defaults to the READ KEYS
+sub\-command of the PRIN command. Other PRIN sub\-commands are
+READ RESERVATION, REPORT CAPABILITIES and READ FULL STATUS.
+.PP
+Before trying to change Persistent reservations and registrations users
+should be aware of what they are doing. The relevant sections of the SCSI
+Primary Commands document (i.e. SPC\-5 ANSI INCITS 502\-2020) are sections
+8.14 (titled "Reservations"), 9.16 (for the PRIN command) and 9.17 (for the
+PROUT command). To safeguard against accidental use, the \fI\-\-out\fR option
+must be given when a PROUT sub\-command (e.g. \fI\-\-register\fR) is used.
+.PP
+The older SCSI RESERVE and RELEASE commands (both 6 and 10 byte variants)
+are not supported by this utility. In SPC\-3, RESERVE and RELEASE are
+deprecated, replaced by Persistent Reservations. RESERVE and RELEASE
+have been removed from SPC\-4 and Annex B is provided showing how to
+convert to persistent reservation commands. See a utility
+called 'scsires' for support of the SCSI RESERVE and RELEASE commands.
+.PP
+The \fIDEVICE\fR is required by all variants of this utility apart
+from \fI\-\-help\fR. The \fIDEVICE\fR can be given either as an
+argument (typically but not necessarily the last one) or via
+the \fI\-\-device=DEVICE\fR option.
+.PP
+SPC\-4 does not use the term "sub\-command". It uses the term "service action"
+for this and for part of a field's name in the parameter block associated
+with the PROUT command (i.e. "service action reservation key"). To lessen
+the potential confusion the term "sub\-command" has been introduced.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The following options are sorted in alphabetical order, based on their
+long option name.
+.TP
+\fB\-l\fR, \fB\-\-alloc-length\fR=\fILEN\fR
+specify the allocation length of the PRIN command. \fILEN\fR is a hex value.
+By default this value is set to the size of the data\-in buffer (8192).
+This parameter is of use for verification that response to PRIN commands
+with various allocation lengths is per section 4.3.5.6 of SPC\-4 revision 18.
+Valid \fILEN\fR values are 0\-8192.
+.TP
+\fB\-C\fR, \fB\-\-clear\fR
+Clear is a sub\-command of the PROUT command. It releases the
+persistent reservation (if any) and clears all registrations from the
+device. It is required to supply a reservation key that is registered
+for this I_T_L nexus (identified by \fI\-\-param\-rk=RK\fR).
+.TP
+\fB\-d\fR, \fB\-\-device\fR=\fIDEVICE\fR
+\fIDEVICE\fR to send SCSI commands to. The \fIDEVICE\fR can either be
+provided via this option or via a freestanding argument. For example,
+these two: 'sg_persist \-\-device=/dev/sg2' and 'sg_persist /dev/sg2'
+are equivalent.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output a usage message showing main options. Use twice (e.g. '\-hh') for
+the other option and more help.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+the response to a valid PRIN sub\-command will be output in hexadecimal.
+By default (i.e. without this option) if the PRIN sub\-command is recognised
+then the response will be decoded as per SPC\-4. May be used more than
+once for more hex and less text.
+.TP
+\fB\-i\fR, \fB\-\-in\fR
+specify that a SCSI PERSISTENT RESERVE IN command is required. This
+is the default.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+\fILEN\fR is used as the ALLOCATION LENGTH field of the PRIN command.
+\fILEN\fR is by default a decimal value. To give a hex value use a '0x'
+or '0X' prefix, or use a 'h' (or 'H') suffix. Can also take multipliers,
+see \fI\-\-maxlen=LEN\fR option in the sg3_utils manual page.
+.br
+This option is the same as \fI\-\-alloc\-length=LEN\fR option apart from
+the representation of \fILEN\fR. The option defaults to decimal while
+\fI\-\-alloc\-length=LEN\fR only takes hex.
+.TP
+\fB\-n\fR, \fB\-\-no\-inquiry\fR
+the default action is to do a standard SCSI INQUIRY command and output
+make, product and revision strings plus the peripheral device type
+prior to executing a PRIN or PROUT command. With this option the
+INQUIRY command is skipped.
+.TP
+\fB\-o\fR, \fB\-\-out\fR
+specify that a SCSI PERSISTENT RESERVE OUT command is required.
+.TP
+\fB\-Y\fR, \fB\-\-param\-alltgpt\fR
+set the 'all target ports' (ALL_TG_PT) flag in the parameter block of the
+PROUT command. Only relevant for 'register' and 'register and ignore existing
+key' sub\-commands.
+.TP
+\fB\-Z\fR, \fB\-\-param\-aptpl\fR
+set the 'activate persist through power loss' (APTPL) flag in the parameter
+block of the PROUT command. Relevant for 'register', 'register and ignore
+existing key' and 'register and move' sub\-commands.
+.TP
+\fB\-K\fR, \fB\-\-param\-rk\fR=\fIRK\fR
+specify the reservation key found in the parameter block of the PROUT
+command. \fIRK\fR is assumed to be hex (up to 8 bytes long). Default value
+is 0. This option is needed by most PROUT sub\-commands.
+.TP
+\fB\-S\fR, \fB\-\-param\-sark\fR=\fISARK\fR
+specify the service action reservation key found in the parameter block
+of the PROUT command. \fISARK\fR is assumed to be hex (up to 8 bytes long).
+Default value is 0. This option is needed by some PROUT sub\-commands.
+.TP
+\fB\-P\fR, \fB\-\-preempt\fR
+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). If a new reservation is established 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). If a new reservation is established 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
+the 'register\-move', 'reserve', 'release' and 'preempt (and abort)'
+sub\-commands. Valid \fITYPE\fR values: 1\-> write exclusive, 3\->
+exclusive access, 5\-> write exclusive \- registrants only, 6\->
+exclusive access \- registrants only, 7\-> write exclusive \- all registrants,
+8\-> exclusive access \- all registrants. Default value is 0 (which is
+an invalid type). Each "persistent reservation type" is explained in more
+detail in a subsection of that name in the read reservation section of
+the PRIN command (section 6.15.3.3 of SPC\-4 revision 37).
+.TP
+\fB\-s\fR, \fB\-\-read\-full\-status\fR
+Read Full Status is a sub\-command of the PRIN command. For each registration
+with the given SCSI device, it lists the reservation key and associated
+information. TransportIDs, if supplied in the response, are decoded.
+.TP
+\fB\-k\fR, \fB\-\-read\-keys\fR
+Read Keys is a sub\-command of the PRIN command. Lists all the reservation
+keys registered (i.e. registrations) with the given SCSI device. This is
+the default sub\-command for the SCSI PRIN command.
+.TP
+\fB\-y\fR, \fB\-\-readonly\fR
+Open \fIDEVICE\fR read\-only. May be useful with PRIN commands if there are
+unwanted side effects with the default read\-write open. When given twice
+is interpreted as forcing a read\-write open thus overriding the
+SG_PERSIST_IN_RDONLY environment variable if present. See the ENVIRONMENT
+VARIABLES section for more.
+.TP
+\fB\-r\fR, \fB\-\-read\-reservation\fR
+Read Reservation is a sub\-command of the PRIN command. List information
+about the current holder of the reservation on the \fIDEVICE\fR. If there
+is no current reservation this will be noted. Information about the current
+holder of the reservation includes its reservation key, scope and type.
+.TP
+\fB\-s\fR, \fB\-\-read\-status\fR
+same as \fI\-\-read\-full\-status\fR.
+.TP
+\fB\-G\fR, \fB\-\-register\fR
+Register is a sub\-command of the PROUT command. It has 3 different
+actions depending on associated parameters. a) add a new registration
+with '\-\-param\-rk=0' and '\-\-param\-sark=<new_rk>'; b) Change an existing
+registration with '\-\-param\-rk=<old_rk>'
+and '\-\-param\-sark=<new_rk>'; or c) Delete an existing registration
+with '\-\-param\-rk=<old_rk>' and '\-\-param\-sark=0'.
+.TP
+\fB\-I\fR, \fB\-\-register\-ignore\fR
+Register and Ignore Existing Key is a sub\-command of the PROUT command.
+Similar to \fI\-\-register\fR except that when changing a reservation key
+the old key is not specified. The '\-\-param\-sark=<new_rk>' option should
+also be given.
+.TP
+\fB\-M\fR, \fB\-\-register\-move\fR
+register (another initiator) and move (the reservation held by the current
+initiator to that other initiator) is a sub\-command of the PROUT command.
+It requires the transportID of the other initiator. [The standard uses the
+term I_T nexus but the point to stress is that there are two initiators
+(the one sending this command and another one) but only one logical unit.]
+The \fI\-\-prout\-type=TYPE\fR and \fI\-\-param\-rk=RK\fR options need to
+match that of the existing reservation while \fI\-\-param\-sark=SARK\fR
+option specifies the reservation key of the new (i.e. destination)
+registration.
+.TP
+\fB\-Q\fR, \fB\-\-relative\-target\-port\fR=\fIRTPI\fR
+relative target port identifier that reservation is to be moved to by
+PROUT 'register and move' sub\-command. \fIRTPI\fR is assumed to be hex
+in the range 0 to ffff inclusive. Defaults to 0 .
+.TP
+\fB\-L\fR, \fB\-\-release\fR
+Release is a sub\-command of the PROUT command. It releases the
+current persistent reservation. The \fI\-\-prout\-type=TYPE\fR
+and \fI\-\-param\-rk=RK\fR options, matching the reservation, must also be
+specified.
+.TP
+\fB\-z\fR, \fB\-\-replace\-lost\fR
+Replace Lost Reservation is a sub\-command of the PROUT command. It "begins
+a recovery process for the lost persistent reservation that is managed by
+application clients". It also stops the device server terminating commands
+due to a lost persistent reservation. Options should be
+be '\-\-param\-rk=0' (or not given), '\-\-param\-sark=<new_rk>'
+and \fI\-\-prout\-type=TYPE\fR.
+.TP
+\fB\-c\fR, \fB\-\-report\-capabilities\fR
+Report Capabilities is a sub\-command of the PRIN command. It lists
+information about the aspects of persistent reservations that the
+\fIDEVICE\fR supports.
+.TP
+\fB\-R\fR, \fB\-\-reserve\fR
+Reserve is a sub\-command of the PROUT command. It creates a new
+persistent reservation (if permitted). The \fI\-\-prout\-type=TYPE\fR
+and \fI\-\-param\-rk=RK\fR options must also be specified.
+.TP
+\fB\-X\fR, \fB\-\-transport\-id\fR=\fITIDS\fR
+The \fITIDS\fR argument can take one of several forms. It can be a
+comma (or single space) separated list of ASCII hex bytes representing
+a single TransportID as defined in SPC\-5. They are usually 24 bytes
+long apart from in iSCSI. The \fITIDS\fR argument may be a transport
+specific form (e.g. "sas,5000c50005b32001" is clearer than an equivalent
+to the hex byte form: "6,0,0,0,5,0,c5,0,5,b3,20,1"). The \fITIDS\fR argument
+may be "\-" in which case one or more TransportIDs can be read from stdin.
+The \fITIDS\fR argument may be of the form "file=<name>" in which case
+one or more TransportIDs can be read from a file called <name>. See
+the "TRANSPORT IDs" section below for more information.
+.TP
+\fB\-U\fR, \fB\-\-unreg\fR
+optional when the PROUT register and move sub\-command is invoked. If given
+it will unregister the current initiator (I_T nexus) after the other initiator
+has been registered and the reservation moved to it. When not given the
+initiator (I_T nexus) that sent the PROUT command remains registered.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+print out cdb of issued commands prior to execution. If used twice
+prints out the parameter block associated with the PROUT command prior
+to its execution as well. If used thrice decodes given transportID(s)
+as well. To see the response to a PRIN command in low level form use
+the \fI\-\-hex\fR option.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string. Ignore all other parameters.
+.TP
+\fB\-?\fR
+output usage message. Ignore all other parameters.
+.SH TRANSPORT IDs
+TransportIDs are used in persistent reservations to identify initiators.
+The format of a TransportID differs depending on the type of transport
+being used. Their format is described in SPC\-4 (in draft revision 37 see
+section 7.6.4).
+.PP
+A TransportID is required for the PROUT 'register and move' sub\-command and
+the PROUT 'register' sub\-command can have zero, one or more TransportIDs.
+.PP
+When the \fI\-\-transport\-id=TIDS\fR option is given then the \fITIDS\fR
+argument may be a comma (or single space) separated list of ASCII hex bytes
+that represent a single TransportID as defined in SPC\-4. Alternatively the
+\fITIDS\fR argument may be a transport specific string starting with
+either "fcp,", "spi,", "sbp,", "srp,", "iqn", "sas," or "sop,". The "iqn"
+form is an iSCSI qualified name. Apart from "iqn" the other transport
+specific leadin string may be given in upper case (e.g. "FCP,").
+.PP
+The "fcp," form should be followed by 16 ASCII hex digits that represent an
+initiator's N_PORT_NAME (e.g. "fcp,10000000C9F3A571"). The "spi," form should
+be followed by "<scsi_address>,<relative_target_port_identifier>" (both
+decimal numbers). The "sbp," form should be followed by 16 ASCII hex digits
+that represent an initiator's EUI\-64 name. The "srp," form should be
+followed by 32 ASCII hex digits that represent an initiator port identifier.
+The "sas," form should be followed by 16 ASCII hex digits that represent an
+initiator's port SAS address (e.g. "sas,5000c50005b32001"). The "sop," form
+takes a hex number that represents a routing id.
+.PP
+There are two iSCSI qualified name forms. The shorter form contains the
+iSCSI name of the initiator
+port (e.g. "iqn.5886.com.acme.diskarrays\-sn\-a8675309"). The longer form
+adds the initiator session id (ISID in hex) separated by ",i,0x".
+For example "iqn.5886.com.acme.diskarrays\-sn\-a8675309,i,0x1234567890ab".
+On the command line to stop punctuation in an iSCSI name
+being (mis)\-interpreted by the shell, putting the option argument
+containing the iSCSI name in double quotes is advised. iSCSI names are
+encoded in UTF\-8 so if non (7 bit) ASCII characters appear in the
+iSCSI name on the command line, there will be difficulties if they are not
+encoded in UTF\-8. The locale can be changed temporarily by prefixing the
+command line invocation of sg_persist with "LANG=en_US.utf\-8" for example.
+.PP
+Alternatively the \fITIDS\fR argument may specify a file (or pipe) from which
+one or more TransportIDs may be read. If the \fITIDS\fR argument is "\-"
+then stdin (standard input) is read. If the \fITIDS\fR argument is of the
+form "file=<name>" then a file called <name> is read.
+A valid SPC\-4 TransportID is built from the transport specific string
+outlined in the previous paragraphs. The parsing of the data read is
+relatively simple. Empty lines are ignored. Everything from and including
+a "#" on a line is ignored. Leading spaces and tabs are ignored. There
+can be one transportID per line. The transportID can either be a comma,
+space or tab separated list of ASCII hex bytes that represent a
+TransportID as defined in SPC\-4. Padding with zero bytes to a minimum
+length of 24 bytes is performed if necessary. The transportID may also
+be transport specific string type discussed above.
+.PP
+In SPC\-3 the SPEC_I_PT bit set to one and TransportIDs were allowed for
+the PROUT register and ignore existing key sub\-command. In SPC\-4 that
+is disallowed yielding a CHECK CONDITION status with and ILLEGAL REQUEST
+sense key and an additional sense code set to INVALID FIELD IN PARAMETER
+LIST.
+.SH NOTES
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be
+a SCSI generic (sg) device. In the 2.6 series any SCSI device
+name (e.g. /dev/sdc, /dev/st1m or /dev/sg3) can be specified.
+For example "sg_persist \-\-read\-keys /dev/sdb"
+will work in the 2.6 series kernels.
+.PP
+The only scope for PROUT commands supported in the current draft of
+SPC\-4 is "LU_SCOPE". Hence there seems to be no point in offering an
+option to set scope to another value.
+.PP
+Most errors with the PROUT sub\-commands (e.g. missing or
+mismatched \fI\-\-prout\-type=TYPE\fR) will result in a RESERVATION
+CONFLICT status. This can be a bit confusing when you know there is
+only one (active) initiator: the "conflict" is with the SPC standard, not
+another initiator.
+.PP
+Some recent disks accept some PRIN and PROUT sub\-commands when the
+media is stopped. One exception was setting the APTPL flag (with
+the \fI\-\-param\-aptpl\fR option) during a key register operation,
+it complained if the disk one stopped. The error indicated it wanted
+the disk spun up and when that happened, the registration was
+successful.
+.SH EXAMPLES
+These examples use Linux device names. For suitable device names in
+other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+Due to the various option defaults the simplest example executes
+the 'read keys' sub\-command of the PRIN command:
+.PP
+ sg_persist /dev/sdb
+.PP
+This is the same as the following (long\-winded) command:
+.PP
+ sg_persist \-\-in \-\-read\-keys \-\-device=/dev/sdb
+.PP
+To read the current reservation either the '\-\-read\-reservation' form or
+the shorter '\-r' can be used:
+.PP
+ sg_persist \-r /dev/sdb
+.PP
+To
+.B register
+the new reservation key 0x123abc the following could be used:
+.PP
+ sg_persist \-\-out \-\-register \-\-param\-sark=123abc /dev/sdb
+.PP
+Given the above registration succeeds, to
+.B reserve
+the \fIDEVICE\fR (with type 'write exclusive') the following
+could be used:
+.PP
+ sg_persist \-\-out \-\-reserve \-\-param\-rk=123abc
+.br
+ \-\-prout\-type=1 /dev/sdb
+.PP
+To
+.B release
+the reservation the following can be given (note that
+the \-\-param\-rk and \-\-prout\-type arguments must match those of the
+reservation):
+.PP
+ sg_persist \-\-out \-\-release \-\-param\-rk=123abc
+.br
+ \-\-prout\-type=1 /dev/sdb
+.PP
+Finally to
+.B unregister
+a reservation key (and not effect other
+registrations which is what '\-\-clear' would do) the command
+is a little surprising:
+.PP
+ sg_persist \-\-out \-\-register \-\-param\-rk=123abc /dev/sdb
+.PP
+Now have a close look at the difference between the register and
+unregister examples above.
+.PP
+An example file that is suitably formatted to pass transportIDs via
+a '\-\-transport\-id=file=transport_ids.txt' option can be found in the
+examples sub\-directory of the sg3_utils package. There is also a
+simple test script called sg_persist_tst.sh in the same directory.
+.PP
+The above sequence of commands was tested successfully on a Seagate Savvio
+10K.3 disk and a 1200 SSD both of which have SAS interfaces.
+.SH EXIT STATUS
+The exit status of sg_persist is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH ENVIRONMENT VARIABLES
+Currently there is one recognised environment variable: SG_PERSIST_IN_RDONLY.
+If present and only if a PRIN command has been selected then the
+given \fIDEVICE\fR is opened read\-only (e.g. in Unix that is with the
+O_RDONLY flag). See the \fI\-\-readonly\fR option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2018 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils(sg3_utils), scsires(internet)
diff --git a/doc/sg_prevent.8 b/doc/sg_prevent.8
new file mode 100644
index 00000000..d0cfb12c
--- /dev/null
+++ b/doc/sg_prevent.8
@@ -0,0 +1,59 @@
+.TH SG_PREVENT "8" "November 2012" "sg3_utils\-1.35" SG3_UTILS
+.SH NAME
+sg_prevent \- send SCSI PREVENT ALLOW MEDIUM REMOVAL command
+.SH SYNOPSIS
+.B sg_prevent
+[\fI\-\-allow\fR] [\fI\-\-help\fR] [\fI\-\-prevent=PC\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI PREVENT ALLOW MEDIUM REMOVAL command to \fIDEVICE\fR.
+The default action of this utility is to prevent the removing or
+ejecting of the medium from a drive. This is done by ignoring the
+SCSI START STOP UNIT command (see sg_start) and ignoring the eject
+button on the drive when the user presses it. Drives that hold removable
+disks, tape cartridges or cd/dvd media typically implement this command.
+The definition of the "prevent" codes for this command differ between
+disks and tapes (covered by SBC\-3 and SSC\-3) and cd/dvd drives (covered
+by MMC\-5). The "prevent codes" described here are from MMC\-5.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-allow\fR
+allow medium removal. This is equivalent to setting to '\-\-prevent=2'.
+Cannot be used with \fI\-\-prevent=PC\fR option (i.e. either use
+no options (hence prevent removal), this option or \fI\-\-prevent=PC\fR).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-p\fR, \fB\-\-prevent\fR=\fIPC\fR
+where \fIPC\fR is a prevent code value. Defined values are: 0 allows removal,
+1 prevents removal (default), 2 allows persistent removal while 3 prevents
+persistent removal. "Persistent" in this context means that the
+initiator (port) that successfully uses code 3 blocks other initiators (ports)
+from allowing removal. A "persistent prevent" state can be cleared by the
+owner allowing persistent removal (code 2) or a power cycle (or anything that
+resets the device (LU)) or some special commands (e.g. various service
+actions of Persistent Reserve Out, see SPC\-3).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH EXIT STATUS
+The exit status of sg_prevent is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2012 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_start(sg3_utils), sg_persist(sg3_utils)
diff --git a/doc/sg_raw.8 b/doc/sg_raw.8
new file mode 100644
index 00000000..6c2bf84f
--- /dev/null
+++ b/doc/sg_raw.8
@@ -0,0 +1,270 @@
+.TH SG_RAW "8" "May 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_raw \- send arbitrary SCSI or NVMe command to a device
+.SH SYNOPSIS
+.B sg_raw
+[\fI\-\-binary\fR] [\fI\-\-cmdfile=CF\fR] [\fI\-\-cmdset=CS\fR]
+[\fI\-\-enumerate\fR] [\fI\-\-help\fR] [\fI\-\-infile=IFILE\fR]
+[\fI\-\-nosense\fR] [\fI\-\-nvm\fR] [\fI\-\-outfile=OFILE\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-request=RLEN\fR] [\fI\-\-scan=FO,LO\fR]
+[\fI\-\-send=SLEN\fR] [\fI\-\-skip=KLEN\fR] [\fI\-\-timeout=SECS\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR [CDB0 CDB1 ...]
+.SH DESCRIPTION
+This utility sends an arbitrary SCSI command (between 6 and 256 bytes) to
+the \fIDEVICE\fR. There may be no associated data transfer; or data may be
+read from a file and sent to the \fIDEVICE\fR; or data may be received from
+the \fIDEVICE\fR and then displayed or written to a file. If supported
+by the pass through, bidirectional commands may be sent (i.e. containing
+both data to be sent to the \fIDEVICE\fR and received from the
+\fIDEVICE\fR).
+.PP
+The SCSI command may be between 6 and 256 bytes long. Each command byte is
+specified in plain hex format (00..FF) without a prefix or suffix. The
+command can be given either on the command line or via the
+\fI\-\-cmdfile=CF\fR option. See EXAMPLES section below.
+.PP
+The commands pass through a generic SCSI interface which is implemented
+for several operating systems including Linux, FreeBSD and Windows.
+.PP
+Experimental support has been added to send NVMe Admin and NVM commands to
+the \fIDEVICE\fR. Since all NVMe commands are 64 bytes long it is more
+convenient to use the \fI\-\-cmdfile=CF\fR option rather than type the 64
+bytes of the NVMe command on the command line. See the section on NVME
+below. A heuristic based on command length is used to decide if the given
+command is SCSI or NVMe, to override this heuristic use the
+\fI\-\-cmdset=CS\fR option.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-b\fR, \fB\-\-binary\fR
+Dump data in binary form, even when writing to stdout.
+.TP
+\fB\-c\fR, \fB\-\-cmdfile\fR=\fICF\fR
+\fICF\fR is the name of a file which contains the command to be executed.
+Without this option the command must be given on the command line, after
+the options and the \fIDEVICE\fR.
+.TP
+\fB\-C\fR, \fB\-\-cmdset\fR=\fICS\fR
+\fICS\fR is a number to indicate which command set (i.e. SCSI or NVMe)
+to use. 0, the default, causes a heuristic based on command length to be
+used. Use a \fICS\fR of 1 to override that heuristic and choose the SCSI
+command set. Use a \fICS\fR of 2 to override that heuristic and choose
+the NVMe command set.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display usage information and exit.
+.TP
+\fB\-i\fR, \fB\-\-infile\fR=\fIIFILE\fR
+Read binary data from \fIIFILE\fR instead of stdin. This option is ignored
+if \fB\-\-send\fR is not specified. That data, if used, will become the
+command's "data\-out" buffer.
+.TP
+\fB\-n\fR, \fB\-\-nosense\fR
+Don't display SCSI Sense information.
+.TP
+\fB\-N\fR, \fB\-\-nvm\fR
+When sending NVMe commands, the Admin command set is assumed. To send the
+NVM command set (e.g. the Read and Write (user data) commands) this option
+needs to be given.
+.TP
+\fB\-o\fR, \fB\-\-outfile\fR=\fIOFILE\fR
+Write data received from the \fIDEVICE\fR to \fIOFILE\fR. That data is
+the command's "data\-in" buffer. The data is written in binary. By default,
+data is dumped in hex format to stdout.
+If \fIOFILE\fR is '\-' then data is dumped in binary to stdout.
+This option is ignored if \fI\-\-request\fR is not specified.
+.TP
+\fB\-w\fR, \fB\-\-raw\fR
+interpret \fICF\fR (i.e. the command file) as containing binary. The default
+is to assume that it contains ASCII hexadecimal.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+Open \fIDEVICE\fR read\-only. The default (without this option) is to open
+it read\-write.
+.TP
+\fB\-r\fR, \fB\-\-request\fR=\fIRLEN\fR
+Expect to receive up to \fIRLEN\fR bytes of data from the \fIDEVICE\fR.
+\fIRLEN\fR may be suffixed with 'k' to use kilobytes (1024 bytes) instead
+of bytes. \fIRLEN\fR is decimal unless it has a leading '0x' or a
+trailing 'h'.
+.br
+If \fIRLEN\fR is too small (i.e. either smaller than indicated by the
+cdb (typically the "allocation length" field) and/or smaller than the
+\fIDEVICE\fR tries to send back) then the HBA driver may complain. Making
+\fIRLEN\fR larger than required should cause no problems. Most
+SCSI "data\-in" commands return a data block that contains (in its early
+bytes) a length that the \fIDEVICE\fR would "like" to send back if
+the "allocation length" field in the cdb is large enough. In practice, the
+\fIDEVICE\fR will return no more bytes than indicated in the "allocation
+length" field of the cdb.
+.TP
+\fB\-Q\fR, \fB\-\-scan\fR=\fIFO\fR,\fILO\fR
+Scan a range of opcodes (i.e. first byte of each command). The first opcode
+in the scan is \fIFO\fR (which is decimal unless it has a '0x' prefix or 'h'
+suffix). The last opcode in the scan is \fILO\fR. The maximum value of
+\fILO\fR is 255. The remaining bytes of the SCSI/NVMe command are as
+supplied at invocation.
+.br
+Warning: this option can be
+.B dangerous.
+Sending somewhat arbitrary commands to a device can have unexpected results.
+It is recommended that this option is used with the \fI\-\-cmdset=CS\fR
+option where \fICS\fR is 1 or 2 in order to stop the command set possibly
+changing during the scan.
+.TP
+\fB\-s\fR, \fB\-\-send\fR=\fISLEN\fR
+Read \fISLEN\fR bytes of data, either from stdin or from a file, and send
+them to the \fIDEVICE\fR. In the SCSI transport, \fISLEN\fR becomes the
+length (in bytes) of the "data\-out" buffer. \fISLEN\fR is decimal unless
+it has a leading '0x' or a trailing 'h'.
+.br
+It is the responsibility of the user to make sure that the "data\-out"
+length implied or stated in the cdb matches \fISLEN\fR. Note that some
+common SCSI commands such as WRITE(10) have a "transfer length" field whose
+units are logical blocks (which are usually 512 or 4096 bytes long).
+.TP
+\fB\-k\fR, \fB\-\-skip\fR=\fIKLEN\fR
+Skip the first \fIKLEN\fR bytes of the input file or stream. This option
+is ignored if \fI\-\-send\fR is not specified. If \fI\-\-send\fR is given
+and this option is not given, then zero bytes are skipped.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR
+Wait up to \fISECS\fR seconds for command completion (default: 20).
+Note that if a command times out the operating system may start by
+aborting the command and if that is unsuccessful it may attempt
+to reset the device.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version and license information and exit.
+.SH NOTES
+The sg_inq utility can be used to send an INQUIRY command to a device
+to determine its peripheral device type (e.g. '1' for a streaming
+device (tape drive)) which determines which SCSI command sets a device
+should support (e.g. SPC and SSC). The sg_vpd utility reads and decodes
+a device's Vital Product Pages which may contain useful information.
+.PP
+The ability to send more than a 16 byte CDB (in some cases 12 byte CDB)
+may be restricted by the pass\-through interface, the low level driver
+or the transport. In the Linux series 3 kernels, the bsg driver can
+handle longer CDBs, block devices (e.g. /dev/sdc) accessed via the
+SG_IO ioctl cannot handle CDBs longer than 16 bytes, and the sg driver
+can handle longer CDBs from lk 3.17 .
+.PP
+The CDB command name defined by T10 for the given CDB is shown if
+the '\-vv' option is given. The command line syntax still needs to be
+correct, so /dev/null may be used for the \fIDEVICE\fR since the CDB
+command name decoding is done before the \fIDEVICE\fR is checked.
+.PP
+The intention of the \fI\-\-scan=FO,LO\fR option is to slightly simplify
+the process of finding hidden or undocumented commands. It should be used
+with care; for example checking for vendor specific SCSI
+commands: 'sg_raw \-\-cmdset=1 \-\-scan=0xc0,0xff /dev/sg1 0 0 0 0 0 0'.
+.SH NVME SUPPORT
+Support for NVMe (a.k.a. NVM Express) is currently experimental. NVMe concepts
+map reasonably well to the SCSI architecture. A SCSI logical unit (LU) is
+similar to a NVMe namespace (although LUN 0 is very common in SCSI while
+namespace IDs start at 1). A SCSI target device is similar to a NVMe
+controller. SCSI commands vary from 6 to 260 bytes long (although SCSI command
+descriptor blocks (cdb_s) longer than 32 bytes are uncommon) while all NVMe
+commands are currently 64 bytes long. The SCSI architecture makes a clear
+distinction between an initiator (often called a HBA) and a target (device)
+while (at least on the PCIe transport) the NVMe controller plays both roles.
+This utility defaults to assuming the user provided 64 byte command belongs
+to NVMe's Admin command set. To issue commands from the "NVM" command set,
+the \fI\-\-nvm\fR option must be given. Admin and NVM commands are sent to
+submission queue 0.
+.PP
+One significant difference is that SCSI uses a big endian representation
+for integers that are longer than 8 bits (i.e. longer than 1 byte) while
+NVMe uses a little endian representation (like most things that have
+originated from the Intel organisation). NVMe specifications talk about
+Words (16 bits), Double Words (32 bits) and sometimes Quad Words (64
+bits) and has tighter alignment requirements than SCSI.
+.PP
+One difference that impacts this utility is that NVMe places pointers to
+host memory in its commands while SCSI leaves this detail to whichever
+transport it is using (e.g. SAS, iSCSI, SRP). Since this utility takes
+the command from the user (either on the command line or in a file named
+\fICF\fR) but this utility allocates a data\-in or data\-out buffer as
+required, the user does not know in advance what the address of that
+buffer will be. Some special addresses have been introduced to help with
+this problem: the address 0xfffffffffffffffe is interpreted as "use the
+data\-in buffer's address" while 0xfffffffffffffffd is interpreted as "use
+the data\-out buffer's address". Since NVMe uses little endian notation
+then that first address appears in the NVMe command byte stream as "fe"
+followed by seven "ff"s. A similar arrangement is made for the length
+of that buffer (in bytes), but since that is a 32 byte quantity, the
+first 4 bytes (all "ff"s) are removed.
+.PP
+Several command file examples can be found in the inhex directory of this
+package's source tarball: nvme_identify_ctl.hex, nvme_dev_self_test.hex,
+nvme_read_ctl.hex and nvme_write_ctl.hex .
+.PP
+Beware: the NVMe standard often refers to some of its fields as "0's based".
+They are typically counts of something like the number of blocks to be read.
+For example in NVMe Read command, a "0's based" number of blocks field
+containing the value 3 means to read 4 blocks! No, this is not a joke.
+.SH EXAMPLES
+These examples, apart from the last one, use Linux device names. For
+suitable device names in other supported Operating Systems see the
+sg3_utils(8) man page.
+.TP
+sg_raw /dev/scd0 1b 00 00 00 02 00
+Eject the medium in CD drive /dev/scd0.
+.TP
+sg_raw \-r 1k /dev/sg0 12 00 00 00 60 00
+Perform an INQUIRY on /dev/sg0 and dump the response data (up to
+1024 bytes) to stdout.
+.TP
+sg_raw \-s 512 \-i i512.bin /dev/sda 3b 02 00 00 00 00 00 02 00 00
+Showing an example of writing 512 bytes to a sector on a disk
+is a little dangerous. Instead this example will read i512.bin (assumed
+to be 512 bytes long) and use the SCSI WRITE BUFFER command to send
+it to the "data" buffer (that is mode 2). This is a safe operation.
+.TP
+sg_raw \-r 512 \-o o512.bin /dev/sda 3c 02 00 00 00 00 00 02 00 00
+This will use the SCSI READ BUFFER command to read 512 bytes from
+the "data" buffer (i.e. mode 2) then write it to the o512.bin file.
+When used in conjunction with the previous example, if both commands
+work then 'cmp i512.bin o512.bin' should show a match.
+.TP
+sg_raw \-\-infile=urandom.bin \-\-send=512 \-\-request=512 \-\-outfile=out.bin "/dev/bsg/7:0:0:0" 53 00 00 00 00 00 00 00 01 00
+This is a bidirectional XDWRITEREAD(10) command being sent via a Linux
+bsg device. Note that data is being read from "urandom.bin" and sent
+to the device (data\-out) while resulting data (data\-in) is placed
+in the "out.bin" file. Also note the length of both is 512 bytes
+which corresponds to the transfer length of 1 (block) in the cdb (i.e.
+the second last byte). urandom.bin can be produced like this:
+.br
+dd if=/dev/urandom bs=512 count=1 of=urandom.bin
+.TP
+sg_raw.exe PhysicalDrive1 a1 0c 0e 00 00 00 00 00 00 e0 00 00
+This example is from Windows and shows a ATA STANDBY IMMEDIATE command
+being sent to PhysicalDrive1. That ATA command is contained within
+the SCSI ATA PASS\-THROUGH(12) command (see the SAT or SAT\-2 standard at
+https://www.t10.org). Notice that the STANDBY IMMEDIATE command does not
+send or receive any additional data, however if it fails sense data
+should be returned and displayed.
+.TP
+For NVME examples see the files in this package's inhex directory that
+start with 'nvme_' such as inhex/nvme_identify_ctl.hex .
+.SH EXIT STATUS
+The exit status of sg_raw is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Ingo van Lil
+.SH "REPORTING BUGS"
+Report bugs to <inguin at gmx dot de> or to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2001\-2021 Ingo van Lil
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_vpd, sg3_utils (sg3_utils), plscsi
diff --git a/doc/sg_rbuf.8 b/doc/sg_rbuf.8
new file mode 100644
index 00000000..078d8b7d
--- /dev/null
+++ b/doc/sg_rbuf.8
@@ -0,0 +1,189 @@
+.TH SG_RBUF "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_rbuf \- reads data using SCSI READ BUFFER command
+.SH SYNOPSIS
+.B sg_rbuf
+[\fI\-\-buffer=EACH\fR] [\fI\-\-dio\fR] [\fI\-\-help\fR] [\fI\-\-mmap\fR]
+[\fI\-\-quick\fR] [\fI\-\-size=OVERALL\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_rbuf
+[\fI\-b=EACH_KIB\fR] [\fI\-d\fR] [\fI\-m\fR] [\fI\-q\fR]
+[\fI\-s=OVERALL_MIB\fR] [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This command reads data with the SCSI READ BUFFER command and then discards
+it. Typically the data being read is from a disk's memory cache. It is
+assumed that the data is sourced quickly (although this is not guaranteed
+by the SCSI standards) so that it is faster than reading data from the media.
+This command is designed for timing transfer speeds across a SCSI transport.
+.PP
+To fetch the data with a SCSI READ BUFFER command and optionally decode it
+see the sg_read_buffer utility. There is also a sg_write_buffer utility
+useful for downloading firmware amongst other things.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later section
+on the old command line syntax outlines the second group of options.
+.PP
+This is a Linux only utility and only works when \fIDEVICE\fR is an sg
+device (e.g. "/dev/sg1"). The sg_read_buffer utility has similar
+functionality and is ported to other OSes and within Linux can use
+bsg and normal block device names (e.g. "/dev/sdc").
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-buffer\fR=\fIEACH\fR
+where \fIEACH\fR is the number of bytes to be transferred by each READ
+BUFFER command. The default is the actual available buffer size returned
+by the READ BUFFER (descriptor) command. The maximum is
+the same as the default, hence this argument can only be used to reduce the
+size of each transfer to less than the device's actual available buffer size.
+.TP
+\fB\-d\fR, \fB\-\-dio\fR
+use direct IO if available. This option is only available if the \fIDEVICE\fR
+is a sg driver device node (e.g. /dev/sg1). In this case the sg driver will
+attempt to configure the DMA from the SCSI adapter to transfer directly into
+user memory. This will eliminate the copy via kernel buffers. If not
+available then this will be reported and indirect IO will be done instead.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print usage message then exit.
+.TP
+\fB\-m\fR, \fB\-\-mmap\fR
+use memory mapped IO if available. This option is only available if the
+\fIDEVICE\fR is a sg driver device node (e.g. /dev/sg1). In this case the
+sg driver will attempt to configure the DMA from the SCSI adapter to transfer
+directly into user memory. This will eliminate the copy via kernel buffers.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-q\fR, \fB\-\-quick\fR
+only transfer the data into kernel buffers (typically by DMA from the SCSI
+adapter card) and do not move it into the user space. This option is only
+available if the \fIDEVICE\fR is a sg driver device node (e.g. /dev/sg1).
+.TP
+\fB\-s\fR, \fB\-\-size\fR=\fIOVERALL\fR
+where \fIOVERALL\fR is the size of total transfer in bytes. The default is
+200 MiB (200*1024*1024 bytes). The actual number of bytes transferred may
+be slightly less than requested since all transfers are the same size (and
+an integer division is involved rounding towards zero).
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+times the bulk data transfer component of this command. The elapsed time
+is printed out plus a MB/sec calculation. In this case "MB" is 1,000,000
+bytes. The gettimeofday() system call is used internally for the time
+calculation.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH NOTES
+This command is typically used on modern SCSI disks which have a RAM cache
+in their drive electronics. If no IO to the magnetic media, or slower devices
+like flash RAM, is involved then the disk may be able to source data fast
+enough to saturate the bandwidth of the SCSI transport. The bottleneck may
+then be the DMA element in the HBA, the Linux drivers or the host machine's
+hardware (e.g. speed of RAM).
+.PP
+Various numeric arguments (e.g. \fIOVERALL\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.SH EXAMPLES
+.PP
+On the test system /dev/sg0 corresponds to a fast disk on a U2W SCSI
+bus (max 80 MB/sec). The disk specifications state that its cache is 4 MB.
+.br
+ $ time ./sg_rbuf /dev/sg0
+.br
+READ BUFFER reports: buffer capacity=3434944,
+.br
+ offset boundary=6
+.br
+Read 200 MiB (actual 199 MiB, 209531584 bytes),
+.br
+ buffer size=3354 KiB
+.br
+real 0m5.072s, user 0m0.000s, sys 0m2.280s
+.PP
+So that is approximately 40 MB/sec at 40 % utilization. Now with
+the addition of the "\-q" option this throughput improves and the
+utilization drops to 0%.
+.br
+ $ time ./sg_rbuf \-q /dev/sg0
+.br
+READ BUFFER reports: buffer capacity=3434944,
+.br
+ offset boundary=6
+.br
+Read 200 MiB (actual 199 MiB, 209531584 bytes),
+.br
+ buffer size=3354 KiB
+.br
+real 0m2.784s, user 0m0.000s, sys 0m0.000s
+.SH EXIT STATUS
+The exit status of sg_rbuf is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-b\fR=\fIEACH_KIB\fR
+where \fIEACH_KIB\fR is the number of Kilobytes (i.e. 1024 byte units) to be
+transferred by each READ BUFFER command. Similar to the
+\fI\-\-buffer=EACH\fR option in the main description but the units are
+different.
+.TP
+\fB\-d\fR
+use direct IO if available. Equivalent to the \fI\-\-dio\fR option in the
+main description.
+.TP
+\fB\-m\fR
+use memory mapped IO if available. Equivalent to the \fI\-\-mmap\fR option
+in the main description.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-q\fR
+only transfer the data into kernel buffers (typically by DMA from
+the SCSI adapter card) and do not move it into the user space.
+Equivalent to the \fI\-\-quick\fR option in the main description.
+.TP
+\fB\-s\fR=\fIOVERALL_MIB\fR
+where \fIOVERALL_MIB\fR is the size of total transfer in Megabytes (1048576
+bytes). Similar to the \fI\-\-size=OVERALL\fR option in the main description
+but the units are different.
+.TP
+\fB\-t\fR
+times the bulk data transfer component of this command. Equivalent to
+the \fI\-\-time\fR option in the main description.
+.TP
+\fB\-v\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2017 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_read_buffer, sg_write_buffer, sg_test_rwbuf(all in sg3_utils)
diff --git a/doc/sg_rdac.8 b/doc/sg_rdac.8
new file mode 100644
index 00000000..ddbda949
--- /dev/null
+++ b/doc/sg_rdac.8
@@ -0,0 +1,46 @@
+.TH SG_RDAC "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_rdac \- display or modify SCSI RDAC Redundant Controller mode page
+.SH SYNOPSIS
+.B sg_rdac
+[\fI\-6\fR] [\fI\-a\fR] [\fI\-f=LUN\fR] [\fI\-v\fR] [\fI\-V\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_rdac displays or modifies the RDAC controller settings via the
+Redundant Controller mode page (0x2C). When modifying the settings it
+allows one to transfer the ownership of individual drives to the
+controller the command was received on.
+.SH OPTIONS
+.TP
+\fB\-6\fR
+Use the 6 byte cdb variants of the SCSI MODE SENSE and MODE SELECT commands.
+The default action (in the absence of this option) is to use the 10 byte
+cdb variants.
+.TP
+\fB\-a\fR
+Transfer all (visible) devices
+.TP
+\fB\-f\fR=\fILUN\fR
+Transfer the device identified by \fILUN\fR. This command will only work
+if the controller supports 'Dual Active Mode' (aka active/active mode).
+\fILUN\fR is a decimal number which cannot exceed 31 when the \fI\-6\fR
+option is given, otherwise is cannot exceed 255.
+.TP
+\fB\-v\fR
+be verbose
+.TP
+\fB\-V\fR
+print version string then exit
+.SH EXIT STATUS
+The exit status of sg_rdac is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Hannes Reinecke <hare at suse dot com>, based on sg_emc_trespass.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2017 Hannes Reinecke, Douglas Gilbert.
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/sg_read.8 b/doc/sg_read.8
new file mode 100644
index 00000000..3f370a62
--- /dev/null
+++ b/doc/sg_read.8
@@ -0,0 +1,192 @@
+.TH SG_READ "8" "September 2019" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_read \- read multiple blocks of data, optionally with SCSI READ commands
+.SH SYNOPSIS
+.B sg_read
+[\fIblk_sgio=\fR0|1] [\fIbpt=BPT\fR] [\fIbs=BS\fR] [\fIcdbsz=\fR6|10|12|16]
+\fIcount=COUNT\fR [\fIdio=\fR0|1] [\fIdpo=\fR0|1] [\fIfua=\fR0|1]
+\fIif=IFILE\fR [\fImmap=\fR0|1] [\fIno_dxfer=\fR0|1] [\fIodir=\fR0|1]
+[\fIskip=SKIP\fR] [\fItime=TI\fR] [\fIverbose=VERB\fR] [\fI\-\-help\fR]
+[\fI\-\-version\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Read data from a Linux SCSI generic (sg) device, a block device or
+a normal file with each read command issued to the same offset or
+logical block address (lba). This can be used to test (or time) disk
+caching, SCSI (or some other) transport throughput, and/or SCSI
+command overhead.
+.PP
+When the \fICOUNT\fR value is positive, then up to \fIBPT\fR blocks are
+read at a time, until the \fICOUNT\fR is exhausted. Each read operation
+starts at the same lba which, if \fISKIP\fR is not given, is the
+beginning of the file or device.
+.PP
+The \fICOUNT\fR value may be negative when \fIIFILE\fR is a sg device
+or is a block device with 'blk_sgio=1' set. Alternatively 'bpt=0' may
+be given. In these cases |\fICOUNT\fR| "zero block" SCSI READ commands
+are issued. "Zero block" means "do nothing" for SCSI READ 10, 12 and
+16 byte commands (but not for the 6 byte variant). In practice "zero
+block" SCSI READ commands have low latency and so are one way to measure
+SCSI command overhead.
+.PP
+Please note: this is a very old utility that uses 32 bit integers for
+disk LBAs and the count. Hence it will not be able to address beyond
+2 Terabytes on a disk with logical blocks that are 512 bytes long.
+Alternatives are the sg_dd and ddpt utilities.
+.SH OPTIONS
+.TP
+\fBblk_sgio\fR=0 | 1
+The default action of this utility is to use the Unix read() command when
+the \fIIFILE\fR is a block device. In lk 2.6 many block devices can handle
+SCSI commands issued via the SG_IO ioctl. So when this option is set
+the SG_IO ioctl sends SCSI READ commands to \fIIFILE\fR if it is a block
+device.
+.TP
+\fBbpt\fR=\fIBPT\fR
+where \fIBPT\fR is the maximum number of blocks each read operation fetches.
+Fewer blocks will be fetched when the remaining \fICOUNT\fR is less than
+\fIBPT\fR. The default value for \fIBPT\fR is 128. Note that each read
+operation starts at the same lba (as given by \fIskip=SKIP\fR or 0).
+If 'bpt=0' then the \fICOUNT\fR is interpreted as the number of zero
+block SCSI READ commands to issue.
+.TP
+\fBbs\fR=\fIBS\fR
+where \fIBS\fR is the size (in bytes) of each block read. This
+.B must
+be the block size of the physical device (defaults to 512) if SCSI commands
+are being issued to \fIIFILE\fR.
+.TP
+\fBcdbsz\fR=6 | 10 | 12 | 16
+size of SCSI READ commands issued on sg device names, or block devices
+if 'blk_sgio=1' is given. Default is 10 byte SCSI READ cdbs.
+.TP
+\fBcount\fR=\fICOUNT\fR
+when \fICOUNT\fR is a positive number, read that number of blocks,
+typically with multiple read operations. When \fICOUNT\fR is negative then
+|\fICOUNT\fR| SCSI READ commands are performed requesting zero blocks
+to be transferred. This option is mandatory.
+.TP
+\fBdio\fR=0 | 1
+default is 0 which selects indirect IO. Value of 1 attempts direct
+IO which, if not available, falls back to indirect IO and notes this
+at completion. This option is only active if \fIIFILE\fR is an sg device.
+If direct IO is selected and /sys/module/sg/parameters/allow_dio
+has the value of 0 then a warning is issued (and indirect IO is performed)
+.TP
+\fBdpo\fR=0 | 1
+when set the disable page out (DPO) bit in SCSI READ commands is set.
+Otherwise the DPO bit is cleared (default).
+.TP
+\fBfua\fR=0 | 1
+when set the force unit access (FUA) bit in SCSI READ commands is set.
+Otherwise the FUA bit is cleared (default).
+.TP
+\fBif\fR=\fIIFILE\fR
+read from this \fIIFILE\fR. This argument must be given. If the \fIIFILE\fR
+is a normal file then it must be seekable (if (\fICOUNT\fR > \fIBPT\fR) or
+\fIskip=SKIP\fR is given). Hence stdin is not acceptable (and giving "\-"
+as the \fIIFILE\fR argument is reported as an error).
+.TP
+\fBmmap\fR=0 | 1
+default is 0 which selects indirect IO. Value of 1 causes memory mapped
+IO to be performed. Selecting both dio and mmap is an error. This option
+is only active if \fIIFILE\fR is an sg device.
+.TP
+\fBno_dxfer\fR=0 | 1
+when set then DMA transfers from the device are made into kernel buffers
+but no further (i.e. there is no second copy into the user space). The
+default value is 0 in which case transfers are made into the user space.
+When neither mmap nor dio is set then data transfer are copied via
+kernel buffers (i.e. a double copy). Mainly for testing.
+.TP
+\fBodir\fR=0 | 1
+when set opens an \fIIFILE\fR which is a block device with an additional
+O_DIRECT flag. The default value is 0 (i.e. don't open block devices
+O_DIRECT).
+.TP
+\fBskip\fR=\fISKIP\fR
+all read operations will start offset by \fISKIP\fR bs\-sized blocks
+from the start of the input file (or device).
+.TP
+\fBtime\fR=\fITI\fR
+When \fITI\fR is 0 (default) doesn't perform timing.
+When 1, times transfer and does throughput calculation, starting at the
+first issued command until completion. When 2, times transfer and does
+throughput calculation, starting at the second issued command until
+completion. When 3 times from third command, etc. An average number of
+commands (SCSI READs or Unix read()s) executed per second is also
+output.
+.TP
+\fBverbose\fR=\fIVERB\fR
+as \fIVERB\fR increases so does the amount of debug output sent to stderr.
+Default value is zero which yields the minimum amount of debug output.
+A value of 1 reports extra information that is not repetitive.
+.TP
+\fB\-\-help\fR
+Output the usage message then exit.
+.TP
+\fB\-\-version\fR
+Output the version string then exit.
+.SH NOTES
+Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+Data usually gets to the user space in a 2 stage process: first the
+SCSI adapter DMAs into kernel buffers and then the sg driver copies
+this data into user memory.
+This is called "indirect IO" and there is a "dio" option to select
+"direct IO" which will DMA directly into user memory. Due to some
+issues "direct IO" is disabled in the sg driver and needs a
+configuration change to activate it. This is typically done with
+"echo 1 > /sys/module/sg/parameters/allow_dio". An alternate way to avoid the
+2 stage copy is to select memory mapped IO with 'mmap=1'.
+.SH SIGNALS
+The signal handling has been borrowed from dd: SIGINT, SIGQUIT and
+SIGPIPE output the number of remaining blocks to be transferred;
+then they have their default action.
+SIGUSR1 causes the same information to be output yet the copy continues.
+All output caused by signals is sent to stderr.
+.SH EXAMPLES
+.PP
+Let us assume that /dev/sg0 is a disk and we wish to time the disk's
+cache performance.
+.PP
+ sg_read if=/dev/sg0 bs=512 count=1MB mmap=1 time=2
+.PP
+This command will continually read 128 512 byte blocks from block 0.
+The "128" is the default value for 'bpt' while "block 0" is chosen
+because the 'skip' argument was not given. This will continue until
+1,000,000 blocks are read. The idea behind using 'time=2' is that the
+first 64 KiB read operation will involve reading the magnetic media
+while the remaining read operations will "hit" the disk's cache. The
+output of third command will look like this:
+.PP
+ time from second command to end was 4.50 secs, 113.70 MB/sec
+.br
+ Average number of READ commands per second was 1735.27
+.br
+ 1000000+0 records in, SCSI commands issued: 7813
+.SH EXIT STATUS
+The exit status of sg_read is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2019 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+To time streaming media read or write time see
+.B sg_dd
+is in the sg3_utils package and
+.B ddpt
+in a package of the same name.
+The lmbench package contains
+.B lmdd
+which is also interesting.
+.B raw(8), dd(1)
diff --git a/doc/sg_read_attr.8 b/doc/sg_read_attr.8
new file mode 100644
index 00000000..8ab59b97
--- /dev/null
+++ b/doc/sg_read_attr.8
@@ -0,0 +1,214 @@
+.TH SG_READ_ATTR "8" "December 2020" "sg3_utils\-1.46" SG3_UTILS
+.SH NAME
+sg_read_attr \- send SCSI READ ATTRIBUTE command
+.SH SYNOPSIS
+.B sg_read_attr
+[\fI\-\-cache\fR] [\fI\-\-enumerate\fR] [\fI\-\-ea=EA\fR]
+[\fI\-\-filter=FL\fR] [\fI\-\-first=FAI\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-in=FN\fR] [\fI\-\-lvn=LVN\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-pn=PN\fR]
+[\fI\-\-quiet\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-sa=SA\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI READ ATTRIBUTE command to \fIDEVICE\fR and outputs the data
+returned. This command was introduced in SPC\-3 revision 1 and thus is
+applicable to all SCSI devices. In practice it is used mainly for tape
+systems. This utility is based on the SPC\-5 draft standard, revision
+17 (spc5r17.pdf).
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-cache\fR
+sets the CACHE bit in the READ ATTRIBUTE cdb. This instructs the device
+server to return cached attributes. By default that bit is cleared
+which instructs the device server not to return cached attributes.
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+enumerates all known attributes and service actions. Attributes include
+an identifier, length, format and a name as defined by T10. If \fIDEVICE\fR
+is given then it is ignored.
+.TP
+\fB\-E\fR, \fB\-\-ea\fR=\fIEA\fR
+where \fIEA\fR is an element address which is placed in the READ ATTRIBUTE
+cdb. This field is only found in SMC\-2 and SMC\-3 drafts for medium
+changers usually associated with tape libraries. By default this field
+is set to zero.
+.TP
+\fB\-f\fR, \fB\-\-filter\fR=\fIFL\fR
+where \fIFL\fR is an attribute identifier in the range 0 to 65535 or \-1.
+Attribute identifiers are typically given in hexadecimal in which case the
+hex number should be prefixed by "0x" or has a trailing "h". "\-1" is
+the default value and means 'match all'; for all other values of \fIFL\fR
+on the matching attribute is output.
+.TP
+\fB\-F\fR, \fB\-\-first\fR=\fIFAI\fR
+where \fIFAI\fR is the "first attribute identifier" field in the cdb. It
+seems as though the intent of this field is that only attributes whose
+identifiers are equal to or greater than \fIFAI\fR are returned. The default
+value of \fIFAI\fR is zero. Attributes are returned in ascending identifier
+order.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal to stdout. When used once the whole
+response is output in ASCII hexadecimal with a leading address (starting at
+0) on each line. When used twice each attribute descriptor in the response
+is output separately in hexadecimal. When used thrice the whole response is
+output in hexadecimal with no leading address (on each line).
+.br
+Output generated by '\-HHH' (or \fI\-\-hex\fR used three times) can be
+redirected to a file. That file will be in suitable format for \fI\-\-in=FN\fR
+to use in a later invocation.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR
+\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII
+hexadecimal or binary representing the response to a READ ATTRIBUTE command
+with service action 0x0 (i.e (fetch) attribute values). When this option is
+given then \fIDEVICE\fR (if also given) is ignored.
+.br
+By default \fIFN\fR is assumed to contain ASCII hexadecimal arranged as
+bytes which a space, tab or comma delimited. All characters from (and
+including) "#" to the end of line are ignored. If the \fI\-\-raw\fR option
+is also given then \fIFN\fR is assumed to contain binary data. When the
+\fI\-\-raw\fR option is given then after processing the input the
+internal raw variable is reset to 0 so it has no effect on the output.
+.br
+Since the READ ATTRIBUTE response does not contain the service action number
+that it is a response to, then the \fI\-\-sa=SA\fR should be given (if not
+service action 0 (attribute values) is assumed.
+.TP
+\fB\-l\fR, \fB\-\-lvn\fR=\fILVN\fR
+where \fILVN\fR is placed in the "logical volume number" field of the cdb.
+The default value is zero which is required to be the logical volume number
+if the device only has one volume.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576.
+.TP
+\fB\-p\fR, \fB\-\-pn\fR=\fIPN\fR
+where \fIPN\fR is placed in the "partition number" field of the cdb. If
+the \fIDEVICE\fR only has one partition then its partition number must be
+zero. The default value of \fIPN\fR is zero.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+this option reduces the amount of information output. For example when
+used once (\fISA\fR=0), it suppresses the header line announcing the
+output of attributes; when used twice it suppresses the name of each
+attribute, leaving only the associated attribute values (or strings).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the SCSI response (i.e. the data\-out buffer) in binary (to stdout).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-s\fR, \fB\-\-sa\fR=\fISA\fR
+where \fISA\fR is placed on the "service action" field of the cdb. Values
+of 0 to 63 are accepted with a default of 0. spc5r08.pdf defines five
+service actions: 0 for attributes values ; 1 for an attribute list (names,
+not values), 2 for the logical volume list; 3 for the partition list; 4
+is restricted for SMC\-3; and 5 for the supported attribute list.
+.br
+Alternatively an acronym can be given for \fISA\fR. The acronym should be
+one of "av", "al", "lvl", "pn", "smc" or "sa" for service actions 0 to 5
+respectively. The acronyms can also be given in upper case.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+Only tape systems seem to implement the SCSI READ ATTRIBUTE command. The vast
+majority of its definition is in the SPC standard so other device types could
+use it.
+.PP
+Much of the information provided by READ ATTRIBUTE can also be found in
+pages returned by LOG SENSE (see the sg_logs utility) and in the VPD
+pages returned by the INQUIRY command.
+.SH EXAMPLES
+To list the attributes of a tape drive whose \fIDEVICE\fR is /dev/sg1 ,
+the following could be used:
+.PP
+# sg_read_attr \-s al /dev/sg1
+.br
+Attribute list:
+.br
+ Remaining capacity in partition [MiB]
+.br
+ Maximum capacity in partition [MiB]
+.br
+ TapeAlert flags
+.br
+ Load count
+.br
+ MAM space remaining [B]
+.br
+ Assigning organization
+.br
+ Format density code
+.br
+ ...
+.PP
+To check the number of partitions:
+.PP
+# sg_read_attr \-s pl /dev/sg1
+.br
+Partition number list:
+.br
+ First partition number: 0
+.br
+ Number of partitions available: 2
+.PP
+And to see the attribute values (which is the default service action):
+.PP
+# sg_read_attr /dev/sg1
+.br
+Attribute values:
+.br
+ Remaining capacity in partition [MiB]: 1386103
+.br
+ Maximum capacity in partition [MiB]: 1386103
+.br
+ TapeAlert flags: 0
+.br
+ ....
+.PP
+To redirect the attribute values response to a file for later decoding:
+.PP
+# sg_read_attr \-HHH /dev/sg1 > av.hex
+.PP
+And later the response held in the av.hex file could be decoded with:
+.PP
+# sg_read_attr \-s av \-\-in=av.hex
+.br
+Attribute values:
+.br
+ Remaining capacity in partition [MiB]: 1386103
+.br
+ Maximum capacity in partition [MiB]: 1386103
+.br
+ TapeAlert flags: 0
+.br
+ ....
+.PP
+.SH EXIT STATUS
+The exit status of sg_read_attr is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2016\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd,sg_logs(sg3_utils)
diff --git a/doc/sg_read_block_limits.8 b/doc/sg_read_block_limits.8
new file mode 100644
index 00000000..75bbd8e3
--- /dev/null
+++ b/doc/sg_read_block_limits.8
@@ -0,0 +1,68 @@
+.TH SG_READ_BLOCK_LIMITS "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_read_block_limits \- send SCSI READ BLOCK LIMITS command
+.SH SYNOPSIS
+.B sg_read_block_limits
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [--mloi] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI READ BLOCK LIMITS command to \fIDEVICE\fR and outputs the
+response. This command is defined for tape (drives) and its description
+is found in the SSC documents at https://www.t10.org .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response in hex (rather than decode it).
+.TP
+\fB\-m\fR, \fB\-\-mloi\fR
+sets the MLOI bit in the READ BLOCK LIMITS command and if that
+succeeds, prints out the Maximum Logical Object Identifier (MLOI)
+value. The MLOI bit was introduced in the ssc4r02.pdf draft.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary to stdout.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open \fIDEVICE\fR in read\-only mode. The default is to open it in
+read\-write mode.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH EXIT STATUS
+The exit status of sg_read_block_limits is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH EXAMPLES
+It is usually okay to use no options. Here is an invocation (on the first
+line following the "#" command prompt) followed by some typical output:
+.PP
+ # sg_read_block_limits /dev/st0
+.br
+Read Block Limits results:
+.br
+ Minimum block size: 1 byte(s)
+.br
+ Maximum block size: 16777215 byte(s), 16383 KB, 15 MB
+.br
+ Granularity: 0
+.br
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2009\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils(sg3_utils)
diff --git a/doc/sg_read_buffer.8 b/doc/sg_read_buffer.8
new file mode 100644
index 00000000..0acc9167
--- /dev/null
+++ b/doc/sg_read_buffer.8
@@ -0,0 +1,175 @@
+.TH SG_READ_BUFFER "8" "February 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_read_buffer \- send SCSI READ BUFFER command
+.SH SYNOPSIS
+.B sg_read_buffer
+[\fI\-\-eh_code=EHC\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-id=ID\fR]
+[\fI\-\-inhex=FN\fR] [\fI\-\-length=LEN\fR] [\fI\-\-mode=MO\fR]
+[\fI\-\-no_output\fR] [\fI\-\-offset=OFF\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-specific=MS\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI READ BUFFER command to the \fIDEVICE\fR, and if there is a
+response either decodes it, prints it in hexadecimal or sends it in binary to
+stdout. If a response is received for a "descriptor" mode then, in the absence
+of \fI\-\-hex\fR and \fI\-\-raw\fR, it is decoded. Response for
+non\-descriptor modes are output in hexadecimal unless the \fI\-\-raw\fR
+option is given.
+.PP
+The responses to the Read microcode status ('rd_microc_st' [0xf]) and Error
+history ('err_hist' [0x1c]) modes are decoded as described in spc6r06.pdf and
+earlier T10 documents.
+.PP
+This utility may be called without a \fIDEVICE\fR but with a
+\fI\-\-inhex=FN\fR option instead. \fIFN\fR is expected to be a file name (or
+ '\-' for stdin). The contents of the file (or stdin stream) is assumed to be
+hexadecimal (or binary) data that represents a SCSI READ BUFFER command
+response and is decoded as such.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-e\fR, \fB\-\-eh_code\fR=\fIEHC\fR
+\fIEHC\fR is the error history code placed in the Buffer ID field of the cdb.
+The Mode field is set to err_hist [0x1c]. The option is equivalent to using
+the '\fI\-\-mode=eh\fR \fI\-\-id=EHC\fR' options. If this option and one of
+the other options is given, then an error will be generated if they
+contradict. The default (maximum) response length is increased to 64 bytes
+when may need to be increased (if so that is noted if the output is
+truncated).
+.br
+An example is setting \fIEHC\fR to 0 in which case the error history
+directory will be decoded (unless \fI\-\-hex\fR or \fI\-\-raw\fR options
+is given).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. If used multiple times also prints
+the mode names and their acronyms.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal. When given twice the response is
+output in hex with the corresponding representation in ASCII to the
+right of each line. When given three time the hex is printed without
+addresses (indexes) at the start of each line; this type of format is
+suitable for the \fI\-\-inhex=FN\fR option on a subsequent invocation.
+.TP
+\fB\-i\fR, \fB\-\-id\fR=\fIID\fR
+this option sets the Buffer ID field in the cdb. \fIID\fR is a value between
+0 (default) and 255 inclusive. The meaning of the Buffer ID field varies
+with the value in the Mode field of the cdb.
+.TP
+\fB\-I\fR, \fB\-\-inhex\fR=\fIFN\fR
+\fIFN\fR is expected to be a file name (or '\-' for stdin) which contains
+ASCII hexadecimal or binary representing a READ BUFFER response. If known
+this utility will then decode that response. It is preferable to also supply
+the \fI\-\-mode=MO\fR, \fI\-\-id=ID\fR and possible \fI\-\-specific=MS\fR
+options, since these are not present in the response. See the "FORMAT OF
+FILES CONTAINING ASCII HEX" section in the sg3_utils manpage for more
+information. If the \fI\-\-raw\fR option is also given then the contents
+of \fIFN\fR is treated as binary.
+.TP
+\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR
+where \fILEN\fR is the length, in bytes, that is placed in the "allocation
+length" field in the cdb. The default value is 4 (bytes) which is increased
+to 64 if the 'err_hist' mode [0x1c] is given or implied. The device may
+respond with less bytes.
+.br
+If the \fI\-\-inhex=FN\fR option is given, then the default value of the
+length is increased to 8192 bytes. This length may then be reduced to match
+the number of bytes decoded from the contents of \fIFN\fR.
+.TP
+\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR
+this option sets the mode field in the cdb. \fIMO\fR is a value between
+0 (default) and 31 inclusive. Alternatively an abbreviation can be given.
+See the MODES section below. To list the available mode abbreviations use
+an invalid one (e.g. '\-\-mode=xxx'). As an example, to fetch the read
+buffer descriptor give '\-\-mode=desc' .
+.TP
+\fB\-N\fR, \fB\-\-no_output\fR
+when this option is given after sending the SCSI command to the \fIDEVICE\fR
+the response is processed, looking for any errors, and then this utility
+exits. Any data read by the READ BUFFER command is ignored.
+.br
+May be useful for timing larger reads from the \fIDEVICE\fR buffer in 'data'
+mode [0x2].
+.TP
+\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR
+this option sets the buffer offset field in the cdb. \fIOFF\fR is a value
+between 0 (default) and 2**24\-1 . It is a byte offset.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+if a response is received then it is sent in binary to stdout. When this
+option is given together with \fI\-\-inhex=FN\fR then the contents of
+\fIFN\fR is assumed to be binary and the output of this utility is
+normal ASCII (i.e. _not_ in binary).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-S\fR, \fB\-\-specific\fR=\fIMS\fR
+this option sets the mode specific field in the cdb. \fIMS\fR is a value
+between 0 and 7 as this is a 3 bit field.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH MODES
+Following is a list of READ BUFFER command settings for the MODE field.
+First is an acronym accepted by the \fIMO\fR argument of this utility.
+Following the acronym in square brackets are the corresponding decimal and
+hex values that may also be given for \fIMO\fR. The following are listed
+in numerical order.
+.TP
+hd [0, 0x0]
+Combined header and data (obsolete in SPC\-4).
+.TP
+vendor [1, 0x1]
+Vendor specific.
+.TP
+data [2, 0x2]
+Data.
+.TP
+desc [3, 0x3]
+Descriptor: yields 4 bytes that contain an offset boundary field (1 byte)
+and buffer capacity (3 bytes).
+.TP
+echo [10, 0xa]
+Read data from echo buffer (was called "Echo buffer" in SPC\-3).
+.TP
+echo_desc [11, 0xb]
+Echo buffer descriptor: yields 4 bytes of which the last (lowest) 13 bits
+represent the echo buffer capacity. The maximum echo buffer size is 4096
+bytes.
+.TP
+rd_microc_st [15, 0xf]
+Read microcode status. Added in spc5r20 .
+.TP
+en_ex [26, 0x1a]
+Enable expander communications protocol and Echo buffer. Made obsolete in
+SPC\-4.
+.TP
+err_hist|eh [28, 0x1c]
+Error history. Either 'err_hist' or the short 'eh' abbreviation can be used
+for this mode. Introduced in SPC\-4.
+.SH NOTES
+All numbers given with options are assumed to be decimal.
+Alternatively numerical values can be given in hexadecimal preceded by
+either "0x" or "0X" (or has a trailing "h" or "H").
+.SH EXIT STATUS
+The exit status of sg_read_buffer is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Luben Tuikov and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2019 Luben Tuikov and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_write_buffer(sg3_utils)
diff --git a/doc/sg_read_long.8 b/doc/sg_read_long.8
new file mode 100644
index 00000000..e1b1591a
--- /dev/null
+++ b/doc/sg_read_long.8
@@ -0,0 +1,102 @@
+.TH SG_READ_LONG "8" "November 2015" "sg3_utils\-1.42" SG3_UTILS
+.SH NAME
+sg_read_long \- send a SCSI READ LONG command
+.SH SYNOPSIS
+.B sg_read_long
+[\fI\-\-16\fR] [\fI\-\-correct\fR] [\fI\-\-help\fR] [\fI\-\-lba=LBA\fR]
+[\fI\-\-out=OF\fR] [\fI\-\-pblock\fR] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-xfer_len=BTL\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send SCSI READ LONG command to \fIDEVICE\fR. The read buffer is output in hex
+and ASCII to stdout or placed in a file. Note that the data returned includes
+the logical block data (typically 512 bytes for a disk) plus ECC
+information (whose format is proprietary) plus optionally other proprietary
+data. Note that the logical block data may be encoded or encrypted.
+.PP
+In SBC\-4 revision 7 the SCSI READ LONG (10 and 16 byte) commands were made
+obsolete. In the same revision all uses of SCSI WRITE LONG (10 and 16 byte)
+commands were made obsolete apart from the case in which the WR_UNCOR bit is
+set.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+uses a SCSI READ LONG(16) command. The default action is to use a SCSI
+READ LONG(10) command. The READ LONG(10) command has a 32 bit field for
+the lba while READ LONG(16) has a 64 bit field.
+.TP
+\fB\-c\fR, \fB\-\-correct\fR
+sets the 'CORRCT' bit in the SCSI READ LONG command. When set the data is
+corrected by the ECC before being transferred back to this utility. The
+default is to leave the 'CORRCT' bit clear in which case the data is
+not corrected.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the logical block address of the sector to read. Assumed
+to be in decimal unless prefixed with '0x' (or has a trailing 'h'). Defaults
+to lba 0. If the lba is larger than can fit in 32 bits then the \fI\-\-16\fR
+option should be used.
+.TP
+\fB\-o\fR, \fB\-\-out\fR=\fIOF\fR
+instead of outputting ASCII hex to stdout, send it in binary to the
+file called \fIOF\fR. If '\-' is given for \fIOF\fR then the (binary)
+output is sent to stdout. Note that all informative and error output is
+sent to stderr.
+.TP
+\fB\-p\fR, \fB\-\-pblock\fR
+sets the 'PBLOCK' bit in the SCSI READ LONG command. When set the
+physical block (plus ECC data) containing the requested logical block
+address is read. The default is to leave the 'PBLOCK' bit clear in
+which case the logical block (plus any ECC data) is read.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+opens the DEVICE read\-only rather than read\-write which is the
+default. The Linux sg driver needs read\-write access for the SCSI
+READ LONG command but other access methods may require read\-only
+access.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR
+where \fIBTL\fR is the byte transfer length (default to 520). If the
+given value (or the default) does not match the "long" block size of the
+device, the appropriate \fIBTL\fR is deduced from the error response and
+printed (to stderr). The idea is that the user will retry this utility
+with the correct transfer length.
+.SH NOTES
+If a defective block is found and its contents, if any, has been
+retrieved then "sg_reassign" could be used to map out the defective
+block. Associated with such an action the number of elements in
+the "grown" defect list could be monitored (with "sg_reassign \-\-grown")
+as the disk could be nearing the end of its useful lifetime.
+.PP
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+As a data point, Fujitsu uses a 54 byte ECC (per block) which is capable
+of correcting up to a single burst error or 216 bits "on the
+fly". [Information obtained from MAV20xxrc product manual.]
+.SH EXIT STATUS
+The exit status of sg_read_long is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2016 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_reassign, sg_write_long, sg_dd
diff --git a/doc/sg_readcap.8 b/doc/sg_readcap.8
new file mode 100644
index 00000000..d936ef9e
--- /dev/null
+++ b/doc/sg_readcap.8
@@ -0,0 +1,196 @@
+.TH SG_READCAP "8" "January 2020" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_readcap \- send SCSI READ CAPACITY command
+.SH SYNOPSIS
+.B sg_readcap
+[\fI\-\-16\fR] [\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-lba=LBA\fR] [\fI\-\-long\fR] [\fI\-\-pmi\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-zbc\fR]
+\fIDEVICE\fR
+.PP
+.B sg_readcap
+[\fI\-16\fR] [\fI\-b\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-lba=LBA\fR]
+[\fI\-pmi\fR] [\fI\-r\fR] [\fI\-R\fR] [\fI\-v\fR] [\fI\-V\fR] [\fI\-z\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+The normal action of the SCSI READ CAPACITY command is to fetch the number
+of blocks (and block size) from the \fIDEVICE\fR.
+.PP
+The SCSI READ CAPACITY command (both 10 and 16 byte cdbs) actually yield
+the block address of the last block and the block size. The number of
+blocks is thus one plus the block address of the last block (as blocks
+are counted origin zero (i.e. starting at block zero)). This is the source
+of many "off by one" errors.
+.PP
+The READ CAPACITY(16) response provides additional information not found in
+the READ CAPACITY(10) response. This includes protection and logical block
+provisioning information, plus the number of logical blocks per physical
+block. So even though the media size may not exceed what READ CAPACITY(10)
+can show, it may still be useful to examine the response to READ
+CAPACITY(16). Sadly there are horrible SCSI command set implementations in
+the wild that crash when the READ CAPACITY(16) command is sent to them.
+.PP
+Device capacity is the product of the number of blocks by the block size.
+This utility outputs this figure in bytes, MiB (1048576 bytes per MiB),
+GB (1000000000 bytes per GB) and, if large enough, TB (1000 GB).
+.PP
+If sg_readcap is called without the \fI\-\-long\fR option then the 10 byte
+cdb version (i.e. READ CAPACITY (10)) is sent to the \fIDEVICE\fR. If the
+number of blocks in the response is reported as
+0xffffffff (i.e. (2**32 \- 1) ) and the \fI\-\-hex\fR option has not been
+given, then READ CAPACITY (16) is called and its response is output.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later section
+on the old command line syntax outlines the second group of options.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-\-16\fR
+Use the 16 byte cdb variant of the READ CAPACITY command. See the '\-\-long'
+option.
+\fB\-b\fR, \fB\-\-brief\fR
+outputs two hex numbers (prefixed with '0x' and space separated)
+to stdout. The first number is the maximum number of blocks on the
+device (which is one plus the lba of the last accessible block). The
+second number is the size in bytes of each block. If the operation fails
+then "0x0 0x0" is written to stdout.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response to the READ CAPACITY command (either the 10 or 16
+byte cdb variant) in ASCII hexadecimal on stdout.
+.TP
+\fB\-L\fR, \fB\-\-lba\fR=\fILBA\fR
+used in conjunction with \fI\-\-pmi\fR option. This variant of READ CAPACITY
+will yield the last block address after \fILBA\fR prior to a delay. For a
+disk, given a \fILBA\fR it yields the highest numbered block on the same
+cylinder (i.e. before the heads need to move). \fILBA\fR is assumed to be
+decimal unless prefixed by "0x" or it has a trailing "h". Defaults to 0.
+This option was made obsolete in SBC\-3 revision 26.
+.TP
+\fB\-l\fR, \fB\-\-long\fR
+Use the 16 byte cdb variant of the READ CAPACITY command. The default
+action is to use the 10 byte cdb variant which limits the maximum
+block address to (2**32 \- 2). When a 10 byte cdb READ CAPACITY command
+is used on a device whose size is too large then a last block address
+of 0xffffffff is returned (if the device complies with SBC\-2 or later).
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-p\fR, \fB\-\-pmi\fR
+partial medium indicator: for finding the next block address prior to
+some delay (e.g. head movement). In the absence of this option, the
+total number of blocks and the block size of the device are output.
+Used in conjunction with the \fI\-\-lba=LBA\fR option. This option was
+made obsolete in SBC\-3 revision 26.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary to stdout.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default for READ CAPACITY(16) is to open it read\-write. The default
+for READ CAPACITY(10) is to open it read\-only so this option does not
+change anything for this case.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+outputs version string then exits.
+.TP
+\fB\-z\fR, \fB\-\-zbc\fR
+additionally prints out the extra ZBC field (RC_BASIS) in the READ CAPACITY
+response. Using the option implicitly sets the \fI\-\-16\fR option.
+.SH NOTES
+The response to READ CAPACITY(16) contains a LBPRZ bit in the SBC\-3
+standard (ANSI INCITS 514\-2014). There was also a LBPRZ bit with the same
+meaning in the Logical block provisioning VPD page (0xb2). Then somewhat
+confusingly T10 expanded the LBPRZ bit to a 3 bit field in SBC\-4 draft
+revision 7, but only in the LB provisioning VPD page. The reason for the
+expansion was to report a new "provisioning initialization pattern"
+state (when an unmapped logical block is read). The new state has been
+assigned LBPRZ=2 in the VPD page and it re\-uses LBPRZ=0 in the READ
+CAPACITY(16) response. LBPRZ=1 retains the same meaning for both variants,
+namely that a block of zeroes will be returned when an unmapped logical block
+is read.
+.SH EXIT STATUS
+The exit status of sg_readcap is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-16\fR
+Use the 16 byte cdb variant of the READ CAPACITY command.
+Equivalent to \fI\-\-long\fR in the main description.
+.TP
+\fB\-b\fR
+utility outputs two hex numbers (prefixed with '0x' and space separated) to
+stdout. The first number is the maximum number of blocks on the device (which
+is one plus the lba of the last accessible block). The second number is the
+size of each block. If the operation fails then "0x0 0x0" is written to
+stdout. Equivalent to \fI\-\-brief\fR in the main description.
+.TP
+\fB\-h\fR
+output the usage message then exit. Giving the \fI\-?\fR option also outputs
+the usage message then exits.
+.TP
+\fB\-H\fR
+output the response to the READ CAPACITY command (either the 10 or 16
+byte cdb variant) in ASCII hexadecimal on stdout.
+.TP
+\fB\-lba\fR=\fILBA\fR
+used in conjunction with \fI\-pmi\fR option. This variant of READ CAPACITY
+will yield the last block address after \fILBA\fR prior to a delay.
+Equivalent to \fI\-\-lba=LBA\fR in the main description.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-pmi\fR
+partial medium indicator: for finding the next block address prior to
+some delay (e.g. head movement). In the absence of this switch, the
+total number of blocks and the block size of the device are output.
+Equivalent to \fI\-\-pmi\fR in the main description.
+.TP
+\fB\-r\fR
+output response in binary (to stdout).
+.TP
+\fB\-R\fR
+Equivalent to \fI\-\-readonly\fR in the main description.
+.TP
+\fB\-v\fR
+verbose: print out cdb of issued commands prior to execution. '\-vv'
+and '\-vvv' are also accepted yielding greater verbosity.
+.TP
+\fB\-V\fR
+outputs version string then exits.
+.TP
+\fB\-z\fR
+Equivalent to \fI\-\-zbc\fR in the main description.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHORS
+Written by Douglas Gilbert
+.SH COPYRIGHT
+Copyright \(co 1999\-2020 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq(sg3_utils)
diff --git a/doc/sg_reassign.8 b/doc/sg_reassign.8
new file mode 100644
index 00000000..bbbea8cd
--- /dev/null
+++ b/doc/sg_reassign.8
@@ -0,0 +1,151 @@
+.TH SG_REASSIGN "8" "October 2019" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_reassign \- send SCSI REASSIGN BLOCKS command
+.SH SYNOPSIS
+.B sg_reassign
+[\fI\-\-address=A,A...\fR] [\fI\-\-dummy\fR] [\fI\-\-eight=0|1\fR]
+[\fI\-\-grown\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-longlist=0|1\fR]
+[\fI\-\-primary\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI REASSIGN BLOCKS command to \fIDEVICE\fR. Alternatively
+this utility can find the number of element in a "grown" or "primary"
+defect list with a SCSI READ DEFECT DATA (10) command. These SCSI commands
+are defined in SBC\-2 for direct access devices (e.g. a disk). Reassign
+blocks is designed to change the physical location of a logical block
+that is known or suspected to be defective to another area on the
+media. Disks are typically formatted with blocks held in reserve
+for this situation.
+.PP
+If neither the \fI\-\-grown\fR nor \fI\-\-primary\fR option is supplied
+then one or more addresses need to be given. If the address (or all of
+the addresses) fit into 4 bytes and '\-\-eight=1' is not given then the
+parameter block passed to \fIDEVICE\fR is made up of 4 byte logical block
+addresses. If any of the addresses need more than 4 bytes to
+represent (i.e. >= 2**32) or '\-\-eight=1' is given then the parameter block
+passed to \fIDEVICE\fR is made up of 8 byte logical block addresses.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-a\fR, \fB\-\-address\fR=\fIA,A...\fR
+where \fIA,A...\fR is a string of comma separated numbers. Each number
+is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a
+trailing 'h' or 'H'). If multiple logical block addresses are given they
+must be separated by a comma or a (single) space. A string that contains
+any space separators needs to be quoted. At least one address must be given.
+.TP
+\fB\-a\fR, \fB\-\-address\fR=\-
+reads one or more logical block addresses from stdin. These may be comma,
+space, tab or linefeed (newline) separated. If a line contains "#" then
+the remaining characters on that line are ignored. Otherwise each non
+separator sequence of characters should resolve to a decimal number
+unless prefixed by '0x' or '0X' (or has a trailing 'h'). At least one
+address must be given. Lines should not be longer than 1023 bytes.
+.TP
+\fB\-d\fR, \fB\-\-dummy\fR
+prepare for but do not execute the SCSI REASSIGN BLOCKS command. Since
+the REASSIGN BLOCKS command is essentially irreversible, paranoid
+users may wish to check the invocation of this utility before reassigning
+defective blocks on a disk. Useful with '\-vv' for those who wish to
+view the parameter block that will accompany the command.
+.TP
+\fB\-e\fR, \fB\-\-eight\fR=0 | 1
+when value is 1 then it sets the 'LONGLBA' flag in the command indicating
+that the addresses in the associated parameter block are 8 byte quantities.
+When value is 0 then it clears the 'LONGLBA' flag in the command indicating
+that the addresses in the associated parameter block are 4 byte quantities.
+If this option is not given then 4 byte quantities are assumed unless one
+of the address is too large.
+.TP
+\fB\-g\fR, \fB\-\-grown\fR
+use the SCSI READ DEFECT DATA (10) command to determine the number of
+elements in the "grown defect list". When this option is given there
+is no reassignment of blocks (i.e. this utility is passive). When this
+option is given then the \fI\-\-address=\fR option is not permitted. See
+the discussion below concerning the relationship between reassigned blocks
+and the grown defect list. This list is sometimes referred to as the GLIST.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+print response in hex (for \fB\-g\fR, \fB\-\-grown\fR, \fB\-p\fR
+or \fB\-\-primary\fR).
+.TP
+\fB\-l\fR, \fB\-\-longlist\fR=0 | 1
+sets the REASSIGN BLOCKS cdb field of the same name to the given value.
+Only 1000 addresses are permitted so there should be no need to specify
+a value of 1. The short list variant restricts the parameter block
+length to 2 ** 16 bytes (i.e. about 16000 4 byte addresses or 8000
+8 byte addresses). Added for completeness.
+.TP
+\fB\-p\fR, \fB\-\-primary\fR
+use the SCSI READ DEFECT DATA (10) command to determine the number of
+elements in the "primary defect list" which is established during the
+manufacturing process. When this option is given there is no reassignment
+of blocks (i.e. this utility is passive). When this option is given then
+the \fI\-\-address=\fR option is not permitted. This list is sometimes
+referred to as the PLIST.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+Note that if the ARRE field (for reads) and/or the AWRE field (for writes)
+are set in the "Read Write Error Recovery" mode page then recoverable read
+and/or write errors cause automatic reassignment of the defective block. The
+PER bit in the same mode page controls whether a RECOVERED ERROR sense key
+is reported on not (PER=1 implies do report). Irrespective of the ARRE, AWRE
+or PER field settings, the error counter log pages reflect any
+errors (recovered or otherwise). Whenever a block is reassigned, a new entry
+is added in the "grown" defect list. Apart from doing selftests (see
+sg_senddiag or smartmontools) regularly, monitoring the grown defect list of
+a disk is a reasonable metric of its health. If the grown list starts growing
+quickly that is an ominous sign. The best grown defect lists are empty
+ones. The number of elements in the grown defect list can be viewed with
+the \fI\-\-grown\fR option. The contents of the grown defect list can be
+viewed with the 'sginfo \-G' utility.
+.PP
+If an unrecoverable error is detected at a logical block address then
+REASSIGN BLOCKS is needed to reassign the block. Also if the ARRE and/or
+AWRE fields are clear and a recoverable error is detected then the
+logical block in question may be reassigned with this utility (otherwise
+the error counter log pages will continually be incremented for each
+recovered access).
+.PP
+The number of blocks held in reserve for the purposes of REASSIGN
+BLOCKS is vendor specific and may well be limited to the zone within
+the media where the original (defective) block lay. When this number
+is exhausted subsequent invocations of this utility may result in
+a sense key of hardware error and an additional sense of 'No defect
+spare location available'. The next step would be to reformat the
+disk (or get a replacement).
+.PP
+The SBC\-2 draft standard (revision 16) notes that when multiple addresses
+are given to the SCSI REASSIGN BLOCKS command and there is some failure
+at one of the later addresses then all addresses prior to that have already
+be reassigned. Care should be taken in such a case. Re\-executing the command
+with the same addresses will cause the earlier addresses to be reassigned
+again. At some stage the disk will run out of reserved locations.
+So unless a large number of addresses are involved it may be safer to
+reassign them one address at a time.
+.SH EXIT STATUS
+The exit status of sg_reassign is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2005\-2019 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_format,sginfo,sg_senddiag(all in sg3_utils), sdparm(sdparm),
+.B smartmontools(internet, sourceforge)
diff --git a/doc/sg_referrals.8 b/doc/sg_referrals.8
new file mode 100644
index 00000000..60111063
--- /dev/null
+++ b/doc/sg_referrals.8
@@ -0,0 +1,71 @@
+.TH SG_REFERRALS "8" "May 2014" "sg3_utils\-1.39" SG3_UTILS
+.SH NAME
+sg_referrals \- send SCSI REPORT REFERRALS command
+.SH SYNOPSIS
+.B sg_referrals
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-one-segment\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send the SCSI REPORT REFERRALS command to the \fIDEVICE\fR and outputs the
+response. This command was introduced in (draft) SBC\-3 revision 24 and
+devices that support referrals should support this command.
+.PP
+The default action is to decode the response for all user data segment
+referral descriptors. The amount of output can be reduced by the
+\fI\-\-lba\fR and \fI\-\-one-segment\fR options.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response to this command in ASCII hex.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the Logical Block Address (LBA) in the first user
+data segment the \fIDEVICE\fR should report the referrals parameter
+data for.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given then 256 is used. 256 is
+enough space for the response header and user data segment descriptors.
+.TP
+\fB\-s\fR, \fB\-\-one-segment\fR
+report the user data segment of the segment specified by the \fILBA\fR
+parameter only.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output). Additional output
+caused by this option is sent to stderr.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+For a discussion of referrals see section 4.25 of sbc3r25.pdf
+at https://www.t10.org (or the corresponding section of a later draft).
+.SH EXIT STATUS
+The exit status of sg_referrals is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert and Hannes Reinecke.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2009\-2014 Douglas Gilbert and Hannes Reinecke
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd(8)
diff --git a/doc/sg_rem_rest_elem.8 b/doc/sg_rem_rest_elem.8
new file mode 100644
index 00000000..13ef9c1a
--- /dev/null
+++ b/doc/sg_rem_rest_elem.8
@@ -0,0 +1,95 @@
+.TH SG_REM_REST_ELEM "8" "June 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_rem_rest_elem \- send SCSI remove or restore element command
+.SH SYNOPSIS
+.B sg_rem_rest_elem
+[\fI\-\-capacity=RC\fR] [\fI\-\-element=EID\fR] [\fI\-\-help\fR]
+[\fI\-\-quick\fR] [\fI\-\-remove\fR] [\fI\-\-restore\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI REMOVE ELEMENT AND TRUNCATE [RMEAT] or RESTORE ELEMENTS AND
+REBUILD [RSEAR] command to the \fIDEVICE\fR. Since both these commands have
+a potentially huge impact on the \fIDEVICE\fR (similar to the FORMAT UNIT
+command: destroying data and taking a long time to complete fully),
+they first give the user the chance to reconsider (3 times within 15
+seconds) before taking action.
+.PP
+Unlike the FORMAT UNIT command, these commands seem designed to work in
+the background. So they will return quickly (although sbc5r01.pdf does not
+state that) and the disk will be placed in a reduced functionality state
+where only a specified number of commands will be executed (e.g. INQUIRY and
+REPORT LUNS) until the operation is complete. Other commands will receive
+sense data with a sense key of NOT READY and an additional sense code
+of 'Depopulation in progress' (for RMEAT) or 'Depopulation restoration in
+progress' (for RSEAR).
+.PP
+The REMOVE ELEMENT AND TRUNCATE has a close relative in ZBC\-2 called the
+REMOVE ELEMENT AND MODIFY ZONES [RMEMZ] command. See the sg_zone utility
+for an implementation of the latter command.
+.br
+The difference between RMEAT and RMEMZ is that the former "changes the
+association between LBAs and physical blocks" and the latter does not
+change that association. Zones affected by the RMEMZ command are placed
+into the zone condition: "Offline".
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-capacity\fR=\fIRC\fR
+RC stands for Requested Capacity and is the number of logical blocks the
+\fIDEVICE\fR should have after the element is removed with the RMEAT
+command. The default value is 0 which allows the \fIDEVICE\fR to decide
+what the reduced capacity will be after the element removal. The RSEAR
+command ignores this value.
+.TP
+\fB\-e\fR, \fB\-\-element\fR=\fIEID\fR
+where \fIEID\fR is an element identifier which is a 32 bit unsigned integer
+starting at one. This field is used by the RMEAT command and ignored
+otherwise. The default value is zero (which is invalid). So the user needs
+to supply a valid element identifier when \fI\-\-remove\fR is used.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-q\fR, \fB\-\-quick\fR
+the default action (i.e. when this option is not given) is to give the user
+15 seconds to reconsider doing a remove or restore element operation on the
+\fIDEVICE\fR. When this option is given that step (i.e. the 15 second
+warning period) is bypassed.
+.TP
+\fB\-r\fR, \fB\-\-remove\fR
+causes the REMOVE ELEMENT AND TRUNCATE command to be sent to the
+\fIDEVICE\fR. In practice, \fI\-\-element=EID\fR needs to be also given.
+.TP
+\fB\-R\fR, \fB\-\-restore\fR
+causes the RESTORE ELEMENTS AND REBUILD command to be sent to the
+\fIDEVICE\fR.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+Once an element is removed successfully it is termed as "depopulated".
+Depopulated elements that have the 'Restoration Allowed' (RALWD) bit
+set (see sg_get_elem_status) are candidates for future restoration.
+.PP
+A (storage) element of a rotating hard disk is one side of a platter
+typically associated with one head. Such hard disks typically have multiple
+platters with two heads per platter (i.e. one head each side of the platter).
+.SH EXIT STATUS
+The exit status of sg_rem_rest_elem is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_get_elem_status,sg_zone(sg3_utils)
diff --git a/doc/sg_rep_density.8 b/doc/sg_rep_density.8
new file mode 100644
index 00000000..f0633c4b
--- /dev/null
+++ b/doc/sg_rep_density.8
@@ -0,0 +1,97 @@
+.TH SG_REP_DENSITY "8" "January 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_rep_density \- send SCSI REPORT DENSITY SUPPORT command
+.SH SYNOPSIS
+.B sg_rep_density
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-media\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-typem\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI REPORT DENSITY command to \fIDEVICE\fR and outputs the data
+returned. This command is tape drive specific. This command is found in
+the SSC\-5 draft standard, revision 6 (ssc5r06.pdf). This command was
+present in the SSC\-2 standard (ANSI INCITS 380\-2003).
+.PP
+By default this utility requests the density code descriptors supported by
+the \fIDEVICE\fR (e.g. a tape drive) and decodes the response. If the
+\fI\-\-typem\fR option is given it fetches the medium type descriptors
+supported by the \fIDEVICE\fR and decodes the response. When the
+\fI\-\-media\fR option is given the density code or medium type descriptors
+supported by the media inside the \fIDEVICE\fR (e.g. a tape cartridge) are
+fetched.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal to stdout. When used once the whole
+response is output in ASCII hexadecimal, prefixed by an address (starting at
+0) on each line. When used twice the whole response is output in hexadecimal
+with no leading address (on each line).
+.br
+Using this option three times will produce output that can be redirected to
+a file and later given to another invocation using the \fI\-\-inhex=FN\fR
+option.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a file name whose contents are assumed to be ASCII
+hexadecimal. If \fIDEVICE\fR is also given then \fIDEVICE\fR is ignored,
+a warning is issued and the utility continues, decoding the file named
+\fIFN\fR. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information. If the \fI\-\-raw\fR option is
+also given then the contents of \fIFN\fR are treated as binary.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 8192 is used. The maximum allowed value of \fILEN\fR is 65535.
+.TP
+\fB\-M\fR, \fB\-\-media\fR
+sets the MEDIA bit in the cdb which causes the density codes (or medium
+types) supported by the tape cartridge in the drive to be placed in the
+response. The default is to request the density codes (or medium types)
+supported by the tape drive itself.
+.br
+If there is no "medium" (e.g. tape cartridge) present in the drive the SCSI
+command will fail with a "not ready" sense key.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the SCSI response (i.e. the data\-out buffer) in binary (to stdout)
+unless the \fI\-\-inhex=FN\fR option is given.
+.br
+When used together with the \fI\-\-inhex=FN\fR option then the contents of
+\fIFN\fR are treated as binary (rather than hexadecimal).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-t\fR, \fB\-\-typem\fR
+sets the MEDIUM TYPE bit in the cdb which causes the medium types supported
+by the tape drive (or tape cartridge) to be placed in the response. The
+default is to request the density codes.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH EXIT STATUS
+The exit status of sg_rep_density is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils(sg3_utils)
diff --git a/doc/sg_rep_pip.8 b/doc/sg_rep_pip.8
new file mode 100644
index 00000000..c614fd3f
--- /dev/null
+++ b/doc/sg_rep_pip.8
@@ -0,0 +1,58 @@
+.TH SG_REP_PIP "8" "January 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_rep_pip \- send SCSI REPORT PROVISIONING INITIALIZATION PATTERN command
+.SH SYNOPSIS
+.B sg_rep_pip
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI REPORT PROVISIONING INITIALIZATION PATTERN command to
+\fIDEVICE\fR and outputs the data returned. This command is found in the
+SBC\-4 draft standard, revision 21 (sbc4r21.pdf).
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal to stdout. When used once the whole
+response is output in ASCII hexadecimal, prefixed by an address (starting at
+0) on each line. When used twice the whole response is output in hexadecimal
+with no leading address (on each line). The default action is the same as
+giving the \fI\-\-hex\fR option once.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the SCSI response (i.e. the data\-out buffer) in binary (to stdout).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH EXIT STATUS
+The exit status of sg_rep_pip is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2020\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg3_utils(sg3_utils)
diff --git a/doc/sg_rep_zones.8 b/doc/sg_rep_zones.8
new file mode 100644
index 00000000..49ee070a
--- /dev/null
+++ b/doc/sg_rep_zones.8
@@ -0,0 +1,214 @@
+.TH SG_REP_ZONES "8" "AUGUST 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_rep_zones \- send SCSI REPORT ZONES, REALMS or ZONE DOMAINS command
+.SH SYNOPSIS
+.B sg_rep_zones
+[\fI\-\-brief\fR] [\fI\-\-domain\fR] [\fI\-\-find=ZT\fR] [\fI\-\-force\fR]
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-json[=JO\fR]]
+[\fI\-\-locator=LBA\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-partial\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-realm\fR]
+[\fI\-\-report=OPT\fR] [\fI\-\-start=LBA\fR] [\fI\-\-statistics\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-wp\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI REPORT ZONES, REPORT REALMS or REPORT ZONE DOMAINS command to
+\fIDEVICE\fR and decodes (or simply outputs) the data returned. These
+commands are found in the ZBC\-2 draft standard, revision 12 (zbc2r12.pdf).
+Only the REPORT ZONES command is defined in the original ZBC
+standard (INCITS 536\-2017) and it is the default for this utility.
+.PP
+The REPORT ZONE DOMAINS command will be sent (and decoded) when the
+\fI\-\-domain\fR option is given. The REPORT REALMS command will be
+sent (and decoded) when the \fI\-\-realm\fR option is given.
+.PP
+Rather than send a SCSI command to \fIDEVICE\fR, if the \fI\-\-inhex=FN\fR
+option is given, then the contents of the file named \fIFN\fR are decoded
+as ASCII hex (or binary if \fI\-\-raw\fR is also given) and then processed
+as if it was the response of the command. By default the REPORT ZONES
+command response is assumed; if the \fI\-\-domain\fR or \fI\-\-realm\fR
+option is given then the corresponding command response is assumed.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+even though a ZBC disk will typically limit the size of the response to the
+REPORT ZONES command (e.g. due to the "allocation length" field), this may
+still be potentially a lot of output. This option will only decode and
+output fields found in the response header plus fields from the last
+descriptor in the current response.
+.TP
+\fB\-d\fR, \fB\-\-domain\fR
+send or decode the SCSI REPORT ZONE DOMAINS command.
+.TP
+\fB\-F\fR, \fB\-\-find\fR=\fIZT\fR
+where \fIZT\fR is a zone type number or an abbreviation for a zone
+type. If \fIZT\fR is prefixed by either '\-' or '!' then the check for
+equality is inverted to be a check for inequality. IOWs it does a: find
+the first occurrence that is
+.B not
+the given zone type.
+.br
+The algorithm used by this option takes into account the \fI\-\-hex\fR,
+\fI\-\-maxlen=LEN\fR, \fI\-\-num=NUM\fR, \fI\-\-report=OPT\fR and
+\fI\-\-start=LBA\fR options, if given, and ignores other options. It is only
+implemented for the Report zones command currently. The algorithm may call
+the Report zones command repeatedly, with the PARTIAL bit set and the Zone
+start LBA field being increased as it goes. This continues until either
+there is a match on the \fIZT\fR condition, \fI\-\-num=NUM\fR is exhausted
+or the number of zones is exhausted.
+.br
+The \fIZT\fR numbers and abbreviations are listed when the \fI\-\-help\fR
+option is given twice. Warning: using '!' for inverting the condition may
+not be so practical as the shell (e.g. bash) may interpret '!' as having
+special meaning. Placing single quotes around \fIZT\fR fixes the problem
+for the bash shell (e.g. \-\-find='!c' meaning find the first zone whose
+type is not conventional).
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+when decoding the response to this command, certain sanity checks are
+done and if they fail a message is sent to stderr and a non\-zero
+exit status is set. If this option is given those sanity checks are
+bypassed.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. When given twice, additional usage
+information is output.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal to stdout. When used once the whole
+response is output in ASCII hexadecimal with a leading address (starting at
+0) on each line. When used twice each zone descriptor in the response is
+output separately in hexadecimal. When used thrice the whole response is
+output in hexadecimal with no leading address (on each line).
+.br
+When this option is used twice, it can be useful with either the
+\fI\-\-brief\fR or \fI\-\-find=ZT\fR option to only output the header
+and zone descriptor in hex that those two options would otherwise print
+in ASCII in the absence of the \fI\-\-hex\fR option.
+.br
+The output format when this option is given thrice is suitable for a later
+invocation with the \fI\-\-inhex=FN\fR option.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a file name whose contents are assumed to be ASCII
+hexadecimal. If \fIDEVICE\fR is also given then \fIDEVICE\fR is ignored,
+a warning is issued and the utility continues, decoding the file named
+\fIFN\fR. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information. If the \fI\-\-raw\fR option is
+also given then the contents of \fIFN\fR are treated as binary.
+.br
+Note that by default this utility assumes then contents are the response
+from a REPORT ZONES command. Use the \fI\-\-domain\fR or \fI\-\-realm\fR
+option for decoding the other two commands.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-l\fR, \fB\-\-locator\fR=\fILBA\fR
+where \fILBA\fR plays a similar role as it does in \fI\-\-start=LBA\fR.
+It is the field name used in the REPORT REALMS and REPORT ZONE DOMAINS
+commands.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 16384 is used. The maximum allowed value of \fILEN\fR is 2097152.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+where \fINUM\fR is the (maximum) number of zone descriptors to print out.
+The default value is zero which is taken to mean print out all zone
+descriptors returned by the REPORT ZONES command.
+.TP
+\fB\-p\fR, \fB\-\-partial\fR
+set the PARTIAL bit in the cdb. Without the PARTIAL bit set a ZBC disk
+will attempt to form a response with all zones from \fILBA\fR to the end
+of the disk. If there are a large number of zones (e.g. > 10,000) this
+large response will be truncated so that it doesn't exceed the "allocation
+length" field in the cdb (see \fI\-\-maxlen=LEN\fR). The advantage of doing
+this is that the number of (remaining) zones on the disk can be calculated.
+The disadvantage is the amount of time that may take.
+.br
+With the PARTIAL bit set in the cdb, only the number of zones implied by
+the "allocation length" field are fetched. This may be considerably faster
+than the same command without the PARTIAL bit set.
+.br
+When iterating through the zones on a ZBC disk, the process will be faster
+when the PARTIAL bit is set. Typically \fI\-\-start=LBA\fR is set to zero
+or the [LBA + zone_length] of the last zone reported in the previous
+iteration.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout) unless the \fI\-\-inhex=FN\fR option
+is also given. In that case the input file name (\fIFN\fR) is decoded as
+binary (and the output is _not_ in binary (but may be hex)).
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-e\fR, \fB\-\-realm\fR
+send or decode the SCSI REPORT REALMS command.
+.TP
+\fB\-o\fR, \fB\-\-report\fR=\fIOPT\fR
+where \fIOPT\fR will become the contents of the REPORTING OPTION field
+in the cdb. The reporting options differ between REPORT ZONES, REPORT ZONE
+DOMAINS and REPORT REALMS. If the \fI\-\-help\fR option is given twice (
+or the equivalent '\-hh') a list of available reporting options (as of
+writing) for each command is output.
+.br
+The default value for REPORT ZONES is 0 which means report a list of all
+zones. Some other values are 1 for list zones with a zone condition of empty;
+2 for list zones with a zone condition of implicitly opened; 3 for list zones
+with a zone condition of explicitly opened; 4 for list zones with a zone
+condition of closed; 5 for list zones with a zone condition of full; 6 for
+list zones with a zone condition of read only; 7 for list zones with a zone
+condition of offline. Other values are 0x10 for list zones with 'RWP
+recommended' set to true; 0x11 for list zones with non\-sequential write
+resource active set to true, 0x3e for list zones apart from GAP zones, and
+0x3f for list zones with a zone condition of 'not write pointer'.
+.TP
+\fB\-s\fR, \fB\-\-start\fR=\fILBA\fR
+where \fILBA\fR is at the start or within the first zone to be reported. The
+default value is 0. If \fILBA\fR is not a zone start LBA then the preceding
+zone start LBA is used for reporting. Assumed to be in decimal unless
+prefixed with '0x' or has a trailing 'h' which indicate hexadecimal.
+.br
+The zone start LBA field used in the REPORT ZONES command was changed to
+the zone domain/realm locator field for the two newer ZBC\-2 commands. For
+this utility \fI\-\-locator=LBA\fR and \fI\-\-start=LBA\fR are
+interchangeable.
+.TP
+\fB\-S\fR, \fB\-\-statistics\fR
+reviews all or a limited number of report zones, collects statistics and
+prints them (on stdout). The number of zones reviewed may be limited by
+any combination of \fI\-\-num=NUM\fR, \fI\-\-report=OPT\fR and
+\fI\-\-start=LBA\fR options. The long option name may be abbreviated to
+\fI\-\-stats\fR.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-w\fR, \fB\-\-wp\fR
+print the write pointer (in hex) only. In the absence of errors, then a hex
+LBA will be printed on each line, one line for each zone. Can be usefully
+combined with the \fI\-\-num=NUM\fR and \fI\-\-start=LBA\fR options.
+.SH EXIT STATUS
+The exit status of sg_rep_zones is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_reset_wp,sg_zone,sg3_utils_json(sg3_utils),
+.B zbd(libzbd), blkzone(util-linux)
diff --git a/doc/sg_requests.8 b/doc/sg_requests.8
new file mode 100644
index 00000000..e1c1605d
--- /dev/null
+++ b/doc/sg_requests.8
@@ -0,0 +1,138 @@
+.TH SG_REQUESTS "8" "October 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_requests \- send one or more SCSI REQUEST SENSE commands
+.SH SYNOPSIS
+.B sg_requests
+[\fI\-\-desc\fR] [\fI\-\-error\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-maxlen=LEN\fR] [\fI\-\-num=NUM\fR] [\fI\-\-number=NUM\fR]
+[\fI\-\-progress\fR] [\fI\-\-raw\fR] [\fI\-\-status\fR] [\fI\-\-time\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send SCSI REQUEST SENSE command to \fIDEVICE\fR and output the parameter
+data response which is expected to be in sense data format. Both fixed
+and descriptor sense data formats are supported.
+.PP
+Multiple REQUEST SENSE commands can be sent with the \fI\-\-num=NUM\fR
+option. This can be used for timing purposes or monitoring the progress
+indication.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-desc\fR
+sets the DESC bit in the REQUEST SENSE SCSI cdb. The \fIDEVICE\fR
+should return sense data in descriptor (rather than fixed) format. This
+will only occur if the \fIDEVICE\fR recognizes descriptor format (SPC\-3
+and later). If the device is pre SPC\-3 then setting a bit in a reserved
+field may cause a check condition status with an illegal request sense key,
+but will most likely be ignored.
+.TP
+\fB\-e\fR, \fB\-\-error\fR
+when used once it changes the REQUEST SENSE opcode from 0x3 to 0xff which
+should be rejected by the \fIDEVICE\fR. There is a small chance that the
+device vendor has implemented a vendor specific command at that opcode (0xff).
+When used twice the pass\-through call to send the SCSI command is bypassed.
+The idea here is to measure the user space overhead of this package's
+library to set up and process the response of a SCSI command. This option
+will be typically used with the \fI\-\-num=NUM\fR and \fI\-\-time\fR
+options where \fINUM\fR is a large number (e.g. 1000000).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response in ASCII hexadecimal.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in the
+cdb's "allocation length" field. If not given (or \fILEN\fR is zero) then
+252 is used. The maximum value of \fILEN\fR is 255 (but SPC\-4 recommends 252).
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+perform \fINUM\fR SCSI REQUEST SENSE commands, stopping when either \fINUM\fR
+is reached or an error occurs. The default value for \fINUM\fR is 1 .
+.TP
+\fB\-\-number\fR=\fINUM\fR
+same action as \fI\-\-num=NUM\fR. Added for compatibility with sg_turs.
+.TP
+\fB\-p\fR, \fB\-\-progress\fR
+show progress indication (a percentage) if available. If \fI\-\-num=NUM\fR
+is given, \fINUM\fR is greater than 1 and an initial progress indication
+was detected then this utility waits 30 seconds before subsequent checks.
+Exits when \fINUM\fR is reached or there are no more progress indications.
+Ignores \fI\-\-hex\fR, \fI\-\-raw\fR and \fI\-\-time\fR options. See
+NOTES section below.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout).
+.TP
+\fB\-s\fR, \fB\-\-status\fR
+if the REQUEST SENSE command finished without error (as indicated by its
+SCSI status) then the contents of the parameter data are analysed as
+sense data and the exit status is set accordingly. The default
+action (i.e. when this option is not given) is to ignore the contents
+of the parameter data for the purposes of setting the exit status.
+Some types of error set a sense key of "NO SENSE" with non\-zero
+information in the additional sense code (e.g. the FAILURE PREDICTION
+THRESHOLD EXCEEDED group of codes); this results in an exit status
+value of 10. If the sense key is "NO SENSE" and both asc and ascq are
+zero then the exit status is set to 0 . See the sg3_utils(8) man page
+for exit status values.
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+time the SCSI REQUEST SENSE command(s) and calculate the average number
+of operations per second.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+Additionally the response (if received) is output in ASCII\-HEX. Use
+this option multiple times for greater verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+In SCSI 1 and 2 the REQUEST SENSE command was very important for error
+and warning processing in SCSI. The autosense capability rendered this
+command almost superfluous.
+.PP
+However recent SCSI drafts (e.g. SPC\-4 rev 14 and SBC\-3 rev 14) increase
+the utility of the REQUEST SENSE command. Idle and standby (low) power
+conditions can be detected with this command.
+.PP
+The REQUEST SENSE command is not marked as mandatory in SPC\-3 (i.e. for
+all SCSI devices) but is marked as mandatory in SBC\-2 (i.e. for disks),
+SSC\-3 (i.e. for tapes) and MMC\-4 (i.e. for CD/DVD/HD\-DVD/BD drives).
+.PP
+The progress indication is optionally part of the sense data. When a prior
+command that takes a long time to complete (and typically precludes other
+media access commands) is still underway, the progress indication can be used
+to determine how long before the device returns to its normal state.
+.PP
+The SCSI FORMAT command for disks used with the IMMED bit set is an example
+of an operation that takes a significant amount of time and precludes other
+media access during that time. The IMMED bit set instructs the FORMAT command
+to return control to the application client once the format has commenced (see
+SBC\-3). Several long duration SCSI commands associated with tape drives also
+use the progress indication (see SSC\-3).
+.PP
+Early standards suggested that the SCSI TEST UNIT READY command be used for
+polling the progress indication (see the sg_turs utility). Since SPC\-3 the
+standards suggest that the SCSI REQUEST SENSE command should be used instead.
+.PP
+The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the
+O_RDONLY flag).
+.SH EXIT STATUS
+The exit status of sg_requests is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2021 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_turs (sg3_utils)
diff --git a/doc/sg_reset.8 b/doc/sg_reset.8
new file mode 100644
index 00000000..7406fa68
--- /dev/null
+++ b/doc/sg_reset.8
@@ -0,0 +1,135 @@
+.TH SG_RESET "8" "March 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_reset \- sends SCSI device, target, bus or host reset; or checks reset
+state
+.SH SYNOPSIS
+.B sg_reset
+[\fI\-\-bus\fR] [\fI\-\-device\fR] [\fI\-\-help\fR] [\fI\-\-host\fR]
+[\fI\-\-no-esc\fR] [\fI\-\-target\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+The sg_reset utility with no options (just a \fIDEVICE\fR) reports on the
+reset state (e.g. if a reset is underway) of the \fIDEVICE\fR. When given
+a \fI\-\-device\fR, \fI\-\-target\fR, \fI\-\-bus\fR or \fI\-\-host\fR
+option it requests a device, target, bus or host reset respectively.
+.PP
+A device reset is applied to the Logical Unit (LU) corresponding to
+\fIDEVICE\fR. It is most likely implemented by a Low level Driver (LLD)
+in Linux as a LOGICAL UNIT RESET task management function.
+.PP
+The ability to reset a SCSI target was added in Linux kernel 2.6.27 . A LLD
+may send Low level Drivers (LLDs) the I_T NEXUS RESET task management
+function. Alternatively it may use a transport mechanism to do the same
+thing (e.g. a hard reset on the link containing a SAS target).
+.PP
+In the Linux kernel 2.6 and 3 series this utility can be called on sd,
+sr (cd/dvd), st or sg device nodes; if the user has appropriate permissions.
+.PP
+Users of this utility can check whether a reset recovery is already underway
+before trying to send a new reset with this utility. Calling this utility
+with no options, just the \fIDEVICE\fR, will do such a check.
+.SH OPTIONS
+.TP
+\fB\-b\fR, \fB\-\-bus\fR
+attempt a SCSI bus reset. A bus reset is a SCSI Parallel Interface (SPI)
+concept not found in modern transports. A recent LLD may implement it as
+a series of resets on targets that might be considered as siblings to the
+target on the \fIDEVICE\fR path.
+.TP
+\fB\-d\fR, \fB\-\-device\fR
+attempt a SCSI device reset. This would typically involve sending a LOGICAL
+UNIT RESET task management function to \fIDEVICE\fR.
+.TP
+\fB\-z\fR, \fB\-\-help\fR
+print the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-host\fR
+attempt a host reset. The "host" in this context is often called
+a Host Bus Adapter (HBA) and contains one or more SCSI initiators.
+.TP
+\fB\-N\fR, \fB\-\-no\-esc\fR
+without this option, if a device reset (\fI\-\-device\fR) fails then it
+will escalate to a target reset. And if a target reset (\fI\-\-target\fR)
+fails then it will escalate to a bus reset. And if a bus
+reset (\fI\-\-bus\fR) fails then it will escalate to a host reset. With this
+option only the requested reset is attempted. An alternate option name of
+\fI\-\-no-escalate\fR is also accepted.
+.TP
+\fB\-\-no\-escalate\fR
+The same as \fB\-N\fR, \fB\-\-no\-esc\fR.
+.TP
+\fB\-t\fR, \fB\-\-target\fR
+attempt a SCSI target reset. A SCSI target contains one or more LUs. This
+would typically involve sending a I_T NEXUS RESET task management function
+to \fIDEVICE\fR There may be a transport action that is equivalent (e.g.
+in SAS a hard reset on the link that contains the target).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+prints the version string then exits.
+.SH NOTES
+The error recovery code within the Linux kernel (SCSI mid\-level) when faced
+with a SCSI command timing out and no response from the device (LU) does the
+following. First it tries a device reset and if that is not successful tries
+a target reset. If that is not successful it tries a bus reset. If that is
+not successful it tries a host reset. The "device,target,bus,host" order is
+the reset escalation that the \fI\-\-no-esc\fR option attempts to stop. In
+large storage configurations the escalation may be (very) undesirable.
+.PP
+This utility calls the SG_SCSI_RESET ioctl and as of lk 3.10.7 the
+\fI\-\-no-esc\fR option is not supported. Patches to implement this
+functionality may be accepted in lk 3.18 or 3.19 .
+.PP
+SAM\-4 and 5 define a hard reset, a LOGICAL UNIT RESET and a I_T NEXUS
+RESET. A hard reset is defined to be a power on condition, a microcode
+change or a transport reset event. LOGICAL UNIT RESET and I_T NEXUS
+RESET can be requested via task management functions (and support for
+LOGICAL UNIT RESET is mandatory). In Linux the SCSI subsystem leaves it up
+to the LLDs as to exactly what type (if any) of reset is performed.
+The "bus reset" is SCSI Parallel Interface (SPI) concept that may not map
+well to recent SCSI transports so it may be a dummy operation. A "host reset"
+attempts to re\-initialize the HBA that the request passes through en route
+to the \fIDEVICE\fR. Note that a "host reset" and a "bus reset" may cause
+collateral damage.
+.PP
+This utility does not allow individual SCSI commands to be aborted. SAM\-4
+defines ABORT TASK and ABORT TASK SET task management functions for that.
+.PP
+Prior to SAM\-3 there was a TARGET RESET task management function. And in
+SAM\-4 I_T NEXUS RESET appeared which seems closely related: the "I_T"
+stands for Initiator\-Target.
+.PP
+Transports may have their own types of resets not supported by this utility.
+For example SAS has a link reset in which both ends of a physical link (e.g.
+between a SAS expander and a SAS tape drive) renegotiate their connection.
+.PP
+Prior to version 0.57 of this utility the command line had short options
+only (e.g. \fI\-d\fR but not \fI\-\-device\fR). Also \fI\-h\fR invoked a host
+reset while in the current version \fI\-h\fR is equivalent to \fI\-\-help\fR
+and both \fI\-H\fR and \fI\-\-host\fR invoke a host reset. For backward
+compatibility define the environment variable SG3_UTILS_OLD_OPTS or
+SG_RESET_OLD_OPTS . In this case \fI\-h\fR will invoke a host reset and the
+output will be verbose as it was previously (equivalent to using the
+\fI\-\-verbose\fR option now).
+For example:
+.PP
+ SG_RESET_OLD_OPTS=1 sg_reset \-h /dev/sg1
+.br
+sg_reset: starting host reset
+.br
+sg_reset: completed host reset
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variables SG3_UTILS_OLD_OPTS
+or SG_RESET_OLD_OPTS can be given. When either is present this utility will
+expect the older command line options as outlined in the NOTES section.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH COPYRIGHT
+Copyright \(co 1999\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/sg_reset_wp.8 b/doc/sg_reset_wp.8
new file mode 100644
index 00000000..47d4453c
--- /dev/null
+++ b/doc/sg_reset_wp.8
@@ -0,0 +1,65 @@
+.TH SG_RESET_WP "8" "February 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_reset_wp \- send SCSI RESET WRITE POINTER command
+.SH SYNOPSIS
+.B sg_reset_wp
+[\fI\-\-all\fR] [\fI\-\-count=ZC\fR] [\fI\-\-help\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-zone=ID\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI RESET WRITE POINTER command to the \fIDEVICE\fR. This command
+is described in ZBC standard (INCITS 536\-2016) and the draft ZBC\-2
+documents at T10 (e.g. zbc2r12.pdf).
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+sets the ALL field in the cdb. This causes a reset write pointer operation of
+all open zones and full zones. When this option is given then the
+\fI\-\-zone=ID\fR option is ignored. Either this option or the
+\fI\-\-zone=ID\fR option is required.
+.TP
+\fB\-C\fR, \fB\-\-count\fR=\fIZC\fR
+ZC is placed in the Zone Count field in the cdb of the RESET WRITE POINTER
+command supported by this utility. ZC should be a value from 0 to
+65535 (0xffff) inclusive.
+.br
+The action that the \fIDEVICE\fR takes with this option depends on whether
+the \fI\-\-all\fR option is set. See the RESET WRITE POINTER command
+description (e.g. section 5.9, table 46 in zbc2r12.pdf).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-z\fR, \fB\-\-zone\fR=\fIID\fR
+where \fIID\fR is placed in the cdb's ZONE ID field. A zone id is a zone
+start logical block address (LBA). This causes a reset write pointer
+operation on the zone identified by the ZONE ID field. The default value is
+0. Either this option or the \fI\-\-all\fR option is required.
+\fIID\fR is assumed to be in decimal unless prefixed with '0x' or has a
+trailing 'h' which indicate hexadecimal.
+.SH NOTES
+The Zones Emptied log parameter in the Zoned Block Device Statistics log
+page counts the number of times the RESET WRITE POINTER command has
+been (successfully) invoked.
+.SH EXIT STATUS
+The exit status of sg_reset_wp is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_rep_zones,sg_zone(sg3_utils)
diff --git a/doc/sg_rmsn.8 b/doc/sg_rmsn.8
new file mode 100644
index 00000000..7fc279ed
--- /dev/null
+++ b/doc/sg_rmsn.8
@@ -0,0 +1,64 @@
+.TH SG_RMSN "8" "November 2012" "sg3_utils\-1.31" SG3_UTILS
+.SH NAME
+sg_rmsn \- send SCSI READ MEDIA SERIAL NUMBER command
+.SH SYNOPSIS
+.B sg_rmsn
+[\fI\-\-help\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI READ MEDIA SERIAL NUMBER command to \fIDEVICE\fR and outputs
+the response.
+.PP
+This command is described in SPC\-3 found at www.t10.org . It was originally
+added to SPC\-3 in revision 11 (2003/2/12). It is not an mandatory command
+and the author has not seen any SCSI devices that support it.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+sends the serial number (if found) to stdout. This output may contain
+non\-printable characters (e.g. the serial number is padded with NULLs
+at the end so its length is a multiple of 4). The default action is
+to print the serial number out in ASCII\-HEX with ASCII characters to
+the right. All error messages are sent to stderr.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+opens the DEVICE read\-only rather than read\-write which is the
+default. The Linux sg driver needs read\-write access for the SCSI
+READ MEDIA SERIAL NUMBER command but other access methods may require
+read\-only access.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+Device identification information is also found in a standard INQUIRY
+response and its VPD pages (see sg_vpd). The relevant VPD pages are
+the "device identification page" (VPD page 0x83) and the "unit serial
+number" page (VPD page 0x80).
+.PP
+The MMC\-4 command set for CD/DVD/HD-DVD/BD drives has a "media serial number"
+feature (0x109) [and a "logical unit serial number" feature]. These
+can be viewed with sg_get_config.
+.SH EXIT STATUS
+The exit status of sg_rmsn is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2005\-2012 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd(sg3_utils), sg_get_config(sg3_utils)
diff --git a/doc/sg_rtpg.8 b/doc/sg_rtpg.8
new file mode 100644
index 00000000..d2ce33c3
--- /dev/null
+++ b/doc/sg_rtpg.8
@@ -0,0 +1,64 @@
+.TH SG_RTPG "8" "May 2014" "sg3_utils\-1.39" SG3_UTILS
+.SH NAME
+sg_rtpg \- send SCSI REPORT TARGET PORT GROUPS command
+.SH SYNOPSIS
+.B sg_rtpg
+[\fI\-\-decode\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI REPORT TARGET PORT GROUPS command to \fIDEVICE\fR and
+outputs the response.
+.PP
+Target port group access is described in SPC\-3 and SPC\-4 found at
+www.t10.org . The most recent draft of SPC\-4 is revision 37 in which
+target port groups are described in section 5.15 .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-decode\fR
+decodes the status code and asymmetric access state from each
+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. This sets the PARAMETER DATA
+FORMAT field in the cdb to 1.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response in hex (rather than partially or fully decode it).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary to stdout.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+The Report Target Port Groups command should be supported whenever the TPGS
+bits in a standard INQUIRY response are greater than zero. [View with
+sg_inq utility.]
+.SH EXIT STATUS
+The exit status of sg_rtpg is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2014 Christophe Varoqui and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq(sg3_utils)
diff --git a/doc/sg_safte.8 b/doc/sg_safte.8
new file mode 100644
index 00000000..c46a5b1a
--- /dev/null
+++ b/doc/sg_safte.8
@@ -0,0 +1,132 @@
+.TH SG_SAFTE "8" "April 2016" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_safte \- access SCSI Accessed Fault\-Tolerant Enclosure (SAF\-TE) device
+.SH SYNOPSIS
+.B sg_safte
+[\fI\-\-config\fR] [\fI\-\-devstatus\fR] [\fI\-\-encstatus\fR]
+[\fI\-\-flags\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-insertions\fR]
+[\fI\-\-raw\fR] [\fI\-\-usage\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Fetches enclosure status (via a SCSI READ BUFFER command).
+The \fIDEVICE\fR should be a SAF\-TE device which may be a storage
+array controller (INQUIRY peripheral device type 0xc) or a generic
+processor device (INQUIRY peripheral device type 0x3).
+.PP
+If no options are given (only the \fIDEVICE\fR argument) then the
+overall enclosure status as reported by the option
+.I
+\-\-config
+.R
+is reported.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-c\fR, \fB\-\-config\fR
+will issues a
+.I
+Read Enclosure Configuration
+.R
+(READ BUFFER ID 0) cdb to the device, which returns a list of the
+enclosure hardware resources.
+.TP
+\fB\-d\fR, \fB\-\-devstatus\fR
+will issue a
+.I
+Read Device Slot Status
+.R
+(READ BUFFER ID 4) cdb to the device, which returns information about
+the current state of each drive or slot.
+.TP
+\fB\-s\fR, \fB\-\-encstatus\fR
+will issue a
+.I
+Read Enclosure Status
+.R
+(READ BUFFER ID 1) cdb to the device, which returns the operational
+state of the components.
+.TP
+\fB\-f\fR, \fB\-\-flags\fR
+will issue a
+.I
+Read Global Flags
+.R
+(READ BUFFER ID 5) cdb to the device, which read the most recent state
+of the global flags of the RAID processor device.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response to a READ BUFFER command in ASCII hex to stdout. If used
+once, output the response to the first READ BUFFER command (i.e. with
+buffer_id=0). This should be the enclosure configuration. If used twice (or
+more often), the response to subsequent READ BUFFER commands is output.
+.TP
+\fB\-i\fR, \fB\-\-insertions\fR
+will issue a
+.I
+Read Device Insertions
+.R
+(READ BUFFER ID 3) cdb to the device, which returns information about
+the number of times devices have been inserted whilst the RAID system
+was powered on.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the response to a READ BUFFER command in binary to stdout. If used
+once, output the response to the first READ BUFFER command (i.e. with
+buffer_id=0). This should be the enclosure configuration. If used twice (or
+more often), the response to subsequent READ BUFFER commands is output.
+.TP
+\fB\-u\fR, \fB\-\-usage\fR
+will issue a
+.I
+Read Usage Statistics
+.R
+(READ BUFFER ID 2) cdb to the device, which returns the information on
+total usage time and number of power\-on cycles of the RAID device.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+This implementation is based on the intermediate review document dated
+19970414 and named "SR041497.pdf". So it is quite old. Intel and nStor
+are the authors. Intel have a zip archive containing this and related
+documents in the "SAF\-TE: SCSI Accessed Fault Tolerant Enclosures
+Interface Specification" section of this page:
+.PP
+https://www.intel.com/content/www/us/en/servers/ipmi/ipmi\-technical\-resources.html
+.PP
+Similar functionality is provided by SPC\-4 SCSI Enclosure Services (SES)
+devices (Peripheral device type 0xd), which can be queried with the
+sg_ses utility.
+.SH EXAMPLES
+To view the configuration:
+.PP
+ sg_safte /dev/sg1
+.PP
+To view the device slot status:
+.PP
+ sg_safte \-\-devstatus /dev/sg1
+.PP
+.SH EXIT STATUS
+The exit status of sg_safte is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Hannes Reinecke and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2016 Hannes Reinecke and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_ses (in sg3_utils package); safte\-monitor (internet)
diff --git a/doc/sg_sanitize.8 b/doc/sg_sanitize.8
new file mode 100644
index 00000000..bb7bdc4d
--- /dev/null
+++ b/doc/sg_sanitize.8
@@ -0,0 +1,267 @@
+.TH SG_SANITIZE "8" "December 2020" "sg3_utils\-1.46" SG3_UTILS
+.SH NAME
+sg_sanitize \- remove all user data from disk with SCSI SANITIZE command
+.SH SYNOPSIS
+.B sg_sanitize
+[\fI\-\-ause\fR] [\fI\-\-block\fR] [\fI\-\-count=OC\fR] [\fI\-\-crypto\fR]
+[\fI\-\-dry\-run\fR] [\fI\-\-desc\fR] [\fI\-\-early\fR] [\fI\-\-fail\fR]
+[\fI\-\-help\fR] [\fI\-\-invert\fR] [\fI\-\-ipl=LEN\fR] [\fI\-\-overwrite\fR]
+[\fI\-\-pattern=PF\fR] [\fI\-\-quick\fR] [\fI\-\-test=TE\fR]
+[\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-wait\fR] [\fI\-\-zero\fR] [\fI\-\-znr\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility invokes the SCSI SANITIZE command. This command was first
+introduced in the SBC\-3 revision 27 draft. The purpose of the sanitize
+operation is to alter the information in the cache and on the medium of a
+logical unit (e.g. a disk) so that the recovery of user data is not
+possible. If that user data cannot be erased, or is in the process of
+being erased, then the sanitize operation prevents access to that user
+data.
+.PP
+Once a SCSI SANITIZE command has successfully started, then user data from
+that disk is no longer available. Even if the disk is power cycled, the
+sanitize operation will continue after power is re\-instated until it is
+complete.
+.PP
+This utility requires either the \fI\-\-block\fR, \fI\-\-crypto\fR,
+\fI\-\-fail\fR or \fI\-\-overwrite\fR option. With the \fI\-\-block\fR,
+\fI\-\-crypto\fR or \fI\-\-overwrite\fR option the user is given 15 seconds
+to reconsider whether they wish to erase all the data on a disk, unless
+the \fI\-\-quick\fR option is given in which case the sanitize operation
+starts immediately. The disk's INQUIRY response strings are printed out just
+in case the wrong \fIDEVICE\fR has been given.
+.PP
+If the \fI\-\-early\fR option is given then this utility will exit soon
+after starting the SANITIZE command with the IMMED bit set. The user can
+monitor the progress of the sanitize operation with
+the "sg_requests \-\-num=9999 \-\-progress" which sends a REQUEST SENSE
+command every 30 seconds. Otherwise if the \fI\-\-wait\fR option is given
+then this utility will wait until the SANITIZE command completes (or fails)
+and that can be many hours.
+.PP
+If the \fI\-\-wait\fR option is not given then the SANITIZE command is
+started with the IMMED bit set. If neither the \fI\-\-early\fR nor the
+\fI\-\-wait\fR options are given then this utility sends a REQUEST SENSE
+command after every 60 seconds until there are no more progress indications
+in which case this utility exits silently. If additionally the
+\fI\-\-verbose\fR option is given the exit will be marked by a short
+message that the sanitize seems to have succeeded.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-A\fR, \fB\-\-ause\fR
+sets the AUSE bit in the cdb. AUSE is an acronym for "allow unrestricted
+sanitize exit". The default action is to leave the AUSE bit cleared.
+.TP
+\fB\-B\fR, \fB\-\-block\fR
+perform a "block erase" sanitize operation.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fIOC\fR
+where \fIOC\fR is the "overwrite count" associated with the "overwrite"
+sanitize operation. \fIOC\fR can be a value between 1 and 31 and 1 is
+the default.
+.TP
+\fB\-C\fR, \fB\-\-crypto\fR
+perform a "cryptographic erase" sanitize operation. Note that this erase is
+often very quick as it simply overwrites an internal cryptographic key with
+a new value. Those keys are not accessible to users and encrypt all data
+written then decrypt all data read from the media. The primary reason for
+doing that is to make this operation fast. This operation can not be
+reversed.
+.TP
+\fB\-d\fR, \fB\-\-desc\fR
+sets the DESC field in the REQUEST SENSE command used for polling. By
+default this field is set to zero. A REQUEST SENSE polling loop is
+used after the SANITIZE command is issued (assuming that neither the
+\fI\-\-early\fR nor the \fI\-\-wait\fR option have been given) to check
+on the progress of this command as it can take some time.
+.TP
+\fB\-D\fR, \fB\-\-dry\-run\fR
+this option will parse the command line, do all the preparation but bypass
+the actual SANITIZE command.
+.TP
+\fB\-e\fR, \fB\-\-early\fR
+the default action of this utility is to poll the disk every 60 seconds to
+fetch the progress indication until the sanitize is finished. When this
+option is given this utility will exit "early" as soon as the SANITIZE
+command with the IMMED bit set to 1 has been acknowledged. This option and
+\fI\-\-wait\fR cannot both be given.
+.TP
+\fB\-F\fR, \fB\-\-fail\fR
+perform an "exit failure mode" sanitize operation. Typically requires the
+preceding SANITIZE command to have set the AUSE bit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage information then exit.
+.TP
+\fB\-i\fR, \fB\-\-ipl\fR=\fILEN\fR
+set the initialization pattern length to \fILEN\fR bytes. By default it is
+set to the length of the pattern file (\fIPF\fR) or 4 if the \fI\-\-zero\fR
+option is given. Only active when the \fI\-\-overwrite\fR option is also
+given. It is the number of bytes from the \fIPF\fR file that will be used
+as the initialization pattern (if the \fI\-\-zero\fR option is not given).
+The minimum size is 1 byte and the maximum is the logical block size of the
+\fIDEVICE\fR (and not to exceed 65535). If \fILEN\fR exceeds the \fIPF\fR
+file size then the initialization pattern is padded with zeros.
+.TP
+\fB\-I\fR, \fB\-\-invert\fR
+set the INVERT bit in the overwrite service action parameter list. This
+only affects the "overwrite" sanitize operation. The default is a clear
+INVERT bit. When the INVERT bit is set then the initialization pattern
+is inverted between consecutive overwrite passes.
+.TP
+\fB\-O\fR, \fB\-\-overwrite\fR
+perform an "overwrite" sanitize operation. When this option is given then
+the \fI\-\-pattern=PF\fR or the \fI\-\-zero\fR option is required.
+.TP
+\fB\-p\fR, \fB\-\-pattern\fR=\fIPF\fR
+where \fIPF\fR is the filename of a file containing the initialization
+pattern required by an "overwrite" sanitize operation. The length of
+this file will be used as the length of the initialization pattern unless
+the \fI\-\-ipl=LEN\fR option is given. The length of the initialization
+pattern must be from 1 to the logical block size of the \fIDEVICE\fR.
+.TP
+\fB\-Q\fR, \fB\-\-quick\fR
+the default action (i.e. when the option is not given) is to give the user
+15 seconds to reconsider doing a sanitize operation on the \fIDEVICE\fR.
+When this option is given that step (i.e. the 15 second warning period)
+is skipped.
+.TP
+\fB\-T\fR, \fB\-\-test\fR=\fITE\fR
+set the TEST field in the overwrite service action parameter list. This
+only affects the "overwrite" sanitize operation. The default is to place
+0 in that field.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR
+where \fISECS\fR is the number of seconds used for the timeout on the
+SANITIZE command.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-w\fR, \fB\-\-wait\fR
+the default action (i.e. without this option and the \fI\-\-early\fR option)
+is to start the SANITIZE command with the IMMED bit set then poll for the
+progress indication with the REQUEST SENSE command until the sanitize
+operation is complete (or fails). When this option is given (and the
+\fI\-\-early\fR option is not given) then the SANITIZE command is started
+with the IMMED bit clear. For a large disk this might take hours. [A
+cryptographic erase operation could potentially be very quick.]
+.TP
+\fB\-z\fR, \fB\-\-zero\fR
+with an "overwrite" sanitize operation this option causes the initialization
+pattern to be zero (4 zeros are used as the initialization pattern). Cannot
+be used with the \fI\-\-pattern=PF\fR option. If this option is given
+twice (e.g. '\-zz') then 0xff is used as the initialization byte.
+.TP
+\fB\-Z\fR, \fB\-\-znr\fR
+sets ZNR bit (zoned no reset) in cdb. Introduced in the SBC\-4 revision 7
+draft.
+.SH NOTES
+The SCSI SANITIZE command is closely related to the ATA SANITIZE command,
+both are relatively new with the ATA command being the first one defined.
+The SCSI to ATA Translation (SAT) definition for the SCSI SANITIZE command
+appeared in the SAT\-3 revision 4 draft.
+.PP
+When a SAT layer is used to a (S)ATA disk then for OVERWRITE the
+initialization pattern must be 4 bytes long. So this means either the
+\fI\-\-zero\fR option may be given, or a pattern file (with the
+\fI\-\-pattern=PF\fR option) that is 4 bytes long or set to that
+length with the \fI\-\-ipl=LEN\fR option.
+.PP
+The SCSI SANITIZE command is related to the SCSI FORMAT UNIT command. It
+is likely that a block erase sanitize operation would take a similar
+amount of time as a format on the same disk (e.g. 9 hours for a 2 Terabyte
+disk). The primary goal of a format is the configuration of the disk at
+the end of a format (e.g. different logical block size or protection
+information added). Removal of user data is only a side effect of a format.
+With the SCSI SANITIZE command, removal of user data is the primary goal.
+If a sanitize operation is interrupted (e.g. the disk is power cycled)
+then after power up any remaining user data will not be available and the
+sanitize operation will continue. When a format is interrupted (e.g. the
+disk is power cycled) the drafts say very little about the state of the
+disk. In practice some of the original user data may remain and the format
+may need to be restarted.
+.PP
+Finding out whether a disk (SCSI or ATA) supports SANITIZE can be a
+challenge. If the user really needs to find out and no other information
+is available then try 'sg_sanitize \-\-fail \-vvv <device>' and observe
+the sense data returned may be the safest approach. Using the \fI\-\-fail\fR
+variant of this utility should have no effect unless it follows an already
+failed sanitize operation. If the SCSI REPORT SUPPORTED OPERATION CODES
+command (see sg_opcodes) is supported then using it would be a better
+approach for finding if sanitize is supported.
+.PP
+If using the dd command to check the before and after data of a particular
+block (i.e. check the erase actually worked) it is a good idea to use
+the 'iflag=direct' operand. Otherwise the first read might be cached and
+returned when the same LBA is read a little later. Obviously this utility
+should only be used to sanitize data on a disk whose mounted file
+systems (if any) have been unmounted prior to the erase!
+.SH EXAMPLES
+These examples use Linux device names. For suitable device names in
+other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+As a precaution if this utility is called with no options then apart from
+printing a usage message, nothing happens:
+.PP
+ sg_sanitize /dev/sdm
+.PP
+To do a "block erase" sanitize the \fI\-\-block\fR option is required.
+The user will be given a 15 second period to reconsider, the SCSI SANITIZE
+command will be started with the IMMED bit set, then this utility will
+poll for a progress indication with a REQUEST SENSE command until the
+sanitize operation is finished:
+.PP
+ sg_sanitize \-\-block /dev/sdm
+.PP
+To start a "block erase" sanitize and return from this utility once it is
+started (but not yet completed) use the \fI\-\-early\fR option:
+.PP
+ sg_sanitize \-\-block \-\-early /dev/sdm
+.PP
+If the 15 second reconsideration time is not required add the
+\fI\-\-quick\fR option:
+.PP
+ sg_sanitize \-\-block \-\-quick \-\-early /dev/sdm
+.PP
+To do an "overwrite" sanitize a pattern file may be given:
+.PP
+ sg_sanitize \-\-overwrite \-\-pattern=rand.img /dev/sdm
+.PP
+If the length of that "rand.img" is 512 bytes (a typically logical block
+size) then to use only the first 17 bytes (repeatedly) in the "overwrite"
+sanitize operation:
+.PP
+ sg_sanitize \-\-overwrite \-\-pattern=rand.img \-\-ipl=17 /dev/sdm
+.PP
+To overwrite with zeros use:
+ sg_sanitize \-\-overwrite \-\-zero /dev/sdm
+.SH EXIT STATUS
+The exit status of sg_sanitize is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page. Unless the \fI\-\-wait\fR option is given, the
+exit status may not reflect the success of otherwise of the format.
+.PP
+The Unix convention is that "no news is good news" but that can be a bit
+unnerving after an operation like sanitize, especially if it finishes
+quickly (i.e. before the first progress poll is sent). Giving the
+\fI\-\-verbose\fR option once should supply enough additional output to
+settle those nerves.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2011\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_requests(8), sg_format(8)
diff --git a/doc/sg_sat_identify.8 b/doc/sg_sat_identify.8
new file mode 100644
index 00000000..595321c9
--- /dev/null
+++ b/doc/sg_sat_identify.8
@@ -0,0 +1,167 @@
+.TH SG_SAT_IDENTIFY "8" "January 2020" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_sat_identify \- send ATA IDENTIFY DEVICE command via SCSI to ATA
+Translation (SAT) layer
+.SH SYNOPSIS
+.B sg_sat_identify
+[\fI\-\-ck_cond\fR] [\fI\-\-extend\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-ident\fR] [\fI\-\-len=CLEN\fR] [\fI\-\-packet\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends either an ATA IDENTIFY DEVICE command or an ATA IDENTIFY
+PACKET DEVICE command to \fIDEVICE\fR and outputs the response. The devices
+that respond to these commands are ATA disks and ATAPI devices respectively.
+Rather than send these commands directly to the device they are sent via a
+SCSI transport which is assumed to contain a SCSI to ATA Translation (SAT)
+Layer (SATL). The SATL may be in an operating system driver, in host bus
+adapter firmware or in some external enclosure.
+.PP
+The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at
+www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16
+byte "cdb" and the other with a 12 byte cdb. This utility defaults to using
+the 16 byte cdb variant. SAT\-4 revision 5 added a SCSI "ATA
+PASS\-THROUGH(32)" command. SAT\-2 and SAT\-3 are now also standards: SAT\-2
+ANSI INCITS 465\-2010 and SAT\-3 ANSI INCITS 517-2015 . The SAT\-4 project
+is near standardization and the most recent draft is sat4r06.pdf .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-ck_cond\fR
+sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set the SATL should yield a
+sense buffer containing a ATA Result descriptor irrespective of whether
+the command succeeded or failed. When clear the SATL should only yield
+a sense buffer containing a ATA Result descriptor if the command failed.
+.TP
+\fB\-e\fR, \fB\-\-extend\fR
+sets the EXTEND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set a 48 bit LBA command is sent
+to the device. This option has no effect when \fI\-\-len=12\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options
+then exits. Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+outputs the ATA IDENTIFY (PACKET) DEVICE response in hex. The default
+action (i.e. without any '\-H' options) is to output the response in
+hex, grouped in 16 bit words (i.e. the ATA standard's preference).
+When given once, the response is output in ASCII hex bytes (i.e. the
+SCSI standard's preference). When given twice (i.e. '\-HH') the output
+is in hex, grouped in 16 bit words, the same as the default but without
+a header. When given thrice (i.e. '\-HHH') the output is in hex, grouped in
+16 bit words, in a format that is acceptable for 'hdparm \-\-Istdin' to
+process. '\-HHHH' simply outputs hex data bytes, space separated, 16 per
+line.
+.TP
+\fB\-i\fR, \fB\-\-ident\fR
+outputs the World Wide Name (WWN) of the device. This should be a NAA\-5
+64 bit number. It is output in hex prefixed with "0x". If not available
+then "0x0000000000000000" is output. The equivalent for a SCSI disk (i.e. its
+logical unit name) can be found with "sg_vpd \-ii".
+.TP
+\fB\-l\fR, \fB\-\-len\fR=CLEN
+CLEN this is the length of the SCSI cdb used for the ATA PASS\-THROUGH
+command. CLEN can either be 12, 16 or 32. The default is 16. The larger
+cdb sizes are needed for 48 bit LBA addressing of ATA devices. The ATA
+Auxiliary and ICC registers are only conveyed with the 32 byte cdb variant.
+.TP
+\fB\-p\fR, \fB\-\-packet\fR
+send an ATA IDENTIFY PACKET DEVICE command (via the SATL). The default
+action is to send an ATA IDENTIFY DEVICE command. Note that the ATAPI
+specification by T13 (i.e. the PACKET interface) is now obsolete.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the ATA IDENTIFY (PACKET) DEVICE response in binary. The output
+should be piped to a file or another utility when this option is used.
+The binary is sent to stdout, and errors are sent to stderr.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string
+.SH NOTES
+Since the response to the IDENTIFY (PACKET) DEVICE command is very
+important for the correct use of an ATA(PI) device (and is typically the
+first command sent), a SATL should provide an ATA Information VPD page
+which contains the similar information.
+.PP
+The SCSI ATA PASS\-THROUGH (12) command's opcode is 0xa1 and it clashes with
+the MMC set's BLANK command used by cd/dvd writers. So a SATL in front
+of an ATAPI device that uses MMC (i.e. has peripheral device type 5)
+probably should treat opcode 0xa1 as a BLANK command and send it through
+to the cd/dvd drive. The ATA PASS\-THROUGH (16) command's opcode (0x85)
+does not clash with anything so it is a better choice.
+.PP
+Prior to Linux kernel 2.6.29 USB mass storage limited sense data to 18 bytes
+which made the \fB\-\-ck_cond\fR option yield strange (truncated) results.
+.SH EXAMPLES
+These examples use Linux device names and a Linux utility called hdparm. For
+suitable device names in other supported Operating Systems see the
+sg3_utils(8) man page.
+.PP
+In this example /dev/sdb is a SATA 2.5" disk connected via a USB (type C
+connector) dongle that implements the UAS (USB attached SCSI) protocol (also
+known as UASP). UAS is a vast improvement over the USB mass storage class.
+.PP
+ # sg_sat_identify /dev/sdb
+.br
+Response for IDENTIFY DEVICE ATA command:
+.br
+ 00 0c5a 3fff c837 0010 0000 0000 003f 0000 .Z ?. .7 .. .. .. .? ..
+.br
+ ....
+.PP
+The hexadecimal ASCII (with plain ASCII to the right) output is abridged
+to a single line (i.e. the first 16 bytes (or 8 words)). Now to decode
+some of that ATA Identify response. First sg_inq can decode a few strings:
+.PP
+ # sg_sat_identify \-HHHH /dev/sdb | sg_inq \-\-ata \-I \-
+.br
+ATA device: model, serial number and firmware revision:
+.br
+ ST9500420AS 5VJCE6R7 0002SDM1
+.PP
+For a lot more details, the hdparm utility is a good choice:
+.PP
+ # sg_sat_identify \-HHH /dev/sdb | hdparm \-\-Istdin
+.br
+ATA device, with non\-removable media
+.br
+ Model Number: ST9500420AS
+.br
+ Serial Number: 5VJCE6R7
+.br
+ Firmware Revision: 0002SDM1
+.br
+ Transport: Serial
+.br
+Standards:
+.br
+ ....
+.PP
+There are about 80 more lines of details decoded by hdparm in this case.
+Notice the difference in the number of "H" options: three give an unadorned
+hex output arranged in (little endian) words (i.e. 16 bits each) while
+four "H" options give an unadorned hex output in bytes (i.e. 8 bits each).
+.SH EXIT STATUS
+The exit status of sg_sat_identify is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm), hdparm(hdparm)
diff --git a/doc/sg_sat_phy_event.8 b/doc/sg_sat_phy_event.8
new file mode 100644
index 00000000..8b842a3f
--- /dev/null
+++ b/doc/sg_sat_phy_event.8
@@ -0,0 +1,109 @@
+.TH SG_SAT_PHY_EVENT "8" "July 2020" "sg3_utils\-1.46" SG3_UTILS
+.SH NAME
+sg_sat_phy_event \- use ATA READ LOG EXT via a SAT pass\-through to fetch
+SATA phy event counters
+.SH SYNOPSIS
+.B sg_sat_phy_event
+[\fI\-\-ck_cond\fR] [\fI\-\-extend\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-ignore\fR] [\fI\-\-len=\fR{16|12}] [\fI\-\-raw\fR] [\fI\-\-reset\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends an ATA READ LOG EXT with the log page ("address") set to
+11h to \fIDEVICE\fR and outputs the response. Log page 11h is defined in
+the SATA 2.5 standard and contains phy event counters. Rather than send this
+command directly to the \fIDEVICE\fR, are sent via a SCSI transport which is
+assumed to contain a SCSI to ATA Translation (SAT) Layer (SATL). The SATL may
+be in an operating system driver, in host bus adapter firmware or in some
+external enclosure.
+.PP
+The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at
+www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16
+byte "cdb" and the other with a 12 byte cdb. This utility defaults to using
+the 16 byte cdb variant. SAT\-2 is also a standard: SAT\-2 ANSI INCITS
+465\-2010 and the draft prior to that is sat2r09.pdf . The SAT-3 project has
+started and the most recent draft is sat3r01.pdf .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-ck_cond\fR
+sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set the SATL should yield a
+sense buffer containing a ATA Result descriptor irrespective of whether
+the command succeeded or failed. When clear the SATL should only yield
+a sense buffer containing a ATA Result descriptor if the command failed.
+.TP
+\fB\-e\fR, \fB\-\-extend\fR
+sets the EXTEND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set a 48 bit LBA command is sent
+to the device. This option has no effect when \fI\-\-len=12\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options
+then exits. Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+outputs the ATA READ LOG EXT response in hex. The default
+action (i.e. without any '\-H' options) is to output the response in
+hex, grouped in 16 bit words (i.e. the ATA standard's preference).
+When given once, the response is output in ASCII hex bytes (i.e. the
+SCSI standard's preference). When given twice (i.e. '\-HH') the output
+is in hex, grouped in 16 bit words, the same as the default but without
+a header.
+.TP
+\fB\-i\fR, \fB\-\-ignore\fR
+usually the phy counter identifier names are decoded. When this option is
+given, the numeric value of the identifier is output, the vendor flag, the
+data length (in bytes) and the corresponding value.
+.TP
+\fB\-l\fR, \fB\-\-len\fR={16|12}
+this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands.
+The argument can either be 16 or 12. The default is 16. The larger cdb
+size is needed for 48 bit LBA addressing of ATA devices. On the other
+hand some SCSI transports cannot convey SCSI commands longer than 12 bytes.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the ATA READ LOG EXT response in binary. The output
+should be piped to a file or another utility when this option is used.
+The binary is sent to stdout, and errors are sent to stderr.
+.TP
+\fB\-R\fR, \fB\-\-reset\fR
+reset the counters after the current values are returned, decoded and
+displayed.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string
+.SH NOTES
+The SCSI ATA PASS\-THROUGH (12) command's opcode is 0xa1 and it clashes with
+the MMC set's BLANK command used by cd/dvd writers. So a SATL in front
+of an ATAPI device that uses MMC (i.e. has peripheral device type 5)
+probably should treat opcode 0xa1 as a BLANK command and send it through
+to the cd/dvd drive. The ATA PASS\-THROUGH (16) command's opcode (0x85)
+does not clash with anything so it is a better choice.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be
+a SCSI generic (sg) device. In the 2.6 series block devices (e.g. disks
+and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda"
+will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char"
+device names may be used as well (e.g. "/dev/st0m"). Prior to lk 2.6.29
+USB mass storage limited sense data to 18 bytes which made the
+\fB\-\-ck_cond\fR option yield strange (truncated) results.
+.SH EXIT STATUS
+The exit status of sg_sat_identify is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_sat_identify,sg_sat_read_gplog(sg3_utils),
+.B smp_rep_phy_err_log(smp_utils),sdparm(sdparm),hdparm(hdparm)
diff --git a/doc/sg_sat_read_gplog.8 b/doc/sg_sat_read_gplog.8
new file mode 100644
index 00000000..552ad147
--- /dev/null
+++ b/doc/sg_sat_read_gplog.8
@@ -0,0 +1,114 @@
+.TH SG_SAT_READ_GPLOG "8" "April 2015" "sg3_utils\-1.41" SG3_UTILS
+.SH NAME
+sg_sat_read_gplog \- use ATA READ LOG EXT command via a SCSI to ATA
+Translation (SAT) layer
+.SH SYNOPSIS
+.B sg_sat_read_gplog
+[\fI\-\-ck_cond\fR] [\fI\-\-count=CO\fR] [\fI\-\-dma\fR] [\fI\-\-help\fR]
+[\fI\-\-hex\fR] [\fI\-\-len=\fR{16|12}] [\fI\-\-log=\fRLA]
+[\fI\-\-page=\fRPN] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends an ATA READ LOG EXT or an ATA READ LOG DMA EXT command to
+the \fIDEVICE\fR. This command is used to read the general purpose log
+of (S)ATA disks (not ATAPI devices such as DVD driver). Rather than send the
+READ LOG (DMA) EXT command directly to the device it is sent via a SCSI
+transport which is assumed to contain a SCSI to ATA Translation (SAT)
+Layer (SATL). The SATL may be in an operating system driver, in host bus
+adapter (HBA) firmware or in some external enclosure.
+.PP
+This utility does not currently attempt to decode the response from the
+ATA disk, rather it outputs the response in ASCII hexadecimal grouped in
+16 bit words. Following ATA conventions those words are decoded little
+endian (note that SCSI commands use a big endian representation). In the
+future this utility may attempt to decode some log pages, perhaps using
+the \fI\-\-decode\fR option.
+.PP
+The SAT\-2 standard (SAT ANSI INCITS 465-2010, prior draft: sat2r09.pdf at
+www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16
+byte "cdb" and the other with a 12 byte cdb. This utility defaults to using
+the 16 byte cdb variant.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-C\fR, \fB\-\-ck_cond\fR
+sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set the SATL should yield a
+sense buffer containing a ATA Result descriptor irrespective of whether
+the ATA command succeeded or failed. When clear the SATL should only yield
+a sense buffer containing a ATA Result descriptor if the ATA command failed.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fICO\fR
+the number \fICO\fR is placed in the "count" field in the ATA READ
+LOG EXT command. This specified the number of 512\-byte blocks of
+data to be read from the specified log.
+.TP
+\fB\-d\fR, \fB\-\-dma\fR
+use the ATA READ LOG DMA EXT command instead of ATA READ LOG EXT command.
+Some devices require this to return valid log data.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options then exits.
+Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+when given once, the response is output in ASCII hexadecimal bytes. When
+given twice, then the response is grouped into 16 bit words using ATA
+conventions (i.e. little endian); this is the default output (i.e. when
+this option is not given). When given thrice (i.e. '\-HHH') the output
+is in hex, grouped in 16 bit words (without a leading offset and trailing
+ASCII on each line), in a format that is acceptable for 'hdparm \-\-Istdin'
+to process.
+.TP
+\fB\-L\fR, \fB\-\-log\fR=\fILA\fR
+the number \fILA\fR is known as the "log address" in the ATA standards and
+is placed in bits 7:0 of the "lba" field of the ATA READ LOG (DMA) EXT
+command. This specifies the log to be returned (See ATA\-ACS for a detailed
+list of available log addresses). The default value placed in the "lba
+field is 0, returning the directory of available logs. The maximum value
+allowed for \fILOG\fR is 0xff.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPN\fR
+the number \fIPN\fR is the page number (within the log address) and is
+placed in bits 32:16 of the "lba" field of the ATA READ LOG (DMA) EXT
+command. The default value placed in the "lba" field is 0. The maximum value
+allowed for \fILOG\fR is 0xffff.
+.TP
+\fB\-l\fR, \fB\-\-len\fR={16|12}
+this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands.
+The argument can either be 16 or 12. The default is 16. Some SCSI
+transports cannot convey SCSI commands longer than 12 bytes.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+causes the \fIDEVICE\fR to be opened with the read\-only flag (O_RDONLY in
+Unix). The default action is to open \fIDEVICE\fR with the read\-write
+flag (O_RDWR in Unix). In some cases sending power management commands to
+ATA disks are defeated by OS actions on the close() if the \fIDEVICE\fR was
+opened with the read\-write flag (e.g. the OS might think it needs to
+flush something to disk).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string
+.SH NOTES
+Prior to Linux kernel 2.6.29 USB mass storage limited sense data to 18 bytes
+which made the \fB\-\-ck_cond\fR option yield strange (truncated) results.
+.SH EXIT STATUS
+The exit status of sg_sat_read_gplog is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Hannes Reinecke and Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2015 Hannes Reinecke, SUSE Linux GmbH
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_sat_identify(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm),
+.B hdparm(hdparm)
diff --git a/doc/sg_sat_set_features.8 b/doc/sg_sat_set_features.8
new file mode 100644
index 00000000..304968fa
--- /dev/null
+++ b/doc/sg_sat_set_features.8
@@ -0,0 +1,112 @@
+.TH SG_SAT_SET_FEATURES "8" "November 2014" "sg3_utils\-1.40" SG3_UTILS
+.SH NAME
+sg_sat_set_features \- use ATA SET FEATURES command via a SCSI to ATA
+Translation (SAT) layer
+.SH SYNOPSIS
+.B sg_sat_set_features
+[\fI\-\-count=CO\fR] [\fI\-\-ck_cond\fR] [\fI--extended\fR]
+[\fI\-\-feature=FEA\fR] [\fI\-\-help\fR] [\fI\-\-lba=LBA\fR]
+[\fI\-\-len=\fR{16|12}] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends an ATA SET FEATURES command to the \fIDEVICE\fR.
+This command is used to change settings of ATA non\-packet (i.e. disks) and
+packet devices (e.g. cd/dvd drives). Rather than send the SET FEATURES
+command directly to the device it is sent via a SCSI transport which is
+assumed to contain a SCSI to ATA Translation (SAT) Layer (SATL). The SATL
+may be in an operating system driver, in host bus adapter firmware or in
+some external enclosure.
+.PP
+The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at
+www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16
+byte "cdb" and the other with a 12 byte cdb. This utility defaults to using
+the 16 byte cdb variant. SAT\-2 is also a standard: SAT\-2 ANSI INCITS
+465\-2010 and the draft prior to that is sat2r09.pdf . The SAT-3 project has
+started and the most recent draft is sat3r05b.pdf .
+.PP
+The features can be read using the sg_sat_identify utility which uses either
+the ATA IDENTIFY DEVICE (for non\-packet devices) or the IDENTIFY PACKET
+DEVICE (for packet devices) command.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fICO\fR
+the number \fICO\fR is placed in the "count" field in the ATA SET
+FEATURES command. Only some subcommands (a term used for the value
+placed in the "feature" field) require the count field to be set.
+The default value placed in the "count" field is 0.
+.TP
+\fB\-C\fR, \fB\-\-ck_cond\fR
+sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The
+default setting is clear (i.e. 0). When set the SATL should yield a
+sense buffer containing a ATA Result descriptor irrespective of whether
+the ATA command succeeded or failed. When clear the SATL should only yield
+a sense buffer containing a ATA Result descriptor if the ATA command failed.
+.TP
+\fB\-e\fR, \fB\-\-extended\fR
+allow for extended LBA numbers (i.e. larger than 32 bits).
+This value is enabled automatically for large LBA numbers, but can be
+enabled explicitly even for low LBA numbers with this option.
+.TP
+\fB\-f\fR, \fB\-\-feature\fR=\fIFEA\fR
+the value \fIFEA\fR is placed in the "feature" field in the ATA SET
+FEATURES command. The term "subcommand" is sometimes used for this
+value. The default value placed in the "feature" field is 0 which
+is reserved and hence should not change anything. Two common examples
+are 2h to enable the write cache and 82h to disable it.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options
+then exits. Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-L\fR, \fB\-\-lba\fR=\fILBA\fR
+the number \fILBA\fR is placed in the "lba" field of the ATA SET
+FEATURES command. Only some sub\-commands (a term used for the value
+placed in the "feature" field) require the lba field to be set. This
+value is typically not a "logical block address" as the acronym might
+imply. The default value placed in the "lba" field is 0. The maximum value
+allowed for \fILBA\fR is 0xfffffffe (or 0xffffff if \fI\-\-len=\fR12).
+.TP
+\fB\-l\fR, \fB\-\-len\fR={16|12}
+this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands.
+The argument can either be 16 or 12. The default is 16. Some SCSI
+transports cannot convey SCSI commands longer than 12 bytes.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+causes the \fIDEVICE\fR to be opened with the read\-only flag (O_RDONLY in
+Unix). The default action is to open \fIDEVICE\fR with the read\-write
+flag (O_RDWR in Unix). In some cases sending power management commands to
+ATA disks are defeated by OS actions on the close() if the \fIDEVICE\fR was
+opened with the read\-write flag (e.g. the OS might think it needs to
+flush something to disk).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string
+.SH NOTES
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be
+a SCSI generic (sg) device. In the 2.6 and 3 series block devices (e.g. disks
+and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda"
+will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char"
+device names may be used as well (e.g. "/dev/st0m"). Prior to lk 2.6.29
+USB mass storage limited sense data to 18 bytes which made the
+\fB\-\-ck_cond\fR option yield strange (truncated) results.
+.SH EXIT STATUS
+The exit status of sg_sat_set_features is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2007\-2014 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_sat_identify(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm),
+.B hdparm(hdparm)
diff --git a/doc/sg_scan.8.linux b/doc/sg_scan.8.linux
new file mode 100644
index 00000000..06980004
--- /dev/null
+++ b/doc/sg_scan.8.linux
@@ -0,0 +1,78 @@
+.TH SG_SCAN "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS
+.SH NAME
+sg_scan \- scans sg devices (or SCSI/ATAPI/ATA devices) and prints
+results
+.SH SYNOPSIS
+.B sg_scan
+[\fI\-a\fR]
+[\fI\-i\fR]
+[\fI\-n\fR]
+[\fI\-w\fR]
+[\fI\-x\fR]
+[\fIDEVICE\fR]*
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+If no \fIDEVICE\fR names are given, sg_scan does a scan of the sg
+devices and outputs a line of information for each sg device that is
+currently bound to a SCSI device. If one or more \fIDEVICE\fRs are given
+only those devices are scanned.
+Each device is opened with the O_NONBLOCK flag so that the scan will
+not "hang" on any device that another process holds an O_EXCL lock on.
+.PP
+Any given \fIDEVICE\fR name is expected to comply
+with (to some extent) the Storage Architecture Model (SAM see www.t10.org).
+Any device names associated with the Linux SCSI subsystem (e.g. /dev/sda
+and /dev/st0m) are suitable. Devices names associated with ATAPI
+devices (e.g. most CD/DVD drives and ATAPI tape drives) are also suitable.
+If the device does not fall into the above categories then an ATA
+IDENTIFY command is tried.
+.PP
+In Linux 2.6 and 3 series kernels, the lsscsi utility may be helpful. Apart
+from providing more information (by data\-mining in the sysfs pseudo file
+system), it does not need root permissions to execute, as this utility
+would typically need.
+.SH OPTIONS
+.TP
+\fB\-a\fR
+do alphabetical scan (i.e. sga, sgb, sgc). Note that sg device nodes with
+an alphabetical index have been deprecated since the Linux kernel 2.2
+series.
+.TP
+\fB\-i\fR
+do a SCSI INQUIRY, output results in a second (indented) line. If the device
+is an ATA disk then output information from an ATA IDENTIFY command
+.TP
+\fB\-n\fR
+do numeric scan (i.e. sg0, sg1...) [default]
+.TP
+\fB\-w\fR
+use a read/write flag when opening sg device (default is read\-only)
+.TP
+\fB\-x\fR
+extra information output about queueing
+.SH NOTES
+This utility was written at a time when hotplugging of SCSI devices
+was not supported in Linux. It used a simple algorithm to scan sg
+device nodes in ascending numeric or alphabetical order, stopping
+after there were 4 consecutive errors.
+.PP
+In the Linux kernel 2.6 series, this utility uses sysfs to find which
+sg device nodes are active and only checks those. Hence there can be
+large "holes" in the numbering of sg device nodes (e.g. after an
+adapter has been removed) and still all active sg device nodes will
+be listed. This utility assumes that sg device nodes are named using
+the normal conventions and searches from /dev/sg0 to /dev/sg4095
+inclusive.
+.SH EXIT STATUS
+The exit status of sg_scan is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert and F. Jansen
+.SH COPYRIGHT
+Copyright \(co 1999\-2013 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B lsscsi(8)
diff --git a/doc/sg_scan.8.win32 b/doc/sg_scan.8.win32
new file mode 100644
index 00000000..32b99b26
--- /dev/null
+++ b/doc/sg_scan.8.win32
@@ -0,0 +1,170 @@
+.TH SG_SCAN "8" "November 2018" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_scan \- scan storage devices and map to volume names
+.SH SYNOPSIS
+.B sg_scan
+[\fI\-\-bus\fR] [\fI\-\-help\fR] [\fI\-\-letter=VL\fR] [\fI\-\-scsi\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility scans for physical drives (a.k.a. "hard drives"), cd/dvd drives
+and tape drives and maps them to the corresponding volumes. There may be
+many, one or no corresponding volumes. There is one line output per device
+with identification strings to the right. Its purpose is to list the
+storage device names that can be used by other utilities in this package.
+.PP
+In later versions of Windows this utility may need to be "run as
+Administrator" for disks and other devices to be seen. If not those devices
+will simply not appear as calls to query them fail with access permission
+problems.
+.PP
+There is an optional SCSI adapter scan which may find additional storage
+devices other than the ones listed above. An example is a SCSI Enclosure
+Services (SES) device typically found in disk arrays.
+.PP
+Storage and related devices can have several device names in Windows.
+Probably the most common in the volume name (e.g. "D:"). There is also
+a "class" device name, and this utility scans for three of
+them: "PhysicalDrive<n>", "CDROM<n>" and "TAPE<n>". <n> is an integer
+starting at 0 allocated in ascending order as devices are discovered (and
+sometimes rediscovered).
+.PP
+Some storage devices have a SCSI lower level device name which starts
+with a SCSI (pseudo) adapter name of the form "SCSI<n>:". To this is added
+sub\-addressing in the form of a "bus" number, a "target" identifier and
+a LUN (Logical Unit Number). The "bus" number is also known as a "PathId".
+These components are combined by the utility to make a device name of the
+form: "SCSI<n>:<bus>,<target>,<lun>". This utility allows the
+trailing ",<lun>" to be omitted in which case a LUN of zero is assumed. This
+lower level device name cannot often be used directly since Windows blocks
+attempts to use it if a class driver has "claimed" the device. There are
+SCSI device types (e.g. Automation/Drive interface type) for which there is
+no class driver. At least two transports ("bus types" in Windows jargin):
+USB and IEEE 1394 do not have a "scsi" device names of this form.
+.PP
+In keeping with DOS file system conventions, the various device names
+can be given in upper, lower or mixed case. Since "PhysicalDrive<n>" is
+tedious to write, a shortened form of "PD<n>" is permitted by all
+utilities in this package.
+.PP
+A single device (e.g. a disk) can have many device names! For
+example: "PDO" can also be "C:", "D:" and "SCSI0:0,1,0". The two volume names
+reflect that the disk has two partitions on it. Disk partitions that are
+not recognised by Windows are not usually given a volume name. However
+Vista does show a volume name for a disk which has no partitions recognised
+by it and when selected invites the user to format it (which is rather
+unfriendly to other OSes).
+.PP
+The scanning logic and output of this command changed significantly in
+sg3_utils version 1.27 . The SCSI adapter based scanned is now an
+optional extra.
+.PP
+For more information see the NOTES section below.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-bus\fR
+show the bus type (or transport) by which the device is attached to the
+operating systems. Two or more transports may be involved. For example,
+a SATA disk may be in the external enclosure connected to the computer via
+USB in which case the bus type is USB.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options
+then exits.
+.TP
+\fB\-l\fR, \fB\-\-letter\fR=\fIVL\fR
+normally a device that has multiple volume names has up to four listed. If
+there are more than that a "+" is added after the fourth. When this option
+is given the \fIVL\fR argument is assumed to be a volume name (i.e. 'C'
+to 'Z') and if found in the scan, only that volume name appears in the
+output. If there are novolume names in the output then \fIVL\fR was not
+found.
+.TP
+\fB\-s\fR, \fB\-\-scsi\fR
+do a SCSI adapter based scan after the normal storage device based scan.
+There is a blank line between the normal scan and the SCSI adapter based
+scan. If this option is given twice then only the SCSI adapter based scan
+is done.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity. Can be used multiple times to display
+more of the internal data, both in normal and error processing.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string
+.SH NOTES
+This utility does not support Windows 95, 98 and ME (and earlier Windows
+operating systems). The target Windows operating systems are currently
+Windows 2000, 2003, XP and Vista (and their variants).
+.PP
+When the \fI\-\-scsi\fR option is given the SCSI adapter tuple is followed
+by a list of two or three fields. First is "claimed=0|1" indicating whether
+a class driver has claimed the device. The next field is "pdt=<num>"
+where <num> is the "peripheral device type" as defined in the SCSI INQUIRY
+command (see SPC\-4 at https://www.t10.org). The <num> has a trailing "h" to
+indicate that it is hexadecimal. Sometimes a third field with the
+word "dubious" appears. This flags that what is supposed to be a SCSI
+INQUIRY command response has a badly formed "additional length" field.
+Thus the corresponding device is unlikely to be a native SCSI device.
+.PP
+The DOS device names given the the CreateFile() call all start with
+a "\\\\.\\" string. That can be given but if not will be supplied
+automatically.
+.PP
+Scanning devices that are hot unplugged and replugged often can be
+problematic, especially with the class device names. Each time a device is
+removed and re\-added it gets a larger class device name (e.g. "PD3"
+becomes "PD4" leaving "PD3" unused). This utility stops scanning class
+devices after it find 8 consecutive "holes".
+.SH EXAMPLES
+The following examples are from a laptop with an internal drive (SATA), a
+CD/DVD drive and a USB attached SATA disk. The latter disk has two volumes
+recognised by Windows.
+.PP
+ # sg_scan
+.br
+PD0 [C] FUJITSU MHY2160BH 0000
+.br
+PD1 [DF] WD 2500BEV External 1.05 WD\-WXE90
+.br
+CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03
+.PP
+Now request bus types as well. BTW That is a SATA disk holding volume C:
+and there is a "Sata" bus type.
+.PP
+ # sg_scan \-b
+.br
+PD0 [C] <Ata > FUJITSU MHY2160BH 0000
+.br
+PD1 [DF] <Usb > WD 2500BEV External 1.05 WD\-WXE90
+.br
+CDROM0 [E] <Atapi> MATSHITA DVD/CDRW UJDA775 CB03
+.PP
+Now request a SCSI adapter scan as well.
+.PP
+ # sg_scan \-b \-s
+.br
+PD0 [C] <Ata > FUJITSU MHY2160BH 0000
+.br
+PD1 [DF] <Usb > WD 2500BEV External 1.05 WD\-WXE90
+.br
+CDROM0 [E] <Atapi> MATSHITA DVD/CDRW UJDA775 CB03
+.br
+
+.br
+SCSI0:0,0,0 claimed=1 pdt=0h FUJITSU MHY2160BH 0000
+.br
+SCSI1:0,0,0 claimed=1 pdt=5h MATSHITA DVD/CDRW UJDA775 CB03
+.PP
+.SH EXIT STATUS
+The exit status of sg_scan is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2006\-2018 Douglas Gilbert
+.br
+This software is distributed under a FreeBSD license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/sg_seek.8 b/doc/sg_seek.8
new file mode 100644
index 00000000..52ced593
--- /dev/null
+++ b/doc/sg_seek.8
@@ -0,0 +1,146 @@
+.TH SG_SEEK "8" "September 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_seek \- send SCSI SEEK, PRE-FETCH(10) or PRE-FETCH(16) command
+.SH SYNOPSIS
+.B sg_seek
+[\fI\-\-10\fR] [\fI\-\-count=NC\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR]
+[\fI\-\-immed\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num\-blocks=NUM\fR]
+[\fI\-\-pre\-fetch\fR] [\fI\-\-readonly\fR] [\fI\-\-skip=SB\fR]
+[\fI\-\-time\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-wrap\-offset=WO\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI SEEK(10), PRE\-FETCH(10) or PRE\-FETCH(16) command to the
+\fIDEVICE\fR. The SEEK command has been obsolete since SBC\-2 (2005) but
+still is supported on some hard disks and even some SSDs (solid state
+disks). The PRE\-FETCH command can be viewed as SEEK's modern replacement.
+Instead of talking about moving the disk heads to the track containing
+the sort after LBA, it talks about bringing the sort after LBA (and a
+given number of blocks) into the disk's cache. Also the PRE\-FETCH commands
+have an IMMED field.
+.PP
+The PRE\-FETCH commands can report "real" errors but usually they will report
+one of two "good" statuses. To do this they return the rarely used CONDITION
+MET status. If the number of blocks does actually fit in the cache (when
+IMMED=0) or there is enough room in the cache when the command arrives (when
+IMMED=1) then a CONDITION MET status is returned. If the requested number of
+blocks did not fit (IMMED=0) or would not fit (IMMED=1) then status GOOD
+is returned. So if a disk has a large cache and PRE\-FETCH is used sparingly
+then the command is more likely to return CONDITION MET than GOOD. This
+presents some SCSI sub\-systems with problems as due to its rareness they
+mishandle CONDITION MET and treat it as an error (see NOTES section below).
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-T\fR, \fB\-\-10\fR
+use a 10 byte cdb command, either SEEK(10) or PRE\-FETCH(10) command. In
+the absence of the \fI\-\-pre\-fetch\fR option, the SEEK(10) command is
+used. If the \fI\-\-pre\-fetch\fR option is given without this option
+then a PRE\-FETCH(16) command is used.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fINC\fR
+\fINC\fR is the number of commands (one of SEEK(10), PRE\-FETCH(10) or
+PRE\-FETCH(16)) that will be executed. The default value is 1. If an error
+occurs it is noted and the program continues until \fINC\fR is exhausted.
+If \fINC\fR is 0 then options are checked and the \fIDEVICE\fR is opened
+but no commands are sent.
+.TP
+\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
+\fIGN\fR is the group number, a value between 0 and 63 (in hex: 0x3f). The
+default value is 0. This option is ignored if the selected command is
+SEEK(10).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-immed\fR
+this option only applies to PRE\-FETCH(10) and PRE\-FETCH(16), setting
+the IMMED bit. Without this option, the \fIDEVICE\fR returns after it has
+completed transferring all, or part of, the requested blocks into the
+cache. If this option is given the \fIDEVICE\fR returns after it has done
+sanity checks on the cdb (e.g. making sure the \fILBA\fR is greater than
+the number of available blocks) and before it does the transfer into the
+cache.
+.br
+Note that even when this option is given, the return status from the
+PRE\-FETCH commands is still either CONDITION MET status (if the cache seems
+to have enough free space for the transfer) or a GOOD status (if the cache
+does not seem to have enough free space).
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+\fILBA\fR is the starting logical block address that is placed in the
+command descriptor block (cdb) of the selected command. Note that the
+\fILBA\fR field in SEEK(10) and PRE\-FETCH(10) is a 32 bit quantity,
+while with PRE\-FETCH(16) it is a 64 bit quantity. The default value is
+0 .
+.TP
+\fB\-n\fR, \fB\-\-num\-blocks\fR=\fINUM\fR
+\fINUM\fR is the number of blocks, starting at and including \fILBA\fR,
+to place in the \fIDEVICE\fR's cache. The SEEK(10) command does not use
+the \fINUM\fR value. For PRE\-FETCH(10) \fINUM\fR is a 16 bit quantity,
+while for PRE\-FETCH(16) it is a 32 bit quantity. The default value is
+1 . If \fINUM\fR is 0 then the \fIDEVICE\fR will attempt to transfer all
+blocks from the given \fILBA\fR to the end of the medium.
+.TP
+\fB\-p\fR, \fB\-\-pre\-fetch\fR
+this option selects either PRE\-FETCH(10) or PRE\-FETCH(16) commands. With
+the \fI\-\-10\fR also given, the PRE\-FETCH(10) command is selected; without
+that option PRE\-FETCH(16) is selected. The default (in the absence of this
+and other 'selecting' options) the SEEK(10) command is selected.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+this option sets a 'read\-only' flag when the underlying operating system
+opens the given \fIDEVICE\fR. This may not work since operating systems can
+not easily determine whether a pass\-through is a logical read or write
+operation so they take a risk averse stance and require read\-write type
+\fIDEVICE\fR opens irrespective of what is performed by the pass\-through.
+.TP
+\fB\-s\fR, \fB\-\-skip\fR=\fISB\fR
+\fISB\fR is the number of logical block addresses to skip, between repeated
+commands when \fINC\fR is greater than 1. The default value of \fISB\fR is
+1 . \fISB\fR may be set to 0 so that all \fINC\fR PRE\-FETCH commands use
+the same \fILBA\fR.
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+if given the elapsed time to execute \fINC\fR commands is recorded. This is
+printed out before this utility exits. If \fINC\fR is greater than 1 then
+the the "per command" time is also printed.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-w\fR, \fB\-\-wrap\-offset\fR=\fIWO\fR
+\fIWO\fR is the number of blocks, relative to \fILBA\fR, that when exceeded,
+set the next command's logical block address back to \fILBA\fR. Whether
+this "reset\-to\-LBA" action occurs depends on the values \fINC\fR and
+\fISB\fR.
+.SH NOTES
+Prior to Linux kernel 4.17 the CONDITION MET status was logged as an error.
+Recent versions of FreeBSD handle the CONDITION MET status properly.
+.PP
+If either the \fI\-\-count=NC\fR or \fI\-\-verbose\fR option is given then
+a summary line like the following is output:
+.PP
+ Command count=5, number of condition_mets=3, number of goods=2
+.PP
+before the utility exits.
+.SH EXIT STATUS
+The exit status of sg_seek is 0 (GOOD) or 25 (CONDITION_MET) when this
+utility is successful. If multiple commands are executed (e.g. when \fINC\fR
+is greater than 1) then the result of the last executed SEEK or PRE\-FETCH
+command sets the exit status. Otherwise see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd(sg3_utils); sdparm(sdparm)
diff --git a/doc/sg_senddiag.8 b/doc/sg_senddiag.8
new file mode 100644
index 00000000..e3fa612f
--- /dev/null
+++ b/doc/sg_senddiag.8
@@ -0,0 +1,300 @@
+.TH SG_SENDDIAG "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_senddiag \- performs a SCSI SEND DIAGNOSTIC command
+.SH SYNOPSIS
+.B sg_senddiag
+[\fI\-\-doff\fR] [\fI\-\-extdur\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-list\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-page=PG\fR] [\fI\-\-pf\fR]
+[\fI\-\-raw=H,H...\fR] [\fI\-\-raw=\-\fR] [\fI\-\-selftest=ST\fR]
+[\fI\-\-test\fR] [\fI\-\-timeout=SECS\fR] [\fI\-\-uoff\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_senddiag
+[\fI\-doff\fR] [\fI\-e\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-l\fR] [\fI\-pf\fR]
+[\fI\-raw=H,H...\fR] [\fI\-raw=\-\fR] [\fI\-s=ST\fR] [\fI\-t\fR]
+[\fI\-T=SECS\fR] [\fI\-uoff\fR] [\fI\-v\fR] [\fI\-V\fR] [\fI\-?\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends a SCSI SEND DIAGNOSTIC command to the \fIDEVICE\fR. It
+can issue self\-tests, find supported diagnostic pages or send arbitrary
+diagnostic pages.
+.PP
+When the \fI\-\-list\fR option and a \fIDEVICE\fR are given then the utility
+sends a SCSI RECEIVE DIAGNOSTIC RESULTS command to fetch the response (i.e.
+the page numbers of supported diagnostic pages).
+.PP
+When the \fI\-\-list\fR option is given without a \fIDEVICE\fR then a list of
+diagnostic page names and their numbers, known by this utility, are listed.
+.PP
+This utility supports two command line syntax\-es, the preferred one is
+shown first in the synopsis and explained in this section. A later section
+on the old command line syntax outlines the second group of options.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-doff\fR
+set the Device Offline (DevOffL) bit (default is clear). Only significant
+when \fI\-\-test\fR option is set for the default self\-test. When set other
+operations on any logical units controlled by the this device server (target)
+may be affected (delayed) while a default self\-test is underway.
+.TP
+\fB\-e\fR, \fB\-\-extdur\fR
+outputs the expected extended self\-test duration. The duration is given in
+seconds (and minutes in parentheses). This figure is obtained from mode page
+0xa (i.e. the control mode page).
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode it.
+Only the Supported Diagnostic Pages diagnostic page (i.e. page_code=0) is
+decoded; other pages (e.g. those used by SES) are output in hex.
+.br
+If \fI\-\-hex\fR is used once, the hex output has a relative address at the
+start of each line. If \fI\-\-hex\fR is used twice, then ASCII is shown to
+the right of each line of hex. If \fI\-\-hex\fR is used three time or more,
+only the hex is output, in two character pairs (i.e. a byte) space separated
+and up to 16 bytes per line. This latter form, if placed in a file or piped
+through to another invocation, is suitable for the \fI\-\-raw=\-\fR option.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+when a \fIDEVICE\fR is also given lists the names of all diagnostic pages
+supported by this device. The request is sent via a SEND DIAGNOSTIC
+command (with the "pF" bit set) and the response is fetched by a RECEIVE
+DIAGNOSTIC RESULTS command. When used in the absence of a \fI\-\-list\fR
+argument then a list of diagnostic page names and their numbers, known
+by this utility, are listed.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the value placed in the parameter list length field of a
+SEND DIAGNOSTIC command or in the allocation length field of a RECEIVE
+DIAGNOSTIC RESULTS command. This only occurs when the other options imply
+there will be data sent or received by the command. The default value
+is 4096 bytes. \fILEN\fR cannot exceed 65535 or 0xffff in hexadecimal.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-P\fR, \fB\-\-page\fR=\fIPG\fR
+where \fIPG\fR is the RECEIVE DIAGNOSTIC RESULTS command page code field.
+If this option is given the PCV bit in that command is set. When this option
+is given then no SEND DIAGNOSTIC command is sent (unlike \fI\-\-list\fR).
+If \fIPG\fR is 0 then the response is decoded as if it is the SPC Supported
+Diagnostic pages diagnostic page. Other \fIPG\fR values (i.e. 1 to 255)
+have their responses output in hex.
+.TP
+\fB\-p\fR, \fB\-\-pf\fR
+set Page Format (PF) bit. By default it is clear (i.e. 0) unless the
+list \fI\-\-list\fR option is given in which case the Page Format
+bit is set (as required by SPC\-3).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR=\fIH,H...\fR
+string of comma separated hex numbers each of which should resolve to
+a byte value (i.e. 0 to ff inclusive). A (single) space separated string
+of hex bytes is also allowed but the list needs to be in quotes. This
+sequence forms a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC
+command. Mostly likely the \fI\-\-pf\fR option should also be given.
+.TP
+\fB\-r\fR, \fB\-\-raw=\-\fR
+reads sequence of bytes from stdin. The sequence may be comma, space, tab
+or linefeed (newline) separated. If a line contains "#" then the remaining
+characters on that line are ignored. Otherwise each non separator character
+should resolve to a byte value (i.e. 0 to ff inclusive). This sequence forms
+a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly
+likely the \fI\-\-pf\fR option should also be given.
+.TP
+\fB\-s\fR, \fB\-\-selftest\fR=\fIST\fR
+where \fIST\fR is the self\-test code. The default value is 0 which is
+inactive. Some other values:
+.br
+ \fB1\fR : background short self\-test
+.br
+ \fB2\fR : background extended self\-test
+.br
+ \fB4\fR : aborts a (background) self\-test that is in progress
+.br
+ \fB5\fR : foreground short self\-test
+.br
+ \fB6\fR : foreground extended self\-test
+.br
+This option is mutually exclusive with default self\-test (i.e.
+can't have (\fIST\fR > 0) and \fI\-\-test\fR).
+.TP
+\fB\-t\fR, \fB\-\-test\fR
+sets the _default_ Self Test (SelfTest) bit. By default this is clear (0).
+The \fI\-\-selftest=ST\fR option should not be active together with this
+option. Both the \fI\-\-doff\fR and/or \fI\-\-uoff\fR options can be used
+with this option.
+.TP
+\fB\-T\fR, \fB\-\-timeout\fR=\fISECS\fR
+where \fISECS\fR is a timeout value (in seconds) for foreground self\-test
+operations. The default value is 7200 seconds (2 hours) and any values
+of \fISECS\fR less than the default are ignored.
+.TP
+\fB\-u\fR, \fB\-\-uoff\fR
+set the Unit Offline (UnitOffL) bit (default is clear). Only significant
+when \fI\-\-test\fR option is set for the default self\-test. When set other
+operations on this logical unit may be affected (delayed) while a default
+self\-test is underway. Some devices (e.g. Fujitsu disks) do more tests
+when this bit is set.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH NOTES
+All devices should support the default self\-test. The 'short' self\-test
+codes should complete in 2 minutes or less. The 'extended' self\-test
+codes' maximum duration is vendor specific (e.g. a little over 10 minutes
+with the author's disks). The foreground self\-test codes wait until they
+are completed while the background self\-test codes return immediately. The
+results of both foreground and background self\-test codes are placed in
+the 'self\-test results' log page (see sg_logs(8)). The SCSI command timeout
+for this utility is set to 60 minutes to allow for slow foreground extended
+self\-tests.
+.PP
+If the \fIDEVICE\fR is a disk then no file systems residing on that disk
+should be mounted during a foreground self\-test. The reason is that other
+SCSI commands may become queued behind the foreground self\-test and timeout.
+.PP
+When the \fI\-\-raw=H,H...\fR option is given then self\-tests should not
+be selected. However the \fB\-\-pf\fR (i.e. "page format") option should be
+given. The length of the diagnostic page to be sent is derived from the
+number of bytes given to the \fI\-\-raw=H,H...\fR option. The diagnostic
+page code (number) should be the first byte of the sequence (i.e. as
+dictated by SPC\-3 diagnostic page format). See the EXAMPLES section below.
+.PP
+Arbitrary diagnostic pages can be read (in hex) with the sg_ses(8)
+utility (not only those defined in SES\-2).
+.PP
+If the utility is used with no options (e.g. "sg_senddiag /dev/sg1")
+Then a degenerate SCSI SEND DIAGNOSTIC command is sent with zero
+in all its fields apart from the opcode. Some devices report this
+as an error while others ignore it. It is not entirely clear from
+SPC\-3 if it is invalid to send such a command.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI
+generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks and
+DVD drives) can also be specified.
+.PP
+To access SCSI enclosures see the sg_ses(8) utility. sg_ses uses the
+SCSI SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS commands as outlined
+in the SES\-2 (draft) standard.
+.SH EXIT STATUS
+The exit status of sg_senddiag is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-doff\fR
+set the Device Offline (DevOffL) bit (default is clear). Only significant
+when \fI\-t\fR option is set for the default self\-test. Equivalent to
+\fI\-\-doff\fR in the main description.
+.TP
+\fB\-e\fR
+outputs the expected extended self\-test duration. Equivalent to
+\fI\-\-extdur\fR in the main description.
+.TP
+\fB\-h\fR
+outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode
+it.
+.TP
+\fB\-H\fR
+outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode it.
+.TP
+\fB\-l\fR
+when a \fIDEVICE\fR is also given lists the names of all diagnostic
+pages supported by this device. The request is sent via a SEND DIAGNOSTIC
+command (with the "pf" bit set) and the response is fetched by a RECEIVE
+DIAGNOSTIC RESULTS command. When used in the absence of a \fIDEVICE\fR
+argument then a list of diagnostic page names and their numbers, known
+by this utility, are listed.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-pf\fR
+set Page Format (PF) bit. By default it is clear (i.e. 0) unless
+the \fI\-l\fR option is given in which case the Page Format bit is set
+(as required by SPC\-3).
+.TP
+\fB\-raw\fR=\fIH,H...\fR
+string of comma separated hex numbers each of which should resolve to
+a byte value (i.e. 0 to ff inclusive). This sequence forms a diagnostic
+page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly likely
+the \fI\-pf\fR option should also be given.
+.TP
+\fB\-raw=-\fR
+reads sequence of bytes from stdin. The sequence may be comma, space, tab
+or linefeed (newline) separated. If a line contains "#" then the remaining
+characters on that line are ignored. Otherwise each non separator character
+should resolve to a byte value (i.e. 0 to ff inclusive). This sequence forms
+a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly
+likely the \fI\-pf\fR option should also be given.
+.TP
+\fB\-s\fR=\fIST\fR
+where \fIST\fR is the self\-test code. The default value is 0 which is
+inactive. A value of 1 selects a background short self\-test; 2 selects
+a background extended self\-test; 5 selects a foreground short self\-test;
+6 selects a foreground extended test. A value of 4 will abort
+a (background) self\-test that is in progress. This option is mutually
+exclusive with default self\-test (i.e. \fI\-t\fR).
+.TP
+\fB\-t\fR
+sets the _default_ Self Test (SelfTest) bit. By default this is clear (0).
+The \fI\-s=ST\fR option should not be active together with this option.
+Both the \fI\-doff\fR and/or \fI\-uoff\fR options can be used with this
+option.
+.TP
+\fB\-T\fR=\fISECS\fR
+where \fISECS\fR is a timeout value (in seconds) for foreground self\-test
+operations. See the \fI\-\-timeout=SECS\fR option above.
+.TP
+\fB\-uoff\fR
+set the Unit Offline (UnitOffL) bit (default is clear). Equivalent to
+\fI\-\-uoff\fR in the main description.
+.TP
+\fB\-v\fR
+increase level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.TP
+\fB\-?\fR
+output usage message. Ignore all other parameters.
+.SH EXAMPLES
+The examples sub\-directory in the sg3_utils packages contains two example
+scripts that turn on the CJTPAT (jitter pattern) on some SAS disks (one
+script for each phy). One possible invocation for phy 1 is:
+.PP
+ sg_senddiag \-\-pf \-\-raw=\- /dev/sg2 < sdiag_sas_p1_cjtpat.txt
+.PP
+There is also an example script that turns on the IDLE pattern. Once a
+test pattern has been started it can be turned off by resetting the phy
+or with the STOP phy pattern function:
+.PP
+ sg_senddiag \-\-pf \-\-raw=\- /dev/sg2 < sdiag_sas_p1_stop.txt
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2003\-2018 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_ses(8), sg_logs(8), smartmontools(see net)
diff --git a/doc/sg_ses.8 b/doc/sg_ses.8
new file mode 100644
index 00000000..79c65978
--- /dev/null
+++ b/doc/sg_ses.8
@@ -0,0 +1,801 @@
+.TH SG_SES "8" "October 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_ses \- access a SCSI Enclosure Services (SES) device
+.SH SYNOPSIS
+.B sg_ses
+[\fI\-\-all\fR] [\fI\-\-ALL\fR] [\fI\-\-descriptor=DES\fR]
+[\fI\-\-dev\-slot\-num=SN\fR] [\fI\-\-eiioe=A_F\fR] [\fI\-\-filter\fR]
+[\fI\-\-get=STR\fR] [\fI\-\-hex\fR] [\fI\-\-index=IIA\fR |
+\fI\-\-index=TIA,II\fR] [\fI\-\-inner\-hex\fR] [\fI\-\-join\fR]
+[\fI\-\-maxlen=LEN\fR] [\fI\-\-page=PG\fR] [\fI\-\-quiet\fR] [\fI\-\-raw\fR]
+[\fI\-\-readonly\fR] [\fI\-\-sas\-addr=SA\fR] [\fI\-\-status\fR]
+[\fI\-\-verbose\fR] [\fI\-\-warn\fR] \fIDEVICE\fR
+.PP
+.B sg_ses
+\fI\-\-control\fR [\fI\-\-byte1=B1\fR] [\fI\-\-clear=STR\fR]
+[\fI\-\-data=H,H...\fR] [\fI\-\-data=@FN\fR] [\fI\-\-descriptor=DES\fR]
+[\fI\-\-dev\-slot\-num=SN\fR] [\fI\-\-index=IIA\fR | \fI\-\-index=TIA,II\fR]
+[\fI\-\-mask\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-nickname=SEN\fR]
+[\fI\-\-nickid=SEID\fR] [\fI\-\-page=PG\fR] [\fI\-\-readonly\fR]
+[\fI\-\-sas\-addr=SA\fR] [\fI\-\-set=STR\fR] [\fI\-\-verbose\fR]
+\fIDEVICE\fR
+.PP
+.B sg_ses
+\fI\-\-data=@FN\fR \fI\-\-status\fR [\fI\-\-raw\fR \fI\-\-raw\fR]
+[<all options from first form>]
+.br
+.B sg_ses
+\fI\-\-inhex=FN\fR \fI\-\-status\fR [\fI\-\-raw\fR \fI\-\-raw\fR]
+[<all options from first form>]
+.PP
+.B sg_ses
+[\fI\-\-enumerate\fR] [\fI\-\-index=IIA\fR] [\fI\-\-list\fR] [\fI\-\-help\fR]
+[\fI\-\-version\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Fetches management information from a SCSI Enclosure Service (SES) device.
+This utility can also modify the state of a SES device. The \fIDEVICE\fR
+should be a SES device which may be a dedicated enclosure services
+processor in which case an INQUIRY response's Peripheral Device Type is
+13 [0xd]. Alternatively it may be attached to another type of SCSI
+device (e.g. a disk) in which case the EncServ bit is set in its INQUIRY
+response.
+.PP
+If the \fIDEVICE\fR argument is given with no options then the names of all
+diagnostic pages (dpages) supported are listed. Most, but not necessarily
+all, of the named dpages are defined in the SES standards and drafts. The
+most recent reference for this utility is the draft SCSI Enclosure Services
+4 document T10/BSR INCITS 555 Revision 5 at https://www.t10.org . Existing
+standards for SES, SES\-2 and SES\-3 are ANSI INCITS 305\-1998 and ANSI
+INCITS 448\-2008 and ANSI INCITS 518\-2017 respectively.
+.PP
+SAS expanders typically have a SES device attached via a virtual port.
+Some HBAs (SCSI initiators) choose to expose a SES device internally. That
+means the SCSI subsystem on the host machine can see the SES device, but
+devices connected to that HBA (e.g. a SAS expander) cannot see the HBA's
+SES device. That internal SES device might report on the temperature(s)
+of the HBA and whether anything is connected to its SCSI ports.
+.PP
+The first form shown in the synopsis is for fetching and decoding dpages or
+fields from the SES \fIDEVICE\fR. A SCSI RECEIVE DIAGNOSTIC RESULTS command
+is sent to the \fIDEVICE\fR to obtain each dpage response. Rather than
+decoding a fetched dpage, it may be output in hex or binary with the
+\fI\-\-hex\fR or \fI\-\-raw \-\-raw\fR options.
+.PP
+The second form in the synopsis is for modifying dpages or fields held in
+the SES \fIDEVICE\fR. A SCSI SEND DIAGNOSTIC command containing a "control"
+dpage is sent to the \fIDEVICE\fR to cause changes. Changing the state of an
+enclosure (e.g. requesting the "ident" (locate) LED to flash on a disk
+carrier in an array) is typically done using a read\-modify\-write cycle.
+See the section on CHANGING STATE below.
+.PP
+The third form in the synopsis has two equivalent invocations shown. They
+decode the contents of a file (named \fIFN\fR) that holds a hexadecimal or
+binary representation of one, or many, SES dpage responses. Typically an
+earlier invocation of the first form of this utility with the '\-HHHH'
+option would have generated that file. Since no SCSI commands are sent, the
+\fIDEVICE\fR argument if given will be ignored.
+.PP
+The last form in the synopsis shows the options for providing command line
+help (i.e. usage information), listing out dpage and field information tables
+held by the utility (\fI\-\-enumerate\fR), or printing the version string
+of this utility.
+.PP
+There is a web page discussing this utility at
+https://sg.danny.cz/sg/sg_ses.html . Support for downloading microcode to
+a SES device has been placed in a separate utility called sg_ses_microcode.
+.PP
+In the following sections "dpage" refers to a diagnostic page, either fetched
+with a SCSI RECEIVE DIAGNOSTIC RESULTS command, sent to the \fIDEVICE\fR with
+a SCSI SEND DIAGNOSTIC command, or fetched from data supplied by the
+\fI\-\-data=\fR option.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+shows (almost) all status dpages, following references and presenting
+the information as a long list whose indentation indicates the level
+of nesting. This option is actually the same as \fI\-\-join\fR, see its
+description for more information.
+.br
+If used twice, adds threshold elements to output (if they are available).
+So it is the same as using \fI\-\-join\fRtwice.
+.TP
+\fB\-z\fR, \fB\-\-ALL\fR
+shows (almost) all status dpages, following references and presenting
+the information as a long list whose indentation indicates the level
+of nesting. Also shows the threshold elements if they are available.
+This option is the same as using \fI\-\-join\fR rwice.
+.TP
+\fB\-b\fR, \fB\-\-byte1\fR=\fIB1\fR
+some modifiable dpages may need byte 1 (i.e. the second byte) set. In the
+Enclosure Control dpage, byte 1 contains the INFO, NON\-CRIT, CRIT and
+UNRECOV bits. In the Subenclosure String Out, Subenclosure Nickname Control
+and Download Microcode Control dpages, byte 1 is the Subenclosure identifier.
+Active when the \fI\-\-control\fR and \fI\-\-data=H,H...\fR options are used
+and the default value is 0. If the \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR
+option is used then the value read from byte 1 is written back to byte 1.
+\fIB1\fR is in decimal unless it is prefixed by '0x' or '0X' (or has a
+trailing 'h' or 'H').
+.TP
+\fB\-C\fR, \fB\-\-clear\fR=\fISTR\fR
+Used to clear an element field in the Enclosure Control or Threshold Out
+dpage. Must be used together with an indexing option to specify which element
+is to be changed. The Enclosure Control dpage is assumed if the
+\fI\-\-page=PG\fR option is not given. See the STR FORMAT and the CLEAR, GET,
+SET sections below.
+.TP
+\fB\-c\fR, \fB\-\-control\fR
+will send control information to the \fIDEVICE\fR via a SCSI SEND
+DIAGNOSTIC command. Cannot give both this option and \fI\-\-status\fR.
+The Enclosure Control, String Out, Threshold Out, Array Control (obsolete
+in SES\-2), Subenclosure String Out, Subenclosure Nickname Control and
+Download Microcode dpages can be set currently. This option is assumed if
+either the \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR option is given.
+.TP
+\fB\-d\fR, \fB\-\-data\fR=\fIH,H...\fR
+permits a string of comma separated (ASCII) hex bytes to be specified (limit
+1024). A (single) space separated string of hex bytes is also allowed but
+the list needs to be in quotes. This option allows the parameters to a
+control dpage to be specified. The string given should not include the first 4
+bytes (i.e. page code and length). See the DATA SUPPLIED section below.
+.TP
+\fB\-d\fR, \fB\-\-data\fR=\-
+reads one or more data strings from stdin, limit almost 2**16 bytes. stdin
+may provide ASCII hex as a comma separated list (i.e. as with the
+\fI\-\-data=H,H...\fR option). Additionally spaces, tabs and line feeds are
+permitted as separators from stdin . Stops reading stdin when an EOF is
+detected. See the DATA SUPPLIED section below.
+.TP
+\fB\-d\fR, \fB\-\-data\fR=@\fIFN\fR
+reads one or more data strings from the file called \fIFN\fR, limit almost
+2**16 bytes. The contents of the file is decoded in the same fashion as
+stdin described in the previous option. See the DATA SUPPLIED section below.
+.TP
+\fB\-D\fR, \fB\-\-descriptor\fR=\fIDES\fR
+where \fIDES\fR is a descriptor name (string) as found in the Element
+Descriptor dpage. This is a medium level indexing alternative to the low
+level \fI\-\-index=\fR options. If the descriptor name contains a space then
+\fIDES\fR needs to be surrounded by quotes (single or double) or the space
+escaped (e.g. preceded by a backslash). See the DESCRIPTOR NAME, DEVICE SLOT
+NUMBER AND SAS ADDRESS section below.
+.TP
+\fB\-x\fR, \fB\-\-dev\-slot\-num\fR=\fISN\fR, \fB\-\-dsn\fR=\fISN\fR
+where \fISN\fR is a device slot number found in the Additional Element Status
+dpage. Only entries for FCP and SAS devices (with EIP=1) have device slot
+numbers. \fISN\fR must be a number in the range 0 to 255 (inclusive). 255 is
+used to indicate there is no corresponding device slot. This is a medium level
+indexing alternative to the low level \fI\-\-index=\fR options. See the
+DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS section below.
+.TP
+\fB\-E\fR, \fB\-\-eiioe\fR=\fIA_F\fR
+\fIA_F\fR is either the string 'auto' or 'force'. There was some fuzziness
+in the interpretation of the 'element index' field in the Additional Element
+Status (AES) dpage between SES\-2 and SES\-3. The EIIOE bit was introduced to
+resolve the problem but not all enclosures have caught up. In the SES\-3
+revision 12 draft the EIIOE bit was expanded to a 2 bit EIIOE field.
+Using '\-\-eiioe=force' will decode the AES dpage as if the EIIOE field is set
+to 1. Using '\-\-eiioe=auto' will decode the AES dpage as if the EIIOE field
+is set to 1 if the first AES descriptor has its EIP bit set and its element
+index field is 1 (in other words a heuristic to guess whether the EIIOE field
+should be set to 1 or 0).
+.br
+If the enclosure sets the actual EIIOE field to 1 or more then this option has
+no effect. It is recommended that HP JBOD users set \-\-eiioe=auto .
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+enumerate all known diagnostic page (dpage) names and SES elements that this
+utility recognizes plus the abbreviations accepted by this utility. Ignores
+\fIDEVICE\fR if it is given. Essentially it is dumping out tables held
+internally by this utility.
+.br
+If \fI\-\-enumerate\fR is given twice, then the recognised acronyms for the
+\fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options are
+listed. The utility exits after listing this information, so most other
+options and \fIDEVICE\fR are ignored. Since there are many acronyms for
+the Enclosure Control/Status dpage then the output can be further restricted
+by giving the \fI\-\-index=IIA\fR option (e.g. "sg_ses \-ee \-I ts" to only
+show the acronyms associated with the Enclosure Control/Status dpage's
+Temperature Sensor Element Type).
+.TP
+\fB\-f\fR, \fB\-\-filter\fR
+cuts down on the amount of output from the Enclosure Status dpage and the
+Additional Element Status dpage. When this option is given, any line which
+has all its binary flags cleared (i.e. 0) is filtered out (i.e. ignored).
+If a line has some other value on it (e.g. a temperature) then it is output.
+When this option is used twice only elements associated with the "status=ok"
+field (in the Enclosure status dpage) are output. The \fI\-\-filter\fR option
+is useful for reducing the amount of output generated by the \fI\-\-join\fR
+option.
+.TP
+\fB\-G\fR, \fB\-\-get\fR=\fISTR\fR
+Used to read a field in a status element. Must be used together with a an
+indexing option to specify which element is to be read. By default the
+Enclosure Status dpage is read, the only other dpages that can be read are the
+Threshold In and Additional Element Status dpages. If a value is found it is
+output in decimal to stdout (by default) or in hexadecimal preceded by "0x"
+if the \fI\-\-hex\fR option is also given. See the STR FORMAT and the CLEAR,
+GET, SET sections below.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. Since there is a lot of information,
+it is split into two pages. The most important is shown on the first page.
+Use this option twice (e.g. '\-hh') to output the second page. Note: the
+\fI\-\-enumerate\fR option might also be viewed as a help or usage type
+option. And like this option it has a "given twice" form: '\-ee'.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+If the \fI\-\-get=STR\fR option is given then output the value found (if
+any) in hexadecimal, with a leading "0x". Otherwise output the response
+in hexadecimal; with trailing ASCII if given once, without it if given
+twice, and simple hex if given three or more times. Ignored when all
+elements from several dpages are being accessed (e.g. when the \fI\-\-join\fR
+option is used). Also see the \fI\-\-raw\fR option which may be used
+with this option.
+.br
+To dump one of more dpage responses to stdout in ASCII parsable hexadecimal
+use \fI\-HHH\fR or \fI\-HHHH\fR. The triple H form only outputs hexadecimals
+which is fine for a single dpage response. When all dpages are dumped (e.g.
+with \fI\-\-page=all\fR) then the quad H form adds the name of each dpage
+following a hash mark ('#'). The \fI\-\-data=\fR option parser ignores
+everything from and including a hash mark to the end of the line. Hence the
+output of the quad H form is still parsable plus it is easier for users to
+view and possibly edit. \fI\-HHHHH\fR (that is 5) adds the page code in
+hex after the page's name in the comment.
+.TP
+\fB\-I\fR, \fB\-\-index\fR=\fIIIA\fR
+where \fIIIA\fR is either an individual index (II) or an Element type
+abbreviation (A). See the INDEXES section below. If the \fI\-\-page=PG\fR
+option is not given then the Enclosure Status (or Control) dpage is assumed.
+May be used with the \fI\-\-join\fR option or one of the \fI\-\-clear=STR\fR,
+\fI\-\-get=STR\fR or \fI\-\-set=STR\fR options. To enumerate the available
+Element type abbreviations use the \fI\-\-enumerate\fR option.
+.TP
+\fB\-I\fR, \fB\-\-index\fR=\fITIA,II\fR
+where \fITIA,II\fR is an type header index (TI) or Element type
+abbreviation (A) followed by an individual index (II). See the INDEXES section
+below. If the \fI\-\-page=PG\fR option is not given then the Enclosure
+Status (or Control) dpage is assumed. May be used with the \fI\-\-join\fR
+option or one of the \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR or
+\fI\-\-set=STR\fR options. To enumerate the available Element type
+abbreviations use the \fI\-\-enumerate\fR option.
+.TP
+\fB\-X\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a filename. It has the equivalent action of the
+\fI\-\-data=@FN\fR option. If \fIFN\fR is '\-' then stdin is read. This
+option has been given for compatibility with other utilities in this
+package that use \fI\-\-inhex=FN\fR (or \fI\-\-in=FN\fR) is a similar
+way. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information.
+.TP
+\fB\-i\fR, \fB\-\-inner\-hex\fR
+the outer levels of a status dpage are decoded and printed out but the
+innermost level (e.g. the Element Status Descriptor) is output in hex. Also
+active with the Additional Element Status and Threshold In dpages. Can be
+used with an indexing option and/or \fI\-\-join\fR options.
+.TP
+\fB\-j\fR, \fB\-\-join\fR
+group elements from the Element Descriptor, Enclosure Status and Additional
+Element Status dpages. If this option is given twice then elements from the
+Threshold In dpage are also grouped. The order is dictated by the Configuration
+dpage.
+.br
+There can be a bewildering amount of information in the "join" output. The
+default is to output everything. Several additional options are provided to
+cut down the amount displayed. If the indexing options is given, only the
+matching elements and their associated fields are output. The \fI\-\-filter\fR
+option (see its description) can be added to reduce the amount of output.
+Also "\-\-page=aes" (or "\-p 0xa") can be added to suppress the output of
+rows that don't have a "aes" dpage component. See the INDEXES and DESCRIPTOR
+NAME, DEVICE SLOT NUMBER AND SAS ADDRESS sections below.
+.TP
+\fB\-l\fR, \fB\-\-list\fR
+This option is equivalent to \fI\-\-enumerate\fR. See that option.
+.TP
+\fB\-M\fR, \fB\-\-mask\fR
+When modifying elements, the default action is a read (status element),
+mask, modify (based on \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR) then write
+back as the control element. The mask step is new in sg_ses version 1.98
+and is based on what is allowable (and in the same location) in draft SES\-3
+revision 6. Those masks may evolve, as they have in the past. This option
+re\-instates the previous logic which was to ignore the mask step. The
+default action (i.e. without this option) is to perform the mask step in
+the read\-mask\-modify\-write sequence.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+\fILEN\fR is placed in the ALLOCATION LENGTH field of the SCSI RECEIVE
+DIAGNOSTIC RESULTS commands sent by the utility. It represents the maximum
+size of data the SES device can return (in bytes). It cannot exceed 65535
+and defaults to 65532 (bytes). Some systems may not permit such large sizes
+hence the need for this option. If \fILEN\fR is less than 0 or greater than
+65535 then an error is generated. If \fILEN\fR is 0 then the default value
+is used, otherwise if it is less than 4 then it is ignored (and a warning is
+sent to stderr).
+.TP
+\fB\-n\fR, \fB\-\-nickname\fR=\fISEN\fR
+where \fISEN\fR is the new Subenclosure Nickname. Only the first 32
+characters (bytes) of \fISEN\fR are used, if more are given they are
+ignored. See the SETTING SUBENCLOSURE NICKNAME section below.
+.TP
+\fB\-N\fR, \fB\-\-nickid\fR=\fISEID\fR
+where \fISEID\fR is the Subenclosure identifier that the new
+Nickname (\fISEN\fR) will be applied to. So \fISEID\fR must be an existing
+Subenclosure identifier. The default value is 0 which is the
+main enclosure.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR
+where \fIPG\fR is a dpage abbreviation or code (a number). If \fIPG\fR
+starts with a digit it is assumed to be in decimal unless prefixed by
+0x for hex. Valid range is 0 to 255 (0x0 to 0xff) inclusive. Default is
+dpage 'sdp' which is page_code 0 (i.e. "Supported Diagnostic Pages") if
+no other options are given.
+.br
+Page code 0xff or abbreviation "all" is not a real dpage (as the highest
+real dpage is 0x3f) but instead causes all dpages whose page code is 0x2f
+or less to be output. This can be used with either the \fI\-HHHH\fR or
+\fI\-rr\fR to send either hexadecimal ASCII or binary respectively to
+stdout.
+.br
+To list the available dpage abbreviations give "xxx" for \fIPG\fR; the same
+information can also be found with the \fI\-\-enumerate\fR option.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+this suppresses the number of warnings and messages output. The exit status
+of the utility is unaffected by this option.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+outputs the chosen status dpage in ASCII hex in a format suitable for a
+later invocation using the \fI\-\-data=\fR option. A dpage less its first
+4 bytes (page code and length) is output. When used twice (e.g. \fI\-rr\fR)
+the full dpage contents is output in binary to stdout.
+.br
+when \fI\-rr\fR is used together with the \fI\-\-data=\-\fR or
+\fI\-\-data=@FN\fR then stdin or file FN is decoded as a binary stream that
+continues to be read until an end of file (EOF). Once that data is read then
+the internal raw option is cleared to 0 so the output is not effected. So
+the \fI\-rr\fR option either changes how the input or output is treated,
+but not both.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag).
+The default is to open it read\-write.
+.TP
+\fB\-A\fR, \fB\-\-sas\-addr\fR=\fISA\fR
+this is an indexing method for SAS end devices (e.g. SAS disks). The utility
+will try to find the element or slot in the Additional Element Status dpage
+whose SAS address matches \fISA\fR. For a SAS disk or tape that SAS address
+is its target port identifier for the port connected to that element or slot.
+Most SAS disks and tapes have two such target ports, usually numbered
+consecutively.
+.br
+SATA devices in a SAS enclosure often receive "manufactured" target port
+identifiers from a SAS expander; typically will have a SAS address close to,
+but different from, the SAS address of the expander itself. Note that this
+manufactured target port identifier is different from a SATA disk's WWN.
+.br
+\fISA\fR is a hex number that is up to 8 digits long. It may have a
+leading '0x' or '0X' or a trailing 'h' or 'H'. This option is a medium level
+ indexing alternative to the low level \fI\-\-index=\fR options.
+See the DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS section below.
+.TP
+\fB\-S\fR, \fB\-\-set\fR=\fISTR\fR
+Used to set an element field in the Enclosure Control or Threshold Out dpage.
+Must be used together with an indexing option to specify which element is to
+be changed. The Enclosure Control dpage is assumed if the \fI\-\-page=PG\fR
+option is not given. See the STR FORMAT and CLEAR, GET, SET sections below.
+.TP
+\fB\-s\fR, \fB\-\-status\fR
+will fetch dpage from the \fIDEVICE\fR via a SCSI RECEIVE DIAGNOSTIC RESULTS
+command (or from \fI\-\-data=@FN\fR). In the absence of other options that
+imply modifying a dpage (e.g. \fI\-\-control\fR or \fI\-\-set=STR\fR) then
+\fI\-\-status\fR is assumed, except when the \fI\-\-data=\fR option is given.
+When the \fI\-\-data=\fR option is given there is no default action: either
+the \fI\-\-control\fR or this option must be given to distinguish between
+the two different ways that data will be treated.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity. For example when this option is given four
+times (in which case the short form is more convenient: '\-vvvv') then if
+the internal join array has been generated then it is output to stderr in
+a form suitable for debugging.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-w\fR, \fB\-\-warn\fR
+warn about certain irregularities with warnings sent to stderr. The join
+is a complex operation that relies on information from several dpages to be
+synchronized. The quality of SES devices vary and to be fair, the
+descriptions from T10 drafts and standards have been tweaked several
+times (see the EIIOE field) in order to clear up confusion.
+.SH INDEXES
+An enclosure can have information about its disk and tape drives plus other
+supporting components like power supplies spread across several dpages.
+Addressing a specific element (overall or individual) within a dpage is
+complicated. This section describes low level indexing (i.e. choosing a
+single element (or a group of related elements) from a large number of
+elements). If available, the medium level indexing described in the
+following section (DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS)
+might be simpler to use.
+.PP
+The Configuration dpage is key to low level indexing: it contains a list
+of "type headers", each of which contains an Element type (e.g. Array
+Device Slot), a Subenclosure identifier (0 for the primary enclosure) and
+a "Number of possible elements". Corresponding to each type header, the
+Enclosure Status dpage has one "overall" element plus "Number of possible
+elements" individual elements all of which have the given Element type. For
+some Element types the "Number of possible elements" will be 0 so the
+Enclosure Status dpage has only one "overall" element corresponding to that
+type header. The Element Descriptor dpage and the Threshold (In and Out)
+dpages follow the same pattern as the Enclosure Status dpage.
+.PP
+The numeric index corresponding to the overall element is "\-1". If the
+Configuration dpage indicates a particular element type has "n" elements
+and n is greater than 0 then its indexes range from 0 to n\-1 .
+.PP
+The Additional Element Status dpage is a bit more complicated. It has
+entries for "Number of possible elements" of certain Element types. It
+does not have entries corresponding to the "overall" elements. To make
+the correspondence a little clearer each descriptor in this dpage optionally
+contains an "Element Index Present" (EIP) indicator. If EIP is set then each
+element's "Element Index" field refers to the position of the corresponding
+element in the Enclosure Status dpage.
+.PP
+Addressing a single overall element or a single individual element is done
+with two indexes: TI and II. Both are origin 0. TI=0 corresponds to the
+first type header entry which must be a Device Slot or Array Device Slot
+Element type (according to the SES\-2 standard). To address the corresponding
+overall instance, II is set to \-1, otherwise II can be set to the individual
+instance index. As an alternative to the type header index (TI), an Element
+type abbreviation (A) optionally followed by a number (e.g. "ps" refers to
+the first Power Supply Element type; "ps1" refers to the second) can be
+given.
+.PP
+One of two command lines variants can be used to specify indexes:
+\fI\-\-index=TIA,II\fR where \fITIA\fR is either an type header index (TI)
+or an Element type abbreviation (A) (e.g. "ps" or "ps1"). \fIII\fR is either
+an individual index or "\-1" to specify the overall element. The second
+variant is \fI\-\-index=IIA\fR where \fIIIA\fR is either an individual
+index (II) or an Element type abbreviation (A). When \fIIIA\fR is an
+individual index then the option is equivalent to \fI\-\-index=0,II\fR. When
+\fIIIA\fR is an Element type abbreviation then the option is equivalent to
+\fI\-\-index=A,\-1\fR.
+.PP
+Wherever an individual index is applicable, it can be replaced by an
+individual index range. It has the form: <first_ii>\-<last_ii>. For
+example: '3\-5' will select individual indexes 3, 4 and 5 .
+.PP
+To cope with vendor specific Element types (whose type codes should be in
+the range 128 to 255) the Element type code can be given as a number with
+a leading underscore. For example these are equivalent: \fI\-\-index=arr\fR
+and \fI\-\-index=_23\fR since the Array Device Slot Element type code is 23.
+Also \fI\-\-index=ps1\fR and \fI\-\-index=_2_1\fR are equivalent.
+.PP
+Another example: if the first type header in the Configuration dpage has
+has Array Device Slot Element type then \fI\-\-index=0,\-1\fR is
+equivalent to \fI\-\-index=arr\fR. Also \fI\-\-index=arr,3\fR is equivalent
+to \fI\-\-index=3\fR.
+.PP
+The \fI\-\-index=\fR options can be used to reduce the amount of
+output (e.g. only showing the element associated with the second 12 volt
+power supply). They may also be used together with with the
+\fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options which
+are described in the STR section below.
+.SH DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS
+The three options: \fI\-\-descriptor=DES\fR, \fI\-\-dev\-slot\-num=SN\fR
+and \fI\-\-sas\-addr=SA\fR allow medium level indexing, as an alternative
+to the low level \fI\-\-index=\fR options. Only one of the three options
+can be used in an invocation. Each of the three options implicitly set the
+\fI\-\-join\fR option since they need either the Element Descriptor dpage
+or the Additional Element Status dpage as well as the dpages needed by the
+\fI\-\-index=\fR option.
+.PP
+These medium level indexing options need support from the SES device and
+that support is optional. For example the \fI\-\-descriptor=DES\fR needs
+the Element Descriptor dpage provided by the SES device however that is
+optional. Also the provided descriptor names need to be useful, and having
+descriptor names which are all "0" is not very useful. Also some
+elements (e.g. overall elements) may not have descriptor names.
+.PP
+These medium level indexing options can be used to reduce the amount of
+output (e.g. only showing the elements related to device slot number 3).
+They may also be used together with with the \fI\-\-clear=STR\fR,
+\fI\-\-get=STR\fR and \fI\-\-set=STR\fR options which are described in the
+following section. Note that even if a field can be set (e.g. "do not
+remove" (dnr)) and that field can be read back with \fI\-\-get=STR\fR
+confirming that change, the disk array may still ignore it (e.g. because it
+does not have the mechanism to lock the disk drawer).
+.SH STR FORMAT
+The \fISTR\fR operands of the \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and
+\fI\-\-set=STR\fR options all have the same structure. There are two forms:
+.br
+ <acronym>[=<value>]
+.br
+ <start_byte>:<start_bit>[:<num_bits>][=<value>]
+.PP
+The <acronym> is one of a list of common fields (e.g. "ident" and "fault")
+that the utility converts internally into the second form. The <start_byte>
+is usually in the range 0 to 3, the <start_bit> must be in the range 0 to
+7 and the <num_bits> must be in the range 1 to 64 (default 1). The
+number of bits are read in the left to right sense of the element tables
+shown in the various SES draft documents. For example the 8 bits of
+byte 2 would be represented as 2:7:8 with the most significant bit being
+2:7 and the least significant bit being 2:0 .
+.PP
+The <value> is optional but is ignored if provided to \fI\-\-get=STR\fR.
+For \fI\-\-set=STR\fR the default <value> is 1 while for \fI\-\-clear=STR\fR
+the default value is 0 . <value> is assumed to be decimal, hexadecimal
+values can be given in the normal fashion.
+.PP
+The supported list of <acronym>s can be viewed by using the
+\fI\-\-enumerate\fR option twice (or "\-ee").
+.SH CLEAR, GET, SET
+The \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options can
+be used up to 8 times in the same invocation. Any <acronym>s used in the
+\fISTR\fR operands must refer to the same dpage.
+.PP
+When multiple of these options are used (maximum: 8), they are applied in the
+order in which they appear on the command line. So if options contradict each
+other, the last one appearing on the command line will be enforced. When
+there are multiple \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR options, then
+the dpage they refer to is only written after the last one.
+.SH DATA SUPPLIED
+This section describes the two scenarios that can occur when the
+\fI\-\-data=\fR option is given. These scenarios are the same irrespective
+of whether the argument to the \fI\-\-data=\fR option is a string of
+hex bytes on the command line, stdin (indicated by \fI\-\-data=\-\fR) or
+names a file (e.g. \fI\-\-data=@thresh_in_dpage.hex\fR).
+.PP
+The first scenario is flagged by the \fI\-\-control\fR option. It uses the
+supplied data to build a 'control' dpage that will be sent to the
+\fIDEVICE\fR using the SCSI SCSI SEND DIAGNOSTIC command. The supplied dpage
+data should not include its first 4 bytes. Those 4 bytes are added by this
+utility using the \fI\-\-page=PG\fR option with \fIPG\fR placed at byte
+offset 0). If needed, the \fI\-\-byte1=B1\fR option sets byte offset 1,
+else 0 is placed in that position. The number of bytes decoded from the data
+provided (i.e. its length) goes into byte offsets 2 and 3.
+.PP
+The second scenario is flagged by the \fI\-\-status\fR option. It decodes
+the supplied data assuming that it represents the response to one or more
+SCSI RECEIVE DIAGNOSTIC RESULTS commands. Those responses have typically
+been captured from some earlier invocation(s) of this utility. Those earlier
+invocations could use the '\-HHH' or '\-HHHH' option and file redirection to
+capture that response (or responses) in hexadecimal. The supplied dpage
+response data is decoded according to the other command line options. For
+example the \fI\-\-join\fR option could be given and that would require the
+data from multiple dpages typically: Configuration, Enclosure status,
+Element descriptor and Additional element status dpages. If in doubt use
+\fI\-\-page=all\fR in the capture phase; having more dpages than needed
+is not a problem.
+.PP
+By default the user supplied data is assumed to be ASCII hexadecimal in
+lines that don't exceed 512 characters. Anything on a line from and
+including a hash mark ('#') to the end of line is ignored. An end of
+line can be a LF or CR,LF and blank lines are ignored. Each separated
+pair (or single) hexadecimal digits represent a byte (and neither a
+leading '0x' nor a trailing 'h' should be given). Separators are either
+space, tab, comma or end of line.
+.PP
+Alternatively binary can be used and this is flagged by the '\-rr' option.
+The \fI\-\-data=H,H...\fR form cannot use binary values for the 'H's, only
+ASCII hexadecimal. The other two forms (\fI\-\-data=\-\fR and
+\fI\-\-data=@FN\fR) may contain binary data. Note that when the '\-rr'
+option is used with \fI\-\-data=@FN\fR that it only changes the
+interpretation of the input data, it does not change the decoding and output
+representation.
+.SH CHANGING STATE
+This utility has various techniques for changing the state of a SES device.
+As noted above this is typically a read\-modify\-write type operation.
+Most modifiable dpages have a "status" (or "in") page that can be read, and
+a corresponding "control" (or "out") dpage that can be written back to change
+the state of the enclosure.
+.PP
+The lower level technique provided by this utility involves outputting
+a "status" dpage in hex with \fI\-\-raw\fR. Then a text editor can be used
+to edit the hex (note: to change an Enclosure Control descriptor the SELECT
+bit needs to be set). Next the control dpage data can fed back with the
+\fI\-\-data=H,H...\fR option together with the \fI\-\-control\fR option;
+the \fI\-\-byte1=B1\fR option may need to be given as well.
+.PP
+Changes to the Enclosure Control dpage (and the Threshold Out dpage) can be
+done at a higher level. This involves choosing a dpage (the default in this
+case is the Enclosure Control dpage). Next choose an individual or overall
+element index (or name it with its Element Descriptor string). Then give
+the element's name (e.g. "ident" for RQST IDENT) or its position within that
+element (e.g. in an Array Device Slot Control element RQST IDENT is byte 2,
+bit 1 and 1 bit long ("2:1:1")). Finally a value can be given, if not the
+value for \fI\-\-set=STR\fR defaults to 1 and for \fI\-\-clear=STR\fR
+defaults to 0.
+.SH SETTING SUBENCLOSURE NICKNAME
+The format of the Subenclosure Nickname control dpage is different from its
+corresponding status dpage. The status dpage reports all Subenclosure
+Nicknames (and Subenclosure identifier 0 is the main enclosure) while the
+control dpage allows only one of them to be changed. Therefore using the
+\fB\-\-data\fR option technique to change a Subenclosure nickname is
+difficult (but still possible).
+.PP
+To simplify changing a Subenclosure nickname the \fI\-\-nickname=SEN\fR and
+\fI\-\-nickid=SEID\fR options have been added. If the \fISEN\fR string
+contains spaces or other punctuation, it should be quoted: surrounded by
+single or double quotes (or the offending characters escaped). If the
+\fI\-\-nickid=SEID\fR is not given then a Subenclosure identifier of 0 is
+assumed. As a guard the \fI\-\-control\fR option must also be given. If
+the \fI\-\-page=PG\fR option is not given then \fI\-\-page=snic\fR is
+assumed.
+.PP
+When \fI\-\-nickname=SEN\fR is given then the Subenclosure Nickname Status
+dpage is read to obtain the Generation Code field. That Generation Code
+together with no more than 32 bytes from the Nickname (\fISEN\fR) and the
+Subenclosure Identifier (\fISEID\fR) are written to the Subenclosure Nickname
+Control dpage.
+.PP
+There is an example of changing a nickname in the EXAMPLES section below.
+.SH NVME ENCLOSURES
+Support has been added to sg_ses (actually, its underlying library) for
+NVMe (also known as NVM Express) Enclosures. It can be considered
+experimental in sg3_utils package version 1.43 and sg_ses version 2.34 .
+.PP
+This support is based on a decision by NVME\-MI (Management Interface)
+developers to support the SES\-3 standard. This was facilitated by adding
+NVME\-MI SES Send and SES Receive commands that tunnel dpage contents as
+used by SES.
+.SH NOTES
+This utility can be used to fetch arbitrary (i.e. non SES) dpages (using
+the SCSI READ DIAGNOSTIC command). To this end the \fI\-\-page=PG\fR and
+\fI\-\-hex\fR options would be appropriate. Non\-SES dpages can be sent to
+a device with the sg_senddiag utility.
+.PP
+The most troublesome part of the join operation is associating Additional
+Element Status descriptors correctly. At least one SES device vendor has
+misinterpreted the SES\-2 standard, specifically with its "element index"
+field interpretation. The code in this utility interprets the "element
+index" field as per the SES\-2 standard and if that yields an inappropriate
+Element type, adjusts its indexing to follow that vendor's
+misinterpretation. The SES\-3 drafts have introduced the EIIOE (Element
+Index Includes Overall Elements) bit which later became a 2 bit field to
+resolve this ambiguity. See the \fI\-\-eiioe=A_F\fR option.
+.PP
+In draft SES\-3 revision 5 the "Door Lock" element name was changed to
+the "Door" (and an OPEN field was added to the status element). As a
+consequence the former 'dl' element type abbreviation has been changed
+to 'do'.
+.PP
+Some RAID controllers hide SES device nodes from the host Operating System.
+It has been reported that some MegaRAID controllers do this and the
+following command is needed to expose them:
+.PP
+ perccli /cx set backplane expose=<on/off>
+.PP
+where perccli is Dell's version of BroadCom's (LSI) storcli utility.
+.PP
+There is a related command set called SAF\-TE (SCSI attached fault\-tolerant
+enclosure) for enclosure (including RAID) status and control. SCSI devices
+that support SAF\-TE report "Processor" peripheral device type (0x3) in their
+INQUIRY response. See the sg_safte utility in this package or the
+safte\-monitor utility on the Internet.
+.PP
+The internal join array is statically allocated and its size is controlled
+by the MX_JOIN_ROWS define. Its current value is 520.
+.SH EXAMPLES
+Examples can also be found at https://sg.danny.cz/sg/sg_ses.html
+.PP
+The following examples use Linux device names. For suitable device names
+in other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+To view the supported dpages:
+.PP
+ sg_ses /dev/bsg/6:0:2:0
+.PP
+To view the Configuration Diagnostic dpage:
+.PP
+ sg_ses \-\-page=cf /dev/bsg/6:0:2:0
+.PP
+To view the Enclosure Status dpage:
+.PP
+ sg_ses \-\-page=es /dev/bsg/6:0:2:0
+.PP
+To get the (attached) SAS address of that device (which is held in the
+Additional Element Sense dpage (dpage 10)) printed on hex:
+.PP
+ sg_ses \-p aes \-D ArrayDevice07 \-G at_sas_addr \-H /dev/sg3
+.PP
+To collate the information in the Enclosure Status, Element Descriptor
+and Additional Element Status dpages the \fI\-\-join\fR option can be used:
+.PP
+ sg_ses \-\-join /dev/sg3
+.PP
+This will produce a lot of output. To filter out lines that don't contain
+much information add the \fI\-\-filter\fR option:
+.PP
+ sg_ses \-\-join \-\-filter /dev/sg3
+.PP
+Fields in the various elements of the Enclosure Control and Threshold dpages
+can be changed with the \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR
+options. [All modifiable dpages can be changed with the \fI\-\-raw\fR and
+\fI\-\-data=H,H...\fR options.] The following example looks at making
+the "ident" LED (also called "locate") flash on "ArrayDevice07" which is a
+disk (or more precisely the carrier drawer the disk is in):
+.PP
+ sg_ses \-\-index=7 \-\-set=2:1:1 /dev/sg3
+.PP
+If the Element Descriptor diagnostic dpage shows that "ArrayDevice07" is
+the descriptor name associated with element index 7 then this invocation
+is equivalent to the previous one:
+.PP
+ sg_ses \-\-descriptor=ArrayDevice07 \-\-set=2:1:1 /dev/sg3
+.PP
+Further the byte 2, bit 1 (for 1 bit) field in the Array Device Slot Control
+element is RQST IDENT for asking a disk carrier to flash a LED so it can
+be located. In this case "ident" (or "locate") is accepted as an acronym
+for that field:
+.PP
+ sg_ses \-\-descriptor=ArrayDevice07 \-\-set=ident /dev/sg3
+.PP
+To stop that LED flashing:
+.PP
+ sg_ses \-\-dev\-slot\-num=7 \-\-clear=ident /dev/sg3
+.PP
+The above assumes the descriptor name 'ArrayDevice07' corresponds to device
+slot number 7.
+.PP
+Now for an example of a more general but lower level technique for changing
+a modifiable diagnostic dpage. The String (In and Out) diagnostics dpage is
+relatively simple (compared with the Enclosure Status/Control dpage). However
+the use of this lower level technique is awkward involving three steps: read,
+modify then write. First check the current String (In) dpage contents:
+.PP
+ sg_ses \-\-page=str /dev/bsg/6:0:2:0
+.PP
+Now the "read" step. The following command will send the contents of the
+String dpage (from byte 4 onwards) to stdout. The output will be in ASCII
+hex with pairs of hex digits representing a byte, 16 pairs per line,
+space separated. The redirection puts stdout in a file called "t":
+.PP
+ sg_ses \-\-page=str \-\-raw /dev/bsg/6:0:2:0 > t
+.PP
+Then with the aid of the SES\-3 document (in revision 3: section 6.1.6)
+use your favourite editor to change t. The changes can be sent to the
+device with:
+.PP
+ sg_ses \-\-page=str \-\-control \-\-data=\- /dev/bsg/6:0:2:0 < t
+.PP
+If the above is successful, the String dpage should have been changed. To
+check try:
+.PP
+ sg_ses \-\-page=str /dev/bsg/6:0:2:0
+.PP
+To change the nickname on the main enclosure:
+.PP
+ sg_ses \-\-nickname='1st enclosure' \-\-control /dev/bsg/6:0:2:0
+.PP
+To capture the whole state of an enclosure (from a SES perspective) for
+later analysis, this can be done:
+.PP
+ sg_ses \-\-page=all \-HHHH /dev/sg5 > enc_sg5_all.hex
+.PP
+Note that if there are errors or warnings they will be sent to stderr so
+they will appear on the command line (since only stdout is redirected).
+A text editor could be used to inspect enc_sg5_all.hex . If all looks in
+order at some later time, potentially on a different machine where
+enc_sg5_all.hex has been copied, a "join" could be done. Note that join
+reflects the state of the enclosure when the capture was done.
+.PP
+ sg_ses \-\-data=@enc_sg5_all.hex \-\-status \-\-join
+.SH EXIT STATUS
+The exit status of sg_ses is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2021 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_safte, sg_senddiag, sg_ses_microcode, sg3_utils (sg3_utils);
+.B safte\-monitor (Internet)
diff --git a/doc/sg_ses_microcode.8 b/doc/sg_ses_microcode.8
new file mode 100644
index 00000000..70709681
--- /dev/null
+++ b/doc/sg_ses_microcode.8
@@ -0,0 +1,279 @@
+.TH SG_SES_MICROCODE "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_ses_microcode \- send microcode to a SCSI enclosure
+.SH SYNOPSIS
+.B sg_ses_microcode
+[\fI\-\-bpw=CS\fR] [\fI\-\-dry\-run\fR] [\fI\-\-ealsd\fR] [\fI\-\-help\fR]
+[\fI\-\-id=ID\fR] [\fI\-\-in=FILE\fR] [\fI\-\-length=LEN\fR]
+[\fI\-\-mode=MO\fR] [\fI\-\-non\fR] [\fI\-\-offset=OFF\fR]
+[\fI\-\-skip=SKIP\fR] [\fI\-\-subenc=MS\fR] [\fI\-\-tlength=TLEN\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility attempts to download microcode to an enclosure (or one of its
+sub\-enclosures) associated with the \fIDEVICE\fR. The process for doing
+this is defined in the SCSI Enclosure Services (SES) standards and drafts
+maintained by the T10 committee.
+.PP
+The process is to send one or more sequences containing a SCSI SEND
+DIAGNOSTIC command followed optionally by a RECEIVE DIAGNOSTIC RESULTS
+command. The former sends a Download microcode Control diagnostic
+page (dpage) and the latter fetches a Download microcode status dpage which
+can be viewed as a report on the former command.
+.PP
+The default action (i.e. when the \fI\-\-mode=MO\fR option is not given)
+is to fetch the Download microcode status dpage and decode it. This does
+not require the microcode (firmware) itself so the \fI\-\-in=FILE\fR option
+is not required.
+.PP
+The most recent reference for this utility is the draft SCSI Enclosure
+Services 3 (SES\-3) document T10/2149\-D Revision 7 at http://www.t10.org .
+Existing standards for SES and SES\-2 are ANSI INCITS 305\-1998 and ANSI
+INCITS 448\-2008 respectively.
+.PP
+Most other support for SES in this package (apart from downloading
+microcode) can be found in the sg_ses utility. Another way of downloading
+firmware to a SCSI device is with the WRITE BUFFER command defined in
+SPC\-4, see the sg_write_buffer utility.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-bpw\fR=\fICS\fR
+where \fICS\fR is the chunk size in bytes and should be a multiple of 4.
+This will be the maximum number of bytes sent per SEND DIAGNOSTIC command.
+So if \fICS\fR is less than the effective length of the microcode then
+multiple SEND DIAGNOSTIC commands are sent, each taking the next chunk
+from the read data and increasing the buffer offset field in the Download
+microcode control dpage by the appropriate amount. The default is
+a chunk size of 0 which is interpreted as a very large number hence only
+one SEND DIAGNOSTIC command will be sent.
+.br
+The number in \fICS\fR can optionally be followed by ",act" or ",activate".
+In this case after the microcode has been successfully sent to the
+\fIDEVICE\fR, an additional Download microcode control dpage with its mode
+set to "Activate deferred microcode" [0xf] is sent.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+the actual calls to perform SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS
+commands are skipped when this option is given. No SCSI commands are sent
+to the \fIDEVICE\fR but it is still opened and is required to be given.
+A dummy device such as /dev/null (in Unix) can be used.
+.br
+This utility expects a "sensible" response to the RECEIVE DIAGNOSTIC RESULTS
+command it sends (and will abort if it doesn't receive one). So this option
+supplies dummy responses with one primary enclosure and three
+sub\-enclosures. The dummy responses include good status values.
+.TP
+\fB\-e\fR, \fB\-\-ealsd\fR
+exit after last SEND DIAGNOSTIC command. A SES device should not start its
+firmware update immediately after the last received "chunk" of its firmware.
+Rather it should wait till at least one RECEIVE DIAGNOSTIC RESULTS command
+is sent to give the device a chance to report any error. However some
+devices do start the firmware update immediately which causes the trailing
+RECEIVE DIAGNOSTIC RESULTS command to be held up and often be aborted with
+a "target reset" error.
+.br
+This option causes the trailing RECEIVE DIAGNOSTIC RESULTS command to be
+skipped. This option would be typically used with the \fI\-\-bpw=CS\fR
+option.
+.br
+Prior to version 1.10 of this utility [20180112] this (i.e. skipping
+the last RECEIVE DIAGNOSTIC RESULTS command) was the default action.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. If used multiple times also prints
+the mode names and their acronyms.
+.TP
+\fB\-i\fR, \fB\-\-id\fR=\fIID\fR
+this option sets the BUFFER ID field in the Download microcode control
+dpage. \fIID\fR is a value between 0 (default) and 255 inclusive.
+.TP
+\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR
+read data from file \fIFILE\fR that will be sent with the SEND DIAGNOSTIC
+command. If \fIFILE\fR is '\-' then stdin is read until an EOF is
+detected (this is the same action as \fI\-\-raw\fR). Data is read from
+the beginning of \fIFILE\fR except in the case when it is a regular file
+and the \fI\-\-skip=SKIP\fR option is given.
+.TP
+\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR
+where \fILEN\fR is the length, in bytes, of data to be written to the device.
+If not given (and the length cannot be deduced from \fI\-\-in=FILE\fR or
+\fI\-\-raw\fR) then defaults to zero. If the option is given and the length
+deduced from \fI\-\-in=FILE\fR or \fI\-\-raw\fR is less (or no data is
+provided), then bytes of 0xff are used as fill bytes.
+.TP
+\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR
+this option sets the MODE. \fIMO\fR is a value between
+0 (which is dmc_status and the default) and 255 inclusive. Alternatively
+an abbreviation can be given. See the MODES section below. To list the
+available mode abbreviations at run time give an invalid
+one (e.g. '\-\-mode=xxx') or use the '\-h' option.
+.TP
+\fB\-N\fR, \fB\-\-non\fR
+allow for non\-standard implementations that reset their Download microcode
+engine after a RECEIVE DIAGNOSTIC RESULTS command with the Download microcode
+status dpage is sent. When this option is given sending that command and
+dpage combination is avoided unless an error has already occurred.
+.TP
+\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR
+this option sets the BUFFER OFFSET field in the Download microcode control
+dpage. \fIOFF\fR is a value between 0 (default) and 2**32\-1 . It is a
+byte offset. This option is ignored (and a warning sent to stderr) if the
+\fI\-\-bpw=CS\fR option is also given.
+.TP
+\fB\-s\fR, \fB\-\-skip\fR=\fISKIP\fR
+this option is only active when \fI\-\-in=FILE\fR is given and \fIFILE\fR is
+a regular file, rather than stdin. Data is read starting at byte offset
+\fISKIP\fR to the end of file (or the amount given by \fI\-\-length=LEN\fR).
+If not given the byte offset defaults to 0 (i.e. the start of the file).
+.TP
+\fB\-S\fR, \fB\-\-subenc\fR=\fISEID\fR
+\fISEID\fR is the sub\-enclosure identify. It defaults to 0 which is the
+primary enclosure identifier.
+.TP
+\fB\-t\fR, \fB\-\-tlength\fR=\fITLEN\fR
+\fITLEN\fR is the total length in bytes of the microcode to be (or being)
+downloaded. It defaults to 0 which is okay in most cases. This option only
+comes into play when \fITLEN\fR is greater than \fILEN\fR. In this case
+\fITLEN\fR is sent to the SES \fIDEVICE\fR so that it knows when it only
+receives \fILEN\fR bytes from this invocation, that it should expect more
+to be sent in the near future (e.g. by another invocation). This option
+is only needed when sections of microcode are being sent in separate
+invocations of this utility (e.g. the microcode is spread across two files).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH MODES
+Following is a list accepted by the \fIMO\fR argument of this utility.
+First shown is an acronym followed in square brackets by the corresponding
+decimal and hex values that may also be given for \fIMO\fR.
+.TP
+dmc_status [0, 0x0]
+Use RECEIVE DIAGNOSTIC RESULTS to fetch the Download microcode status dpage
+and print it out.
+.TP
+dmc_offs [6, 0x6]
+Download microcode with offsets and activate.
+.TP
+dmc_offs_save [7, 0x7]
+Download microcode with offsets, save, and activate.
+.TP
+dmc_offs_defer [14, 0xe]
+Download microcode with offsets, save, and defer activate.
+.TP
+activate_mc [15, 0xf]
+Activate deferred microcode. There is no follow\-up RECEIVE DIAGNOSTIC
+RESULTS to fetch the Download microcode status dpage since the \fIDEVICE\fR
+might be resetting.
+.PP
+Apart from dmc_status, these are placed in the Download microcode mode
+field in the Download microcode control dpage. In the case of dmc_status
+the Download microcode status dpage is fetched with the RECEIVE DIAGNOSTIC
+RESULTS command and decoded.
+.SH WHEN THE DOWNLOAD FAILS
+Firstly, if it succeeds, this utility should stay silent and return.
+Typically vendors will change the "revision" string (which is 4 characters
+long) whenever they release new firmware. That can be seen in the response
+to a SCSI INQUIRY command, for example by using the sg_inq utility.
+It is possible that the device needs to be power cycled before the new
+microcode becomes active. Also if mode dmc_offs_defer [0xe] is used to
+download the microcode, then another invocation with activate_mc may
+be needed.
+.PP
+If something goes wrong, there will typically be messages printed out
+by this utility. The first thing to check is the microcode (firmware)
+file itself. Is it designed for the device model; has it been corrupted,
+and if downgrading (i.e. trying to reinstate older firmware), does
+the vendor allow that?
+.PP
+Getting new firmware on a device is a delicate operation that is not
+always well defined by T10's standards and drafts. One might speculate
+that they are deliberately vague. In testing this utility one vendor's
+interpretation of the standard was somewhat surprising. The \fI\-\-non\fR
+option was added to cope with their interpretation. So if the above
+suggestions don't help, try adding the \fI\-\-non\fR option.
+.SH NOTES
+This utility can handle a maximum size of 128 MB of microcode which
+should be sufficient for most purposes. In a system that is memory
+constrained, such large allocations of memory may fail.
+.PP
+The user should be aware that most operating systems have limits on the
+amount of data that can be sent with one SCSI command. In Linux this
+depends on the pass through mechanism used (e.g. block SG_IO or the sg
+driver) and various setting in sysfs in the Linux lk 2.6/3
+series (e.g. /sys/block/sda/queue/max_sectors_kb). Devices (i.e. logical
+units) also typically have limits on the maximum amount of data they can
+handle in one command. These two limitations suggest that modes
+containing the word "offset" together with the \fI\-\-bpw=CS\fR option
+are required as firmware files get larger and larger. And \fICS\fR
+can be quite small, for example 4096 bytes, resulting in many SEND
+DIAGNOSTIC commands being sent.
+.PP
+The exact error from the non\-standard implementation was a sense key of
+ILLEGAL REQUEST and an asc/ascq code of 0x26,0x0 which is "Invalid field in
+parameter list". If that is seen try again with the \fI\-\-non\fR option.
+.PP
+Downloading incorrect microcode into a device has the ability to render
+that device inoperable. One would hope that the device vendor verifies
+the data before activating it.
+.PP
+A long (operating system) timeout of 7200 seconds is set on each SEND
+DIAGNOSTIC command.
+.PP
+All numbers given with options are assumed to be decimal.
+Alternatively numerical values can be given in hexadecimal preceded by
+either "0x" or "0X" (or has a trailing "h" or "H").
+.SH EXAMPLES
+If no microcode/firmware file is given then this utility fetches and decodes
+the Download microcode status dpage which could possibly show another
+initiator in the process of updating the microcode. Even if that is
+happening, fetching the status page should not cause any problems:
+.PP
+ sg_ses_microcode /dev/sg3
+.br
+Download microcode status diagnostic page:
+.br
+ number of secondary sub\-enclosures: 0
+.br
+ generation code: 0x0
+.br
+ sub\-enclosure identifier: 0 [primary]
+.br
+ download microcode status: No download microcode operation in progress [0x0]
+.br
+ download microcode additional status: 0x0
+.br
+ download microcode maximum size: 1048576 bytes
+.br
+ download microcode expected buffer id: 0x0
+.br
+ download microcode expected buffer id offset: 0
+.PP
+The following sends new microcode/firmware to an enclosure. Sending a 1.5 MB
+file in one command caused the enclosure to lock up temporarily and did
+not update the firmware. Breaking the firmware file into 4 KB chunks (an
+educated guess) was more successful:
+.PP
+ sg_ses_microcode \-b 4k \-m dmc_offs_save \-I firmware.bin /dev/sg4
+.PP
+The firmware update occurred in the following enclosure power cycle. With
+a modern enclosure the Extended Inquiry VPD page gives indications in which
+situations a firmware upgrade will take place.
+.SH EXIT STATUS
+The exit status of sg_ses_microcode is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_ses, sg_write_buffer, sg_inq(sg3_utils)
diff --git a/doc/sg_start.8 b/doc/sg_start.8
new file mode 100644
index 00000000..1b251c1e
--- /dev/null
+++ b/doc/sg_start.8
@@ -0,0 +1,283 @@
+.TH SG_START "8" "April 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_start \- send SCSI START STOP UNIT command: start, stop, load or eject
+medium
+.SH SYNOPSIS
+.B sg_start
+[\fI0\fR] [\fI1\fR] [\fI\-\-eject\fR] [\fI\-\-help\fR] [\fI\-\-fl=FL\fR]
+[\fI\-\-immed\fR] [\fI\-\-load\fR] [\fI\-\-loej\fR] [\fI\-\-mod=PC_MOD\fR]
+[\fI\-\-noflush\fR] [\fI\-\-pc=PC\fR] [\fI\-\-readonly\fR] [\fI\-\-start\fR]
+[\fI\-\-stop\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_start
+[\fI\-\-eject\fR] [\fI\-\-fl=FL\fR] [\fI\-i\fR] [\fI\-\-imm=0|1\fR]
+[\fI\-\-load\fR] [\fI\-\-loej\fR] [\fI\-\-mod=PC_MOD\fR] [\fI\-\-noflush\fR]
+[\fI\-\-pc=PC\fR] [\fI\-r\fR] [\fI\-\-start\fR] [\fI\-\-stop\fR] [\fI\-v\fR]
+[\fI\-V\fR] [\fI0|1\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_start sends a SCSI START STOP UNIT command to the \fIDEVICE\fR with
+the selected options. The most used options are \fI\-\-stop\fR to spin
+down a disk and \fI\-\-start\fR to spin up a disk. Using \fI\-\-start\fR
+on a disk that is already spinning is harmless. There is also finer grain
+control with "power condition": active, idle or standby. This is set
+with the \fI\-\-pc=PC\fR option. In some contexts the "stop" state can
+be considered an additional power condition.
+.PP
+Devices that contain removable media such as cd/dvds can use the
+\fI\-\-loej\fR option to load the medium when used in conjunction
+with \fI\-\-start\fR (i.e. load medium then spin up). Alternatively
+\fI\-\-loej\fR may be used to eject the medium when used in conjunction
+with \fI\-\-stop\fR (i.e. spin down then eject medium). More simply, the
+loading or ejecting of a removable medium can be requested with the
+\fI\-\-load\fR or \fI\-\-eject\fR' option.
+.PP
+If no option or argument is given then a \fI\-\-start\fR is assumed; as the
+utility's name suggests.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later
+section on the old command line syntax outlines the second group of
+options.
+.PP
+Linux note: best not to use a standard block device name (e.g. /dev/sdc)
+with the \fI\-\-stop\fR option. Use a sg or bsg device node instead (see
+lsscsi(8) ). The block layer will sometimes notice the disk spinning
+down and decide: "that's not right" and spin it up again!
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB0\fR
+same action as \fI\-\-stop\fR.
+.TP
+\fB1\fR
+same action as \fI\-\-start\fR.
+.TP
+\fB\-e\fR, \fB\-\-eject\fR
+stop the medium and eject it from the drive. Only appropriate for a
+device with removable medium. Might be ignored (prevented), see below.
+Note, this is an operation that can be done on a tape drive or CD/DVD/BD
+player, not on a hard disk or SSD!
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-f\fR, \fB\-\-fl\fR=\fIFL\fR
+sets the format layer number for the disc to "jump" to (defined in MMC\-5).
+Values of \fIFL\fR can be 0 to 3. When this option is chosen, the FL, LoEj
+and Start bits are set in the cdb as required by MMC\-5; thus the user does
+not need to set the \fI\-\-start\fR and/or \fI\-\-load\fR options.
+.TP
+\fB\-i\fR, \fB\-\-immed\fR
+sets the IMM bit on the START STOP UNIT command so this utility will
+return immediately and not wait for the media to complete the requested
+action. The default is to wait until the media to complete the requested
+action before returning.
+.TP
+\fB\-l\fR, \fB\-\-load\fR
+load the medium in the drive and start it. Only appropriate for a removable
+medium.
+.TP
+\fB\-L\fR, \fB\-\-loej\fR
+sets the LOEJ bit on the START STOP UNIT command. This loads the media when
+the unit is started or eject it when the unit is stopped (i.e. works in
+conjunction with START bit in cdb). This option is ignored if 'pc > 0'.
+Default is off (i.e. don't attempt to load or eject media). If a start/start
+indication is not given (i.e. neither \fI\-\-start\fR nor \fI\-\-stop\fR)
+and this option is given then a load and start action is assumed.
+.TP
+\fB\-m\fR, \fB\-\-mod\fR=\fIPC_MOD\fR
+where \fIPC_MOD\fR is the 'power condition modifier' value. 0 to 15 (inclusive)
+are valid and 0 is the default. This 'power condition modifier' field in the
+cdb was added after sbc3r13.
+.TP
+\fB\-n\fR, \fB\-\-noflush\fR
+do not perform a flush to media (e.g. like SYNCHRONIZE CACHE does) before
+a variant of this utility that limits access to the media. Using the
+\fB\-\-stop\fR option is an example of something that limits access to the
+media. This 'noflush' field in the cdb was added after sbc3r13.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-p\fR, \fB\-\-pc\fR=\fIPC\fR
+where \fIPC\fR is the 'power conditions' value. 0 to 15 (inclusive) are valid.
+Default value is 0. When '\-\-pc=0' then \fB\-\-eject\fR, \fB\-\-load\fR,
+\fB\-\-loej\fR, \fB\-\-start\fR and \fB\-\-stop\fR are active. Some common
+values are 1 for the "active" power condition (SBC); 2 for the idle power
+condition; 3 for the standby power condition; 5 for sleep power
+condition (MMC); 7 for LU_CONTROL (SBC), 0xa (decimal 10) for
+FORCE_IDLE_0 (SBC) and 0xb (decimal 11) for FORCE_STANDBY_0 (SBC). See recent
+SBC\-3, MMC\-5 and SAS drafts at www.t10.org for more information.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR in read\-only mode. Maybe required in Linux to stop a
+nuisance spin\-up if the \fIDEVICE\fR is an ATA disk. The nuisance spin\-up
+may occur at the end of this command negating the effect of the
+\fI\-\-stop\fR option.
+.TP
+\fB\-s\fR, \fB\-\-start\fR
+start (spin\-up) the \fIDEVICE\fR. This sets the START bit in the cdb. Using
+this option on an already started device is harmless. In the absence of
+other options, this option defaults (i.e. set the START cdb bit).
+.TP
+\fB\-S\fR, \fB\-\-stop\fR
+stop (spin\-down) the \fIDEVICE\fR. This clears the START bit in the cdb.
+This operation is typically done on a hard disk or SSD. In the case of a
+SSD it will be placed in low power mode and may need a start operation
+later before normal IO can resume.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity. Can be used multiple times.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH NOTES
+To avoid confusion, only one of \fI0\fR, \fI1\fR \fI\-\-eject\fR,
+\fI\-\-load\fR, \fI\-\-start\fR and \fI\-\-stop\fR should be given.
+.PP
+There is an associated "power condition" mode page (0x1a) in which timer
+values can be set for transitioning to either idle or standby state after
+a period of inactivity. The sdparm utility can be used to view the power
+condition mode page and if required change it. If a \fIDEVICE\fR is in either
+idle or standby power condition state then a REQUEST SENSE command (see
+the sg_requests utility) should yield a sense key of "no sense" and an
+additional sense code of "Low power condition on" on recent SCSI devices.
+.PP
+Ejection of removable media (e.g. 'sg_start \-\-eject /dev/hdd' where
+the \fIDEVICE\fR is an ATAPI cd/dvd drive) may be prevented by a prior
+SCSI PREVENT ALLOW MEDIUM REMOVAL command (see sg_prevent). In this
+case this utility should fail with an error generated by the device:
+illegal request / medium removal prevented. This can be overridden
+using sg_prevent or, for example, 'sdparm \-\-command=unlock /dev/hdd'.
+.PP
+The SCSI TEST UNIT READY command can be used to find out whether a
+\fIDEVICE\fR is ready to transfer data. If rotating media is stopped or
+still coming up to speed, then the TEST UNIT READY command will yield
+a "not ready" sense key and an more informative additional sense
+code. See the sg_turs utility.
+.PP
+In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI
+generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks
+and DVD drives) can also be specified. For example "sg_start 0 /dev/sda"
+will work in the 2.6 series kernels.
+.PP
+In the Linux 2.6 series, especially with ATA disks, using this utility
+to stop (spin down) a disk may not be sufficient and other mechanisms
+will start the disk again some time later. The user might additionally
+mark the disk as "offline" with 'echo offline > /sys/block/sda/device/state'
+where sda is the block name of the disk. To restart the disk "offline"
+can be replaced with "running". Note that once the 'state' is set to
+offline, no SCSI commands can be sent to the device until it is set back
+to running. Also stopping a disk via a pass\-through
+interface (e.g. /dev/sg1 or /dev/bsg/1:0:0:0) may reduce unwanted side
+effects (such as restarting it again when this utility completes).
+.SH EXIT STATUS
+The exit status of sg_start is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.PP
+Note that the action of \fI\-\-loej\fR is slightly different in the older
+interface: when neither \fI\-\-start\fR nor \fI\-\-stop\fR (nor proxies
+for them) are given, \fI\-\-loej\fR performs an eject operation. In the
+same situation the newer interface will perform a load operation.
+.PP
+Earlier versions of sg_start had a '\-s' option to perform a SYNCHRONIZE
+CACHE command before the START STOP UNIT command was issued. According to
+recent SBC\-2 drafts this is done implicitly if required. Hence the '\-s'
+option has been dropped.
+.PP
+All options, other than '\-v' and '\-V', can be given with a single "\-".
+For example: "sg_start \-stop /dev/sda" and "sg_start \-\-stop /dev/sda"
+are equivalent. The single "\-" form is for backward compatibility.
+.TP
+\fB0\fR
+stop (spin\-down) \fIDEVICE\fR.
+.TP
+\fB1\fR
+start (spin\-up) \fIDEVICE\fR.
+.TP
+\fB\-\-eject\fR
+stop the medium and eject it from the drive.
+.TP
+\fB\-\-fl\fR=\fIFL\fR
+sets the format layer number for the disc to "jump" to (defined in MMC\-5).
+.TP
+\fB\-i\fR
+sets the IMM bit on the START STOP UNIT command so this utility will return
+immediately and not wait for the media to spin down. Same effect
+as '\-\-imm=1'. The default action (without this option or a '\-\-imm=1'
+option) is to wait until the media spins down before returning.
+.TP
+\fB\-\-imm\fR=\fI0|1\fR
+when the immediate bit is 1 then this utility returns immediately after the
+\fIDEVICE\fR has received the command. When this option is 0 (the default)
+then the utility returns once the command has completed its action (i.e. it
+waits until the device is started or stopped).
+.TP
+\fB\-\-load\fR
+load the medium in the drive and start it.
+.TP
+\fB\-\-loej\fR
+sets the LOEJ bit in the START STOP UNIT cdb. When a "start" operation is
+indicated, then a load and start is performed. When a "stop" operation is
+indicated, then a stop and eject is performed. When neither a "start"
+or "stop" operation is indicated does a stop and eject. [Note that the last
+action differs from the new interface in which the option of this name
+defaults to load and start.]
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-\-mod\fR=\fIPC_MOD\fR
+where \fIPC_MOD\fR is the 'power condition modifier' value. 0 to 15 (inclusive)
+are valid and 0 is the default. This field was added after sbc3r13.
+.TP
+\fB\-\-noflush\fR
+do not perform a flush to media (e.g. like SYNCHRONIZE CACHE does) before
+a variant of this utility that limits access to the media. Using the
+\fB\-\-stop\fR option is an example of something that limits access to the
+media. This field was added after sbc3r13.
+.TP
+\fB\-\-pc\fR=\fIPC\fR
+where \fIPC\fR is the 'power condition' value (in hex). 0 to f (inclusive)
+are valid. Default value is 0.
+.TP
+\fB\-r\fR
+see the \fI\-\-readonly\fR option above. May be useful for ATA disks.
+.TP
+\fB\-\-start\fR
+start (spin\-up) \fIDEVICE\fR.
+.TP
+\fB\-\-stop\fR
+stop (spin\-down) \fIDEVICE\fR. Same meaning as "0" argument.
+.TP
+\fB\-v\fR
+verbose: outputs SCSI command in hex to console before with executing
+it. '\-vv' and '\-vvv' are also accepted yielding greater verbosity.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHOR
+Written by K. Garloff and D. Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2002\-2021 Kurt Garloff, Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_prevent(sg3_utils), sg_requests(sg3_utils), sg_turs(sg3_utils)
+.B sdparm(sdparm), lsscsi(lsscsi)
diff --git a/doc/sg_stpg.8 b/doc/sg_stpg.8
new file mode 100644
index 00000000..5594878a
--- /dev/null
+++ b/doc/sg_stpg.8
@@ -0,0 +1,122 @@
+.TH SG_STPG "8" "January 2014" "sg3_utils\-1.38" SG3_UTILS
+.SH NAME
+sg_stpg \- send SCSI SET TARGET PORT GROUPS command
+.SH SYNOPSIS
+.B sg_stpg
+[\fI\-\-active\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-offline\fR]
+[\fI\-\-optimized\fR] [\fI\-\-raw\fR] [\fI\-\-standby\fR]
+[\fI\-\-state=S,S...\fR] [\fI\-\-tp=P,P...\fR] [\fI\-\-unavailable\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI SET TARGET PORT GROUPS command to \fIDEVICE\fR. This utility
+has different modes depending on whether the \fI\-\-tp=\fR option is given.
+.PP
+If \fI\-\-tp=\fR is given then the SET TARGET PORT GROUPS command parameter
+block is built with a descriptor for each element in the list given to
+\fI\-\-tp=\fR. The corresponding asymmetric access state value is either
+taken from the \fI\-\-state=\fR list or, if that is not given, from one
+of the explicit state options (e.g. \fI\-\-unavailable\fR), used repeatedly
+if required.
+.PP
+If \fI\-\-tp=\fR is not given then a sequence of SCSI commands are sent to
+the \fIDEVICE\fR leading up to the SET TARGET PORT GROUPS command. First an
+INQUIRY is sent to fetch the device identification VPD page to find
+the (primary) target port group associated with \fIDEVICE\fR. Then a REPORT
+TARGET PORT GROUPS command is issued to find the current state and
+whether a transition to the requested state is supported. If so the
+SET TARGET PORT GROUPS command is sent.
+.PP
+Target port group access is described in SPC\-4 found at www.t10.org
+in sections 5.8 and 5.16 (in rev 36e dated 2012/8/24). The SET TARGET PORT
+GROUPS command is also described in section 6.45 of that document.
+.PP
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-a\fR, \fB\-\-active\fR
+set active/non\-optimized state.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output response to the REPORT TARGET PORT GROUPS command in hex then exit.
+.TP
+\fB\-O\fR, \fB\-l\fR, \fB\-\-offline\fR
+set offline state. This is the appropriate state to set a target port
+to prior to removing the device. Note that a relative target port identifier
+should be given with this state (rather than a target port group identifier
+that all other states take).
+.TP
+\fB\-o\fR, \fB\-\-optimized\fR
+set active/optimized state. If no other state options or \fI\-\-tp=\fR
+option are given then active/optimized is the default state.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response to the REPORT TARGET PORT GROUPS command in binary to stdout
+then exit.
+.TP
+\fB\-s\fR, \fB\-\-standby\fR
+set standby state. Port group shall accept those commands listed
+for "unavailable" state plus LOG SELECT/SENSE, MODE SELECT/SENSE, RECEIVE
+DIAGNOSTIC RESULTS, SEND DIAGNOSTIC, PERSISTENT RESERVE IN/OUT commands.
+.TP
+\fB\-S\fR, \fB\-\-state\fR=\fIS,S...\fR
+specifies a comma separated list (one element of more) of states. Either
+a number or an abbreviation can be given. A number is assumed to be a
+decimal number unless it is prefixed by "0x" or has a trailing "h" in
+which case a hexadecimal value is assumed. Only the values 0, 1, 2, 3
+or 14 are accepted. The accepted abbreviations are "an", "ao", "o", "s"
+or "u"; which represent active/non\-optimized(1), active/optimized(0),
+offline(14), standby(2) or unavailable(3) respectively.
+.TP
+\fB\-t\fR, \fB\-\-tp\fR=\fIP,P...\fR
+specifies a comma separated list (one element of more). Each elements is
+either a target port group identifier (when the corresponding state is
+other than "offline") or a relative target port identifier (when the
+corresponding state is "offline"). Each element is assumed to be a
+decimal number unless it is prefixed by "0x" or has a trailing "h" in
+which case a hexadecimal value is assumed.
+.TP
+\fB\-u\fR, \fB\-\-unavailable\fR
+set unavailable state. Port group shall only accept INQUIRY, REPORT LUNS,
+REPORT/SET TARGET PORT GROUPS, REQUEST SENSE and READ/WRITE BUFFER commands.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+The SET TARGET PORT GROUPS command should be supported whenever the TPGS
+value in a standard INQUIRY response is 2 or 3. [View with sg_inq utility.]
+.PP
+Notice that the offline state is termed as a "secondary target port
+asymmetric access state" and takes a relative target port identifier (i.e.
+acts on a single target port). All the other states are termed as "primary
+target port asymmetric access states" and each takes a target port group
+identifier (i.e. acts on one or more target ports).
+.PP
+When \fI\-\-tp=\fR is given then the same number of elements should be
+given to the \fI\-\-state=\fR option. If more than one list element is
+given to \fI\-\-tp=\fR and an equal number of elements is _not_ given
+to the \fI\-\-state=\fR option, then if only one state is specified
+then it is repeated.
+.SH EXIT STATUS
+The exit status of sg_stpg is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2007\-2014 Hannes Reinecke, Christophe Varoqui and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_rtpg (sg3_utils)
diff --git a/doc/sg_stream_ctl.8 b/doc/sg_stream_ctl.8
new file mode 100644
index 00000000..18d9641c
--- /dev/null
+++ b/doc/sg_stream_ctl.8
@@ -0,0 +1,117 @@
+.TH SG_STREAM_CTL "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_stream_ctl \- send SCSI STREAM CONTROL or GET STREAM STATUS command
+.SH SYNOPSIS
+.B sg_stream_ctl
+[\fI\-\-brief\fR] [\fI\-\-close\fR] [\fI\-\-ctl=CTL\fR] [\fI\-\-get\fR]
+[\fI\-\-help\fR] [\fI\-\-id=SID\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-open\fR]
+[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI STREAM CONTROL or GET STREAM STATUS command to the \fIDEVICE\fR.
+These commands, together with WRITE STREAM(16 and 32) and several fields in
+the Block Limits Extension VPD page [0xb7] support the streams concept.
+The stream commands were added in SBC\-4 draft 8 (September 2015).
+.PP
+Both STREAM CONTROL and GET STREAM STATUS commands expect data from the
+\fIDEVICE\fR (referred to as 'data\-in'). In the case of STREAM CONTROL
+only the 'open' (STR_CTL<\-\-0x1) actually needs the data\-in as it contains
+the "Assigned stream id" if the open was successful. The assigned stream
+id should be used by subsequent WRITE STREAM commands and ultimately
+by the STREAM CONTROL close (STR_CTL<\-\-0x2). Valid stream ids are between
+1 and 65535 inclusive.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-b\fR, \fB\-\-brief\fR
+this option reduces the output of the GET STREAM STATUS command to just
+one number (in decimal) per line sent to stdout. Those numbers are the
+currently open stream ids. If an error occurs then \-1 is sent to stdout
+and error related messages are sent to stderr. The default is to print more
+words (and fields) from the GET STREAM STATUS response.
+.TP
+\fB\-c\fR, \fB\-\-close\fR
+selects the STREAM CONTROL command and sets STR_CTL<\-\-0x2 (i.e. 'close').
+The \fI\-\-id=SID\fR option should also be given because it defaults to 0
+which is not a valid stream id.
+.TP
+\fB\-C\fR, \fB\-\-ctl\fR=\fICTL\fR
+\fICTL\fR is the value placed in the STR_CTL field of the STREAM CONTROL
+command (cdb). It is a two bit field so has 4 variants: 0 and 3 are reserved;
+1 opens are new stream and 2 closes the given stream id. '\-\-ctl=1' is
+equivalent to '\-\-open' while '\-\-ctl=2' is equivalent to '\-\-close'.
+.TP
+\fB\-g\fR, \fB\-\-get\fR
+selects the GET STREAM STATUS command. If the \fI\-\-id=SID\fR option is
+also given the the response starts lists open stream ids from and including
+\fISID\fR. If the \fI\-\-id=SID\fR option is not given (or \fISID\fR is 0)
+then all open stream id will be returned in the response (data\-in) as long
+as the allocation length (defaults to 248 bytes which can be overridden by
+the \fI\-\-maxlen=LEN\fR option) is long enough. This is the default action
+of this utility (i.e. GET STREAM STATUS command) if no "selecting" options
+are given.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-id\fR=\fISID\fR
+\fISID\fR is a stream id, a value between 1 and 65535. It is used by STREAM
+CONTROL (close) to identify the stream to close. It is used by the GET
+STREAM STATUS command as the starting stream id (from and including); so
+stream ids that are less than \fISID\fR will not appear in the response.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+\fILEN\fR is the maximum length the response can be. It becomes the
+ALLOCATION LENGTH field in both commands. The default (in the absence of
+this option) is 8 bytes for STREAM CONTROL and 248 bytes for GET STREAM
+STATUS.
+.TP
+\fB\-o\fR, \fB\-\-open\fR
+selects the STREAM CONTROL command and sets STR_CTL<\-\-0x1 (i.e. 'open').
+If the \fI\-\-id=SID\fR option is given then it is ignored. The user should
+observe the response as the "Assigned stream id" is printed on stdout if
+the open is successful, if not '\-1' is sent to stdout and error messages are
+sent to stderr. If the \fI\-\-brief\fR option is also given then the only
+thing sent to stdout is a number of the assigned stream id (1 to
+65535 inclusive) or '\-1' if there is an error.
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+this option sets a 'read\-only' flag when the underlying operating system
+opens the given \fIDEVICE\fR. This may not work since operating systems can
+not easily determine whether a pass\-through command is a logical read or
+write operation on the media (or its metadata) so they take a risk averse
+stance and require read\-write type permissions on the \fIDEVICE\fR open
+irrespective of what is performed by the pass\-through.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+There are no special read commands for streams. This implies that "normal"
+READs (6, 10, 12, 16 or 32) can be used. Note that when a stream is closed,
+all resources associated with that stream id are removed, apart from the
+data in the written LBAs. To make sure the reading back data is not delayed
+too much by error recovery (in the presence of media errors) the user may
+set the RECOVERY TIME LIMIT field (RTL, units for non\-zero values:
+milliseconds) in the 'Read\-write error recovery' mode page. This can be done
+with the sdparm utility.
+.PP
+The SCSI WRITE STREAM (16 and 32) commands can be found in the sg_write_x
+utility in this package.
+.SH EXIT STATUS
+The exit status of sg_stream_ctl is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_vpd,sg_write_x(sg3_utils); sdparm(sdparm)
diff --git a/doc/sg_sync.8 b/doc/sg_sync.8
new file mode 100644
index 00000000..77266184
--- /dev/null
+++ b/doc/sg_sync.8
@@ -0,0 +1,97 @@
+.TH SG_SYNC "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_sync \- send SCSI SYNCHRONIZE CACHE command
+.SH SYNOPSIS
+.B sg_sync
+[\fI\-\-16\fR] [\fI\-\-count=COUNT\fR] [\fI\-\-group=GN\fR]
+[\fI\-\-help\fR] [\fI\-\-immed\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-sync\-nv\fR]
+[\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send SYNCHRONIZE CACHE(10) or SYNCHRONIZE CACHE(16) command to \fIDEVICE\fR.
+These commands are defined for SCSI block devices (see SBC\-3). If successful
+these commands make sure that any blocks whose latest versions are held in
+cache are written to (also termed as "synchronized with") the medium.
+.PP
+If the \fILBA\fR and \fICOUNT\fR arguments are both zero (their defaults)
+then all blocks in the cache are synchronized. If \fILBA\fR is greater than
+zero while \fICOUNT\fR is zero then blocks in the cache whose addresses are
+from and including \fILBA\fR to the highest lba on the device are
+synchronized. If both \fILBA\fR and \fICOUNT\fR are non zero then blocks in
+the cache whose addresses lie in the range \fILBA\fR to
+\fILBA\fR+\fICOUNT\fR\-1 inclusive are synchronized with the medium.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+performs a SYNCHRONIZE CACHE(16) command. Default is to perform a
+SYNCHRONIZE CACHE(10) command.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR
+where \fICOUNT\fR is the number of blocks to synchronize from and including
+\fILBA\fR. Default value is 0. When 0 then all blocks in the cache from and
+including \fILBA\fR argument to the highest block address are synchronized.
+.TP
+\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR
+where \fIGN\fR is the group number which can be between 0 and 63 inclusive.
+The default value is 0 . Group numbers are used to segregate data collected
+within the device. This is a new feature in SBC\-2 and can probably be
+ignored for the time being.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-immed\fR
+sets the IMMED bit in the SYNCHRONIZE CACHE command. This instructs the
+device, if the format of the command is acceptable, to return a GOOD
+status immediately rather than wait for the blocks in the cache to be
+synchronized with (i.e. written to) the medium.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the lowest logical block address in the cache to
+synchronize to the medium. Default value is 0 .
+.TP
+\fB\-s\fR, \fB\-\-sync\-nv\fR
+synchronize the (volatile) cache with the non\-volatile cache. Without this
+option (or if there is no non\-volatile cache in the device) the
+synchronization is with the medium. The SYNC_NV bit was made obsolete in
+SBC\-3 revision 35d.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR
+where \fISECS\fR is the number of seconds the OS allows the SYNCHRONIZE
+CACHE(16) to complete before it tries to cancel the command. Cancelling
+commands (typically with the task management function "abort task") is
+best avoided. Note this option is only active together with the \fI\-\-16\fR
+option. The default timeout is 60 seconds for both SYNCHRONIZE CACHE(10)
+and SYNCHRONIZE CACHE(16). Note that timeout issues can be avoided with
+the \fI\-\-immed\fR option.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+With the SYNCHRONIZE CACHE(16) command \fILBA\fR can be up to 64 bits
+in size and \fICOUNT\fR up to 32 bits in size. With the SYNCHRONIZ
+CACHE(10) command \fILBA\fR can be up to 32 bits in size and \fICOUNT\fR
+up to 16 bits in size.
+.PP
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.SH EXIT STATUS
+The exit status of sg_sync is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_start(sg3_utils)
diff --git a/doc/sg_test_rwbuf.8 b/doc/sg_test_rwbuf.8
new file mode 100644
index 00000000..d610531e
--- /dev/null
+++ b/doc/sg_test_rwbuf.8
@@ -0,0 +1,86 @@
+.TH SG_TEST_RWBUF "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_test_rwbuf \- test a SCSI host adapter by issuing dummy writes
+and reads
+.SH SYNOPSIS
+.B sg_test_rwbuf
+[\fI\-\-addrd=AR\fR] [\fI\-\-addwr=AW\fR] [\fI\-\-help\fR]
+[\fI\-\-quick\fR] \fI\-\-size=SZ\fR [\fI\-\-times=NUM\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+or an older deprecated format
+.B sg_test_rwbuf
+\fIDEVICE\fR \fISZ\fR [\fIAW\fR] [\fIAR\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sg_test_rwbuf writes and reads back \fISZ\fR bytes to the internal buffer of
+\fIDEVICE\fR (e.g. /dev/sda or /dev/sg0). A pseudo random pattern is
+written to the data buffer on the device then read back. If the same pattern
+is found 'Success' is reported. If they do not match (checksums unequal) then
+this is reported and up to 24 bytes from the first point of mismatch are
+reported; the first line shows what was written and the second line shows
+what was received. For testing purposes, you can ask it to write \fIAW\fR or
+read \fIAR\fR additional bytes.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-r\fR, \fB\-\-addrd\fR=\fIAR\fR
+Read an additional \fIAR\fR bytes (more than indicated by \fISZ\fR) from the
+data buffer. Checksum is performed over the first \fISZ\fR bytes.
+.TP
+\fB\-w\fR, \fB\-\-addwr\fR=\fIAW\fR
+Write an additional \fIAW\fR bytes (more than indicated by \fISZ\fR) of
+zeros into the data buffer. Checksum is generated over the first \fISZ\fR
+bytes.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Print out a usage message the exit.
+.TP
+\fB\-q\fR, \fB\-\-quick\fR
+Perform a READ BUFFER descriptor command to find out the available data
+buffer length and offset, print them out then exit (without testing
+with write/read sequences).
+.TP
+\fB\-s\fR, \fB\-\-size\fR=\fISZ\fR
+where \fISZ\fR is the size of buffer in bytes to be written then read and
+checked. This number needs to be less than or equal to the size of the
+device's data buffer which can be seen from the \fI\-\-quick\fR option.
+Either this option or the \fI\-\-quick\fR option should be given.
+.TP
+\fB\-t\fR, \fB\-\-times\fR=\fINUM\fR
+where \fINUM\fR is the number of times to repeat the write/read to buffer
+test. Default value is 1 .
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase verbosity of output.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print version number (and data of last change) then exit.
+.SH NOTES
+The microcode in a SCSI device is _not_ modified by doing a WRITE BUFFER
+command with its mode set to "data" (0x2) as done by this utility. Therefore
+this utility is safe in that respect. [Mode values 0x4, 0x5, 0x6 and 0x7
+are the dangerous ones :\-)]
+.PP
+\fBWARNING\fR: If you access the device at the same time (e.g. because it's
+a hard disk with a mounted file system on it) the device's buffer may be
+used by the device itself for other data at the same time, and overwriting
+it may or may not cause data corruption! \fBHOWEVER\fR the SPC\-3 draft
+standard does state in its WRITE BUFFER command: "This command shall not
+alter any medium of the logical unit when data mode ... is specified". This
+implies that it _is_ safe to use this utility with devices that have mounted
+file systems on them.
+Following this theme further, a disk with active mounted file systems may
+cause the data read back to be different (due to caching activity) to what
+was written and hence a checksum error.
+.SH EXIT STATUS
+The exit status of sg_test_rwbuf is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by D. Gilbert and K. Garloff
+.SH COPYRIGHT
+Copyright \(co 2000\-2018 Douglas Gilbert, Kurt Garloff
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/sg_timestamp.8 b/doc/sg_timestamp.8
new file mode 100644
index 00000000..dbc1ef16
--- /dev/null
+++ b/doc/sg_timestamp.8
@@ -0,0 +1,155 @@
+.TH SG_TIMESTAMP "8" "April 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_timestamp \- report or set timestamp on SCSI device
+.SH SYNOPSIS
+.B sg_timestamp
+[\fI\-\-elapsed\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR]
+[\fI\-\-milliseconds=MS\fR] [\fI\-\-no\-timestamp\fR] [\fI\-\-origin\fR]
+[\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-seconds=SECS\fR] [\fI\-\-srep\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI REPORT TIMESTAMP or SET TIMESTAMP command to the \fIDEVICE\fR.
+These commands are found in the SPC\-5 draft standard revision
+7 (spc5r07.pdf).
+.PP
+If either the \fI\-\-milliseconds=MS\fR or \fI\-\-seconds=SECS\fR option is
+given (and both can't be given) then the SET TIMESTAMP command is sent;
+otherwise the REPORT TIMESTAMP command is sent.
+.PP
+The timestamp is sent and received from the \fIDEVICE\fR as the number of
+milliseconds since the epoch of 1970\-01\-01 00:00:00 UTC and is held in a 48
+bit unsigned integer. That same epoch is used by Unix machines, but they
+usually hold the number of seconds since that epoch. The Unix date command
+and especially its "+%s" format is useful in converting to and from
+timestamps and more humanly readable forms. See the EXAMPLES section below.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-e\fR, \fB\-\-elapsed\fR
+assume the timestamp in the REPORT TIMESTAMP is an elapsed time from an
+event such as a power cycle or hard reset and format the output as '<n>
+days hh:mm:ss.xxx' where hh is hours (00 to 23 inclusive); mm is
+minutes (00 to 59 inclusive); ss is seconds (00 to 59 inclusive) and xxx
+is milliseconds (000 to 999 inclusive). If the number of days is 0
+then '0 days' is not output unless this option is given two or more times.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response to REPORT TIMESTAMP in ASCII hexadecimal on stderr. The
+response is not decoded.
+.TP
+\fB\-m\fR, \fB\-\-milliseconds\fR=\fIMS\fR
+where \fIMS\fR is the number of milliseconds since 1970\-01\-01 00:00:00 UTC
+to set in the \fIDEVICE\fR with the SCSI SET TIMESTAMP command.
+.TP
+\fB\-N\fR, \fB\-\-no\-timestamp\fR
+when REPORT TIMESTAMP is called this option suppress the output of the
+timestamp value (in either seconds or milliseconds). This may be useful
+in uncluttering the output when trying to decode the timestamp origin (see
+the \fI\-\-origin\fR option).
+.TP
+\fB\-o\fR, \fB\-\-origin\fR
+the REPORT TIMESTAMP returned parameter data contains a "timestamp origin"
+field. When this option is given, that field is decoded and printed out
+before the timestamp value is output. The default action (i.e. when the
+option is not given) is not to print out this decoded field.
+.br
+T10 defines this field as "the most recent event that initialized the
+returned device clock". The value 0 indicates a power up of hard reset
+initialized the clock; 2 indicates a SET TIMESTAMP initialized the
+clock while 3 indicates some other method initialized the clock.
+.br
+When used once a descriptive string is output (in a line before the
+timestamp value). When used twice the value of the TIMESTAMP ORIGIN
+field is output (in decimal, a value between 0 and 7 inclusive). When
+used thrice a line of the form 'TIMESTAMP_ORIGIN=<value>' is output.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output the SCSI REPORT TIMESTAMP response (i.e. the data\-out buffer) in
+binary (to stdout). Note that the \fI\-\-origin\fR and \fI\-\-srep\fR
+options are ignored when this option is given. Also all error and
+verbose messages are output to stderr.
+.TP
+\fB\-R\fR, \fB\-\-readonly\fR
+open the \fIDEVICE\fR read\-only. The default action is to open the
+\fIDEVICE\fR read\-write.
+.TP
+\fB\-s\fR, \fB\-\-seconds\fR=\fISECS\fR
+where \fISECS\fR is the number of seconds since 1970\-01\-01 00:00:00 UTC
+to set in the \fIDEVICE\fR with the SCSI SET TIMESTAMP command. \fISECS\fR
+is multiplied by 1000 before being used in the SET TIMESTAMP command.
+.TP
+\fB\-S\fR, \fB\-\-srep\fR
+report the number of seconds since 1970\-01\-01 00:00:00 UTC. This is done
+by dividing by 1000 the value returned by the SCSI REPORT TIMESTAMP command.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH EXIT STATUS
+The exit status of sg_timestamp is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH NOTES
+The TCMOS and the SCSIP bits in the Control extension mode page (see sdparm)
+modify the actions of the timestamp held by a \fIDEVICE\fR.
+.PP
+Currently only the "Utilization usage rate based on date and time" parameters
+within the Utilization log page (sbc4r09.pdf) use timestamps. See the sg_logs
+utility. Vendor specific commands and pages may also be using timestamps.
+.SH EXAMPLES
+On Unix machines (e.g. Linux, FreeBSD and Solaris) the date command is useful
+when working with timestamps.
+.PP
+To fetch the timestamp from a \fIDEVICE\fR and display it in a humanly
+readable form the following could be used:
+.PP
+ # sg_timestamp \-S /dev/sdb
+.br
+1448993950
+.br
+ # date \-\-date=@1448993950
+.br
+Tue Dec 1 13:19:10 EST 2015
+.br
+ # date \-R \-\-date="@1448993950"
+.br
+Tue, 01 Dec 2015 13:19:10 \-0500
+.PP
+The latter two date commands show different forms of the same date (i.e.
+1448993950 seconds since 1970\-01\-01 00:00:00 UTC). The sg_timestamp and
+date commands can be combined using backquotes:
+.PP
+ # date \-R \-\-date=@`sg_timestamp \-S /dev/sdc`
+.br
+Wed, 16 Dec 2015 20:12:59 \-0500
+.PP
+To set the timestamp on the \fIDEVICE\fR to now (approximately) the
+following could be used:
+.PP
+ # date +%s
+.br
+1448993955
+.br
+ # sg_timestamp \-\-seconds=1448993955 /dev/sdb
+.PP
+Those two command lines could be combined into one by using backquotes:
+.PP
+ # sg_timestamp \-\-seconds=`date +%s` /dev/sdb
+.PP
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2015\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sdparm(sdparm), sg_logs(sg3_utils)
diff --git a/doc/sg_turs.8 b/doc/sg_turs.8
new file mode 100644
index 00000000..bec99f59
--- /dev/null
+++ b/doc/sg_turs.8
@@ -0,0 +1,152 @@
+.TH SG_TURS "8" "November 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_turs \- send one or more SCSI TEST UNIT READY commands
+.SH SYNOPSIS
+.B sg_turs
+[\fI\-\-delay=MS\fR] [\fI\-\-help\fR] [\fI\-\-low\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-number=NUM\fR] [\fI\-\-progress\fR] [\fI\-\-time\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.PP
+.B sg_turs
+[\fI\-d=MS\fR] [\fI\-n=NUM\fR] [\fI\-p\fR] [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility sends one or more SCSI TEST UNIT READY commands to the
+\fIDEVICE\fR. This may be useful for timing the per command overhead.
+Note that TEST UNIT READY has no associated data, just a 6 byte
+command (with each byte a zero) and a returned SCSI status value.
+.PP
+This utility supports two command line syntaxes, the preferred one is
+shown first in the synopsis and explained in this section. A later section
+on the old command line syntax outlines the second group of options.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-d\fR, \fB\-\-delay\fR=\fIMS\fR
+this option causes a delay of \fIMS\fR milliseconds to occur before each
+TEST UNIT READY command is issued.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+print out the usage message then exit.
+.TP
+\fB\-l\fR, \fB\-\-low\fR
+when [\fI\-\-progress\fR] is not being used, this utility tries to complete
+the SCSI TEST UNIT READY command(s) as quickly as possible. Usually it
+calls a library function to do each TUR (sg_ll_test_unit_ready). With this
+option it uses the lower level sg_pt interface (see sg_pt.h) to save a
+little time on each TUR.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1.
+These suffix multipliers are permitted: c C *1; w W *2; b B *512;
+k K KiB *1,024; KB *1,000; m M MiB *1,048,576; MB *1,000,000;
+g G GiB *1,073,741,824; and GB *1,000,000,000 . Also a suffix of the
+form "x<n>" multiplies the leading number by <n>. Alternatively a hex
+number may be given, prefixed by either '0x' or has a trailing 'h'.
+.TP
+\fB\-\-number\fR=\fINUM\fR
+same as \fI\-\-num=NUM\fR. Added for compatibility with sg_requests and
+other utilities in this package. The sg_request utility has taken over the
+role of polling the progress indication which was originally assigned to
+the TEST UNIT READY command. This is a change by T10.
+.TP
+\fB\-O\fR, \fB\-\-old\fR
+Switch to older style options. Please use as first option.
+.TP
+\fB\-p\fR, \fB\-\-progress\fR
+show progress indication (a percentage) if available. If \fI\-\-num=NUM\fR
+is given, \fINUM\fR is greater than 1 and an initial progress indication
+was detected then this utility waits 30 seconds before subsequent checks.
+If the \fI\-\-delay=MS\fR option is given then it will wait for that number
+of milliseconds instead of 30 seconds.
+Exits when \fINUM\fR is reached or there are no more progress indications.
+Ignores \fI\-\-time\fR option. See NOTES section below.
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+after completing the requested number of TEST UNIT READY commands, outputs
+the total duration and the average number of commands executed per second.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print version string then exit.
+.SH NOTES
+The progress indication is optionally part of the sense data. When a prior
+command that takes a long time to complete (and typically precludes other
+media access commands) is still underway, the progress indication can be used
+to determine how long before the device returns to its normal state. Around
+SPC\-3 T10 changed the preferred command for polling the progress indication
+from TEST UNIT READY to REQUEST SENSE (see the sg_requests utility).
+.PP
+The SCSI FORMAT command for disks used with the IMMED bit set is an example
+of an operation that takes a significant amount of time and precludes other
+media access during that time. The IMMED bit set instructs the FORMAT command
+to return control to the application client once the format has commenced (see
+SBC\-3). Several long duration SCSI commands associated with tape drives also
+use the progress indication (see SSC\-3).
+.PP
+The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the
+O_RDONLY flag).
+.PP
+Early standards suggested that the SCSI TEST UNIT READY command be used for
+polling the progress indication. More recent standards seem to suggest
+the SCSI REQUEST SENSE command should be used instead.
+.SH EXIT STATUS
+The exit status of sg_turs is 0 when it is successful (e.g. in the case of
+a mechanical disk, it is spun up and ready to accept IO commands). For this
+utility the other exit status values of interest are 2, 12 and 13. 12 is for
+the case when the sense key is "not ready" [0x2] and the additional sense
+code ends with "Target port in standby state" [0x4, 0xb]. 13 is for the
+case when the sense key is "not ready" [0x2] and the additional sense code
+is "Target port in unavailable state" [0x4, 0xc]. All other cases when
+the sense key is "not ready" [0x2] will set the exit status to 2.
+For other exit status values see the sg3_utils(8) man page.
+.SH OLDER COMMAND LINE OPTIONS
+The options in this section were the only ones available prior to sg3_utils
+version 1.23 . Since then this utility defaults to the newer command line
+options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the
+first option. See the ENVIRONMENT VARIABLES section for another way to
+force the use of these older command line options.
+.TP
+\fB\-d\fR, \fB\-\-delay\fR=\fIMS\fR
+this option causes a delay of \fIMS\fR milliseconds to occur before each
+TEST UNIT READY command is issued.
+.TP
+\fB\-n\fR=\fINUM\fR
+performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1.
+Equivalent to \fI\-\-num=NUM\fR in the main description.
+.TP
+\fB-N\fR, \fB\-\-new\fR
+Switch to the newer style options.
+.TP
+\fB\-p\fR
+show progress indication (a percentage) if available.
+Equivalent to \fI\-\-progress\fR in the main description.
+.TP
+\fB\-t\fR
+after completing the requested number of TEST UNIT READY commands, outputs
+the total duration and the average number of commands executed per second.
+Equivalent to \fI\-\-time\fR in the main description.
+.TP
+\fB\-v\fR
+increase level of verbosity.
+.TP
+\fB\-V\fR
+print out version string then exit.
+.SH ENVIRONMENT VARIABLES
+Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS
+can be given. When it is present this utility will expect the older command
+line options. So the presence of this environment variable is equivalent to
+using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
+.SH AUTHORS
+Written by D. Gilbert
+.SH COPYRIGHT
+Copyright \(co 2000\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq, sg_requests (sg3_utils)
diff --git a/doc/sg_unmap.8 b/doc/sg_unmap.8
new file mode 100644
index 00000000..b72ecb05
--- /dev/null
+++ b/doc/sg_unmap.8
@@ -0,0 +1,166 @@
+.TH SG_UNMAP "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_unmap \- send SCSI UNMAP command (known as 'trim' in ATA specs)
+.SH SYNOPSIS
+.B sg_unmap
+[\fI\-\-all=ST,RN[,LA]\fR] [\fI\-\-anchor\fR] [\fI\-\-dry\-run\fR]
+[\fI\-\-force\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] [\fI\-\-in=FILE\fR]
+[\fI\-\-lba=LBA,LBA...\fR] [\fI\-\-num=NUM,NUM...\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send a SCSI UNMAP command to \fIDEVICE\fR to unmap one or more logical
+blocks. This command was introduced in SBC\-3 revision 18 under the broad
+heading of "logical block provisioning". Logical blocks may also be unmapped
+by the SCSI WRITE SAME command; see the sg_write_same utility. The unmap
+capability is closely related to the ATA DATA SET MANAGEMENT command with
+the "Trim" bit set.
+.PP
+Logical blocks to be unmapped can be specified in one of three ways to this
+utility. One way is by supplying the start LBAs to the '\-\-lba=' option
+and the corresponding number(s) to unmap to the '\-\-num=' option. Another
+way is by putting start LBA and number to unmap pairs in a file whose name
+is given to the '\-\-in=' option. Alternatively a large segment or all of
+a disk (SSD) can be unmapped with the \fI\-\-all=ST_RN[,LA]\fR option. All
+values are assumed to be decimal unless prefixed by "0x" (or "0X") or have
+a trailing "h" (or "H") in which case they are interpreted as hexadecimal.
+Suffix multipliers are permitted on decimal values (e.g. '\-\-num=1m').
+.PP
+When the '\-\-lba=' option is given then the '\-\-num=' option must also be
+given. If one has a comma separated list as its argument then the other must
+have the same number of elements in its list. The arguments can use a single
+space as a separator but need to be in quotes or escaped to not be
+misinterpreted by the shell.
+.PP
+With the '\-\-in=FILE' option an even number of values must be found and are
+interpreted as pairs: the first value in each pair is a starting LBA and the
+second value is the number to unmap from that LBA. Everything from and
+including a "#" on a line is ignored as are blank lines. Values may be
+comma, space and tab separated or appear on separate lines. Each line should
+not exceed 1023 bytes in length.
+.PP
+Since a lot of data can be lost with this utility, a 15 second "cooling off"
+period is given before any UNMAP commands are sent. During this period the
+user is reminded what will happen, and to which device, so they can use
+control\-C (or some other technique) to terminate this utility before any
+unmapping takes place. This period can be bypassed with the \fI\-\-force\fR
+option.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-A\fR, \fB\-\-all\fR=\fIST,RN[,LA]\fR
+where \fIST\fR is the starting LBA, \fIRN\fR is the repeat number which is
+the maximum number of blocks in each SCSI UNMAP command, and \fILA\fR, if
+given, is the last LBA to unmap. If \fILA\fR is not given, then the last
+LBA on the \fIDEVICE\fR is used. That is obtained by the SCSI READ CAPACITY
+command.
+.TP
+\fB\-a\fR, \fB\-\-anchor\fR
+sets the 'Anchor' bit in the command (introduced in sbc3r22).
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+perform all the preparation, including opening \fIDEVICE\fR plus sending
+a 'standard' SCSI INQUIRY command (and optionally a READ CAPACITY), but
+exit before performing any SCSI UNMAP commands.
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+bypass the 15 second warning period that occurs before any UNMAP commands
+are sent.
+.TP
+\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
+sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero.
+\fIGN\fR should be a value between 0 and 63.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR
+where \fIFILE\fR is a file name containing pairs of values. The first
+member of each pair is a starting LBA and the second member of the
+pair is the number of logical blocks to unmap from and including that
+starting LBA. Values are interpreted as decimal unless indicated
+otherwise. This option cannot be present with the '\-\-lba=' option.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA,LBA...\fR
+where \fILBA,LBA...\fR is a string of comma (or space) separated values
+that are interpreted as starting logical block addresses. Each number
+is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a
+trailing 'h' or 'H'). An argument that contains any space separators needs
+to be quoted (or otherwise escaped). When this option is given then
+the '\-\-num=' option must also be given and they must contain the same
+number of elements in their arguments.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM,NUM...\fR
+where \fINUM,NUM...\fR is a string of comma (or space) separated values
+that are interpreted as a number of logical blocks to unmap. Each number
+is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a
+trailing 'h' or 'H'). Note that 0 blocks is acceptable. An argument that
+contains any space separators needs to be quoted (or otherwise escaped).
+When this option is given then the '\-\-lba=' option must also be given
+and they must contain the same number of elements in their arguments.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+where \fITO\fR is a timeout value (in seconds) for the UNMAP command.
+The default value is 60 seconds.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+Some limits: an LBA can be up to 64 bits, a NUM up to 32 bits (imposed
+by structure of UNMAP SCSI command parameter data). The NUM is
+further constrained by the MAXIMUM UNMAP LBA COUNT field in the
+BLOCK LIMITS VPD page (0xb0). The maximum number of LBA,NUM pairs is
+limited to 128 by this utility and may be further constrained by the
+MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT field in the BLOCK LIMITS VPD
+page.
+.PP
+Since it is unclear how long the UNMAP command will take to execute
+a '\-\-timeout=" option has been provided. The default timeout
+period is 60 seconds. If all the logical blocks on a logical unit (e.g.
+a disk drive) are to be unmapped then the FORMAT UNIT SCSI command (see
+the sg_format utility) may be considered as an alternative.
+.PP
+Support for logical block provisioning is indicated by the LBPME bit in the
+response to the SCSI READ CAPACITY (16) command (see the sg_readcap utility).
+.PP
+In SBC\-3 revision 25 the LBPU and ANC_SUP bits where added to the
+Logical Block Provisioning VPD page. When LBPU is set it indicates that
+the device supports the UNMAP command. When the ANC_SUP bit is set it
+indicates the device supports anchored LBAs.
+.PP
+The SCSI UNMAP command does the "right thing" with respect to command
+queueing. However its ATA counterpart: the DATA SET MANAGEMENT command with
+the "Trim" bit set does not interact well with SATA queueing known as NCQ.
+To address this problem T13 have introduced a new command called SFQ DATA SET
+MANAGEMENT which also has a Trim bit.
+.SH EXAMPLES
+In the examples directory of the sg3_utils package there is a
+sg_unmap_example.txt file that shows the format that the '\-\-in='
+option accepts.
+.PP
+To unmap all blocks from and including LBA 0x2000 to the end of the
+device (e.g. disk or SSD) with each SCSI UNMAP command given 1024
+blocks to unmap:
+.PP
+ sg_unmap \-\-all=0x2000,1k /dev/sg2
+.PP
+Add '\-\-force' to bypass the 15 seconds of warnings. So '\-\-force' is
+appropriate for batch files.
+.SH EXIT STATUS
+The exit status of sg_unmap is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2009\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_format,sg_get_lba_status,sg_readcap,sg_vpd,sg_write_same(sg3_utils)
diff --git a/doc/sg_verify.8 b/doc/sg_verify.8
new file mode 100644
index 00000000..dae36042
--- /dev/null
+++ b/doc/sg_verify.8
@@ -0,0 +1,219 @@
+.TH SG_VERIFY "8" "December 2019" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_verify \- invoke SCSI VERIFY command(s) on a block device
+.SH SYNOPSIS
+.B sg_verify
+[\fI\-\-0\fR] [\fI\-\-16\fR] [\fI\-\-bpc=BPC\fR] [\fI\-\-count=COUNT\fR]
+[\fI\-\-dpo\fR] [\fI\-\-ff\fR] [\fI\-\-ebytchk=BCH\fR] [\fI\-\-group=GN\fR]
+[\fI\-\-help\fR] [\fI\-\-in=IF\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-ndo=NDO\fR]
+[\fI\-\-quiet\fR] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-vrprotect=VRP\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends one or more SCSI VERIFY (10 or 16) commands to \fIDEVICE\fR. These SCSI
+commands are defined in the SBC\-2 and SBC\-3 standards at https://www.t10.org
+and SBC\-4 drafts.
+.PP
+When \fI\-\-ndo=NDO\fR is not given then the verify starts at the logical
+block address given by the \fI\-\-lba=LBA\fR option and continues for
+\fI\-\-count=COUNT\fR blocks. No more than \fI\-\-bpc=BPC\fR blocks are
+verified by each VERIFY command so if necessary multiple VERIFY commands are
+sent. Medium verification operations are performed by the \fIDEVICE\fR (e.g.
+assuming each block has additional EEC data, check this against the logical
+block contents). No news is good news (i.e. if there are no verify errors
+detected then no messages are sent to stderr and the Unix exit status is 0).
+.PP
+When \fI\-\-ndo=NDO\fR is given then the \fI\-\-bpc=BPC\fR option is
+ignored. A single VERIFY command is issued and a comparison starts at the
+logical block address given by the \fI\-\-lba=LBA\fR option and continues for
+\fI\-\-count=COUNT\fR blocks. The VERIFY command has an associated data\-out
+buffer that is \fINDO\fR bytes long. The contents of the data\-out buffer are
+obtained from the \fIFN\fR file (if \fI\-\-in=FN\fR is given) or from stdin.
+A comparison takes place between data\-out buffer and the logical blocks
+on the \fIDEVICE\fR. If the comparison is good then no messages are sent to
+stderr and the Unix exit status is 0. If the comparison fails then a sense
+buffer with a sense key of MISCOMPARE is returned; in this case the Unix exit
+status will be 14. Messages will be sent to stderr associated with MISCOMPARE
+sense buffer unless the \fI\-\-quiet\fR option is given.
+.PP
+In SBC\-3 revision 34 the BYTCHK field in all SCSI VERIFY commands was
+expanded from one to two bits. That required some changes in the options
+of this utility, see the section below on OPTION CHANGES.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-0\fR, \fB\-\-0\fR
+a buffer \fINDO\fR bytes long full of zeros is sent as the data\-out
+part of a VERIFY command. So stdin is not read and if \fI\-\-in=IF\fR
+is given, an error is generated. Useful when \fIBCH\fR is 3 to check
+if some or all of \fIDEVICE\fR (e.g. a disk) is zero filled blocks.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+uses a VERIFY(16) command (default VERIFY(10)). Even without this option,
+using an \fI\-\-lba=LBA\fR which is too large, will cause the utility
+to issue a VERIFY(16) command.
+.TP
+\fB\-b\fR, \fB\-\-bpc\fR=\fIBPC\fR
+this option is ignored if \fI\-\-ndo=NDO\fR is given. Otherwise \fIBPC\fR
+specifies the maximum number of blocks that will be verified by a single SCSI
+VERIFY command. The default value is 128 blocks which equates to 64 KB for a
+disk with 512 byte blocks. If \fIBPC\fR is less than \fICOUNT\fR then
+multiple SCSI VERIFY commands are sent to the \fIDEVICE\fR. For the default
+VERIFY(10) \fIBPC\fR cannot exceed 0xffff (65,535) while for VERIFY(16)
+\fIBPC\fR cannot exceed 0x7fffffff (2,147,483,647). For recent block
+devices (disks) this value may be constrained by the maximum transfer length
+field in the block limits VPD page.
+.TP
+\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR
+where \fICOUNT\fR specifies the number of blocks to verify. The default value
+is 1 . If \fICOUNT\fR is greater than \fIBPC\fR (or its default value of 128)
+and \fINDO\fR is not given, 0 or less than multiple SCSI VERIFY commands are
+sent to the device. Otherwise \fICOUNT\fR becomes the contents of the
+verification length field of the SCSI VERIFY command issued. The
+.B sg_readcap
+utility can be used to find the maximum number of blocks that a block
+device (e.g. a disk) has.
+.TP
+\fB\-d\fR, \fB\-\-dpo\fR
+disable page out changes the cache retention priority of blocks read on
+the device's cache to the lowest priority. This means that blocks read by
+other commands are more likely to remain in the device's cache.
+.TP
+\fB\-E\fR, \fB\-\-ebytchk\fR=\fIBCH\fR
+sets the BYTCHK field to \fIBCH\fR overriding the value (1) set by the
+\fI\-\-ndo=NDO\fR option. Values of 1, 2 or 3 are accepted for \fIBCH\fR
+however sbc3r34 reserves the value 2. If this option is given then
+\fI\-\-ndo=NDO\fR must also be given. If \fIBCH\fR is 3 then \fINDO\fR
+should be the size of one logical block (plus the size of some or all
+of the protection information if \fIVRP\fR is greater
+than 0).
+.TP
+\fB\-f\fR, \fB\-\-ff\fR
+a buffer \fINDO\fR bytes long full of 0xff bytes is sent as the data\-out
+part of a VERIFY command. So stdin is not read and if \fI\-\-in=IF\fR
+is given, an error is generated. Useful when \fIBCH\fR is 3 to check
+if some or all of \fIDEVICE\fR (e.g. a disk) is 0xff byte filled blocks.
+.TP
+\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR
+where \fIGN\fR becomes the contents of the group number field in the SCSI
+VERIFY(16) command. It can be from 0 to 63 inclusive. The default value for
+\fIGN\fR is 0. Note that this option is ignored for the SCSI VERIFY(10)
+command.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+where \fIIF\fR is the name of a file from which \fINDO\fR bytes will be read
+and placed in the data\-out buffer. This is only done when the
+\fI\-\-ndo=NDO\fR option is given. If this option is not given then stdin
+is read. If \fIIF\fR is "\-" then stdin is also used.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR specifies the logical block address of the first block to
+start the verify operation. \fILBA\fR is assumed to be decimal unless prefixed
+by '0x' or a trailing 'h' (see below). The default value is 0 (i.e. the start
+of the device).
+.TP
+\fB\-n\fR, \fB\-\-ndo\fR=\fINDO\fR
+\fINDO\fR is the number of bytes to obtain from the \fIFN\fR file (if
+\fI\-\-in=FN\fR is given) or from stdin. Those bytes are placed in the
+data\-out buffer associated with the SCSI VERIFY command and \fINDO\fR
+is placed in the verification length field in the cdb. The default value
+for \fINDO\fR is 0 and the maximum value is dependent on the OS. If the
+\fI\-\-ebytchk=BCH\fR option is not given then the BYTCHK field in the cdb
+is set to 1.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+suppress the sense buffer messages associated with a MISCOMPARE sense key
+that would otherwise be sent to stderr. Still set the exit status to 14
+which is the sense key value indicating a MISCOMPARE .
+.TP
+\fB\-r\fR, \fB\-\-readonly\fR
+opens the DEVICE read\-only rather than read\-write which is the
+default. The Linux sg driver needs read\-write access for the SCSI
+VERIFY command but other access methods may require read\-only access.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-P\fR, \fB\-\-vrprotect\fR=\fIVRP\fR
+where \fIVRP\fR is the value in the vrprotect field in the VERIFY command
+cdb. It must be a value between 0 and 7 inclusive. The default value is
+zero.
+.SH BYTCHK
+BYTCHK is the name of a field (two bits wide) in the VERIFY(10) and
+VERIFY(16) commands. When set to 1 or 3 (sbc3r34 reserves the value 2) it
+indicates that associated with the SCSI VERIFY command, a data\-out buffer
+will be sent for the device (disk) to check. Using the \fI\-\-ndo=NDO\fR
+option sets the BYTCHK field to 1 and \fINDO\fR is the number of bytes
+placed in the data\-out buffer. Those bytes are obtained from stdin or
+\fIIF\fR (from the \fI\-\-in=FN\fR option). The \fI\-\-ebytchk=BCH\fR
+option may be used to override the BYTCHK field value of 1 with \fIBCH\fR.
+.PP
+The calculation of \fINDO\fR is left up to the user. Its value depends
+on the logical block size (which can be found with the sg_readcap utility),
+the \fICOUNT\fR and the \fIVRP\fR values. If the \fIVRP\fR is greater than
+0 then each logical block will contain an extra 8 bytes (at least) of
+protection information.
+.PP
+When the BYTCHK field is 0 then the verification process done by the
+device (disk) is vendor specific. It typically involves checking each
+block on the disk against its error correction codes (ECC) which is
+additional data also held on the disk.
+.PP
+Many Operating Systems put limits on the maximum size of the
+data\-out (and data\-in) buffer. For Linux at one time the limit was
+less than 1 MB but has been increased somewhat.
+.SH OPTION CHANGES
+Earlier versions of this utility had a \fI\-\-bytchk=NDO\fR option which
+set the BYTCHK bit and set the cdb verification length field to \fINDO\fR.
+The shorter form of that option was \fI\-B NDO\fR. For backward
+compatibility that option is still present but not documented. In its place
+is the \fI\-\-ndo=NDO\fR whose shorter form of \fI\-n NDO\fR.
+\fI\-\-ndo=NDO\fR sets the BYTCHK field to 1 unless that is overridden by
+the \fI\-\-ebytchk=BCH\fR.
+.SH NOTES
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The amount of error correction and the number of retries attempted before a
+block is considered defective are controlled in part by the Verify Error
+Recovery mode page. A note in the SBC\-3 draft (rev 29 section 6.4.9 on the
+Verify Error Recovery mode page) advises that to minimize the number of
+checks (and hence have the most "sensitive" verify check) do the following
+in that mode page: set the EER bit to 0, the PER bit to 1, the DTE bit to 1,
+the DCR bit to 1, the verify retry count to 0 and the verify recovery time
+limit to 0. Mode pages can be modified with the
+.B sdparm
+utility.
+.PP
+The SCSI VERIFY(6) command defined in the SSC\-2 standard and later (i.e.
+for tape drive systems) is not supported by this utility.
+.SH EXIT STATUS
+The exit status of sg_verify is 0 when it is successful. When \fIBCH\fR is
+other than 0 then a comparison takes place and if it fails then the exit
+status is 14 which happens to be the sense key value of MISCOMPARE.
+Otherwise see the EXIT STATUS section in the sg3_utils(8) man page.
+.PP
+Earlier versions of this utility set an exit status of 98 when there was a
+MISCOMPARE.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2019 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sdparm(sdparm), sg_modes(sg3_utils), sg_readcap(sg3_utils),
+.B sg_inq(sg3_utils)
diff --git a/doc/sg_vpd.8 b/doc/sg_vpd.8
new file mode 100644
index 00000000..8bb88eef
--- /dev/null
+++ b/doc/sg_vpd.8
@@ -0,0 +1,365 @@
+.TH SG_VPD "8" "August 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_vpd \- fetch SCSI VPD page and/or decode its response
+.SH SYNOPSIS
+.B sg_vpd
+[\fI\-\-all\fR] [\fI\-\-enumerate\fR] [\fI\-\-examine\fR] [\fI\-\-force\fR]
+[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-ident\fR] [\fI\-\-inhex=FN\fR]
+[\fI\-\-json[=JO]\fR] [\fI\-\-long\fR] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-page=PG\fR] [\fI\-\-quiet\fR] [\fI\-\-raw\fR]
+[\fI\-\-sinq_inraw=RFN\fR] [\fI\-\-vendor=VP\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fIDEVICE\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility, when \fIDEVICE\fR is given, fetches a Vital Product Data (VPD)
+page and decodes it or outputs it in ASCII hexadecimal or binary. VPD pages
+are fetched with a SCSI INQUIRY command.
+.PP
+Alternatively the \fI\-\-inhex=FN\fR option can be given. In this case
+\fIFN\fR is assumed to be a file name ('\-' for stdin) containing ASCII
+hexadecimal representing a VPD page response. If the \fI\-\-raw\fR option
+is also given then binary input is assumed (rather than ASCII hexadecimal).
+.PP
+Probably the most important page is the Device Identification
+VPD page (page number: 0x83). Since SPC\-3, support for this page
+has been flagged as mandatory. This page can be fetched by
+using the \fI\-\-ident\fR option.
+.PP
+The reference document used for interpreting VPD pages (and the INQUIRY
+standard response) is T10/BSR INCITS 566 Revision 6 which is draft SPC\-6
+dated 22 October 2021. It can be found at https://www.t10.org .
+.PP
+When no options are given, other than a \fIDEVICE\fR, then the "Supported
+VPD pages" (0x0) VPD page is fetched and decoded.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+decode all VPD pages. When used with \fIDEVICE\fR the pages to be decoded
+are found in the "Supported VPD pages" VPD page. Pages that cannot be
+decoded are displayed in hex; add the \fI\-\-long\fR option to have ASCII
+displayed to the right of each line of hex.
+.br
+If this option is used with the \fI\-\-inhex=FN\fR option then the file
+\fIFN\fR is assumed to contain 1 or more VPD pages (in ASCII hex or binary).
+Decoding continues until the file is exhausted (or an error occurs). Sanity
+checks are applied on each VPD page's length and the ascending order of VPD
+page numbers (required by SPC\-4) so bad data may be detected.
+.br
+If the \fI\-\-page=PG\fR option is also given then no VPD page whose page
+number is greater than \fIPG\fR (or its numeric equivalent) is decoded.
+.TP
+\fB\-e\fR, \fB\-\-enumerate\fR
+list the names of the known VPD pages, first the standard pages (i.e.
+those defined by T10), then the vendor specific pages. Each group is sorted
+in abbreviation order. The \fIDEVICE\fR and most other options are ignored
+and this utility exits after listing the VPD page names. May be used together
+with \fI\-\-page=PG\fR where \fIPG\fR is numeric. If so, it searches for the
+summary lines of all VPD pages whose number matches \fIPG\fR. May be used
+with \fI\-\-vendor=VP\fR to restrict output to known vendor specific pages
+for vendor/product \fIVP\fR.
+.TP
+\fB\-E\fR, \fB\-\-examine\fR
+scan part of all of the VPD space (page numbers 0x0 to 0xff) and output any
+pages found. If this option is given once, the scan starts at page 0x80;
+if it is given twice, the scan starts at 0x0; and if given three times the
+scan starts at 0xc0. This option takes no notice of the contents of VPD page
+0x0 which should contain a list of all supported VPD pages. Some vendors
+either forget to list some standard pages or perhaps purposely don't list
+vendor specific pages which are in the range 0xc0 to 0xff.
+.br
+If the \fI\-\-page=PG\fR option is not given then the scan finishes at page
+0xff. if the \fI\-\-page=PG\fR option is given then the scan finishes at
+page \fIPG\fR. A check is made before the scan to make sure the start page
+is less than or equal to the finish page; if not the start and finish page
+numbers are swapped.
+.br
+The sdparm utility which lists mode and VPD pages also has a \fB\-\-examine\fR
+option will similar functionility. Note that T10 has changed most of the
+pages that list supported pages (e.g. VPD, mode and log pages; supported
+commands) to add the weasel words "may or may not list all ...".
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+As a sanity check, the normal action when fetching VPD pages other than
+page 0x0 (the "Supported VPD pages" VPD page), is to first fetch page 0x0
+and only if the requested page is one of the supported pages, to go ahead
+and fetch the requested page.
+.br
+When this option is given, skip checking of VPD page 0x0 before accessing
+the requested VPD page. The prior check of VPD page 0x0 is known to
+crash certain USB devices, so use with care.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs the usage message summarizing command line options then exits.
+Ignores \fIDEVICE\fR if given.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+outputs the requested VPD page in ASCII hexadecimal. Can be used multiple
+times, see section on the ATA information vpd page.
+.br
+To generate output suitable for placing in a file that can be used by a
+later invocation with the \fI\-\-inhex=FN\fR option, use the '\-HHHH'
+option (e.g. 'sg_vpd \-p di \-HHHH /dev/sg3 > dev_id.hex'). The
+reason '\-HHHH' is used is to flag that unadorned hexadecimal (without other
+text or address offsets) is sent to stdout.
+.TP
+\fB\-i\fR, \fB\-\-ident\fR
+decode the device identification (0x83) VPD page. When used once this option
+has the same effect as '\-\-page=di'. When use twice then the short form of
+the device identification VPD page's logical unit designator is decoded. In
+the latter case this option has the same effect as '\-\-quiet \-\-page=di_lu'.
+.TP
+\fB\-I\fR, \fB\-\-inhex\fR=\fIFN\fR
+\fIFN\fR is expected to be a file name (or '\-' for stdin) which contains
+ASCII hexadecimal or binary representing a VPD page (or a standard INQUIRY)
+response. This utility will then decode that response. It is preferable to
+also supply the \fI\-\-page=PG\fR option, if not this utility will attempt
+to guess which VPD page (or standard INQUIRY) the response is associated
+with. The hexadecimal should be arranged as 1 or 2 digits representing a
+byte each of which is whitespace or comma separated. Anything from and
+including a hash mark to the end of line is ignored. If the \fI\-\-raw\fR
+option is also given then \fIFN\fR is treated as binary.
+.TP
+\fB\-j\fR, \fB\-\-json[\fR=\fIJO\fR]
+output is in JSON format instead of human readable form. See sg3_utils_json
+manpage or use '?' for \fIJO\fR for a summary.
+.TP
+\fB\-l\fR, \fB\-\-long\fR
+when decoding some VPD pages, give a little more output. For example the ATA
+Information VPD page only shows the signature (in hex) and the IDENTIFY
+(PACKET) DEVICE (in hex) when this option is given.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in the
+cdb's "allocation length" field. If not given (or \fILEN\fR is zero) then
+252 is used (apart from the ATA Information VPD page which defaults to 572)
+and, if the response indicates this value is insufficient, another INQUIRY
+command is sent with a larger value in the cdb's "allocation length" field.
+If this option is given and \fILEN\fR is greater than 0 then only one INQUIRY
+command is sent. Since many simple devices implement the INQUIRY command
+badly (and do not support VPD pages) then the safest value to use for
+\fILEN\fR is 36. See the sg_inq(8) man page for the more information.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR
+where \fIPG\fR is the VPD page to be decoded or output. The \fIPG\fR argument
+can either be an abbreviation, a number or a pair or numbers/abbreviations
+separated by a comma. The VPD page abbreviations can be seen by using the
+\fI\-\-enumerate\fR option. If a number is given it is assumed to be decimal
+unless it has a hexadecimal indicator which is either a leading '0x' or a
+trailing 'h'. If one number is given then it is assumed to be a VPD page
+number. If two numbers (or abbreviations) are given then the second one is
+the same as \fIVP\fR (see the \fI\-\-vendor=VP\fR option). If this option
+is not given (nor '\-i', '\-l' nor '\-V') then the "Supported VPD pages" (0x0)
+VPD page is fetched and decoded. If \fIPG\fR is '\-1' or 'sinq' then the
+standard INQUIRY response is output. This option may also be used with the
+\fI\-\-enumerate\fR (see its description).
+.br
+If \fIPG\fR is not found in the 'Supported VPD pages' VPD page (0x0) then
+EDOM is returned. To bypass this check use the \fI\-\-force\fR option.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+suppress the amount of decoding output.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+if not used with \fI\-\-inhex=FN\fR then output requested VPD page in binary.
+The output should be piped to a file or another utility when this option is
+used. The binary is sent to stdout, and errors are sent to stderr.
+.br
+if used with \fI\-\-inhex=FN\fR then the contents of \fIFN\fR is treated as
+binary.
+.TP
+\fB\-Q\fR, \fB\-\-sinq_inraw\fR=\fIRFN\fR
+where \fIRFN\fR is a filename containing binary standard INQUIRY response
+data that matches either \fIDEVICE\fR or \fIFN\fR. Linux places this
+standard INQUIRY response in its sysfs pseudo filesystem. A typical
+location is at /sys/class/scsi_disk/<hctl>/device/inquiry where <hctl> is
+a four part numeric tuple separated by colons. This tuple distinguishes
+the device from any others on the system. Linux also places some VPD page
+responses in binary in the same directory with names like "vpd_pg83" where
+the last two digits form the hexadecimal VPD page number whose binary
+contents are therein.
+.br
+Some VPD pages (e.g. the Extended Inquiry VPD page) depend on knowing
+the settings in the standard INQUIRY response to interpret the fields
+in that VPD page. This option together with the \fI\-\-all\fR,
+\fI\-\-examine\fR or \fI\-\-page=PG\fR allows this utility to process
+both the standard INQUIRY response and VPD pages in the same invocation.
+.TP
+\fB\-M\fR, \fB\-\-vendor\fR=\fIVP\fR
+where \fIVP\fR is a vendor (e.g. "sea" for Seagate) or vendor/product
+acronym (e.g. "hp3par" for the 3PAR array from HP). Many vendors have
+re\-used the numbers at the beginning of the vendor specific VPD page
+range (e.g. page 0xc0) and this option is a way of selecting only those
+which are of interest. Using a \fIVP\fR of "xxx" will list the available
+acronyms.
+.br
+If this option is used with \fI\-\-page=PG\fR and \fIPG\fR is an acronym
+then this option is ignored. If \fIPG\fR is a number (e.g. 0xc0) then
+\fIVP\fR is used to choose the which vendor specific page (e.g. sharing
+page number 0xc0) to decode.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increases the level or verbosity.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print out version string then exit.
+.SH ATA INFORMATION VPD PAGE
+This VPD page (0x89 or 'ai') is defined by the SCSI to ATA Translation
+standard. It contains information about the SAT layer, the "signature" of
+the ATA device and the response to the ATA IDENTIFY (PACKET) DEVICE
+command. The latter part has 512 bytes of identity, capability and
+settings data which the hdparm utility is capable of decoding (so this
+utility doesn't decode it).
+.PP
+To unclutter the output for this page, the signature and the IDENTIFY (PACKET)
+DEVICE response are not output unless the \fI\-\-long\fR option (or
+\fI\-\-hex\fR or \fI\-\-raw\fR) are given. When the \fI\-\-long\fR option
+is given the IDENTIFY (PACKET) DEVICE response is output as 256 (16 bit)
+words as is the fashion for ATA devices. To see that response as a string of
+bytes use the '\-HH' option. To format the output suitable for hdparm to
+decode use either the '\-HHH' or '\-rr' option. For example if 'dev/sdb' is
+a SATA disk behind a SAT layer then this
+command: 'sg_vpd \-p ai \-HHH /dev/sdb | hdparm \-\-Istdin'
+should decode the ATA IDENTIFY (PACKET) DEVICE response.
+.SH NOTES
+Since some VPD pages (e.g. the Extended INQUIRY page) depend on settings
+in the standard INQUIRY response, then the standard INQUIRY response is
+output as a pseudo VPD page when \fIPG\fR is set to '\-1' or 'sinq'. Also
+the decoding of some fields (e.g. the Extended INQUIRY page's SPT field)
+is expanded when the '\-\-long' option is given using the standard INQUIRY
+response information (e.g. the PDT and the PROTECT fields).
+.PP
+The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the
+O_RDONLY flag).
+.SH EXIT STATUS
+The exit status of sg_vpd is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH EXAMPLES
+The examples in this page use Linux device names. For suitable device
+names in other supported Operating Systems see the sg3_utils(8) man page.
+.PP
+To see the VPD pages that a device supports, use with no options. The
+command line invocation is shown first followed by a typical response:
+.PP
+ # sg_vpd /dev/sdb
+.br
+Supported VPD pages VPD page:
+.br
+ Supported VPD pages [sv]
+.br
+ Unit serial number [sn]
+.br
+ Device identification [di]
+.br
+ Extended inquiry data [ei]
+.br
+ Block limits (SBC) [bl]
+.PP
+To see the VPD page numbers associated with each supported page then
+add the '\-\-long' option to the above command line. To view a
+VPD page either its number or abbreviation can be given to
+the '\-\-page=' option. The page name abbreviations are shown within
+square brackets above. In the next example the Extended inquiry data
+VPD page is listed:
+.PP
+ # sg_vpd \-\-page=ei /dev/sdb
+.br
+extended INQUIRY data VPD page:
+.br
+ ACTIVATE_MICROCODE=0 SPT=0 GRD_CHK=0 APP_CHK=0 REF_CHK=0
+.br
+ UASK_SUP=0 GROUP_SUP=0 PRIOR_SUP=0 HEADSUP=1 ORDSUP=1 SIMPSUP=1
+.br
+ WU_SUP=0 CRD_SUP=0 NV_SUP=0 V_SUP=0
+.br
+ P_I_I_SUP=0 LUICLR=0 R_SUP=0 CBCS=0
+.br
+ Multi I_T nexus microcode download=0
+.br
+ Extended self\-test completion minutes=0
+.br
+ POA_SUP=0 HRA_SUP=0 VSA_SUP=0
+.PP
+To check if any protection types are supported by a disk use the '\-\-long'
+option on the Extended inquiry data VPD page:
+.PP
+ # sg_vpd \-\-page=ei \-\-long /dev/sdb
+.br
+ extended INQUIRY data VPD page:
+.br
+ ACTIVATE_MICROCODE=0
+.br
+ SPT=1 [protection types 1 and 2 supported]
+.br
+ GRD_CHK=1
+.br
+ ....
+.PP
+Search for the name (and acronym) of all pages that share VPD page number
+0xb0 .
+.PP
+ # sg_vpd \-\-page=0xb0 \-\-enumerate
+.br
+ Matching standard VPD pages:
+.br
+ bl 0xb0 Block limits (SBC)
+.br
+ oi 0xb0 OSD information
+.br
+ sad 0xb0 Sequential access device capabilities (SSC)
+.PP
+Some examples follow using the "\-\-all" option. Send an ASCII hexadecimal
+representation of all VPD pages to a file:
+.PP
+ # sg_vpd \-\-all \-HHHH /dev/sg3 > all_vpds.hex
+.PP
+At some later time that file could be decoded with:
+.PP
+ # sg_vpd \-\-all \-\-inhex=all_vpds.hex
+.PP
+To do the equivalent as the previous example but use a file containing
+binary:
+.PP
+ # sg_vpd \-\-all \-\-raw /dev/sg3 > all_vpds.bin
+.br
+ # sg_vpd \-\-all \-\-raw \-\-inhex=all_vpds.bin
+.PP
+Notice that "\-\-raw" must be given with the second (\-\-inhex) invocation
+to alert the utility that all_vpds.bin contains binary as it assumes ASCII
+hexadecimal by default. Next we only decode T10 specified VPD pages
+excluding vendor specific VPD pages that start at page number 0xc0:
+.PP
+ # sg_vpd \-\-all \-\-page=0xbf \-\-raw \-\-inhex=all_vpds.bin
+.PP
+In Linux, binary images of some important VPD page responses (e.g. 0, 80h
+and 83h) are cached in files within the sysfs pseudo file system. Since
+VPD pages hardly ever change their contents, decoding those files will
+give the same output as probing the device with the added benefit that
+decoding those files doesn't need root permissions. The long and short
+forms are shown:
+.PP
+ sg_vpd \-\-raw \-\-inhex=/sys/class/scsi_generic/sg3/device/vpd_pg83
+.PP
+ sg_vpd \-rI /sys/class/scsi_generic/sg3/device/vpd_pg83
+.PP
+If /dev/sg3 is a disk at 2:0:0:0 , then this invocation should give more
+verbose output but essentially the same as the previous two examples.
+.PP
+ sg_vpd \-v \-r \-I /sys/class/scsi_disk/2:0:0:0/device/vpd_pg83
+.PP
+Further examples can be found on the https://sg.danny.cz/sg/sg3_utils.html
+web page.
+.SH AUTHOR
+Written by Douglas Gilbert
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_inq(sg3_utils), sg3_utils(sg3_utils), sdparm(sdparm), hdparm(hdparm)
diff --git a/doc/sg_wr_mode.8 b/doc/sg_wr_mode.8
new file mode 100644
index 00000000..e917c76e
--- /dev/null
+++ b/doc/sg_wr_mode.8
@@ -0,0 +1,225 @@
+.TH SG_WR_MODE "8" "April 2018" "sg3_utils\-1.43" SG3_UTILS
+.SH NAME
+sg_wr_mode \- write (modify) SCSI mode page
+.SH SYNOPSIS
+.B sg_wr_mode
+[\fI\-\-contents=H,H...\fR] [\fI\-\-dbd\fR] [\fI\-\-force\fR] [\fI\-\-help\fR]
+[\fI\-\-len=10|6\fR] [\fI\-\-mask=M,M...\fR] [\fI\-\-page=PG_H[,SPG_H]\fR]
+[\fI\-\-rtd\fR] [\fI\-\-save\fR] [\fI\-\-six\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Writes a modified mode page to \fIDEVICE\fR. Uses the SCSI MODE SENSE (6
+or 10 byte variant) command to fetch the existing mode data which includes
+a mode page (or subpage). It then combines that with the contents,
+potentially masked, and writes the modified mode page with the SCSI MODE
+SELECT (6 or 10 byte variant) command. This utility does not modify
+the block descriptor(s); if any block descriptors are fetched by the MODE
+SENSE command then the same block descriptors are written back with the
+following MODE SELECT command.
+.PP
+If the \fI\-\-rtd\fR option is given then most other options apart from
+\fI\-\-save\fR, \fI\-\-len=\fR10|6\fR and \fI\-\-six\fR are ignored. In this
+case only a MODE SELECT command is sent to the \fIDEVICE\fR with the RTD
+bit (Revert To Defaults) set. This bit was added to this command in SPC\-5
+revision 11, so older devices may not support it. The Extended Inquiry VPD
+page has the RTD_SUP bit to indicate whether the \fIDEVICE\fR supports the
+RTD bit in the MODE SELECT(6 and 10) commands. When the \fI\-\-rtd\fR option
+is given the rest of this section can be ignored.
+.PP
+If a contents argument is not given then the various components (i.e.
+header, block descriptor(s) and mode page) of the "current" values of
+the existing mode page are printed out. In this case the mode page is
+not altered on the device.
+.PP
+If the contents are specified, and a mask is not specified, then the contents
+must match the existing mode page in various aspects unless the
+\fI\-\-force\fR option is given. These include length, mode page code and
+subpage code if applicable. If all is well then the contents string is
+written to \fIDEVICE\fR as the new mode page.
+.PP
+If both contents and mask strings are specified then only bit positions
+in the contents corresponding to set bits in the mask are taken while the
+existing mode page supplies bit positions corresponding to clear bits.
+When a mask is given then the mask and/or the contents may be shorter
+than the existing mode page. If the mask is shorter than the contents then
+the remaining bytes are taken from the contents. If the contents are shorter
+than the existing mode page then the remaining bytes are taken from the
+existing mod page.
+.PP
+The force option allows the contents string to be written as the new
+mode page without any prior checks on the existing mode page. This should
+only be required for vendor specific mode pages. The existing mode data
+is ignored apart from the block descriptors which can be suppressed with
+the \fI\-\-dbd\fR option if need be.
+.PP
+Changing individual fields in a mode page is probably more easily done
+with the sdparm utility. Fields can be identified by acronym or by a
+numerical descriptor.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-c\fR, \fB\-\-contents\fR=\fIH,H...\fR
+where \fIH,H...\fR is a string of comma separated hex numbers each of
+which should resolve to a byte value (i.e. 0 to ff inclusive). A (single)
+space separated string of hex numbers is also allowed but the list needs to
+be in quotes. This is the new contents of the mode page to be written to
+\fIDEVICE\fR, potentially filtered by the mask string.
+.TP
+\fB\-c\fR, \fB\-\-contents\fR=-
+reads contents string from stdin. The hex numbers in the string may be comma,
+space, tab or linefeed (newline) separated. If a line contains "#" then the
+remaining characters on that line are ignored. Otherwise each non separator
+character should resolve to a byte value (i.e. 0 to ff inclusive). This
+forms the new contents of the mode page to be written to \fIDEVICE\fR,
+potentially filtered by the mask string.
+.TP
+\fB\-d\fR, \fB\-\-dbd\fR
+disable block descriptors (DBD flag in cdb). Some device types include
+block descriptors in the mode data returned by a MODE SENSE command. If
+so the same block descriptors are written by the MODE SELECT command.
+This option instructs the MODE SENSE command not to return any block
+descriptors. This would be a sensible default for this utility apart
+from the fact that not all SCSI devices support the DBD bit in the cdb.
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+force the contents string to be taken as the new mode page, or at least
+doesn't do checks on the existing mode page. Note that \fIDEVICE\fR may
+still reject the new contents for the mode page. Cannot be given with
+the \fI\-\-mask=M,M...\fR option.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-l\fR, \fB\-\-len\fR=10 | 6
+length of the SCSI commands (cdb) sent to \fIDEVICE\fR. The default is 10
+so 10 byte MODE SENSE and MODE SELECT commands are issued. Some old devices
+don't support the 10 byte variants hence this option.
+.TP
+\fB\-m\fR, \fB\-\-mask\fR=\fIM,M...\fR
+where \fIM,M...\fR is a string of comma separated hex numbers each of which
+should resolve to a byte value (i.e. 0 to ff inclusive). A (single) space
+separated string of hex numbers is also allowed but the list needs to be in
+quotes. The mask chooses (bit by bit) whether the new mode page comes from
+the contents (mask bit set) or from the existing mode page (mask bit clear).
+If the mask string is shorter than the contents string then the remaining
+bytes are taken from the contents string. If the contents string is shorter
+than the existing mode page then the remaining bytes are taken from the
+existing mode page (i.e. they are left unaltered).
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG_H\fR
+where \fIPG_H\fR is the page code value to fetch and modify. The page code
+is in hex and should be between 0 and 3e inclusive. Notice that page code
+3f to fetch all mode pages is disallowed.
+.TP
+\fB\-p\fR, \fB\-\-page\fR=\fIPG_H,SPG_H\fR
+where \fIPG_H\fR is the page code value and \fISPG_H\fR is the subpage code
+value to fetch and modify. Both values are in hex. The subpage code should
+be between 0 and fe inclusive. Notice that subpage code ff to fetch all
+mode subpages (for a given mode page or all mode pages in the case of 3f,ff)
+is disallowed.
+.TP
+\fB\-R\fR, \fB\-\-rtd\fR
+when this option is given most other actions are bypassed and a MODE
+SELECT(6 or 10) command is sent to the \fIDEVICE\fR with the RTD bit set.
+This will cause all current values (and saved values if the \fI\-\-save\fR
+option is also given) of all mode pages to be reverted to their default
+values.
+.TP
+\fB\-s\fR, \fB\-\-save\fR
+changes the "saved" mode page when MODE SELECT is successful. By
+default (i.e. when \fI\-\-save\fR is not used) only the "current" mode page
+values are changed when MODE SELECT is successful. In this case the new mode
+page will stay in effect until the device is reset (e.g. power cycled).
+When it restarts the "saved" values for the mode page will be re\-instated.
+So to make changes permanent use the \fI\-\-save\fR option.
+.br
+When used with the \fI\-\-rtd\fR option then both the current and saved
+values in each mode page are reverted to their default values. In the
+absence of \fI\-\-save\fR option only the current values in each mode page
+are reverted to their default values.
+.TP
+\fB\-6\fR, \fB\-\-six\fR
+this option will cause the 6 byte variants of MODE SENSE and MODE SELECT
+commands to be used. The default is to use the 10 byte options. This option
+is equivalent to using the \fI\-\-len=6\fR option.
+
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+This utility does not check whether the contents string is trying to
+modify parts of the mode page which are changeable. The device should
+do that and if some part is not changeable then it should
+report: "Invalid field in parameter list".
+.PP
+Some mode pages are not saveable. If so an attempt to use the \fI\-\-save\fR
+option should cause an error to be reported from the device: "Illegal field
+in cdb".
+.PP
+The device is required to do various checks before it accepts a new
+mode page. If these checks fail then the mode page is not altered and
+either a "parameter list length error" or an "invalid field in
+parameter list" error is returned by the device in the sense data.
+.PP
+The recommended way to modify a mode page is to read it with a
+MODE SENSE, modify some part of it then write it back to the
+device with a MODE SELECT command. For example, reading an existing mode
+page can be accomplished with 'sg_modes \-p=1a \-r /dev/sdb > mp_1a.txt' (the
+power condition mode page). The mp_1a.txt file can be edited and then used
+as the contents string to this
+utility (e.g. 'sg_wr_mode \-p 1a \-s \-c \- /dev/sdb < mp_1a.txt').
+.PP
+Two fields differ between what is read from the device with MODE SENSE and
+what is written to the device with MODE SELECT:
+the mode data length is reserved (i.e. zero(es)) in a MODE
+SELECT command while the PS bit ((sub)page byte 0 bit 7) in each
+mode (sub)page is reserved (zero) in a MODE SELECT command.
+The PS bit given in the contents string is zeroed unless
+the \fI\-\-force\fR option is selected.
+.SH EXAMPLES
+This utility can be used together with the sg_modes utility. To re\-instate
+the default mode page values (i.e. the mode page values chosen by the
+manufacturer of the device) as both the current and saved mode page
+values the following sequence could be used:
+.PP
+ $ sg_modes \-\-control=2 \-\-page=1a \-r /dev/sda > t
+.br
+ $ sg_wr_mode \-\-page=1a \-\-contents=\- \-\-save /dev/sda < t
+.PP
+Next is an example of using a mask to modify the "idle condition counter"
+of the "power condition" mode page (0x1a) from 0x28 to 0x37. Note that the
+change is not saved so the "idle condition counter" will revert to 0x28
+after the next power cycle. The output from sg_modes is abridged.
+.PP
+ $ sg_modes \-\-page=1a /dev/hdc
+.br
+ >> Power condition (mmc), page_control: current
+.br
+ 00 1a 0a 00 03 00 00 00 28 00 00 01 2c
+.PP
+ $ sg_wr_mode \-p 1a \-c 0,0,0,0,0,0,0,37 \-m 0,0,0,0,0,0,0,ff /dev/hdc
+.PP
+ $ sg_modes \-p 1a /dev/hdc
+.br
+ >> Power condition (mmc), page_control: current
+.br
+ 00 1a 0a 00 03 00 00 00 37 00 00 01 2c
+.SH EXIT STATUS
+The exit status of sg_wr_mode is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sdparm(sdparm), sg_modes(sg3_utils), sginfo(sg3_utils)
diff --git a/doc/sg_write_buffer.8 b/doc/sg_write_buffer.8
new file mode 100644
index 00000000..23913e37
--- /dev/null
+++ b/doc/sg_write_buffer.8
@@ -0,0 +1,227 @@
+.TH SG_WRITE_BUFFER "8" "November 2018" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_write_buffer \- send SCSI WRITE BUFFER commands
+.SH SYNOPSIS
+.B sg_write_buffer
+[\fI\-\-bpw=CS\fR] [\fI\-\-dry\-run\fR] [\fI\-\-help\fR] [\fI\-\-id=ID\fR]
+[\fI\-\-in=FILE\fR] [\fI\-\-length=LEN\fR] [\fI\-\-mode=MO\fR]
+[\fI\-\-offset=OFF\fR] [\fI\-\-read\-stdin\fR] [\fI\-\-skip=SKIP\fR]
+[\fI\-\-specific=MS\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends one or more SCSI WRITE BUFFER commands to \fIDEVICE\fR, along with data
+provided by the user. In some cases no data is required, or data can be read
+from the file given in the \fI\-\-in=FILE\fR option, or data is read from
+stdin when either \fI\-\-read\-stdin\fR or \fI\-\-in=\-\fR is given.
+.PP
+Some WRITE BUFFER command variants do not have associated data to send to the
+device. For example "activate_mc" activates deferred microcode that was sent
+via prior WRITE BUFFER commands. There is a different method used to download
+microcode to SES devices, see the sg_ses_microcode utility.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-b\fR, \fB\-\-bpw\fR=\fICS\fR
+where \fICS\fR is the chunk size in bytes. This will be the maximum number
+of bytes sent per WRITE BUFFER command. So if \fICS\fR is less than the
+effective length then multiple WRITE BUFFER commands are sent, each taking
+the next chunk from the read data and increasing the buffer offset field
+in the WRITE BUFFER command by the appropriate amount. The default is
+a chunk size of 0 which is interpreted as a very large number hence only
+one WRITE BUFFER command will be sent. This option should only be used with
+modes that "download microcode, with offsets ..."; namely either mode 0x6,
+0x7, 0xd or 0xe.
+.br
+The number in \fICS\fR can optionally be followed by ",act" or ",activate".
+In this case after WRITE BUFFER commands have been sent until the
+effective length is exhausted another WRITE BUFFER command with its mode
+set to "Activate deferred microcode mode" [mode 0xf] is sent.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+Do all the command line processing and sanity checks including reading
+the input file. However at the point where a WRITE BUFFER SCSI command(s)
+would be sent, step over that call and assume it completed without errors
+and continue. \fIDEVICE\fR is still opened but can be /dev/null (in Unix).
+It is recommended to use \fI\-\-verbose\fR with this option to get an
+overview of what would have happened.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. If used multiple times also prints
+the mode names and their acronyms.
+.TP
+\fB\-i\fR, \fB\-\-id\fR=\fIID\fR
+this option sets the buffer id field in the cdb. \fIID\fR is a value between
+0 (default) and 255 inclusive.
+.TP
+\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR
+read data from file \fIFILE\fR that will be sent with the WRITE BUFFER
+command. If \fIFILE\fR is '\-' then stdin is read until an EOF is
+detected (this is the same action as \fI\-\-read\-stdin\fR). Data is read
+from the beginning of \fIFILE\fR except in the case when it is a regular file
+and the \fI\-\-skip=SKIP\fR option is given.
+.TP
+\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR
+where \fILEN\fR is the length, in bytes, of data to be written to the device.
+If not given (and the length cannot be deduced from \fI\-\-in=FILE\fR or
+\fI\-\-read\-stdin\fR) then defaults to zero. If the option is given and the
+length deduced from \fI\-\-in=FILE\fR or \fI\-\-read\-stdin\fR is less (or no
+data is provided), then bytes of 0xff are used as fill bytes.
+.TP
+\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR
+this option sets the MODE field in the cdb. \fIMO\fR is a value between
+0 (default) and 31 inclusive. Alternatively an abbreviation can be given.
+See the MODES section below. To list the available mode abbreviations at
+run time give an invalid one (e.g. '\-\-mode=xxx') or use the '\-hh' option.
+.TP
+\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR
+this option sets the BUFFER OFFSET field in the cdb. \fIOFF\fR is a value
+between 0 (default) and 2**24\-1 . It is a byte offset.
+.TP
+\fB\-r\fR, \fB\-\-read\-stdin\fR
+read data from stdin until an EOF is detected. This data is sent with
+the WRITE BUFFER command to \fIDEVICE\fR. The action of this option is the
+same as using '\-\-in=\-'. Previously this option's long name was
+\fI\-\-raw\fR and it may still be used for backward compatibility.
+.TP
+\fB\-s\fR, \fB\-\-skip\fR=\fISKIP\fR
+this option is only active when \fI\-\-in=FILE\fR is given and \fIFILE\fR is
+a regular file, rather than stdin. Data is read starting at byte offset
+\fISKIP\fR to the end of file (or the amount given by \fI\-\-length=LEN\fR).
+If not given the byte offset defaults to 0 (i.e. the start of the file).
+.TP
+\fB\-S\fR, \fB\-\-specific\fR=\fIMS\fR
+\fIMS\fR is the MODE SPECIFIC field in the cdb. This is a 3\-bit field
+so the values 0 to 7 are accepted. This field was introduced in SPC\-4
+revision 32 and can be used to specify additional events that activate
+deferred microcode (when \fIMO\fR is 0xD).
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+\fITO\fR is the command timeout (in seconds) for each WRITE BUFFER command
+issued by this utility. Its default value is 300 seconds (5 minutes) and
+should only be altered if this is not sufficient.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH MODES
+Following is a list of WRITE BUFFER command settings for the MODE field.
+First is an acronym accepted by the \fIMO\fR argument of this utility.
+Following the acronym in square brackets are the corresponding decimal and
+hex values that may also be given for \fIMO\fR. The following are listed
+in numerical order.
+.TP
+hd [0, 0x0]
+Combined header and data (obsolete in SPC\-4).
+.TP
+vendor [1, 0x1]
+Vendor specific.
+.TP
+data [2, 0x2]
+Data (was called "Write Data" in SPC\-3).
+.TP
+dmc [4, 0x4]
+Download microcode and activate (was called "Download microcode" in SPC\-3).
+.TP
+dmc_save [5, 0x5]
+Download microcode, save, and activate (was called "Download microcode and
+save" in SPC\-3).
+.TP
+dmc_offs [6, 0x6]
+Download microcode with offsets and activate (was called "Download microcode
+with offsets" in SPC\-3).
+.TP
+dmc_offs_save [7, 0x7]
+Download microcode with offsets, save, and activate (was called "Download
+microcode with offsets and save" in SPC\-3).
+.TP
+echo [10, 0xa]
+Write data to echo buffer (was called "Echo buffer" in SPC\-3).
+.TP
+dmc_offs_ev_defer [13, 0xd]
+Download microcode with offsets, select activation events, save, and defer
+activate (introduced in SPC\-4).
+.TP
+dmc_offs_defer [14, 0xe]
+Download microcode with offsets, save, and defer activate (introduced in
+SPC\-4).
+.TP
+activate_mc [15, 0xf]
+Activate deferred microcode (introduced in SPC\-4).
+.TP
+en_ex [26, 0x1A]
+Enable expander communications protocol and Echo buffer (obsolete in SPC\-4).
+.TP
+dis_ex [27, 0x1B]
+Disable expander communications protocol (obsolete in SPC\-4).
+.TP
+deh [28, 0x1C]
+Download application client error history (was called "Download application
+log" in SPC\-3).
+.SH NOTES
+If no \fI\-\-length=LEN\fR is given this utility reads up to 8 MiB of data
+from the given file \fIFILE\fR (or stdin). If a larger amount of data is
+required then the \fI\-\-length=LEN\fR option should be given.
+.PP
+The user should be aware that most operating systems have limits on the
+amount of data that can be sent with one SCSI command. In Linux this
+depends on the pass through mechanism used (e.g. block SG_IO or the sg
+driver) and various setting in sysfs in the Linux lk 2.6/3
+series (e.g. /sys/block/sda/queue/max_sectors_kb). Devices (i.e. logical
+units) also typically have limits on the maximum amount of data they can
+handle in one command. These two limitations suggest that modes
+containing the word "offset" together with the \fI\-\-bpw=CS\fR option
+are required as firmware files get larger and larger. And \fICS\fR
+can be quite small, for example 4096 bytes, resulting in many WRITE
+BUFFER commands being sent.
+.PP
+Attempting to download a microcode/firmware file that is too large may
+cause an error to occur in the pass\-through layer (i.e. before the
+SCSI command is issued). In Linux such error reports can be obscure as
+in "pass through os error invalid argument". FreeBSD reports such
+errors well to the machine's console but returns a cryptic error message
+to this utility.
+.PP
+Downloading incorrect microcode into a device has the ability to render
+that device inoperable. One would hope that the device vendor verifies
+the data before activating it. If the SCSI WRITE BUFFER command is given
+values in its cdb (e.g. \fILEN\fR) that are inappropriate (e.g. too large)
+then the device should respond with a sense key of ILLEGAL REQUEST and
+an additional sense code of INVALID FIELD in CDB. If a WRITE BUFFER
+command (or a sequence of them) fails due to device vendor verification
+checks then it should respond with a sense key of ILLEGAL REQUEST and
+an additional sense code of COMMAND SEQUENCE ERROR.
+.PP
+All numbers given with options are assumed to be decimal.
+Alternatively numerical values can be given in hexadecimal preceded by
+either "0x" or "0X" (or has a trailing "h" or "H").
+.SH EXAMPLES
+The following sends new firmware to an enclosure. Sending a 1.5 MB
+file in one WRITE BUFFER command caused the enclosure to lock up
+temporarily and did not update the firmware. Breaking the firmware file
+into 4 KB chunks (an educated guess) was more successful:
+.PP
+ sg_write_buffer \-b 4k \-m dmc_offs_save \-I firmware.bin /dev/sg4
+.PP
+The firmware update occurred in the following enclosure power cycle. With
+a modern enclosure the Extended Inquiry VPD page gives indications in which
+situations a firmware upgrade will take place.
+.SH EXIT STATUS
+The exit status of sg_write_buffer is 0 when it is successful. Otherwise
+see the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Luben Tuikov and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2006\-2018 Luben Tuikov and Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_read_buffer, sg_ses_microcode(sg3_utils)
diff --git a/doc/sg_write_long.8 b/doc/sg_write_long.8
new file mode 100644
index 00000000..2d5560d7
--- /dev/null
+++ b/doc/sg_write_long.8
@@ -0,0 +1,176 @@
+.TH SG_WRITE_LONG "8" "January 2016" "sg3_utils\-1.42" SG3_UTILS
+.SH NAME
+sg_write_long \- send SCSI WRITE LONG command
+.SH SYNOPSIS
+.B sg_write_long
+[\fI\-\-16\fR] [\fI\-\-cor_dis\fR] [\fI\-\-help\fR] [\fI\-\-in=IF\fR]
+[\fI\-\-lba=LBA\fR] [\fI\-\-pblock\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-wr_uncor\fR] [\fI\-\-xfer_len=BTL\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Send the SCSI WRITE LONG (10 or 16 byte) command to \fIDEVICE\fR. The buffer
+to be written to the \fIDEVICE\fR is filled with
+.B 0xff
+bytes or read from the \fIIF\fR file. This buffer includes the logical
+data (e.g. 512 bytes) and the ECC bytes.
+.PP
+This utility can be used to generate a MEDIUM ERROR at a specific logical
+block address. This can be useful for testing error handling. Prior to
+such a test, the
+.B sg_dd
+utility could be used to copy the original contents of the logical
+block address to some safe location. After the test the
+.B sg_dd
+utility could be used to write back the original contents of the
+logical block address. An alternate strategy would be to read the "long"
+contents of the logical block address with
+.B sg_read_long
+utility prior to testing and restore it with this utility after testing.
+.PP
+.B Take care:
+If recoverable errors are being injected (e.g. only one or a few bits
+changed so that the ECC is able to correct the data) then care should
+be taken with the settings in the "read write error recovery" mode page.
+Specifically if the ARRE (for reads) and/or AWRE (for writes) are set
+then recovered errors will cause the lba to be reassigned (and the old
+location to be added to the grown defect list (PLIST)). This is not easily
+reversed and uses (one of the finite number of) the spare sectors set
+aside for this purpose. If in doubt it is probably safest to clear the
+ARRE and AWRE bits. These bits can be checked and modified with the
+sdparm utility. For example: "sdparm \-c AWRE,ARRE /dev/sda" will clear
+the bits until the disk is power cycled.
+.PP
+In SBC\-4 revision 7 all uses of SCSI WRITE LONG (10 and 16 byte) commands
+were made obsolete apart from the case in which the WR_UNCOR bit is set.
+The SCSI READ LONG (10 and 16 byte) commands were made obsolete in the
+same revision.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+send a SCSI WRITE LONG (16) command to \fIDEVICE\fR. The default action (in
+the absence of this option) is to send a SCSI WRITE LONG (10) command.
+.TP
+\fB\-c\fR, \fB\-\-cor_dis\fR
+sets the correction disabled (i.e 'COR_DIS') bit. This inhibits various
+other mechanisms such as automatic block reallocation, error recovery
+and various informational exception conditions being triggered.
+This bit is relatively new in SBC\-3 .
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+read data (binary) from file named \fIIF\fR and use it for the SCSI WRITE
+LONG command. If \fIIF\fR is "\-" then stdin is read. If this option is
+not given then 0xff bytes are used as fill.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the logical block address of the sector to overwrite.
+Defaults to lba 0 which is a dangerous block to overwrite on a disk that is
+in use. Assumed to be in decimal unless prefixed with '0x' or has a
+trailing 'h'. If \fILBA\fR is larger than can fit in 32 bits then the
+\fI\-\-16\fR option should be used.
+.TP
+\fB\-p\fR, \fB\-\-pblock\fR
+sets the physical block (i.e 'PBLOCK') bit. This instructs \fIDEVICE\fR
+to use the given data (unless \fI\-\-wr_uncor\fR is also given) to write
+to the physical block specified by \fILBA\fR. The default action
+is to write to the logical block corresponding to the given lba.
+This bit is relatively new in SBC\-3 .
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-wr_uncor\fR
+sets the "write uncorrected" (i.e 'WR_UNCOR') bit. This instructs the
+\fIDEVICE\fR to flag the given lba (or the physical block that contains it
+if \fI\-\-pblock\fR is also given) as having an unrecoverable error
+associated with it. Note: no data is transferred to \fIDEVICE\fR,
+other than the command (i.e. the cdb). In the absence of this option, the
+default action is to use the provided data or 0xff
+bytes (\fI\-\-xfer_len=BTL\fR in length) and write it to \fIDEVICE\fR.
+This bit is relatively new in SBC\-3 .
+.TP
+\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR
+where \fIBTL\fR is the byte transfer length (default to 520). If the
+given value (or the default) does not match the "long" block size of the
+device, nothing is written to \fIDEVICE\fR and the appropriate xfer_len value
+may be deduced from the error response which is printed (to stderr).
+.SH NOTES
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The 10 byte SCSI WRITE LONG command limits the logical block address
+to a 32 bit quantity. For larger LBAs use the \fI\-\-16\fR option for the
+SCSI WRITE LONG (16) command.
+.SH EXAMPLES
+This section outlines setting up a block with corrupted data, checking the
+error condition, then restoring useful contents to that sector.
+.PP
+First, if the data in a sector is important, save it with the sg_read_long
+utility:
+.PP
+ sg_read_long \-\-lba=0x1234 \-\-out=0x1234_1.img \-x \fIBTL\fR /dev/sda
+.PP
+This utility may need to be executed several time in order to determine
+what the correct value for \fIBTL\fR is.
+Next use this utility to "corrupt" that sector. That might be done with:
+.PP
+ sg_write_long \-\-lba=0x1234 \-x \fIBTL\fR /dev/sda
+.PP
+This will write a sector (and ECC data) of 0xff bytes. Some disks may
+reject this (at least one of the author's does). Another approach is
+to copy the 0x1234_1.img file (to 0x1234_2.img in this example) and
+change some values with a hex editor. Then write the changed image with:
+.PP
+ sg_write_long \-\-lba=0x1234 \-\-in=0x1234_2.img \-x \fIBTL\fR /dev/sda
+.PP
+Yet another approach is to use the \fI\-\-wr_uncor\fR option, if supported:
+.PP
+ sg_write_long \-\-lba=0x1234 \-\-wr_uncor /dev/sda
+.PP
+Next we use the sg_dd utility to check that the sector is corrupted. Here is an
+example:
+.PP
+ sg_dd if=/dev/sda blk_sgio=1 skip=0x1234 of=. bs=512 count=1 verbose=4
+.PP
+Notice that the "blk_sgio=1" option is given. This is to make sure that
+the sector is read (and no others) and the error is fully reported.
+The "blk_sgio=1" option causes the SG_IO ioctl to be used by sg_dd rather
+than the block subsystem.
+.PP
+Finally we should restore sector 0x1234 to a non\-corrupted state. A sector
+full of zeros could be written with:
+.PP
+ sg_dd if=/dev/zero of=/dev/sda blk_sgio=1 seek=0x1234 bs=512 count=1
+.PP
+This will result in a sector (block) with 512 bytes of 0x0 without a
+MEDIUM ERROR since the ECC and associated data will be regenerated and
+thus well formed. The 'blk_sgio=1' option is even more important in this
+case as it may stop the block subsystem doing a read before write (since
+the read will most likely fail).
+Another approach is to write back the original contents:
+.PP
+ sg_write_long \-\-lba=0x1234 \-\-in=0x1234_1.img \-x \fIBTL\fR /dev/sda
+.PP
+.SH EXIT STATUS
+The exit status of sg_write_long is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Saeed Bishara. Further work by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2016 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_read_long, sg_dd (both in sg3_utils), sdparm(sdparm)
diff --git a/doc/sg_write_same.8 b/doc/sg_write_same.8
new file mode 100644
index 00000000..9d4c7049
--- /dev/null
+++ b/doc/sg_write_same.8
@@ -0,0 +1,355 @@
+.TH SG_WRITE_SAME "8" "June 2020" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sg_write_same \- send SCSI WRITE SAME command
+.SH SYNOPSIS
+.B sg_write_same
+[\fI\-\-10\fR] [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-anchor\fR]
+[\fI\-\-ff\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] [\fI\-\-in=IF\fR]
+[\fI\-\-lba=LBA\fR] [\fI\-\-lbdata\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-ndob\fR] [\fI\-\-pbdata\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-unmap\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-wrprotect=WPR\fR] [\fI\-\-xferlen=LEN\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+Send the SCSI WRITE SAME (10, 16 or 32 byte) command to \fIDEVICE\fR. This
+command writes the given block \fINUM\fR times to consecutive blocks on
+the \fIDEVICE\fR starting at logical block address \fILBA\fR.
+.PP
+The length of the block to be written multiple times is obtained from either
+the \fILEN\fR argument, or the length of the given input file \fIIF\fR,
+or by calling READ CAPACITY(16) on \fIDEVICE\fR. The contents of the
+block to be written are obtained from the input file \fIIF\fR or
+zeros are used. If READ CAPACITY(16) is called (which implies \fIIF\fR
+was not given) and the PROT_EN bit is set then an extra 8 bytes (i.e.
+more than the logical block size) of 0xff are sent. If READ CAPACITY(16)
+fails then READ CAPACITY(10) is used to determine the block size.
+.PP
+If neither \fI\-\-10\fR, \fI\-\-16\fR nor \fI\-\-32\fR is given then
+WRITE SAME(10) is sent unless one of the following conditions is met.
+If \fILBA\fR (plus \fINUM\fR) exceeds 32 bits, \fINUM\fR exceeds 65535,
+or the \fI\-\-unmap\fR option is given then WRITE SAME(16) is sent.
+The \fI\-\-10\fR, \fI\-\-16\fR and \fI\-\-32\fR options are mutually
+exclusive.
+.PP
+SBC\-3 revision 35d introduced a "No Data\-Out Buffer" (NDOB) bit which, if
+set, bypasses the requirement to send a single block of data to the
+\fIDEVICE\fR together with the command. Only WRITE SAME (16 and 32 byte)
+support the NDOB bit. If given, a user block of zeros is assumed; if
+required, protection information of 0xffs is assumed.
+.PP
+In SBC\-3 revision 26 the UNMAP and ANCHOR bits were added to the
+WRITE SAME (10) command. Since the UNMAP bit has been in WRITE SAME (16)
+and WRITE SAME (32) since SBC\-3 revision 18, the lower of the two (i.e.
+WRITE SAME (16)) is the default when the \fI\-\-unmap\fR option is given.
+To send WRITE SAME (10) use the \fI\-\-10\fR option.
+.PP
+.B Take care:
+The WRITE SAME(10, 16 and 32) commands may interpret a \fINUM\fR of zero as
+write to the end of \fIDEVICE\fR. This utility defaults \fINUM\fR to 1 .
+The WRITE SAME commands have no IMMED bit so if \fINUM\fR is large (or
+zero) then an invocation of this utility could take a long time, potentially
+as long as a FORMAT UNIT command. In such situations the command timeout
+value \fITO\fR may need to be increased from its default value of 60
+seconds. In SBC\-3 revision 26 the WSNZ (write same no zero) bit was added
+to the Block Limits VPD page [0xB0]. If set the WRITE SAME commands will not
+accept a \fINUM\fR of zero. The same SBC\-3 revision added the "Maximum
+Write Same Length" field to the Block Limits VPD page.
+.PP
+The Logical Block Provisioning VPD page [0xB2] contains the LBPWS and
+LBPWS10 bits. If LBPWS is set then WRITE SAME (16) supports the UNMAP bit.
+If LBPWS10 is set then WRITE SAME (10) supports the UNMAP bit. If either
+LBPWS or LBPWS10 is set and the WRITE SAME (32) is supported then WRITE
+SAME (32) supports the UNMAP bit.
+.PP
+As a precaution against an accidental 'sg_write_same /dev/sda' (for example)
+overwriting LBA 0 on /dev/sda with zeros, at least one of the
+\fI\-\-in=IF\fR, \fI\-\-lba=LBA\fR or \fI\-\-num=NUM\fR options must be
+given. Obviously this utility can destroy a lot of user data so check the
+options carefully.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-R\fR, \fB\-\-10\fR
+send a SCSI WRITE SAME (10) command to \fIDEVICE\fR. The ability to
+set the \fI\-\-unmap\fR (and \fI\-\-anchor\fR) options to this command
+was added in SBC\-3 revision 26.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+send a SCSI WRITE SAME (16) command to \fIDEVICE\fR.
+.TP
+\fB\-T\fR, \fB\-\-32\fR
+send a SCSI WRITE SAME (32) command to \fIDEVICE\fR.
+.TP
+\fB\-a\fR, \fB\-\-anchor\fR
+sets the ANCHOR bit in the cdb. Introduced in SBC\-3 revision 22.
+That draft requires the \fI\-\-unmap\fR option to also be specified.
+.TP
+\fB\-f\fR, \fB\-\-ff\fR
+the data\-out buffer sent with this command is initialized with 0xff bytes
+when this option is given.
+.TP
+\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
+sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero.
+\fIGN\fR should be a value between 0 and 63.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+read data (binary) from file named \fIIF\fR and use it as the data\-out
+buffer for the SCSI WRITE SAME command. The length of the data\-out buffer
+is \fI\-\-xferlen=LEN\fR or, if that is not given, the length of the \fIIF\fR
+file. If \fIIF\fR is "\-" then stdin is read. If this option and the
+\fI\-\-ff\fR are not given then 0x00 bytes are used as fill with the length
+of the data\-out buffer obtained from \fI\-\-xferlen=LEN\fR or by calling
+READ CAPACITY(16 or 10). If the response to READ CAPACITY(16) has the
+PROT_EN bit set then data\- out buffer size is modified accordingly with
+the last 8 bytes set to 0xff.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the logical block address to start the WRITE SAME command.
+Defaults to lba 0 which is a dangerous block to overwrite on a disk that is
+in use. Assumed to be in decimal unless prefixed with '0x' or has a
+trailing 'h'.
+.TP
+\fB\-L\fR, \fB\-\-lbdata\fR
+sets the LBDATA bit in the WRITE SAME cdb. This bit was made obsolete in
+sbc3r32 in September 2012.
+.TP
+\fB\-N\fR, \fB\-\-ndob\fR
+sets the NDOB bit in the WRITE SAME (16 and 32 byte) commands. NDOB stands
+for No Data\-Out Buffer. Default is to clear this bit. When this option
+is given then \fI\-\-in=IF\fR is not allowed and \fI\-\-xferlen=LEN\fR can
+only be given if \fILEN\fR is 0 .
+.br
+By default zeros are written in each block, but it is possible that
+the "provisioning initialization pattern" is written depending on other
+settings.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to write the
+data\-out buffer to. The default value for \fINUM\fR is 1. The value
+corresponds to the 'Number of logical blocks' field in the WRITE SAME cdb.
+.br
+Note that a value of 0 in \fINUM\fR may be interpreted as write the data\-out
+buffer on every block starting at \fILBA\fR to the end of the \fIDEVICE\fR.
+If the WSNZ bit (introduced in sbc3r26, January 2011) in the Block Limits VPD
+page is set then the value of 0 is disallowed, yielding an Invalid request
+sense key.
+.TP
+\fB\-P\fR, \fB\-\-pbdata\fR
+sets the PBDATA bit in the WRITE SAME cdb. This bit was made obsolete in
+sbc3r32 in September 2012.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+where \fITO\fR is the command timeout value in seconds. The default value is
+60 seconds. If \fINUM\fR is large (or zero) a WRITE SAME command may require
+considerably more time than 60 seconds to complete.
+.TP
+\fB\-U\fR, \fB\-\-unmap\fR
+sets the UNMAP bit in the WRITE SAME(10, 16 and 32) cdb. See UNMAP section
+below.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWPR\fR
+sets the "Write protect" field in the WRITE SAME cdb to \fIWPR\fR. The
+default value is zero. \fIWPR\fR should be a value between 0 and 7.
+When \fIWPR\fR is 1 or greater, and the disk's protection type is 1 or
+greater, then 8 extra bytes of protection information are expected or
+generated (to place in the command's data\-out buffer).
+.TP
+\fB\-x\fR, \fB\-\-xferlen\fR=\fILEN\fR
+where \fILEN\fR is the data\-out buffer length. Defaults to the length of
+the \fIIF\fR file or, if that is not given, then the READ CAPACITY(16 or 10)
+command is used to find the 'Logical block length in bytes'. That figure
+may be increased by 8 bytes if the \fIDEVICE\fR's protection type is 1 or
+greater and the WRPROTECT field (see \fI\-\-wrprotect=WPR\fR) is 1 or
+greater. If both this option and the \fIIF\fR option are given and
+\fILEN\fR exceeds the length of the \fIIF\fR file then \fILEN\fR is the
+data\-out buffer length with zeros used as pad bytes.
+.SH UNMAP
+Logical block provisioning is a new term introduced in SBC\-3 revision 25
+for the ability to mark blocks as unused. For large storage arrays, it is a
+way to provision less physical storage than the READ CAPACITY command reports
+is available, potentially allocating more physical storage when WRITE
+commands require it. For flash memory (e.g. SSD drives) it is a way of
+potentially saving power (and perhaps access time) when it is known large
+sections (or almost all) of the flash memory is not in use. SSDs need wear
+levelling algorithms to have acceptable endurance and typically over
+provision to simplify those algorithms; hence they typically contain more
+physical flash storage than their logical size would dictate.
+.PP
+Support for logical block provisioning is indicated by the LBPME bit being
+set in the READ CAPACITY(16) command response (see the sg_readcap utility).
+That implies at least one of the UNMAP or WRITE SAME(16) commands is
+implemented. If the UNMAP command is implemented then
+the "Maximum unmap LBA count" and "Maximum unmap block descriptor count"
+fields in the Block Limits VPD page should both be greater than zero. The
+READ CAPACITY(16) command response also contains a LBPRZ bit which if set
+means that if unmapped blocks are read then zeros will be returned for the
+data (and if protection information is active, 0xff bytes are returned for
+that). In SBC\-3 revision 27 the same LBPRZ bit was added to the Logical
+Block Provisioning VPD page.
+.PP
+In SBC\-3 revision 25 the LBPU and ANC_SUP bits where added to the
+Logical Block Provisioning VPD page. When LBPU is set it indicates that
+the device supports the UNMAP command (see the sg_unmap utility). When the
+ANC_SUP bit is set it indicates the device supports anchored LBAs.
+.PP
+When the UNMAP bit is set in the cdb then the data\-out buffer is also sent.
+Additionally the data section of that data\-out buffer should be full of 0x0
+bytes while the data protection block, 8 bytes at the end if present, should
+be set to 0xff bytes. If these conditions are not met and the LBPRZ bit is
+set then the UNMAP bit is ignored and the data\-out buffer is written to the
+\fIDEVICE\fR as if the UNMAP bit was zero. In the absence of the
+\fI\-\-in=IF\fR option, this utility will attempt build a data\-out buffer
+that meets the requirements for the UNMAP bit in the cdb to be acted on by
+the \fIDEVICE\fR.
+.PP
+Logical blocks may also be unmapped by the SCSI UNMAP and FORMAT UNIT
+commands (see the sg_unmap and sg_format utilities).
+.PP
+The unmap capability in SCSI is closely related to the ATA DATA SET
+MANAGEMENT command with the "Trim" bit set. That ATA trim capability does
+not interact well with SATA command queueing known as NCQ. T13 have
+introduced a new command called the SFQ DATA SET MANAGEMENT command also
+with a the "Trim" bit to address that problem. The SCSI WRITE SAME with
+the UNMAP bit set and the UNMAP commands do not have any problems with
+SCSI queueing.
+.SH NOTES
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+In Linux, prior to lk 3.17, the sg driver did not support cdb sizes greater
+than 16 bytes. Hence a device node like /dev/sg1 which is associated with
+the sg driver would fail with this utility if the \fI\-\-32\fR option was
+given (or implied by other options). The bsg driver with device nodes like
+/dev/bsg/6:0:0:1 does support cdb sizes greater than 16 bytes since its
+introduction in lk 2.6.28 .
+.SH EXIT STATUS
+The exit status of sg_write_same is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH EXAMPLES
+BEWARE: all these examples will overwrite the data on one or more blocks,
+potentially CLEARING the WHOLE DISK.
+.PP
+One simple usage is to write blocks of zero from (and including) a given LBA
+for 63 blocks:
+.PP
+ sg_write_same \-\-lba=0x1234 \-\-num=63 /dev/sdc
+.PP
+Since \fI\-\-xferlen=LEN\fR has not been given, then this utility will
+call the READ CAPACITY command on /dev/sdc to determine the number
+of bytes in a logical block. Let us assume that is 512 bytes. Since
+\fI\-\-in=IF\fR is not given a block of zeros is assumed. So 63 blocks
+of zeros (each block containing 512 bytes) will be written from (and
+including) LBA 0x1234 . Note that only one block of zeros is passed
+to the SCSI WRITE SAME command in the data\-out buffer (as required by
+SBC\-3). Using the WRITE SAME SCSI command to write one or more blocks
+blocks of zeros is equivalent to the NVMe command: Write Zeroes.
+.br
+Now we will write zero blocks to the WHOLE disk. [Note sanitize type commands
+will also clear blocks and metadata that are not directly visible]:
+.PP
+ sg_write_same \-\-lba=0x0 \-\-num=0 /dev/sdc
+.PP
+Yes, in this context \-\-num=0 means the rest of the disk. The above
+invocation may give an error due to the WSNZ bit in the Block Limits VPD
+page being set. To get around that try:
+.PP
+ sg_write_same \-\-lba=0x0 \-\-ndob /dev/sdc
+.PP
+this invocation, if supported, has the added benefit of not sending a data
+out buffer of zeros. Notes that it is possible that the "provisioning
+initialization pattern" is written to each block instead of zeros.
+.PP
+A similar example follows but in this case the blocks
+are "unmapped" ("trimmed" in ATA speak) rather than zeroed:
+.PP
+ sg_write_same \-\-unmap \-L 0x1234 \-n 63 /dev/sdc
+.PP
+Note that if the LBPRZ bit in the READ CAPACITY(16) response is set (i.e.
+LPPRZ is an acronym for logical block provisioning read zeros) then these
+two examples do the same thing, at least seen from the point of view of
+subsequent reads.
+.PP
+This utility can also be used to write protection information (PI) on disks
+formatted with a protection type greater than zero. PI is 8 bytes of extra
+data appended to the user data of a logical block: the first two bytes are a
+CRC (the "guard"), the next two bytes are the "application tag" and the last
+four bytes are the "reference tag". With protection types 1 and 2 if the
+application tag is 0xffff then the guard should not be checked (against the
+user data).
+.PP
+In this example we assume the logical block size (of the user data) is 512
+bytes and the disk has been formatted with protection type 1. Since we are
+going to modify LBA 2468 then we take a copy of it first:
+.PP
+ dd if=/dev/sdb skip=2468 bs=512 of=2468.bin count=1
+.PP
+The following command line sets the user data to zeros and the PI to 8
+0xFF bytes on LBA 2468:
+.PP
+ sg_write_same \-\-lba=2468 /dev/sdb
+.PP
+Reading back that block should be successful because the application tag
+is 0xffff which suppresses the guard (CRC) check (which would otherwise be
+wrong):
+.PP
+ dd if=/dev/sdb skip=2468 bs=512 of=/dev/null count=1
+.PP
+Now an attempt is made to create a binary file with zeros in the user data,
+0x0000 in the application tag and 0xff bytes in the other two PI fields. It
+is awkward to create 0xff bytes in a file (in Unix) as the "tr" command
+below shows:
+.PP
+ dd if=/dev/zero bs=1 count=512 of=ud.bin
+.br
+ tr "\\000" "\\377" < /dev/zero | dd bs=1 of=ff_s.bin count=8
+.br
+ cat ud.bin ff_s.bin > lb.bin
+.br
+ dd if=/dev/zero bs=1 count=2 seek=514 conv=notrunc of=lb.bin
+.PP
+The resulting file can be viewed with 'hexdump \-C lb.bin' and should
+contain 520 bytes. Now that file can be written to LBA 2468 as follows:
+.PP
+ sg_write_same \-\-lba=2468 wrprotect=3 \-\-in=lb.bin /dev/sdb
+.PP
+Note the \fI\-\-wrprotect=3\fR rather than being set to 1, since we want
+the WRITE SAME command to succeed even though the PI data now indicates
+the user data is corrupted. When an attempt is made to read the LBA, an
+error should occur:
+.PP
+ dd if=/dev/sdb skip=2468 bs=512 of=/dev/null count=1
+.PP
+dd errors are not very expressive, if dmesg is checked there should be
+a line something like this: "[sdb] Add. Sense: Logical block guard check
+failed". The block can be corrected by doing a "sg_write_same \-\-lba=1234
+/dev/sdb" again or restoring the original contents of that LBA:
+.PP
+ dd if=2468.bin bs=512 seek=2468 of=/dev/sdb conv=notrunc count=1
+.PP
+Hopefully the dd command would never try to truncate the output file when
+it is a block device.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2009\-2020 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_format,sg_get_lba_status,sg_readcap,sg_vpd,sg_unmap,
+.B sg_write_x(sg3_utils)
diff --git a/doc/sg_write_verify.8 b/doc/sg_write_verify.8
new file mode 100644
index 00000000..6a784e0a
--- /dev/null
+++ b/doc/sg_write_verify.8
@@ -0,0 +1,191 @@
+.TH "WRITE AND VERIFY" "8" "January 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_write_and_verify \- send the SCSI WRITE AND VERIFY command
+.SH SYNOPSIS
+.B sg_write_verify
+[\fI\-\-16\fR] [\fI\-\-bytchk=BC\fR] [\fI\-\-dpo\fR] [\fI\-\-group=GN\fR]
+[\fI\-\-help\fR] [\fI\-\-ilen=ILEN\fR] [\fI\-\-in=IF\fR] \fI\-\-lba=LBA\fR
+[\fI\-\-num=NUM\fR] [\fI\-\-repeat\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-wrprotect=WP\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+Send a SCSI WRITE AND VERIFY (10) or (16) command to \fIDEVICE\fR. The
+data to be written is read from the \fIIF\fR file or, in its absence, a
+buffer full of 0xff bytes is used. The length of the data\-out buffer sent
+with the command is \fIILEN\fR bytes or, if that is not given, then it is
+the length of the \fIIF\fR file.
+.PP
+The write operation is to the \fIDEVICE\fR's medium (optionally to its cache)
+starting at logical block address \fILBA\fR for \fINUM\fR logical blocks.
+After the write to medium is performed a verify operation takes place which
+may viewed as a medium read (with appropriate checks) but without the data
+being returned. Additionally, if \fIBS\fR is set to one, the data read back
+from the medium in the verify operation is compared to the original data\-out
+buffer.
+.PP
+The relationship between the number of logical blocks to be written (i.e.
+\fINUM\fR) and the length (in bytes) of the data\-out buffer (i.e.
+\fIILEN\fR) may be simply found by multiplying the former by the logical
+block size. However if the \fIDEVICE\fR has protection information (PI)
+then it becomes a bit more complicated. Hence the calculation is left to
+the user with the default \fIILEN\fR, in the absence of the \fIIF\fR file,
+being set to \fINUM\fR * 512.
+.PP
+For sending large amounts of data to contiguous logical blocks, a single
+WRITE AND VERIFY command may not be appropriate (e.g. due to operating
+system limitations). In such cases see the REPEAT section below.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long option name.
+.TP
+\fB\-S\fR, \fB\-\-16\fR
+Send a WRITE AND VERIFY(16) command. The default is to send a WRITE AND
+VERIFY(10) command unless \fILBA\fR or \fINUM\fR are too large for the
+10 byte variant.
+.TP
+\fB\-b\fR, \fB\-\-bytchk\fR=\fIBC\fR
+where \fIBC\fR is the value to place in the command's BYTCHK field. Values
+between 0 and 3 (inclusive) are accepted. The default is value is 0 which
+implies only a write to the medium then a verify operation are performed. The
+only other value T10 defines currently is 1 which does performs an additional
+comparison between the data\-out buffer that was used by the write operation
+and the contents of the logical blocks read back from the medium.
+.TP
+\fB\-d\fR, \fB\-\-dpo\fR
+Set the DPO (disable page out) bit in the command. The default is to leave
+it clear.
+.TP
+\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR
+where \fIGN\fR is the value to place in the command's GROUP NUMBER field.
+Values between 0 and 63 (inclusive) are accepted. The default is value is 0.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-I\fR, \fB\-\-ilen\fR=\fIILEN\fR
+where \fIILEN\fR is the number of bytes that will be placed in the data\-out
+buffer. If the \fIIF\fR file is given then no more than \fIILEN\fR bytes
+are read from that file. If the \fIIF\fR file does not contain \fIILEN\fR
+bytes then an error is reported. If the \fIIF\fR file is not given then
+a data\-out buffer with \fIILEN\fR bytes of 0xff is sent.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+read data (binary) from file named \fIIF\fR. If \fIIF\fR is "\-" then
+stdin is used. This data will become the data\-out buffer and will be written
+to the \fIDEVICE\fR's medium. If \fIBC\fR is 1 then that data\-out buffer
+will be held until after the verify operation and compared to the data read
+back from the medium.
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
+where \fILBA\fR is the logical block address to start the write to medium.
+Assumed to be in decimal unless prefixed with '0x' or has a trailing 'h'.
+Must be provided.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
+where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to write
+to the medium. The default value for \fINUM\fR is 1.
+.TP
+\fB\-R\fR, \fB\-\-repeat\fR
+this option will continue to do WRITE AND VERIFY commands until the \fIIF\fR
+file is exhausted. This option requires both the \fI\-\-ilen=ILEN\fR and
+\fI\-\-in=IF\fR options to be given. Each command starts at the next logical
+block address and is for no more than \fINUM\fR blocks. The last command may
+be shorter with the number of blocks scaled as required. If there are
+residue bytes a warning is sent to stderr. See the REPEAT section.
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+where \fITO\fR is the command timeout value in seconds. The default value is
+60 seconds. If \fINUM\fR is large then command may require considerably more
+time than 60 seconds to complete.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWP\fR
+set the WRPROTECT field in the cdb to \fIWP\fR. The default value is 0 which
+implies no protection information is sent (along with the user data) in the
+data\-out buffer.
+.SH REPEAT
+For data sizes around a megabyte and larger, it may be appropriate to send
+multiple SCSI WRITE AND VERIFY commands due to operating system
+limitations (e.g. pass\-through SCSI interfaces often limit the amount
+of data that can be passed with a SCSI command). With this utility the
+mechanism for doing that is the \fI\-\-repeat\fR option.
+.PP
+In this mode the \fI\-\-ilen=ILEN\fR and \fI\-\-in=IF\fR options must be
+given. The \fIILEN\fR and \fINUM\fR values are treated as a per SCSI command
+parameters. Up to \fIILEN\fR bytes will be read from the \fIIF\fR file
+continually until it is exhausted. If the \fIIF\fR file is stdin, reading
+continues until an EOF is detected. The data read from each iteration becomes
+the data\-out buffer for a new WRITE AND VERIFY command.
+.PP
+The last read from the file (or stdin) may read less than \fIILEN\fR bytes
+in which case the number of logical blocks sent to the last WRITE AND VERIFY
+is scaled back accordingly. If there is a residual number of bytes left
+after that scaling then that is reported to stderr.
+.PP
+If an error occurs then that is reported to stderr and via the exit status
+and the utility stops at that point.
+.SH NOTES
+Other SCSI WRITE commands have a Force Unit Access (FUA) bit but that is
+set (implicitly) by WRITE AND VERIFY commands hence there is no option to set
+it. The data\-out buffer may still additionally be placed in the
+\fIDEVICE\fR's cache and setting the DPO bit is a hint not to do that.
+.PP
+Normal SCSI WRITEs can be done with the ddpt and the sg_dd utilities. The
+SCSI WRITE SAME command can be done with the sg_write_same utility while
+the SCSI COMPARE AND WRITE command (sg_compare_and_write utility) offers
+a "test and set" facility.
+.PP
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.SH EXIT STATUS
+The exit status of sg_write_verify is 0 when it is successful. If the verify
+operation fails that is typically indicated with a medium error which leads
+to an exit status of 3.
+.PP
+If \fIBC\fR is set to 1 and the comparison it causes fails this utility will
+indicate the miscompare with an exit status of 14. For other exit status
+values see the EXIT STATUS section in the sg3_utils(8) man page.
+.SH EXAMPLES
+To start with, a simple example: write 1 block of data held in file t.bin
+that is 512 bytes long then write that block to LBA 0x1234 on /dev/sg4 .
+.PP
+ # sg_write_verify \-\-lba=0x1234 \-\-in=t.bin /dev/sg4
+.PP
+Since '\-\-num=' is not given then it defaults to 1. Further the \fIILEN\fR
+value is obtained from the file size of t.bin . To additionally do a
+data\-out comparison to the read back data:
+.PP
+ # sg_write_verify \-l 0x1234 \-i t.bin --bytchk=1 /dev/sg4
+.PP
+The ddpt command can do copies between SCSI devices using READ and WRITE
+commands. However, currently it has no facility to promote those WRITES
+to WRITE AND VERIFY commands. Using a pipe, that could be done like this:
+.PP
+ # ddpt if=/dev/sg2 bs=512 bpt=8 count=11 of=\- |
+.br
+sg_write_verify \-\-in=\- \-l 0x567 \-n 8 \-\-ilen=4096 \-\-repeat /dev/sg4
+.PP
+Both ddpt and sg_write_verify are configured for segments of 8 512 byte
+logical blocks. Since 11 logical blocks are read then first 8 logical blocks
+are copied followed by a copy of the remaining 3 blocks. Since it is assumed
+that there is no protection information then the data\-in and data\-out
+buffers will be 4096 bytes each. For sg_write_verify this needs to be stated
+explicitly with the \-\-ilen=4096 option.
+.SH AUTHORS
+Bruno Goncalves and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2018 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B ddpt(in a package of that name), sg_compare_and_write(8), sg_dd(8),
+.B sg_write_same(8)
diff --git a/doc/sg_write_x.8 b/doc/sg_write_x.8
new file mode 100644
index 00000000..90fefc63
--- /dev/null
+++ b/doc/sg_write_x.8
@@ -0,0 +1,600 @@
+.TH SG_WRITE_X "8" "October 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_write_x \- SCSI WRITE normal/ATOMIC/SAME/SCATTERED/STREAM, ORWRITE commands
+.SH SYNOPSIS
+.B sg_write_x
+[\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app\-tag=AT\fR] [\fI\-\-atomic=AB\fR]
+[\fI\-\-bmop=OP,PGP\fR] [\fI\-\-bs=BS\fR] [\fI\-\-combined=DOF\fR]
+[\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR] [\fI\-\-dry\-run\fR] [\fI\-\-fua\fR]
+[\fI\-\-generation=EOG,NOG\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR]
+\fI\-\-in=IF\fR [\fI\-\-lba=LBA[,LBA...]\fR] [\fI\-\-normal\fR]
+[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-or\fR]
+[\fI\-\-quiet\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-same=NDOB\fR]
+[\fI\-\-scat\-file=SF\fR] [\fI\-\-scat\-raw\fR] [\fI\-\-scattered=RD\fR]
+[\fI\-\-stream=ID\fR] [\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR]
+[\fI\-\-timeout=TO\fR] [\fI\-\-unmap=U_A\fR] [\fI\-\-verbose\fR]
+[\fI\-\-version\fR] [\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
+.PP
+Synopsis per supported command:
+.PP
+.B sg_write_x
+\fI\-\-normal\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR]
+[\fI\-\-app\-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR]
+[\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR]
+[\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR]
+\fIDEVICE\fR
+.PP
+.B sg_write_x
+\fI\-\-or\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR]
+[\fI\-\-bmop=OP,PGP\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR]
+[\fI\-\-generation=EOG,NOG\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR]
+[\fI\-\-num=NUM\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-strict\fR]
+[\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=OPR\fR] \fIDEVICE\fR
+.PP
+.B sg_write_x
+\fI\-\-atomic=AB\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR]
+[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR]
+[\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR]
+[\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
+.PP
+.B sg_write_x
+\fI\-\-same=NDOB\fR [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app-tag=AT\fR]
+[\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR]
+[\fI\-\-in=IF\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR]
+[\fI\-\-timeout=TO\fR] [\fI\-\-unmap=U_A\fR]
+[\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
+.PP
+.B sg_write_x
+\fI\-\-scattered=RD\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR]
+[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR]
+[\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA[,LBA...]\fR]
+[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR]
+[\fI\-\-ref\-tag=RT\fR] [\fI\-\-scat\-file=SF\fR] [\fI\-\-scat\-raw\fR]
+[\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
+.PP
+.B sg_write_x
+\fI\-\-stream=ID\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR]
+[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR]
+[\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR]
+[\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+This utility will send one of six SCSI commands, all associated with writing
+data to the given \fIDEVICE\fR. They are a "normal" WRITE, ORWRITE, WRITE
+ATOMIC, WRITE SAME, WRITE SCATTERED or WRITE STREAM. This utility supports
+the 16 and 32 byte variants of all six commands. Hence some closely related
+commands are not supported (e.g. WRITE(10)). All 32 byte variants, apart from
+ORWRITE(32), require the \fIDEVICE\fR to be formatted with type 1, 2 or 3
+Protection Information (PI), making all logical blocks 8 bytes (or a multiple
+of 8 bytes) longer on the media.
+.PP
+The command line interface is a little crowded with over thirty options. Hence
+the SYNOPSIS, after listing all the (long) options, lists those applicable
+to each supported command. For each command synopsis, the option that selects
+the SCSI command is shown first followed by any required options. If no
+command option is given then a "normal" WRITE is assumed. Even though the
+\fI\-\-scat\-file=SF\fR option can be given for every command, it is only
+shown for WRITE SCATTERED where it is most useful. If the
+\fI\-\-scat\-file=SF\fR option is given then neither the
+\fI\-\-lba=LBA[,LBA...]\fR nor the \fI\-\-num=NUM[,NUM...]\fR options
+should be given. Only the first item of the \fI\-\-lba=LBA[,LBA...]\fR and
+the \fI\-\-num=NUM[,NUM...]\fR options (or first pair (or quintet) from the
+\fI\-\-scat\-file=SF\fR option) is used for all but the WRITE SCATTERED
+command. All commands can take \fI\-\-dry\-run\fR and \fI\-\-verbose\fR in
+addition to those shown in the SYNOPSIS.
+.PP
+The logical block size in bytes can be given explicitly with the
+\fI\-\-bs=BS\fR option, as long as \fIBS\fR is greater than zero. It
+is typically a power of two, 512 or greater. If the \fI\-\-bs=BS\fR option
+is not given or \fIBS\fR is zero then the SCSI READ CAPACITY command is
+used to find the logical block size. First the READ CAPACITY(16) command is
+tried and if successful the logical block size in the response is typically
+used as the actual block size for this utility. The exception is when
+PROT_EN is set in the response and the \fI\-\-wrprotect=WPR\fR option is
+given and non\-zero; in which case 8 (bytes) is added to the logical block
+size to yield the actual block size used by this utility. If READ
+CAPACITY(16) fails then READ CAPACITY(10) is tried and if that works then
+the logical block size in the response is used as the actual block size.
+.PP
+The number of bytes this utility will attempt to read from the file named by
+\fIIF\fR is the product of the actual block size and the
+number_of_blocks (\fINUM\fR or the sum of \fINUM\fR arguments). If less bytes
+are read from the file \fIIF\fR and the \fI\-\-strict\fR option is given then
+this utility exits with an exit status of SG_LIB_FILE_ERROR. If less bytes
+are read from the file \fIIF\fR and the \fI\-\-strict\fR option is not
+given then bytes of zero are substituted for the "missing" bytes and this
+utility continues.
+.PP
+Attempts to write multi megabyte data with a single command are likely to fail
+for one of several reasons. First the operating system might object to
+allocating a buffer that large. Next the SCSI pass\-through usually limits
+data blocks to a few megabytes or less. Finally the storage device might
+have a limited amount of RAM to support a write operation such as atomic (as
+it may need to roll back). The storage device can inform the application
+client of its limitations via the block limits VPD page (0xb0), with the
+maximum atomic transfer length field amongst others.
+.PP
+A degenerate LBA (Logical Block Address) range descriptor with no PI has
+an LBA and NUM of zero. A degenerate LBA range descriptor with PI
+additionally has its RT, AT and TM fields set to zero (note: that is not
+the default values for RT, AT and TM). They are degenerate in the sense
+that they are indistinguishable from a pad of zeros that follow the scatter
+list in the data\-out buffer. SBC\-4 makes clear that a degenerate LBA
+range descriptor is valid. This may become an issue if \fIRD\fR given in the
+\fI\-\-scattered=RD\fR option has the value 0. In this case the logic may
+need to scan the user provided data to calculate the number of LBA
+range descriptors which is required by the WRITE SCATTERED cdb. In the
+absence of other information the logic will take a degenerate LBA range
+descriptor as a terminator of the scatter list.
+.PP
+The reference for these commands is SBC\-4 (T10/BSR INCITS 506\-2021)
+and draft SBC\-5 INCITS 571 revision 1 dated 21 May 2021. All six SCSI
+commands are described in those documents. WRITE ATOMIC was added in
+SBC\-4 revision 3; WRITE STREAM was added in SBC\-4 revision 7; WRITE
+SCATTERED was added in SBC\-4 revision 11 while the others are in the
+previous SBC\-3 standard.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+The options are arranged in alphabetical order based on the long
+option name.
+.TP
+\fB\-6\fR, \fB\-\-16\fR
+send the 16 byte cdb variant of the selected SCSI command. If no command
+is selected then the (normal) SCSI WRITE(16) command is sent. If neither
+this option nor the \fI\-\-32\fR option is given then this option is
+assumed.
+.TP
+\fB\-3\fR, \fB\-\-32\fR
+send the 32 byte cdb variant of the selected SCSI command. If no command
+is selected then the (normal) SCSI WRITE(32) command is sent. If neither
+this option nor the \fI\-\-16\fR option is given then then the
+\fI\-\-16\fR option is assumed. If both this option and the \fI\-\-16\fR
+option are given then this option takes precedence. Note that apart
+from ORWRITE(32) all other 32 byte cdb variants require a \fIDEVICE\fR
+formatted with type 1, 2 or 3 protection information.
+.TP
+\fB\-a\fR, \fB\-\-app\-tag\fR=\fIAT\fR
+where \fIAT\fR is the "expected logical block application tag" field found in
+most of the 32 byte cdb variants (the exception is ORWRITE(32)). \fIAT\fR is
+a 16 bit field which means the maximum value is 0xffff. The default value
+is 0xffff .
+.TP
+\fB\-A\fR, \fB\-\-atomic\fR=\fIAB\fR
+selects the WRITE ATOMIC command and \fIAB\fR is placed in the Atomic
+Boundary field of its cdb. It is a 16 bit field so the maximum value
+is 0xffff. If unsure what value to set, try 0 which will attempt to
+write the whole data\-out buffer in a single atomic operation.
+.TP
+\fB\-B\fR, \fB\-\-bmop\fR=\fIOP,PGP\fR
+where \fIOP\fR and \fIPGP\fR are the values to be placed in ORWRITE(32)'s
+BMOP and 'Previous Generation Processing' fields respectively. BMOP is
+a 3 bit field (ranges from 0 to 7) and PGP is a 4 bit field (ranges from
+0 to 15). Both fields default to 0.
+.TP
+\fB\-b\fR, \fB\-\-bs\fR=\fIBS\fR
+where \fIBS\fR is the logical block size or the actual block size which
+will be slightly bigger. The default value is zero. If this option
+is not given or is given with a \fIBS\fR of zero then the SCSI READ
+CAPACITY(16) command is sent to \fIDEVICE\fR. If that fails then the READ
+CAPACITY(10) command is sent. The logical and actual block size will be
+derived from the response of the READ CAPACITY command.
+.br
+This section assumes \fIBS\fR is greater than zero. If \fIBS\fR is less than
+512 (bytes) or not a multiple of 8, a warning is issued and the utility
+continues unless the \fI\-\-strict\fR option is also given. If \fIBS\fR
+is a power of two (e.g. 512) then the logical and actual block size is
+set to \fIBS\fR (e.g. 512). If \fIBS\fR is not a power of two (e.g. 520)
+then the logical block size is set to the closest power of two less than
+\fIBS\fR (e.g. 512) and the actual block size is set to \fIBS\fR (e.g.
+520).
+.br
+If the logical and actual block size are different then a later check
+will reduce the actual block size back to the logical block size unless
+\fI\-\-wrprotect=WPR\fR is greater than zero.
+.TP
+\fB\-c\fR, \fB\-\-combined\fR=\fIDOF\fR
+This option only applies to WRITE SCATTERED and assumes the whole data\-out
+buffer can be read from \fIIF\fR given by the \fI\-\-in=IF\fR option. The
+whole data\-out buffer is the parameter list header, followed by zero or more
+LBA range descriptors, optionally followed by some pad bytes and then the
+data to be written to the media. If the \fI\-\-lba=LBA[,LBA...]\fR,
+\fI\-\-num=NUM[,NUM...]\fR or \fI\-\-scat\-file=SF\fR options are also given
+then an error is generated. The \fIDOF\fR argument should be the value
+suitable for the 'Logical Block Data Offset' field in the WRITE SCATTERED
+cdb. This is the offset in the data\-out buffer where the data to write
+to the media commences. The unit of that field is the actual block size
+which is the logical block size plus a multiple of 8, if protection
+information (PI) is being sent. When \fIWPR\fR (from \fI\-\-wrprotect=WPR\fR)
+is greater than zero then PI is expected. SBC\-4 revision 15 does not state
+it but it would appear that a \fIDOF\fR value of 0 is invalid. It is
+suggested that this option be used with the \fI\-\-strict\fR option while
+experimenting as random or incorrect data fed in via the \fI\-\-in=IF\fR
+option could write a lot of "interesting" data all over the \fIDEVICE\fR.
+If \fIDOF\fR is given as 0 the utility will scan the data in \fIIF\fR until
+\fIRD\fR LBA range descriptors are found; or if \fIRD\fR is also 0 until a
+degenerate LBA range descriptor is found.
+.TP
+\fB\-D\fR, \fB\-\-dld\fR=\fIDLD\fR
+where \fIDLD\fR is the duration limits descriptor spread across 3 bits in
+the SCSI WRITE(16) and the WRITE SCATTERED(16) cdbs. \fIDLD\fR is between 0
+to 7 inclusive with a default of zero. The DLD0 field in WRITE(16) and WRITE
+SCATTERED(16) is set if (0x1 & \fIDLD\fR) is non\-zero. The DLD1 field in
+both cdbs is set if (0x2 & \fIDLD\fR) is non\-zero. The DLD2 field in both
+cdbs is set if (0x4 & \fIDLD\fR) is non\-zero.
+.TP
+\fB\-d\fR, \fB\-\-dpo\fR
+if this option is given then the DPO (disable page out) bit field in the
+cdb is set. The default is to clear this bit field. Applies to all
+commands supported by thus utility except WRITE SAME.
+.TP
+\fB\-x\fR, \fB\-\-dry\-run\fR
+this option exits (with a status of 0) just before it would otherwise send
+the selected SCSI write command. It may still send a SCSI READ CAPACITY
+command (16 byte variant and perhaps 10 byte variant as well) so the
+\fIDEVICE\fR is still required. It reads the data in and processes it if the
+\fI\-\-in=IF\fR and/or the \fI\-\-scat\-file=SF\fR options are given. All
+command line processing and sanity checks (e.g. if the \fI\-\-strict\fR
+option is given) will be performed and if there is an error then there will
+be a non zero exit status value.
+.br
+If this option is given twice (e.g. \-xx) then instead of performing the
+selected write SCSI command, the data\-out buffer is written to a file
+called sg_write_x.bin . If it doesn't exist then that file is created in
+the current directory and is truncated if it previously did exist with
+longer contents. The data\-out buffer is written in binary with some
+information about it written to stdout. For writes other than scattered
+the filename and its length in bytes is output to stdout. For write
+scattered additionally its number of LBA range descriptors and its
+logical block data offset written to stdout.
+.TP
+\fB\-f\fR, \fB\-\-fua\fR
+if this option is given then the FUA (force unit access) bit field in the
+cdb is set. The default is to clear this bit field. Applies to all
+commands supported by thus utility except WRITE SAME.
+.TP
+\fB\-G\fR, \fB\-\-generation\fR=\fIEOG,NOG\fR
+the arguments for this option are used by the ORWITE(32) command only.
+\fIEOG\fR is placed in the "Expected ORWgeneration" field while \fINOG\fR
+is placed in the "New ORWgeneration" field. Both are 32 bits long and
+default to zero.
+.TP
+\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
+sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero.
+\fIGN\fR should be a value between 0 and 63.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit. Use multiple times for more help.
+Currently '\-h' to '\-hhhh' provide different output.
+.TP
+\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
+read data (in binary) from a file named \fIIF\fR in a single OS system
+call (in Unix: read(2)). That data is placed in a continuous buffer and then
+used as the data\-out buffer for all SCSI write commands apart from WRITE
+SCATTERED(16 or 32) which may include other data in the data\-out buffer.
+For WRITE SCATTERED (16 or 32) the data\-out buffer is made up of 3 or 4
+components in this order: a parameter list header (32 zero bytes); zero or
+more LBA range descriptors, optionally some pad bytes (zeros) and then data
+to write to the media. For WRITE SCATTERED \fIIF\fR only provides the data
+to write to the media unless \fI\-\-combined=DOF\fR is given. When the
+\fI\-\-combined=DOF\fR option is given \fIIF\fR contains all components of
+the WRITE SCATTERED data\-out buffer in binary. The data read from \fIIF\fR
+starts from byte offset \fIOFF\fR which defaults to zero and no more than
+\fIDLEN\fR bytes are read from that point (i.e. from the file byte offset
+\fIOFF\fR). If \fIDLEN\fR is zero or not given the rest of the file \fIIF\fR
+is read. This option is mandatory apart from when \-\-same=1 is given (that
+sets the NDOB bit which stands for "No Data Out Buffer"). In Unix based
+OSes, any number of zeros can be produced by using the /dev/zero device file.
+.br
+\fIIF\fR may be "\-" which is taken as stdin. In this case the
+\fI\-\-offset=OFF,DLEN\fR can be given with \fIOFF\fR set to 0 and
+\fILEN\fR set to a non\-zero value, preferably a multiple of the actual block
+size. The utility can also deduce how long the \fIIF\fR should be from
+\fINUM\fR (or the sum of them in the case of a scatter list).
+.TP
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA[,LBA...]\fR
+where the argument is a single Logical Block Address (LBA) or a comma
+separated list of \fILBA\fRs each of which is the address of the first block
+written by the selected write command. Only the WRITE SCATTERED command
+can usefully take more than one \fILBA\fR. Whatever number of \fILBA\fRs is
+given, there needs to be an equal number of \fINUM\fRs given to the
+\fI\-\-num=NUM[,NUM...]\fR option. The first given \fILBA\fR joins with the
+first given \fINUM\fR to form the first LBA range descriptor (which T10
+number from zero in SBC\-4). The second \fILBA\fR joins with the second
+\fILBA\fR to form the second LBA range descriptor, etc. A more convenient
+way to define a large number of LBA range descriptors is with the
+\fI\-\-scat\-file=SF\fR option. Defaults to logical block 0 (which could be
+dangerous) while \fINUM\fR defaults to 0 which makes the combination harmless.
+\fILBA\fR is assumed to be in decimal unless prefixed with '0x' or has a
+trailing 'h'.
+.TP
+\fB\-N\fR, \fB\-\-normal\fR
+the choice of a "normal" WRITE (16 or 32) command can be made explicitly
+with this option. In the absence of selecting any other command (e.g.
+\fI\-\-atomic=AB\fR ), the choice of a "normal" WRITE is the default.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fINUM[,NUM...]\fR
+where the argument is a single NUMber of blocks (NUM) or a comma separated
+list of \fINUM\fRs that pair with the corresponding entries in the
+\fI\-\-lba=LBA[,LBA...]\fR option. If a \fINUM\fR is given and is not
+provided by another method (e.g. by using the \fI\-\-scat\-file=SF\fR option)
+then it defaults to the number of blocks derived from the size of the file
+named by \fIIF\fR (starting at byte offset \fIOFF\fR to the end or the file
+or \fIDLEN\fR). Apart from the \fI\-\-combined=DOF\fR option, an LBA must
+be explicitly given (either with \fII\-\-lba=LBA\fR or via
+\fI\-\-scat\-file=SF\fR), if not \fINUM\fR defaults to 0 as a safety measure.
+.TP
+\fB\-o\fR, \fB\-\-offset\fR=\fIOFF[,DLEN]\fR
+where \fIOFF\fR is the byte offset within the file named \fIIF\fR to start
+reading from. The default value of \fIOFF\fR is zero which is the beginning
+of file named \fIIF\fR. \fIDLEN\fR is the maximum number of bytes to read,
+starting at byte offset \fIOFF\fR, from the file named \fIIF\fR. Less bytes
+will be read if an end of file occurs before \fIDLEN\fR is exhausted. If
+\fIDLEN\fR is zero or not given then reading from byte offset \fIOFF\fR to
+the end of the file named \fIIF\fR is assumed.
+.TP
+\fB\-O\fR, \fB\-\-or\fR
+selects the ORWRITE command. ORWRITE(16) has similar fields to WRITE(16)
+apart from the WRPROTECT field being named ORPROTECT with slightly different
+semantics and the absence of the 3 DLD bit fields. ORWRITE(32) has four
+extra fields that are set with the \fI\-\-bmop=OP,PGP\fR and
+\fI\-\-generation=EOG,NOG\fR options. ORWRITE(32) is the only 32 byte cdb
+command in this utility that does not require a \fIDEVICE\fR formatted with
+type 1, 2 or 3 PI (although it will still work if it is formatted with PI).
+.TP
+\fB\-Q\fR, \fB\-\-quiet\fR
+suppress some informational messages such as the ones associated with
+detected errors when this utility is about to exit. The exit status value
+is still returned to the operating system when this utility exits.
+.TP
+\fB\-r\fR, \fB\-\-ref\-tag\fR=\fIRT\fR
+where \fIRT\fR is the "expected initial logical block reference tag" field
+found in the 32 byte cdb variants of WRITE, WRITE ATOMIC, WRITE SAME and
+WRITE STREAM. The field is also found in the WRITE SCATTERED(32) LBA range
+descriptors. It is a 32 bit field which means the maximum value is
+0xffffffff. The default value is 0xffffffff.
+.TP
+\fB\-S\fR, \fB\-\-same\fR=\fINDOB\fR
+selects the WRITE SAME command with the NDOB field set to \fINDOB\fR which
+stands for No Data\-Out Buffer. \fINDOB\fR can take values 0 or 1 (i.e. it
+is a single bit field). When \-\-same=1 all options associated with the
+data\-out buffer are ignored.
+.TP
+\fB\-q\fR, \fB\-\-scat\-file\fR=\fISF\fR
+where \fISF\fR is the name of an auxiliary file containing the scatter list
+for the WRITE SCATTERED command. If the \fI\-\-scat\-raw\fR option is also
+given then \fISF\fR is assumed to contain both the parameter list header (32
+bytes of zeros) followed by zero or more LBA range descriptors which are
+also 32 bytes long each. These components are as defined by SBC\-4 (i.e.
+in binary with integers in big endian format). If the \fI\-\-scat\-raw\fR
+option is not given then a file of ACSII hexadecimal is expected as described
+in the SCATTERED FILE ASCII FORMAT section below.
+.br
+If this option is given with the \fI\-\-combined=DOF\fR option then this
+utility will exit with a syntax error. \fISF\fR must not be "\-", a way
+of stopping the user trying to redirect stdin.
+.TP
+\fB\-R\fR, \fB\-\-scat\-raw\fR
+this option only effects the way that the file named \fISF\fR from the
+\fI\-\-scat\-file=SF\fR option for WRITE SCATTERED is interpreted. By
+default (i.e. without this option), \fISF\fR is parsed as ASCII hexadecimal
+with blank lines and line contents from and including '#' to the end of
+line ignored. Hence it can contain comments and other indications. When
+this option is given, the file named \fISF\fR is interpreted as binary.
+As binary it is assumed to contain 32 bytes of zeros (the WRITE SCATTERED
+parameter list header) followed by zero or more LBA range descriptors (which
+are 32 bytes each). If the \fI\-\-strict\fR option is given the reserved
+field in those two items are checked with any non zero bytes causing an
+error.
+.TP
+\fB\-S\fR, \fB\-\-scattered\fR=\fIRD\fR
+selects the WRITE SCATTERED command with \fIRD\fR being the number of LBA
+range descriptors that will be placed in the data\-out buffer. If \fIRD\fR
+is zero then the logic will try and determine the number of range descriptors
+by other means (e.g. by parsing the file named by \fISF\fR, if there is one).
+The LBA range descriptors differ between the 16 and 32 byte cdb variants of
+WRITE SCATTERED. In the 16 byte cdb variant the 32 byte LBA range descriptor
+is made up of an 8 byte LBA, followed by a 4 byte number_of_blocks followed
+by 20 bytes of zeros. In the 32 byte variant the LBA and number_of_blocks
+are followed by a RT (4 bytes), an AT (2 bytes) and a TM (2 bytes) then
+12 bytes of zeros.
+.br
+This paragraph applies when \fIRD\fR is greater than zero.
+If \fIRD\fR is less than the number of LBA range descriptors built from
+command line options, from the \fI\-\-scat\-file=SF\fR option or
+decoded from \fIIF\fR (when the \fI\-\-combined=DOF\fR option is given)
+then \fIRD\fR takes precedence; so \fIRD\fR is placed in the "Number of
+LBA Range Descriptors" field in the cdb. If \fIRD\fR is greater than
+the number of LBA range descriptors found from the provided data and
+options, then an error is generated.
+.TP
+\fB\-T\fR, \fB\-\-stream\fR=\fIID\fR
+selects the WRITE STREAM command with the STR_ID field set to \fIID\fR.
+\fIID\fR can take values from 0 to 0xffff (i.e. it is a 16 bit field).
+.TP
+\fB\-s\fR, \fB\-\-strict\fR
+when this option is present, more things (e.g. that reserved fields contain
+zeros) and any irregularities will terminate the utility with a message to
+stderr and an indicative exit status. While experimenting with these commands,
+especially WRITE SCATTERED, it is recommended to use this option.
+.TP
+\fB\-t\fR, \fB\-\-tag\-mask\fR=\fITM\fR
+where \fITM\fR is the "logical block application tag mask" field found in the
+32 byte cdb variants of WRITE, WRITE ATOMIC, WRITE SAME and WRITE STREAM. The
+field is also found in the WRITE SCATTERED(32) LBA range descriptors. It is a
+16 bit field which means the maximum value is 0xffff. The default value is
+0xffff.
+.TP
+\fB\-I\fR, \fB\-\-timeout\fR=\fITO\fR
+where \fITO\fR is the command timeout value in seconds. The default value is
+120 seconds. If \fINUM\fR is large on slow media then these WRITE commands
+may require considerably more time than 120 seconds to complete.
+.TP
+\fB\-u\fR, \fB\-\-unmap\fR=\fIU_A\fR
+where \fIU_A\fR is OR\-ed bit values used to set the UNMAP and ANCHOR bit
+fields in the WRITE SAME (16 or 32) cdb. If \fIU_A\fR is 1 then the UNMAP
+bit field is set; if \fIU_A\fR is 2 then the ANCHOR bit field is set; if
+\fIU_A\fR is 3 then both the UNMAP and ANCHOR bit fields are set. The
+default value for both bit fields is clear (0); setting \fIU_A\fR to 0 will
+also clear both bit fields.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the degree of verbosity (debug messages). These messages are usually
+written to stderr.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+output version string then exit.
+.TP
+\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWPR\fR
+sets the WRPROTECT field (3 bits) in all sg_write_x commands apart from
+ORWRITE which has a 3 bit ORPROTECT field (and the synopsis shows \fIOPR\fR
+to highlight the difference). In all cases \fIWPR\fR is placed
+in that 3 bit field. The default value is zero which does not send any PI
+in the data\-out buffer. \fIWPR\fR should be a value between 0 and 7.
+.SH SCATTERED FILE ASCII FORMAT
+All commands in this utility can take a \fI\-\-scat\-file=SF\fR and that
+option can be seen as a replacement for the \fI\-\-lba=LBA[,LBA...]\fR and
+\fI\-\-num=NUM[,NUM...]\fR options. if both the \fI\-\-scat\-file=SF\fR and
+\fI\-\-scat\-raw\fR options are given then the file named \fISF\fR is
+expected to be binary and contain the parameter list header (32 bytes of
+zeros for both the 16 and 32 byte variants) followed by zero or more LBA
+range descriptors, each of 32 bytes each. This section describes what is
+expected in \fISF\fR when the \fI\-\-scat\-raw\fR option is not given.
+.PP
+The ASCII hexadecimal "scatter file" (named by \fISF\fR) can contain
+comments, empty lines and numbers. If multiple numbers appear on one line
+they can be separated by spaces, tabs or a single comma. Numbers are parsed
+as decimal unless prefixed by "0x" (or "0X") or have a suffix of "h". Ox is
+the prefix of hexadecimal number is the C language while T10 uses the "h"
+suffix for the same purpose. Anything from and including a "#" character
+to the end\-of\-line is ignored, so comments can be placed there.
+.PP
+For the WRITE SCATTERED (16) command, its LBA range descriptors contain two
+items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks.
+The remaining 20 bytes of the descriptor are zeros. The format accepted
+is relatively loose with each decoded value being placed in an LBA and
+then a number_of_blocks until the end\-of\-file is reached. The pattern
+starts with a LBA and if it doesn't finish with a number_of_blocks (i.e.
+an odd number of values are parsed) an error occurs. So the number of
+LBA range descriptors generated will be half the number of values parsed
+in \fISF\fR.
+.PP
+For the WRITE SCATTERED (32) command, its LBA range descriptors contain five
+items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks,
+then a 4 byte RT, a 2 byte AT, and a 2 byte TM. The last three items are
+associated with protection information (PI). The accepted format in the
+\fISF\fR file is more constrained than the 16 byte cdb variant. The items
+for each LBA range descriptor must be found on one line with adjacent items
+being comma separated. The first two items (LBA and number_of_blocks) must be
+given, and if no more items are on the line then RT, AT and TM are given
+their default values (all "ff" bytes). Spaces and tabs may appear between
+items but commas are the separators. Two commas with no value between them
+will cause the "missing" item to receive its default value.
+.SH NOTES
+Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+In Linux, prior to lk 3.17, the sg driver did not support cdb sizes greater
+than 16 bytes. Hence a device node like /dev/sg1 which is associated with
+the sg driver would fail with this utility if the \fI\-\-32\fR option was
+given (or implied by other options). The bsg driver with device nodes like
+/dev/bsg/6:0:0:1 does support cdb sizes greater than 16 bytes since its
+introduction in lk 2.6.28 .
+.SH EXIT STATUS
+The exit status of sg_write_x is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH EXAMPLES
+One simple usage is to write 4 blocks of zeros from (and including) a given
+LBA according to the rules of WRITE ATOMIC with an atomic boundary of 0.
+Since no cdb size option is given, the 16 byte cdb will be assumed (i.e.
+WRITE ATOMIC(16)):
+.PP
+ sg_write_x \-\-atomic=0 \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4 /dev/sdc
+.PP
+Since \fI\-\-bs=BS\fR has not been given, then this utility will call the
+READ CAPACITY(16) command on /dev/sdc to determine the number of bytes in a
+logical block. If the READ CAPACITY(16) command fails then the READ
+CAPACITY(10) command is tried. Let us assume one of them works and that
+the number of bytes in each logical block is 512 bytes. So 4 blocks of
+zeros (each block containing 512 bytes) will be written from (and including)
+LBA 0x1234 . Now to bypass the need for the READ CAPACITY command(s) the
+\fI\-\-bs=BS\fR option can be used:
+.PP
+ sg_write_x \-\-atomic=0 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4
+/dev/sdc
+.PP
+Since \-\-bs= is given and its value (512) is a power of 2, then the actual
+block size is also 512. If instead 520 was given then the logical block size
+would be 512 (the highest power of 2 less than 520) and the actual block size
+would be 520 bytes. To send the 32 byte variant add \-\-32 as in:
+.PP
+ sg_write_x \-\-atomic=0 \-\-32 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234
+\-\-num=4 /dev/sdc
+.PP
+For examples using 'sg_write_x \-\-same=NDOB' see the manpage for
+sg_write_same(8). The syntax is a little different but the semantics are the
+same.
+.PP
+To send a WRITE STREAM(32) with a STR_ID of 1 use the following:
+.PP
+ sg_write_x \-\-stream=1 \-\-32 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234
+\-\-num=4 /dev/sdc
+.PP
+Next is a WRITE SCATTERED(16) command with the scatter list, split between
+the \-\-lba= and \-\-num= options, on the command line:
+.PP
+ sg_write_x \-\-scattered=2 \-\-lba=2,0x33 \-\-num=4,1 -i /dev/zero /dev/sg1
+.PP
+Example of a WRITE SCATTERED(16) command with a degenerate LBA range
+descriptor (first element to \-\-lba= and \-\-num=):
+.PP
+ sg_write_x \-\-scattered=2 \-\-lba=0,0x33 \-\-num=0,1 -i /dev/zero /dev/sg1
+.PP
+Example of a WRITE SCATTERED(16) command with the scatter list in
+scat_file.txt
+.PP
+ sg_write_x \-\-scattered=3 \-q scat_file.txt \-i /dev/zero /dev/sg1
+.PP
+Next a WRITE SCATTERED(16) command with its scatter list and data in a
+single file. Note that the argument to \-\-scattered= is 0 so the number of
+LBA range descriptors is calculated by analyzing the first two blocks of
+scat_data.bin (because the argument to \-\-combined= is 2) :
+.PP
+ sg_write_x \-\-scattered=0 \-\-combined=2 \-i scat_data.bin /dev/sg1
+.PP
+When the \-xx option is used, a WRITE SCATTERED command is not executed
+but instead the contents of the data\-out buffer are written to a file
+called sg_write_x.bin . In the case of WRITE SCATTERED that binary file
+is suitable for supplying to a later invocation to do the actual write
+to media. For example:
+.PP
+ sg_write_x \-\-scattered=3 \-q scat_file.txt \-xx \-i /dev/zero /dev/sg1
+.br
+Wrote 8192 bytes to sg_write_x.bin, LB data offset: 1
+.br
+Number of LBA range descriptors: 3
+.br
+ sg_write_x \-\-scattered=0 \-\-combined=1 \-i sg_write_x.bin /dev/sg1
+.PP
+Notice when the sg_write_x.bin is written (and nothing is written to the
+media), a summary of what has happened is sent to stdout. The value shown
+for "LB data offset:" (1) should be given to the \-\-combined= option
+when the write to media actually occurs (i.e. the second invocation shown
+directly above).
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2017\-2021 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_readcap,sg_vpd,sg_write_same,sg_stream_ctl(sg3_utils)
diff --git a/doc/sg_xcopy.8 b/doc/sg_xcopy.8
new file mode 100644
index 00000000..1c2cd170
--- /dev/null
+++ b/doc/sg_xcopy.8
@@ -0,0 +1,381 @@
+.TH SG_XCOPY "8" "September 2021" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sg_xcopy \- copy data to and from files and devices using SCSI EXTENDED
+COPY (XCOPY)
+.SH SYNOPSIS
+.B sg_xcopy
+[\fIbs=BS\fR] [\fIconv=CONV\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR]
+[\fIif=IFILE\fR] [\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR]
+[\fIoflag=FLAGS\fR] [\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR]
+[\fI\-\-version\fR]
+.PP
+[\fIapp=\fR0|1] [\fIbpt=BPT\fR] [\fIcat=\fR0|1] [\fIdc=\fR0|1] [\fIfco=\fR0|1]
+[\fIid_usage=\fR{hold|discard|disable}] [\fIlist_id=ID\fR] [\fIprio=PRIO\fR]
+[\fItime=\fR0|1] [\fIverbose=VERB\fR] [\fI\-\-on_dst|\-\-on_src\fR]
+[\fI\-\-verbose\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Copy data to and from any files. Specialized for "files" that are Linux SCSI
+devices that support the SCSI EXTENDED COPY (XCOPY) command.
+.PP
+This utility
+has similar syntax and semantics to
+.B dd(1)
+but with no "conversions" is supported.
+.PP
+The first group in the synopsis above are "standard" Unix
+.B dd(1)
+operands. The second group are extra options added by this utility.
+Both groups are defined below in combined, alphabetical order.
+.PP
+By default the XCOPY command is sent to \fIOFILE\fR. This can be changed
+with the \fI\-\-on_src\fR or \fIiflag=xflag\fR options which cause the XCOPY
+command to be sent to \fIIFILE\fR instead. Also see the section on
+ENVIRONMENT VARIABLES.
+.PP
+In the SPC\-4 standard the T10 committee has expanded the XCOPY command so
+that it now has two variants: "LID1" (for a List Identifier length of 1 byte)
+and "LID4" (for a List Identifier length of 4 bytes). This utility supports
+the older, LID1 variant which is also found in SPC\-3 and earlier. While the
+LID1 variant in SPC\-4 is command level (binary) compatible with XCOPY as
+defined in SPC\-3, some of the command naming has changed. This utility uses
+the older, SPC\-3 XCOPY names.
+.PP
+The ddpt utility supports the same xcopy(LID1) functionality as this utility
+with the same options and flags. Additionally ddpt supports a subset of
+xcopy(LID4) functionality variously called "xcopy version 2, lite" or ODX.
+ODX is a market name and stands for Offloaded Data Xfer (i.e. transfer).
+.SH OPTIONS
+.TP
+\fBapp\fR={0|1}
+if 1 start the destination of the copy at the end of OFILE. This assumes
+that OFILE is a regular file. The default is 0 in which case the destination
+of the copy starts at the beginning of OFILE (possibly offset be SEEK). This
+option cannot be used with the \fIseek=SEEK\fR option.
+.TP
+\fBbpt\fR=\fIBPT\fR
+each IO transaction will be made using \fIBPT\fR blocks (or less if near
+the end of the copy). Default is 128 for logical block sizes less that 2048
+bytes, otherwise the default is 32. So for bs=512 the reads and writes
+will each convey 64 KiB of data by default (less if near the end of the
+transfer or memory restrictions). When cd/dvd drives are accessed, the
+logical block size is typically 2048 bytes and bpt defaults to 32 which again
+implies 64 KiB transfers.
+.TP
+\fBbs\fR=\fIBS\fR
+where \fIBS\fR
+.B must
+be the logical block size of the physical device (if either the input or
+output files are accessed via SCSI commands). Note that this differs from
+.B dd(1)
+which permits \fIBS\fR to be an integral multiple. Defaults to the
+device logical block size.
+.TP
+\fBcat\fR={0|1}
+sets the SCSI EXTENDED COPY command segment descriptor CAT bit to 0 or
+1 (default: 0). The CAT bit (in conjunction with the PAD bit) controls
+the handling of residual data. See section
+.B HANDLING OF RESIDUAL DATA
+for details.
+.TP
+\fBconv\fR=\fBCONV\fR
+all \fBCONV\fR arguments are ignored.
+.TP
+\fBcount\fR=\fICOUNT\fR
+copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the
+minimum (\fIIFILE\fR if \fIdc=0\fR or \fIOFILE\fR if \fIdc=1\fR)
+number of blocks that SCSI devices report from SCSI READ CAPACITY
+commands or that block devices (or their partitions) report. Normal
+files are not probed for their size. If \fIskip=SKIP\fR or
+\fIseek=SEEK\fR are given and the count is derived (i.e. not
+explicitly given) then the derived count is scaled back so that the
+copy will not overrun the device. If the file name is a block device
+partition and \fICOUNT\fR is not given then the size of the partition
+rather than the size of the whole device is used. If \fICOUNT\fR is
+not given (or \fIcount=\-1\fR) and cannot be derived then an error
+message is issued and no copy takes place.
+.TP
+\fBdc\fR={0|1}
+sets the SCSI EXTENDED COPY command segment descriptor DC bit to 0 or
+1 (default: 0). The DC bit controls whether \fICOUNT\fR
+refers to the source (\fIdc=0\fR) or the target (\fIdc=1\fR) descriptor.
+.TP
+\fBfco\fR={0|1}
+sets the SCSI EXTENDED COPY command segment descriptor FCO bit to 0 or
+1 (default: 0). The Fast Copy Only (FCO) bit set will result in the
+copy being done but a technique faster than SCSI READ and WRITE commands.
+If the copy cannot but done in a faster manner then a sense key of "Copy
+aborted" with and additional sense of "Fast copy not possible" is
+returned.
+.TP
+\fBibs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBid_usage\fR={hold|discard|disable}
+sets the SCSI EXTENDED COPY command parameter list field called LIST ID
+USAGE to 0 if the argument is 'hold', to 2 if the argument is 'discard',
+or to '3' if the argument is 'disable'.
+.br
+If the device has the ability to hold data (as indicated by "held data
+limit" being greater than zero) then \fIid_usage\fR defaults to 'hold'
+otherwise it defaults to 'discard'.
+.TP
+\fBif\fR=\fIIFILE\fR
+read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin
+is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR
+is given.
+.TP
+\fBiflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIIFILE\fR and are ignored when
+\fIIFILE\fR is stdin.
+.TP
+\fBlist_id\fR=\fIID\fR
+sets the SCSI EXTENDED COPY command parameter list field called LIST
+IDENTIFIER to \fIID\fR. \fIID\fR should be a value between 0 and
+255 (inclusive). \fIID\fR usually defaults to 1 unless
+\fIid_usage=disable\fR in which case it defaults to 0.
+.TP
+\fBobs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBof\fR=\fIOFILE\fR
+write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes
+to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed.
+If \fIOFILE\fR is '.' (period) then it is treated the same way as
+/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it
+is _not_ truncated; it is overwritten from the start of \fIOFILE\fR
+unless 'oflag=append' or \fISEEK\fR is given.
+.TP
+\fBoflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIOFILE\fR and are ignored when
+\fIOFILE\fR is /dev/null, '.' (period), or stdout.
+.TP
+\fBprio\fR=\fIPRIO\fR
+sets the SCSI EXTENDED COPY command parameter list field called PRIORITY
+to \fIPRIO\fR. The default value is 1.
+.TP
+\fBseek\fR=\fISEEK\fR
+start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBskip\fR=\fISKIP\fR
+start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBtime\fR={0|1}
+when 1, times transfer and does throughput calculation, outputting the
+results (to stderr) at completion. When 0 (default) doesn't perform timing.
+.TP
+\fBverbose\fR=\fIVERB\fR
+as \fIVERB\fR increases so does the amount of debug output sent to stderr.
+Default value is zero which yields the minimum amount of debug output.
+A value of 1 reports extra information that is not repetitive. A value
+2 reports cdbs and responses for SCSI commands that are not repetitive
+(i.e. other that READ and WRITE). Error processing is not considered
+repetitive. Values of 3 and 4 yield output for all SCSI commands (and
+Unix read() and write() calls) so there can be a lot of output.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs usage message and exits.
+.TP
+\fB\-\-on_dst\fR
+send the XCOPY command to the output file/device (i.e. \fIOFILE\fR). This is
+the default unless overridden by the \fI\-\-on_src\fR or \fIiflag=xflag\fR
+options. Also see the section below on ENVIRONMENT VARIABLES.
+.TP
+\fB\-\-on_src\fR
+send the XCOPY command to the input file/device (i.e. \fIIFILE\fR).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+equivalent to \fIverbose=1\fR. When used twice, equivalent to
+\fIverbose=2\fR, etc.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+outputs version number information and exits.
+.SH FLAGS
+Here is a list of flags and their meanings:
+.TP
+append
+causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For regular
+files this will lead to data appended to the end of any existing data.
+Cannot be used together with the \fIseek=SEEK\fR option as they conflict.
+The default action of this utility is to overwrite any existing data
+from the beginning of the file or, if \fISEEK\fR is given, starting at
+block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g.
+a disk) will usually be ignored or may cause an error to be reported.
+.TP
+excl
+causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR.
+.TP
+flock
+after opening the associated file (i.e. \fIIFILE\fR and/or \fIOFILE\fR)
+an attempt is made to get an advisory exclusive lock with the flock()
+system call. The flock arguments are "FLOCK_EX | FLOCK_NB" which will
+cause the lock to be taken if available else a "temporarily unavailable"
+error is generated. An exit status of 90 is produced in the latter case
+and no copy is done.
+.TP
+null
+has no affect, just a placeholder.
+.TP
+pad
+sets the SCSI EXTENDED COPY command segment descriptor PAD bit. The
+PAD bit (in conjunction with the CAT bit) controls the handling of
+residual data.(See section
+.B HANDLING OF RESIDUAL DATA
+for details.
+.TP
+xcopy
+has no affect; for compatibility with ddpt.
+.SH HANDLING OF RESIDUAL DATA
+The \fIpad\fR and \fIcat\fR bits control the handling of residual
+data. As the data can be specified either in terms of source or target
+logical block size and both might have different block sizes residual data
+is likely to happen in these cases.
+If both logical block sizes are identical these bits have no effect as
+residual data will not occur.
+.PP
+If none of these bits are set, the EXTENDED COPY command will be
+aborted with additional sense 'UNEXPECTED INEXACT SEGMENT'.
+.PP
+If only the \fIcat\fR bit is set the residual data will be retained
+and made available for subsequent segment descriptors. Residual data
+will be discarded for the last segment descriptor.
+.PP
+If the \fIpad\fR bit is set for the source descriptor only, any
+residual data for both source or destination will be discarded.
+.PP
+If the \fIpad\fR bit is set for the target descriptor only any
+residual source data will be handled as if the \fIcat\fR bit is set,
+but any residual destination data will be padded to make a whole block
+transfer.
+.PP
+If the \fIpad\fR bit is set for both source and target any residual
+source data will be discarded, and any residual destination data will
+be padded.
+.SH ENVIRONMENT VARIABLES
+If the command line invocation does not explicitly (and unambiguously)
+indicate whether the XCOPY SCSI command should be sent to \fIIFILE\fR (i.e.
+the source) or \fIOFILE\fR (i.e. the destination) then a check is
+made for the presence of the XCOPY_TO_SRC and XCOPY_TO_DST environment
+variables. If either one exists (but not both) then it indicates where
+the SCSI XCOPY command will be sent. By default the XCOPY command is
+sent to \fIOFILE\fR.
+.SH RETIRED OPTIONS
+Here are some retired options that are still present:
+.TP
+append=0 | 1
+when set, equivalent to 'oflag=append'. When clear the action is
+to overwrite the existing file (if it exists); this is the default.
+See the 'append' flag.
+.SH NOTES
+Copying data behind an Operating System's back can cause problems. In the
+case of Linux, users should look at this link:
+https://linux\-mm.org/Drop_Caches
+.br
+This command sequence may be useful:
+.br
+ sync; echo 3 > /proc/sys/vm/drop_caches
+.PP
+Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit
+values (i.e. very big numbers). Other values are limited to what can fit in
+a signed 32 bit number.
+.PP
+All informative, warning and error output is sent to stderr so that
+dd's output file can be stdout and remain unpolluted. If no options
+are given, then the usage message is output and nothing else happens.
+.PP
+If a device supports xcopy operations then it should set the 3PC
+field (3PC stands for Third Party Copy) in its standard INQUIRY response.
+This utility will attempt a xcopy operation irrespective of the value
+in the 3PC field but if it is zero (cleared) one would expect the
+xcopy operation to fail.
+.PP
+The status of the SCSI EXTENDED COPY command can be queried with
+.B sg_copy_results(sg3_utils)
+.PP
+Currently only block\-to\-block transfers are implemented; \fIIFILE\fR
+and \fIOFILE\fR must refer to a SCSI block device.
+.PP
+No account is taken of partitions so, for example, /dev/sbc2, /dev/sdc,
+/dev/sg2, and /dev/bsg/3:0:0:1 would all refer to the same thing: the
+whole logical unit (i.e. the whole disk) starting at LBA 0. So any
+partition indication (e.g. /dev/sdc2) is ignored. The user should set
+\fISKIP\fR, \fISEEK\fR and \fICOUNT\fR with information obtained
+from a command like 'fdisk \-l \-u /dev/sdc' to account for partitions.
+.PP
+XCOPY (LID1) capability has been added to the ddpt utility which is in
+a package of the same name. The ddpt utility will run on other
+OSes (e.g. FreeBSD and Windows) while sg_xcopy only runs on Linux. Also
+ddpt permits the arguments to \fIibs=\fR and \fIibs=\fR to be different.
+.SH EXAMPLES
+Copy 2M of data from the start of one device to another:
+.PP
+# sg_xcopy if=/dev/sdo of=/dev/sdp count=2048 list_id=2 dc=1
+.br
+sg_xcopy: if=/dev/sdo skip=0 of=/dev/sdp seek=0 count=1024
+.br
+Start of loop, count=1024, bpt=65535, lba_in=0, lba_out=0
+.br
+sg_xcopy: 1024 blocks, 1 command
+.PP
+Check the status of the EXTENDED COPY command:
+.PP
+# sg_copy_results \-\-status \-\-list_id=2 /dev/sdp
+.br
+Receive copy results (copy status):
+ Held data discarded: Yes
+ Copy manager status: Operation completed without errors
+ Segments processed: 1
+ Transfer count units: 0
+ Transfer count: 0
+.SH SIGNALS
+The signal handling has been borrowed from dd: SIGINT, SIGQUIT and
+SIGPIPE output the number of remaining blocks to be transferred and
+the records in + out counts; then they have their default action.
+SIGUSR1 causes the same information to be output yet the copy continues.
+All output caused by signals is sent to stderr.
+.SH EXIT STATUS
+The exit status of sg_xcopy is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.PP
+An additional exit status of 90 is generated if the flock flag is given
+and some other process holds the advisory exclusive lock.
+.SH AUTHORS
+Written by Hannes Reinecke and Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2021 Hannes Reinecke and Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+There is a web page discussing sg_dd at https://sg.danny.cz/sg/sg_dd.html
+.PP
+A POSIX threads version of this utility called
+.B sgp_dd
+is in the sg3_utils package. Another version from that package is called
+.B sgm_dd
+and it uses memory mapped IO to speed transfers from sg devices.
+.PP
+The lmbench package contains
+.B lmdd
+which is also interesting. For moving data to and from tapes see
+.B dt
+which is found at https://www.scsifaq.org/RMiller_Tools/index.html
+.PP
+To change mode parameters that effect a SCSI device's caching and error
+recovery see
+.B sdparm(sdparm)
+.PP
+See also
+.B dd(1), sg_copy_results(sg3_utils), ddrescue(GNU), ddpt,ddptctl(ddpt)
diff --git a/doc/sg_z_act_query.8 b/doc/sg_z_act_query.8
new file mode 100644
index 00000000..7cda1e2c
--- /dev/null
+++ b/doc/sg_z_act_query.8
@@ -0,0 +1,115 @@
+.TH SG_Z_ACT_QUERY "8" "December 2021" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_z_act_query \- send a SCSI ZONE ACTIVATE or ZONE QUERY command
+.SH SYNOPSIS
+.B sg_z_act_query
+[\fI\-\-activate\fR] [\fI\-\-all\fR] [\fI\-\-force\fR] [\fI\-\-help\fR]
+[\fI\-\-hex\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-maxlen=LEN\fR]
+[\fI\-\-num=ZS\fR] [\fI\-\-other=ZDID\fR] [\fI\-\-query\fR] [\fI\-\-raw\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-zone=ID\fR]
+\fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI ZONE ACTIVATE or ZONE QUERY command to the \fIDEVICE\fR. If the
+\fI\-\-activate\fR option is not given, then a ZONE QUERY command is sent.
+These commands were added in the ZBC\-2 draft revision 4 (zbc2r04.pdf).
+.PP
+Both of these commands have similar cdb_s and responses hence they are both
+placed in this utility. The difference is that only the ZONE ACTIVATE command
+will potentially activate or deactivate zones. Both commands will perform
+a "Verify activations operation" as defined in ZBC\-2 .
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-A\fR, \fB\-\-activate\fR
+sends a ZONE ACTIVATE command to the \fIDEVICE\fR. The default (i.e. without
+this option) is to send a ZONE QUERY command.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+sets the ALL field in the cdb.
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+when decoding the response to this command, certain sanity checks are
+done and if they fail a message is sent to stderr and a non\-zero
+exit status is set. If this option is given those sanity checks are
+bypassed.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR, \fB\-\-hex\fR
+output the response in hexadecimal to stdout. When used once the whole
+response is output in ASCII hexadecimal with a leading address (starting at
+0) on each line. When used twice each zone activation descriptor in the
+response is output separately in hexadecimal. When used thrice the whole
+response is output in hexadecimal with no leading address (on each line).
+.br
+The output format when this option is given thrice is suitable for a later
+invocation with the \fI\-\-inhex=FN\fR option.
+.TP
+\fB\-i\fR, \fB\-\-inhex\fR=\fIFN\fR
+where \fIFN\fR is a file name whose contents are assumed to be ASCII
+hexadecimal. If \fIDEVICE\fR is also given then \fIDEVICE\fR is ignored,
+a warning is issued and the utility continues, decoding the file named
+\fIFN\fR. See the "FORMAT OF FILES CONTAINING ASCII HEX" section in the
+sg3_utils manpage for more information. If the \fI\-\-raw\fR option is
+also given then the contents of \fIFN\fR are treated as binary.
+.br
+By default it is assumed the response is from a ZONE QUERY command but
+that shouldn't matter because the response of the ZONE ACTIVATE and
+ZONE QUERY commands is of the same form.
+.TP
+\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR
+where \fILEN\fR is the (maximum) response length in bytes. It is placed in
+the cdb's "allocation length" field. If not given (or \fILEN\fR is zero)
+then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576.
+.br
+The draft standard disallows allocation lengths less than 64.
+.TP
+\fB\-n\fR, \fB\-\-num\fR=\fIZS\fR
+where \fIZS\fR is placed in the "Number of zones" field in the cdb. This
+option is usually ignored if the \fI\-\-all\fR option is given. If the
+\fI\-\-all\fR option is not given, the default value of this field is 1 .
+.TP
+\fB\-o\fR, \fB\-\-other\fR=\fIZDID\fR
+where the \fIZDID\fR value will be placed in the "Other zone domain ID"
+field of the cdb to be sent to the \fIDEVICE\fR.
+.TP
+\fB\-q\fR, \fB\-\-query\fR
+causes the ZONE QUERY command to be sent to the \fIDEVICE\fR. Since this
+is the default action, this option is typically not needed. If both this
+option and the \fI\-\-activate\fR option are given, an error will be
+reported (and no command will be sent).
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+output response in binary (to stdout) unless the \fI\-\-inhex=FN\fR option
+is also given. In that case the input file name (\fIFN\fR) is decoded as
+binary (and the output is _not_ in binary (but may be hex)).
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-z\fR, \fB\-\-zone\fR=\fIID\fR
+where \fIID\fR is placed in the cdb's ZONE ID field. A zone id is a zone
+start logical block address (LBA). The default value is 0. \fIID\fR is
+assumed to be in decimal unless prefixed with '0x' or has a trailing 'h'
+which indicate hexadecimal. The maximum value that can be given is
+2^64 - 2. In the unlikely event of wanting to give 2^64 - 1, enter "\-1".
+.SH EXIT STATUS
+The exit status of sg_z_act_query is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2021 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_zone,sg_rep_zones,sg_reset_wp(sg3_utils)
diff --git a/doc/sg_zone.8 b/doc/sg_zone.8
new file mode 100644
index 00000000..256009ef
--- /dev/null
+++ b/doc/sg_zone.8
@@ -0,0 +1,97 @@
+.TH SG_ZONE "8" "June 2022" "sg3_utils\-1.48" SG3_UTILS
+.SH NAME
+sg_zone \- send a SCSI ZONE modifying command
+.SH SYNOPSIS
+.B sg_zone
+[\fI\-\-all\fR] [\fI\-\-close\fR] [\fI\-\-count=ZC\fR] [\fI\-\-element=EID\fR]
+[\fI\-\-finish\fR] [\fI\-\-help\fR] [\fI\-\-open\fR] [\fI\-\-remove\fR]
+[\fI\-\-sequentialize\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
+[\fI\-\-zone=ID\fR] \fIDEVICE\fR
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Sends a SCSI OPEN ZONE, CLOSE ZONE, FINISH ZONE, REMOVE ELEMENT AND MODIFY
+ZONES or SEQUENTIALIZE ZONE command to the \fIDEVICE\fR. All but the last
+two are found in the ZBC standard (INCITS 536\-2016). The REMOVE ELEMENT AND
+MODIFY ZONES command was added in zbc2r07 while the SEQUENTIALIZE ZONE command
+was added in zbc2r01b.
+.PP
+One and only one of the \fI\-\-open\fR, \fI\-\-close\fR, \fI\-\-finish\fR,
+\fI\-\-remove\fR and \fI\-\-sequentialize\fR options can be chosen.
+.PP
+The REPORT ZONES, REPORT REALMS and REPORT ZONE DOMAINS commands may be
+accessed via the sg_rep_zones utility. The ZONE ACTIVATE and ZONE QUERY
+commands may be accessed via the sg_z_act_query utility. The RESET WRITE
+POINTER command may be accessed via the sg_reset_wp utility.
+.SH OPTIONS
+Arguments to long options are mandatory for short options as well.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+sets the ALL field in the cdb.
+.TP
+\fB\-c\fR, \fB\-\-close\fR
+causes the CLOSE ZONE command to be sent to the \fIDEVICE\fR.
+.TP
+\fB\-C\fR, \fB\-\-count\fR=\fIZC\fR
+ZC is placed in the Zone Count field in the cdb of all four commands
+supported by this utility. ZC should be a value from 0 to 65535 (0xffff)
+inclusive.
+.TP
+\fB\-e\fR, \fB\-\-element\fR=\fIEID\fR
+where \fIEID\fR is an element identifier which is a 32 bit unsigned integer
+starting at one. This field is used by the REMOVE ELEMENT AND MODIFY ZONES
+command and its default value is zero (which is invalid). So the user needs
+to supply a valid element identifier when \fI\-\-remove\fR is used.
+.TP
+\fB\-f\fR, \fB\-\-finish\fR
+causes the FINISH ZONE command to be sent to the \fIDEVICE\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+output the usage message then exit.
+.TP
+\fB\-o\fR, \fB\-\-open\fR
+causes the OPEN ZONE command to be sent to the \fIDEVICE\fR.
+.TP
+\fB\-r\fR, \fB\-\-remove\fR
+causes the REMOVE ELEMENT AND MODIFY ZONES command to be sent to the
+\fIDEVICE\fR. In practice, \fI\-\-element=EID\fR needs to be also given.
+.TP
+\fB\-S\fR, \fB\-\-sequentialize\fR
+causes the SEQUENTIALIZE ZONE command to be sent to the \fIDEVICE\fR.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+increase the level of verbosity, (i.e. debug output).
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.TP
+\fB\-z\fR, \fB\-\-zone\fR=\fIID\fR
+where \fIID\fR is placed in the cdb's ZONE ID field. A zone id is a zone
+start logical block address (LBA). The default value is 0. \fIID\fR is
+assumed to be in decimal unless prefixed with '0x' or has a trailing 'h'
+which indicate hexadecimal.
+.SH NOTES
+After a REMOVE ELEMENT AND MODIFY ZONES command has completed, the element
+in question is said to be depopulated and any affected zones are placed in
+the 'offline' zone condition.
+.PP
+SBC\-4 has a similar command to REMOVE ELEMENT AND MODIFY ZONES called REMOVE
+ELEMENT AND TRUNCATE. The difference is that the latter "changes the
+association between LBAs and physical blocks" and the former does not change
+that association. In both cases, depopulated elements that have
+the 'Restoration Allowed' (RALWD) bit set (see sg_get_elem_status) may be
+restored with the RESTORE ELEMENTS AND REBUILD command (see sg_rem_rest_elem).
+.SH EXIT STATUS
+The exit status of sg_zone is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page.
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2014\-2022 Douglas Gilbert
+.br
+This software is distributed under a BSD\-2\-Clause license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B sg_rem_rest_elem,sg_rep_zones,sg_reset_wp,sg_z_act_query(sg3_utils)
diff --git a/doc/sginfo.8 b/doc/sginfo.8
new file mode 100644
index 00000000..66e4fb89
--- /dev/null
+++ b/doc/sginfo.8
@@ -0,0 +1,325 @@
+.TH SGINFO "8" "January 2014" "sg3_utils\-1.38" SG3_UTILS
+.SH NAME
+sginfo \- access mode page information for a SCSI (or ATAPI) device
+.SH SYNOPSIS
+.B sginfo
+[\fIOPTIONS\fR]
+[\fIDEVICE\fR]
+[\fIREPLACEMENT_PARAMETERS\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+sginfo is a port of the Linux
+.B scsiinfo
+program by Eric Youngdale. It uses SCSI generic (sg) devices; however in
+some cases the high level device name (i.e. sd, sr, st, osst, or hd) can
+also be used. The primary role of this program is to access mode page
+information. If permitted, mode page information can be altered. In
+addition information from the INQUIRY and READ DEFECTS commands are also
+available.
+.PP
+This utility is in legacy mode, only obvious bugs will be fixed. Options
+like \fI\-l\fR (to list devices) are broken in recent versions of
+Linux (e.g. 2.6 series and later); the lsscsi(8) utility can be used
+instead. Also mode pages are not being updated as https://www.t10.org
+adds and modifies mode page fields. Those interested in SCSI mode pages
+may find the
+.B sdparm
+utility more up to date and easier use, especially for changing parameters.
+.PP
+Four sets of values are maintained by a SCSI device for each mode
+page: current (active), default (manufacturer's supplied values),
+saved (values that are retained if the SCSI device is powered down),
+and changeable (mask indicating those values that can be changed).
+By default when a mode page is displayed the current values are
+shown. This can be overridden by "\-M" (defaults), "\-S" (saved)
+or "\-m" (modifiable (i.e. changeable)).
+.PP
+Many mode pages are decoded: for disks (see SBC\-2), for CD/DVDs (see
+MMC\-2/3/4/5), for tapes (see SSC\-2) and for enclosures (see SES\-2).
+Some mode pages common to all SCSI peripheral device types are defined
+in SPC\-4 (primary commands). A decoded mode page has its field names
+in the first column and the corresponding value in the second column.
+A "hex" mode page (and subpage) has its byte position in the first
+column (in hex and starting at 0x2) and the corresponding hex value
+in the second column. Decoded pages can be viewed with the '\-t' option
+or with a specific option (e.g. 'c' for the caching mode page).
+Naturally decoded pages must be supplied by the \fIDEVICE\fR and
+recognised by this program. If supported by the device, decoded pages
+may be modified. All mode pages (and subpages) that the device supports
+can be viewed in hex (and potentially modified) via the "\-u" option
+.PP
+If no options are given that will cause mode page(s) or INQUIRY data
+to be printed out, then a brief INQUIRY response is output. This
+includes the vendor, product and revision level of the device.
+.SH OPTIONS
+.TP
+\fB\-6\fR
+Perform 6 byte MODE SENSE and MODE SELECT commands; by default the
+10 byte variants are used.
+.TP
+\fB\-a\fR
+Display some INQUIRY data and the unit serial number followed by
+all mode pages reported by the device. It is similar to
+the '\-t 0x3f' option. If the mode page is known then it is output
+in decoded form otherwise it is output in hexadecimal.
+.TP
+\fB\-A\fR
+Display some INQUIRY data and the unit serial number followed by
+all mode pages and all mode subpages reported by the device.
+It is similar to the '\-t 0x3f,0xff' option. If a mode (sub)page
+is known then it is output in decoded form otherwise it is output in
+hexadecimal.
+.TP
+\fB\-c\fR
+Access information in the Caching mode page.
+.TP
+\fB\-C\fR
+Access information in the Control mode Page.
+.TP
+\fB\-d\fR
+Display defect lists (default format: index).
+.TP
+\fB\-D\fR
+Access information in the Disconnect\-Reconnect mode page.
+.TP
+\fB\-e\fR
+Access information in the Error Recovery mode page.
+.TP
+\fB\-E\fR
+Access information in the Control Extension mode page.
+.TP
+\fB\-f\fR
+Access information in the Format Device mode page.
+.TP
+\fB\-F\fR\fIarg\fR
+Format of the defect lists:
+ \-Flogical \- logical block addresses (32 bit)
+ \-Flba64 \- logical block addresses (64 bit)
+ \-Fphysical \- physical blocks
+ \-Findex \- defect bytes from index
+ \-Fhead \- sort by head
+.br
+Used in conjunction with "\-d" or "\-G". If a format is not given "index" is
+assumed.
+.TP
+\fB\-g\fR
+Access information in the Rigid Disk Drive Geometry mode page.
+.TP
+\fB\-G\fR
+Display grown defect list (default format: index).
+.TP
+\fB\-i\fR
+Display the response to a standard INQUIRY command.
+.TP
+\fB\-I\fR
+Access the Informational Exceptions mode page.
+.TP
+\fB\-l\fR
+Deprecated. Only use in old versions of Linux (e.g. 2.4 and
+earlier). Please use lsscsi(8) in the Linux 2.6 series and
+later. List known SCSI devices on the system.
+.TP
+\fB\-n\fR
+Access information in the Notch and Partition mode page.
+.TP
+\fB\-N\fR
+Negate (i.e. stop) mode page changes being placed in the "saved"
+page (by default changes go to the current and the saved page).
+Only active when used together with '\-R'.
+.TP
+\fB\-P\fR
+Access information in the Power Condition mode page.
+.TP
+\fB\-r\fR
+Display all raw (or primary) SCSI device names visible in the /dev
+directory. Examples are /dev/sda, /dev/st1 and /dev/scd2. Does not
+list sg device names so devices such as a SCSI enclosure which only
+have an sg device name are not listed.
+.TP
+\fB\-s\fR
+Display information in the unit serial number page which is a
+INQUIRY command variant.
+.TP
+\fB\-t\fR \fIPN\fR[,\fISPN\fR]
+Display information from mode page number \fIPN\fR (and optionally sub
+page number \fISPN\fR) in decoded format (if known, otherwise in hex form).
+\fIPN\fR is a mode page number in a decimal number from 0 to 63 inclusive.
+\fISPN\fR is the mode subpage number and is assumed to be 0 if not given.
+\fISPN\fR is a decimal number from 1 to 255 inclusive. A page number of 63
+returns all pages supported by the device in ascending order except for
+page 0 which, if present, is last. Page 0 is vendor specific and not
+necessarily in mode page format. Alternatively hex values can be given for
+both \fIPN\fR and \fISPN\fR (both prefixed by '0x').
+.TP
+\fB\-T\fR
+Trace commands to obtain more verbose output (for debugging). When used once
+SCSI commands are shown (in hex) and any errors from these SCSI commands are
+spelt out (i.e. with a decoded and raw sense buffer). When used twice, the
+additional data sent with mode select and the response from mode sense are
+shown (in hex).
+.TP
+\fB\-u\fR \fIPN\fR[,\fISPN\fR]
+Display information from mode page number \fIPN\fR (and optionally \fISPN\fR)
+in hex form. \fIPN\fR is a mode page number in a decimal number from 0 to 63
+inclusive. \fISPN\fR is the mode subpage number and is assumed to be 0 if
+not given. \fISPN\fR is a decimal number from 1 to 255 inclusive. A page
+number of 63 returns all pages supported by the device in ascending order
+except for page 0 which, if present, is last. Page 0 is vendor specific and
+not necessarily in mode page format. Alternatively hex values can be given
+for both \fIPN\fR and \fISPN\fR (both prefixed by '0x'). For example 63 and
+0x3f are equivalent.
+.TP
+\fB\-v\fR
+Display version string then exit. [N.B. This option increases verbosity for
+most other utilities in this package as outlined in 'man 8 sg3_utils'.
+This odd usage is for backward compatibility with the scsiinfo utility.]
+.TP
+\fB\-V\fR
+Access information in the Verify Error Recovery mode page. [N.B. This
+option prints the version string then exits in most other utilities in
+this package as outlined in 'man 8 sg3_utils'. This odd usage is for
+backward compatibility with the scsiinfo utility.]
+.TP
+\fB\-z\fR
+do a single fetch for mode pages (over\-estimating the expected length
+of the returned response). The default action is to do a double
+fetch, the first fetch is to find the response length that could be
+returned. Devices that closely adhere to SCSI standards should not
+require this option, some real world devices do require it.
+.SH ADVANCED OPTIONS
+Only one of the following three options can be specified.
+None of these three implies the current values are returned.
+.TP
+\fB\-m\fR
+Display modifiable fields instead of current values
+.TP
+\fB\-M\fR
+Display manufacturer's defaults instead of current values
+.TP
+\fB\-S\fR
+Display saved defaults instead of current values
+.PP
+The following are advanced options, not generally suited for most users:
+.TP
+\fB\-X\fR
+Display output values in a list. Make them suitable for editing and
+being given back to the '\-R' (replace command).
+.TP
+\fB\-R\fR
+Replace parameters \- best used with \-X (expert use only)
+.SH CHANGING MODE PAGE PARAMETERS
+Firstly you should know what you are doing before changing existing
+parameters. Taking the control page as an example, first list it out
+normally (e.g. "sginfo \-C /dev/sda") and
+decide which parameter is to be changed (note its position relative
+to the other lines output). Then execute the same sginfo command with
+the "\-X" option added; this will output the parameter values in a
+single row in the same relative positions as the previous command. Now
+execute "sginfo \-CXR /dev/sda ..." with the "..." replaced by the
+single row of values output by the previous command, with the relevant
+parameter changed. Here is a simplified example:
+.PP
+ $ sginfo \-C /dev/sda
+.br
+ Control mode page (0xa)
+.br
+ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
+.br
+ TST 0
+.br
+ D_SENSE 0
+.br
+ GLTSD 1
+.br
+ RLEC 0
+.PP
+[Actually the Control page has more parameters that shown above.] Next
+output those parameters in single line form:
+.PP
+ $ sginfo \-CX /dev/sda
+.br
+ 0 0 1 0
+.PP
+Let us assume that the GLTSD bit is to be cleared. The command that
+will clear it is:
+.PP
+ $ sginfo \-CXR /dev/sda 0 0 0 0
+.PP
+The same number of parameters output by the "\-CX" command needs to be
+placed at the end of the "\-CXR" command line (after the device name).
+Now check that the change took effect:
+.PP
+ $ sginfo \-C /dev/sda
+.br
+ Control mode page (0xa)
+.br
+ \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
+.br
+ TST 0
+.br
+ D_SENSE 0
+.br
+ GLTSD 0
+.br
+ RLEC 0
+.PP
+When a mode page is "replaced" the default action is to change both the
+current page and the saved page. [For some reason versions of sginfo and
+scsiinfo prior to 2.0 did not change the "saved" page.] To change only
+the current mode page but not the corresponding saved page use the "\-N"
+option.
+.PP
+.SH GENERATING SCRIPT FILES AND HEX PAGES
+The "\-aX" or "\-AX" option generates output suitable for a script file.
+Mode pages are output in list format (after the INQUIRY and serial
+number) one page per line. To facilitate running the output as (part
+of) a script file to assert chosen mode page values, each line is
+prefixed by "sginfo \-t \fIPN\fR[,\fISPN\fR] \-XR ". When such a script
+file is run, it will have the effect of re\-asserting the mode
+page values to what they were when the "\-aX" generated the output.
+.PP
+All mode pages (and subpages) supported by the device can be accessed via
+the \-t and \-u options. To see all
+mode pages supported by the device use "\-u 63". [To see all mode pages
+and all subpages use "\-u 63,255".] To list the control mode page in
+hex (mode page index in the first column and the corresponding byte
+value in the second column) use "\-u 0xa". Mode pages (subpage code == 0)
+start at index position 2 while subpages start at index position 4.
+If the "\-Xu ..." option is used then a list a hex values each value
+prefixed by "@" is output. Mode (sub)page values can then be modified
+with the "\-RXu ..." option.
+.PP
+.SH RESTRICTIONS
+The SCSI MODE SENSE command yields block descriptors as well as a mode
+page(s). This utility ignores block descriptors and does not display
+them. The "disable block descriptor" switch (DBD) in the MODE SENSE command
+is not set since some devices yield errors when it is set. When mode page
+values are being changed (the "\-R" option), the same block descriptor
+obtained by reading the mode page (i.e. via a MODE SENSE command) is sent
+back when the mode page is written (i.e. via a MODE SELECT command).
+.PP
+.SH REFERENCES
+SCSI (draft) standards can be found at https://www.t10.org . The relevant
+documents are SPC\-4 (mode pages common to all device types),
+SBC\-2 (direct access devices [e.g. disks]), MMC\-4 (CDs and DVDs) and
+SSC\-2 (tapes).
+.PP
+.SH AUTHORS
+Written by Eric Youngdale, Michael Weller, Douglas Gilbert, Kurt Garloff,
+Thomas Steudten
+.PP
+.SH HISTORY
+scsiinfo version 1.0 was released by Eric Youngdale on 1st November 1993.
+The most recent version of scsiinfo is version 1.7 with the last patches
+by Michael Weller. sginfo is derived from scsiinfo and uses the sg
+interface to get around the 4 KB buffer limit in scsiinfo that cramped
+the display of defect lists especially. sginfo was written by Douglas
+Gilbert with patches from Kurt Garloff. This manpage corresponds with
+version 2.25 of sginfo.
+.PP
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B lsscsi(lsscsi), scsiinfo(internet); sg_modes, sg_inq, sg_vpd (sg3_utils),
+.B sdparm(sdparm)
diff --git a/doc/sgm_dd.8 b/doc/sgm_dd.8
new file mode 100644
index 00000000..58782d72
--- /dev/null
+++ b/doc/sgm_dd.8
@@ -0,0 +1,284 @@
+.TH SGM_DD "8" "February 2019" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+sgm_dd \- copy data to and from files and devices, especially SCSI
+devices
+.SH SYNOPSIS
+.B sgm_dd
+[\fIbs=BS\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] [\fIif=IFILE\fR]
+[\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] [\fIoflag=FLAGS\fR]
+[\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] [\fI\-\-version\fR]
+.PP
+[\fIbpt=BPT\fR] [\fIcdbsz=\fR6|10|12|16] [\fIdio=\fR0|1] [\fIsync=\fR0|1]
+[\fItime=\fR0|1] [\fIverbose=VERB\fR] [\fI\-\-dry\-run\fR]
+[\fI\-\-verbose\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Copy data to and from any files. Specialized for "files" that are
+Linux SCSI generic (sg) devices and raw devices. Uses memory mapped
+transfers on sg devices. Similar syntax and semantics to
+.B dd(1)
+but does not perform any conversions.
+.PP
+Will only perform memory mapped transfers when \fIIFILE\fR or \fIOFILE\fR
+are SCSI generic (sg) devices.
+.PP
+If both \fIIFILE\fR and \fIOFILE\fR are sg devices then memory mapped
+transfers are performed on \fIIFILE\fR. If no other flags are specified
+then indirect IO is performed on \fIOFILE\fR. If 'oflag=dio' is given then
+direct IO is attempted on \fIOFILE\fR. If direct IO is not available, then
+this utility falls back to indirect IO and reports this at the end of the
+copy.
+.PP
+The first group in the synopsis above are "standard" Unix
+.B dd(1)
+operands. The second group are extra options added by this utility.
+Both groups are defined below.
+.SH OPTIONS
+.TP
+\fBbpt\fR=\fIBPT\fR
+each IO transaction will be made using \fIBPT\fR blocks (or less if
+near the end of the copy). Default is 128 for block sizes less that 2048
+bytes, otherwise the default is 32. So for bs=512 the reads and writes
+will each convey 64 KiB of data by default (less if near the end of the
+transfer or memory restrictions). When cd/dvd drives are accessed, the
+block size is typically 2048 bytes and bpt defaults to 32 which again
+implies 64 KiB transfers.
+.TP
+\fBbs\fR=\fIBS\fR
+where \fIBS\fR
+.B must
+be the block size of the physical device. Note that this differs from
+.B dd(1)
+which permits \fIBS\fR to be an integral multiple. Default is 512 which
+is usually correct for disks but incorrect for cdroms (which normally
+have 2048 byte blocks). For this utility the maximum size of each individual
+IO operation is \fIBS\fR * \fIBPT\fR bytes.
+.TP
+\fBcdbsz\fR=6 | 10 | 12 | 16
+size of SCSI READ and/or WRITE commands issued on sg device names.
+Default is 10 byte SCSI command blocks (unless calculations indicate
+that a 4 byte block number may be exceeded, in which case it defaults
+to 16 byte SCSI commands).
+.TP
+\fBcount\fR=\fICOUNT\fR
+copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the
+minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices
+report from SCSI READ CAPACITY commands or that block devices (or their
+partitions) report. Normal files are not probed for their size. If
+\fIskip=SKIP\fR or \fIseek=SEEK\fR are given and the count is derived (i.e.
+not explicitly given) then the derived count is scaled back so that the
+copy will not overrun the device. If the file name is a block device
+partition and \fICOUNT\fR is not given then the size of the partition rather
+than the size of the whole device is used. If \fICOUNT\fR is not given and
+cannot be derived then an error message is issued and no copy takes place.
+.TP
+\fBdio\fR=0 | 1
+permits direct IO to be selected on the write\-side (i.e. on \fIOFILE\fR).
+Only allowed when the read\-side (i.e. \fIIFILE\fR) is a sg device. When
+1 there may be a "zero copy" copy (i.e. mmap\-ed transfer on the read into
+the user space and direct IO from there on the write, potentially two DMAs
+and no data copying from the CPU). Default is 0.
+The same action as 'dio=1' is also available with 'oflag=dio'.
+.TP
+\fBibs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBif\fR=\fIIFILE\fR
+read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin
+is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR
+is given.
+.TP
+\fBiflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIIFILE\fR and are ignored when
+\fIIFILE\fR is stdin.
+.TP
+\fBobs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBof\fR=\fIOFILE\fR
+write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes
+to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed.
+If \fIOFILE\fR is '.' (period) then it is treated the same way as
+/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it
+is _not_ truncated; it is overwritten from the start of \fIOFILE\fR
+unless 'oflag=append' or \fISEEK\fR is given.
+.TP
+\fBoflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIOFILE\fR and are ignored when
+\fIOFILE\fR is /dev/null, '.' (period), or stdout.
+.TP
+\fBseek\fR=\fISEEK\fR
+start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBskip\fR=\fISKIP\fR
+start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBsync\fR=0 | 1
+when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the
+transfer. Only active when \fIOFILE\fR is a sg device file name.
+.TP
+\fBtime\fR=0 | 1
+when 1, times transfer and does throughput calculation, outputting the
+results (to stderr) at completion. When 0 (default) doesn't perform timing.
+.TP
+\fBverbose\fR=\fIVERB\fR
+as \fIVERB\fR increases so does the amount of debug output sent to stderr.
+Default value is zero which yields the minimum amount of debug output.
+A value of 1 reports extra information that is not repetitive. A value
+2 reports cdbs and responses for SCSI commands that are not repetitive
+(i.e. other that READ and WRITE). Error processing is not considered
+repetitive. Values of 3 and 4 yield output for all SCSI commands (and
+Unix read() and write() calls) so there can be a lot of output.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+does all the command line parsing and preparation but bypasses the actual
+copy or read. That preparation may include opening \fIIFILE\fR or
+\fIOFILE\fR to determine their lengths. This option may be useful for
+testing the syntax of complex command line invocations in advance of
+executing them.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs usage message and exits.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+when used once, this is equivalent to \fIverbose=1\fR. When used
+twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+outputs version number information and exits.
+.SH FLAGS
+Here is a list of flags and their meanings:
+.TP
+append
+causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For normal
+files this will lead to data appended to the end of any existing data.
+Cannot be used together with the \fIseek=SEEK\fR option as they conflict.
+The default action of this utility is to overwrite any existing data
+from the beginning of the file or, if \fISEEK\fR is given, starting at
+block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g.
+a disk) will usually be ignored or may cause an error to be reported.
+.TP
+dio
+is only active with oflag (i.e. 'oflag=dio'). Its action is described in
+the 'dio=1' option description above.
+.TP
+direct
+causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user
+memory buffers are aligned to the page size. Has no effect on sg, normal
+or raw files.
+.TP
+dpo
+set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not
+supported for 6 byte cdb variants of READ and WRITE. Indicates that
+data is unlikely to be required to stay in device (e.g. disk) cache.
+May speed media copy and/or cause a media copy to have less impact
+on other device users.
+.TP
+dsync
+causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. The "d" is prepended to lower confusion with the 'sync=0|1'
+option which has another action (i.e. a synchronisation to media at the
+end of the transfer).
+.TP
+excl
+causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR.
+.TP
+fua
+causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE
+commands. This only has effect with sg devices. The 6 byte variants
+of the SCSI READ and WRITE commands do not support the FUA bit.
+Only active for sg device file names.
+.TP
+null
+has no affect, just a placeholder.
+.SH RETIRED OPTIONS
+Here are some retired options that are still present:
+.TP
+fua=0 | 1 | 2 | 3
+force unit access bit. When 3, fua is set on both \fIIFILE\fR and
+\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR; when 1, fua is set on
+\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag.
+.SH NOTES
+A raw device must be bound to a block device prior to using sgm_dd.
+See
+.B raw(8)
+for more information about binding raw devices. To be safe, the sg device
+mapping to SCSI block devices should be checked with the lsscsi utility
+before use.
+.PP
+Raw device partition information can often be found with
+.B fdisk(8)
+[the "\-ul" argument is useful in this respect].
+.PP
+Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The count, skip and seek parameters can take 64 bit values (i.e. very
+big numbers). Other values are limited to what can fit in a signed
+32 bit number.
+.PP
+Data usually gets to the user space in a 2 stage process: first the
+SCSI adapter DMAs into kernel buffers and then the sg driver copies
+this data into user memory (write operations reverse this sequence).
+With memory mapped transfers a kernel buffer reserved by sg is memory
+mapped (see the
+.B mmap(2)
+system call) into the user space. When this is done
+the second (redundant) copy from kernel buffers to user space is
+not needed. Hence the transfer is faster and requires less "grunt"
+from the CPU.
+.PP
+All informative, warning and error output is sent to stderr so that
+dd's output file can be stdout and remain unpolluted. If no options
+are given, then the usage message is output and nothing else happens.
+.PP
+For sg devices this utility issues SCSI READ and WRITE (SBC) commands which
+are appropriate for disks and reading from CD/DVD/BD drives. Those commands
+are not formatted correctly for tape devices so sgm_dd should not be used
+on tape devices.
+.PP
+This utility stops the copy if any error is encountered. For more
+advanced "copy on error" logic see the
+.B sg_dd
+utility (and its 'coe' flag).
+.SH EXAMPLES
+See the examples given in the man page for
+.B sg_dd(8).
+.SH SIGNALS
+The signal handling has been borrowed from dd: SIGINT, SIGQUIT and
+SIGPIPE output the number of remaining blocks to be transferred and
+the records in + out counts; then they have their default action.
+SIGUSR1 causes the same information to be output yet the copy continues.
+All output caused by signals is sent to stderr.
+.SH EXIT STATUS
+The exit status of sgm_dd is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page. Since this utility works at a higher level
+than individual commands, and there are 'coe' and 'retries' flags,
+individual SCSI command failures do not necessary cause the process
+to exit.
+.SH AUTHORS
+Written by Douglas Gilbert and Peter Allworth.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2019 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+The simplest variant of this utility is called
+.B sg_dd.
+A POSIX threads version of this utility called
+.B sgp_dd
+is in the sg3_utils package. The lmbench package contains
+.B lmdd
+which is also interesting.
+.B dd(1), ddpt(ddpt), raw(8)
diff --git a/doc/sgp_dd.8 b/doc/sgp_dd.8
new file mode 100644
index 00000000..b6184a03
--- /dev/null
+++ b/doc/sgp_dd.8
@@ -0,0 +1,345 @@
+.TH SGP_DD "8" "August 2022" "sg3_utils\-1.47" SG3_UTILS
+.SH NAME
+sgp_dd \- copy data to and from files and devices, especially SCSI
+devices
+.SH SYNOPSIS
+.B sgp_dd
+[\fIbs=BS\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] [\fIif=IFILE\fR]
+[\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] [\fIoflag=FLAGS\fR]
+[\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] [\fI\-\-version\fR]
+.PP
+[\fIbpt=BPT\fR] [\fIcoe=\fR0|1] [\fIcdbsz=\fR6|10|12|16] [\fIdeb=VERB\fR]
+[\fIdio=\fR0|1] [\fIsync=\fR0|1] [\fIthr=THR\fR] [\fItime=\fR0|1]
+[\fIverbose=VERB\fR] [\fI\-\-chkaddr\fR] [\fI\-\-dry\-run\fR]
+[\fI\-\-verbose\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+Copy data to and from any files. Specialised for "files" that are
+Linux SCSI generic (sg) and raw devices. Similar syntax and semantics to
+.B dd(1)
+but does not perform any conversions. Uses POSIX threads (often
+called "pthreads") to increase the amount of parallelism. This improves
+speed in some cases.
+.PP
+The first group in the synopsis above are "standard" Unix
+.B dd(1)
+operands. The second group are extra options added by this utility.
+Both groups are defined below.
+.SH OPTIONS
+.TP
+\fBbpt\fR=\fIBPT\fR
+each IO transaction will be made using \fIBPT\fR blocks (or less if
+near the end of the copy). Default is 128 for block sizes less that 2048
+bytes, otherwise the default is 32. So for bs=512 the reads and writes
+will each convey 64 KiB of data by default (less if near the end of the
+transfer or memory restrictions). When cd/dvd drives are accessed, the
+block size is typically 2048 bytes and bpt defaults to 32 which again
+implies 64 KiB transfers.
+.TP
+\fBbs\fR=\fIBS\fR
+where \fIBS\fR
+.B must
+be the block size of the physical device. Note that this differs from
+.B dd(1)
+which permits 'bs' to be an integral multiple of the actual device block
+size. Default is 512 which is usually correct for disks but incorrect for
+cdroms (which normally have 2048 byte blocks).
+.TP
+\fBcdbsz\fR=6 | 10 | 12 | 16
+size of SCSI READ and/or WRITE commands issued on sg device names.
+Default is 10 byte SCSI command blocks (unless calculations indicate
+that a 4 byte block number may be exceeded, in which case it defaults
+to 16 byte SCSI commands).
+.TP
+\fBcoe\fR=0 | 1
+set to 1 for continue on error. Only applies to errors on sg devices.
+Thus errors on other files will stop sgp_dd. Default is 0 which
+implies stop on any error. See the 'coe' flag for more information.
+.TP
+\fBcount\fR=\fICOUNT\fR
+copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the
+minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices
+report from SCSI READ CAPACITY commands or that block devices (or their
+partitions) report. Normal files are not probed for their size. If
+\fIskip=SKIP\fR or \fIseek=SEEK\fR are given and the count is deduced (i.e.
+not explicitly given) then that count is scaled back so that the copy will
+not overrun the device. If the file name is a block device partition and
+\fICOUNT\fR is not given then the size of the partition rather than the
+size of the whole device is used. If \fICOUNT\fR is not given and cannot be
+deduced then an error message is issued and no copy takes place.
+.TP
+\fBdeb\fR=\fIVERB\fR
+outputs debug information. If \fIVERB\fR is 0 (default) then there is
+minimal debug information and as \fIVERB\fR increases so does the amount
+of debug (max debug output when \fIVERB\fR is 9).
+.TP
+\fBdio\fR=0 | 1
+default is 0 which selects indirect IO. Value of 1 attempts direct
+IO which, if not available, falls back to indirect IO and notes this
+at completion. If direct IO is selected and /sys/module/sg/parameters/allow_dio
+has the value of 0 then a warning is issued (and indirect IO is performed)
+For finer grain control use 'iflag=dio' or 'oflag=dio'.
+.TP
+\fBibs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBif\fR=\fIIFILE\fR
+read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin
+is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR
+is given.
+.TP
+\fBiflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIIFILE\fR and are ignored when
+\fIIFILE\fR is stdin.
+.TP
+\fBobs\fR=\fIBS\fR
+if given must be the same as \fIBS\fR given to 'bs=' option.
+.TP
+\fBof\fR=\fIOFILE\fR
+write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes
+to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed.
+If \fIOFILE\fR is '.' (period) then it is treated the same way as
+/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it
+is _not_ truncated; it is overwritten from the start of \fIOFILE\fR
+unless 'oflag=append' or \fISEEK\fR is given.
+.TP
+\fBoflag\fR=\fIFLAGS\fR
+where \fIFLAGS\fR is a comma separated list of one or more flags outlined
+below. These flags are associated with \fIOFILE\fR and are ignored when
+\fIOFILE\fR is /dev/null, '.' (period), or stdout.
+.TP
+\fBseek\fR=\fISEEK\fR
+start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBskip\fR=\fISKIP\fR
+start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR.
+Default is block 0 (i.e. start of file).
+.TP
+\fBsync\fR=0 | 1
+when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the
+transfer. Only active when \fIOFILE\fR is a sg device file name.
+.TP
+\fBthr\fR=\fITHR\fR
+where \fITHR\fR is the number or worker threads (default 4) that attempt to
+copy in parallel. Minimum is 1 and maximum is 1024.
+.TP
+\fBtime\fR=0 | 1
+when 1, the transfer is timed and throughput calculation is
+performed, outputting the results (to stderr) at completion. When
+0 (default) no timing is performed.
+.TP
+\fBverbose\fR=\fIVERB\fR
+increase verbosity. Same as \fIdeb=VERB\fR. Added for compatibility with
+sg_dd and sgm_dd.
+.TP
+\fB\-c\fR, \fB\-\-chkaddr\fR
+this option checks that every block read contains the (32 bit) block address
+of that block. If that check fails, the copy exits with a miscompare error.
+This check complements the 'sg_dd iflag=00,ff' generation of blocks that
+contain their own (32 bit, big endian) block address. When \fI\-\-chkaddr\fR
+is used once, only the first block address in each block is checked. When
+used twice, each block address (that fits in a block) is checked.
+.TP
+\fB\-d\fR, \fB\-\-dry\-run\fR
+does all the command line parsing and preparation but bypasses the actual
+copy or read. That preparation may include opening \fIIFILE\fR or
+\fIOFILE\fR to determine their lengths. This option may be useful for
+testing the syntax of complex command line invocations in advance of
+executing them.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+outputs usage message and exits.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+when used once, this is equivalent to \fIverbose=1\fR. When used
+twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+outputs version number information and exits.
+.SH FLAGS
+Here is a list of flags and their meanings:
+.TP
+append
+causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For normal
+files this will lead to data appended to the end of any existing data.
+Cannot be used together with the \fIseek=SEEK\fR option as they conflict.
+The default action of this utility is to overwrite any existing data
+from the beginning of the file or, if \fISEEK\fR is given, starting at
+block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g.
+a disk) will usually be ignored or may cause an error to be reported.
+.TP
+coe
+continue on error. When given with 'iflag=', an error that is detected
+in a single SCSI command (typically 'bpt' blocks) is noted (by an error
+message sent to stderr), then zeros are substituted into the buffer
+for the corresponding write operation and the copy continues. Note that the
+.B sg_dd
+utility is more sophisticated in such error situations when 'iflag=coe'.
+When given with 'oflag=', any error reported by a SCSI WRITE command is
+reported to stderr and the copy continues (as if nothing went wrong).
+.TP
+dio
+request the sg device node associated with this flag does direct IO.
+If direct IO is not available, falls back to indirect IO and notes
+this at completion. If direct IO is selected and
+/sys/module/sg/parameters/allow_dio has the value of 0 then a warning is
+issued (and indirect IO is performed).
+.TP
+direct
+causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user
+memory buffers are aligned to the page size. Has no effect on sg, normal
+or raw files.
+.TP
+dpo
+set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not
+supported for 6 byte cdb variants of READ and WRITE. Indicates that
+data is unlikely to be required to stay in device (e.g. disk) cache.
+May speed media copy and/or cause a media copy to have less impact
+on other device users.
+.TP
+dsync
+causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR. The 'd' is prepended to lower confusion with the 'sync=0|1'
+option which has another action (i.e. a synchronisation to media at the
+end of the transfer).
+.TP
+excl
+causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or
+\fIOFILE\fR.
+.TP
+mmap
+can only be used in the \fIiflag=FLAGS\fR or the \fIoflag=FLAGS\fR argument
+list but not both. The nominated side of the copy will use memory mapped IO
+based on the mmap(2) system call. The sg driver will remap its DMA
+destination or source buffer into the user space when the mmap(2) system call
+is used on a sg device.
+.TP
+fua
+causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE
+commands. This only has effect with sg devices. The 6 byte variants
+of the SCSI READ and WRITE commands do not support the FUA bit.
+Only active for sg device file names.
+.TP
+null
+has no affect, just a placeholder.
+.SH RETIRED OPTIONS
+Here are some retired options that are still present:
+.TP
+coe=0 | 1
+continue on error is 0 (off) by default. When it is 1, it is
+equivalent to 'iflag=coe oflag=coe' described in the FLAGS section
+above. Similar to 'conv=noerror,sync' in
+.B dd(1)
+utility. Default is 0 which implies stop on error. More advanced
+coe=1 processing on reads is performed by the sg_dd utility.
+.TP
+.TP
+fua=0 | 1 | 2 | 3
+force unit access bit. When 3, fua is set on both \fIIFILE\fR and
+\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR;, when 1, fua is set on
+\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag.
+.SH NOTES
+A raw device must be bound to a block device prior to using sgp_dd.
+See
+.B raw(8)
+for more information about binding raw devices. To be safe, the sg device
+mapping to SCSI block devices should be checked with 'sg_map'
+before use.
+.PP
+Raw device partition information can often be found with
+.B fdisk(8)
+[the "\-ul" argument is useful in this respect].
+.PP
+Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative
+suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
+in the sg3_utils(8) man page.
+.PP
+The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit
+values (i.e. very big numbers). Other values are limited to what can fit in
+a signed 32 bit number.
+.PP
+Data usually gets to the user space in a 2 stage process: first the
+SCSI adapter DMAs into kernel buffers and then the sg driver copies
+this data into user memory (write operations reverse this sequence).
+This is called "indirect IO" and there is a 'dio' option to select
+"direct IO" which will DMA directly into user memory. Due to some
+issues "direct IO" is disabled in the sg driver and needs a
+configuration change to activate it.
+.PP
+All informative, warning and error output is sent to stderr so that
+dd's output file can be stdout and remain unpolluted. If no options
+are given, then the usage message is output and nothing else happens.
+.PP
+Why use sgp_dd? Because in some cases it is twice as fast as dd
+(mainly with sg devices, raw devices give some improvement).
+Another reason is that big copies fill the block device caches
+which has a negative impact on other machine activity.
+.SH SIGNALS
+The signal handling has been borrowed from dd: SIGINT, SIGQUIT and
+SIGPIPE output the number of remaining blocks to be transferred and
+the records in + out counts; then they have their default action.
+SIGUSR1 causes the same information to be output yet the copy continues.
+All output caused by signals is sent to stderr.
+.SH EXAMPLES
+.PP
+Looks quite similar in usage to dd:
+.PP
+ sgp_dd if=/dev/sg0 of=t bs=512 count=1MB
+.PP
+This will copy 1 million 512 byte blocks from the device associated with
+/dev/sg0 (which should have 512 byte blocks) to a file called t.
+Assuming /dev/sda and /dev/sg0 are the same device then the above is
+equivalent to:
+.PP
+ dd if=/dev/sda of=t bs=512 count=1000000
+.PP
+although dd's speed may improve if bs was larger and count was
+correspondingly scaled. Using a raw device to do something similar on a
+ATA disk:
+.PP
+ raw /dev/raw/raw1 /dev/hda
+.br
+ sgp_dd if=/dev/raw/raw1 of=t bs=512 count=1MB
+.PP
+To copy a SCSI disk partition to an ATA disk partition:
+.PP
+ raw /dev/raw/raw2 /dev/hda3
+.br
+ sgp_dd if=/dev/sg0 skip=10123456 of=/dev/raw/raw2 bs=512
+.PP
+This assumes a valid partition is found on the SCSI disk at the given
+skip block address (past the 5 GB point of that disk) and that
+the partition goes to the end of the SCSI disk. An explicit count
+is probably a safer option.
+.PP
+To do a fast copy from one SCSI disk to another one with similar
+geometry (stepping over errors on the source disk):
+.PP
+ sgp_dd if=/dev/sg0 of=/dev/sg1 bs=512 coe=1
+.SH EXIT STATUS
+The exit status of sgp_dd is 0 when it is successful. Otherwise see
+the sg3_utils(8) man page. Since this utility works at a higher level
+than individual commands, and there are 'coe' and 'retries' flags,
+individual SCSI command failures do not necessary cause the process
+to exit.
+.SH AUTHORS
+Written by Douglas Gilbert and Peter Allworth.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2000\-2022 Douglas Gilbert
+.br
+This software is distributed under the GPL version 2. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+A simpler, non\-threaded version of this utility but with more
+advanced "continue on error" logic is called
+.B sg_dd
+and is also found in the sg3_utils package. The lmbench package contains
+.B lmdd
+which is also interesting.
+.B raw(8), dd(1)
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 00000000..d8156744
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,127 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?)
+# the default C compiler is clang. Swap the comment marks (lines starting
+# with '#') on the next 4 (non-blank) lines.
+CC = gcc
+# CC = clang
+
+LD = gcc
+# LD = clang
+
+
+EXECS = sg_simple1 sg_simple2 sg_simple3 sg_simple4 sg_simple16 \
+ scsi_inquiry sg_excl sg_simple5 sg__sat_identify \
+ sg__sat_phy_event sg__sat_set_features sg_sat_chk_power \
+ sg_sat_smart_rd_data
+
+EXTRAS = sgq_dd
+
+BSG_EXTRAS =
+
+
+MAN_PGS =
+MAN_PREF = man8
+
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+CPPFLAGS = -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+# CPPFLAGS = -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) -DDEBUG
+
+CFLAGS = -g -O2 -W -Wall
+# CFLAGS = -g -O2 -Wall -DSG_KERNEL_INCLUDES
+# CFLAGS = -g -O2 -Wall -pedantic
+
+LDFLAGS =
+
+LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_io_linux.o
+LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_common.o ../lib/sg_pt_linux.o ../lib/sg_pt_linux_nvme.o
+
+all: $(EXECS)
+
+extras: $(EXTRAS)
+
+bsg: $(BSG_EXTRAS)
+
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend
+
+sg_simple1: sg_simple1.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple2: sg_simple2.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple3: sg_simple3.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple4: sg_simple4.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple16: sg_simple16.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+scsi_inquiry: scsi_inquiry.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_excl: sg_excl.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_simple5: sg_simple5.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg__sat_identify: sg__sat_identify.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg__sat_phy_event: sg__sat_phy_event.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg__sat_set_features: sg__sat_set_features.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_sat_chk_power: sg_sat_chk_power.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_sat_smart_rd_data: sg_sat_smart_rd_data.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sgq_dd: sgq_dd.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $^; \
+ do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+# Linux uses GNU make and FreeBSD uses Berkely make. The following lines
+# only work in Linux. Possible solutions in FreeBSD:
+# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq'
+# c) build with 'make -f Makefile.freebsd'
+# In Linux one can install bmake (but that won't help here).
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
diff --git a/examples/Makefile.freebsd b/examples/Makefile.freebsd
new file mode 100644
index 00000000..d983736d
--- /dev/null
+++ b/examples/Makefile.freebsd
@@ -0,0 +1,82 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?)
+# the default C compiler is clang. Swap the comment marks (lines starting
+# with '#') on the next 4 (non-blank) lines.
+# CC = gcc
+# CC = clang
+
+# LD = gcc
+# LD = clang
+
+
+EXECS = sg_simple5
+
+# EXTRAS = sgq_dd
+
+MAN_PGS =
+MAN_PREF = man8
+
+OS_FLAGS = -DSG_LIB_FREEBSD
+EXTRA_FLAGS = $(OS_FLAGS)
+
+# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) -I ../include
+CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) -I ../include
+# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) -I ../include
+
+CFLAGS_PTHREADS = -D_REENTRANT
+
+# there is no rule to make the following in the parent directory,
+# it is assumed they are already built.
+D_FILES = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_cmds_basic.o ../lib/sg_pt_common.o ../lib/sg_pt_freebsd.o
+
+LDFLAGS = -lcam
+
+all: $(EXECS)
+
+extras: $(EXTRAS)
+
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) $(EXTRAS) core .depend
+
+sg_simple5: sg_simple5.o $(D_FILES)
+ $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES)
+
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $^; \
+ do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+# Linux uses GNU make and FreeBSD uses Berkely make. The following lines
+# only work in Linux. Possible solutions in FreeBSD:
+# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq'
+# c) build with 'make -f Makefile.freebsd'
+# In Linux one can install bmake (but that won't help here).
+# ifeq (.depend,$(wildcard .depend))
+# include .depend
+# endif
diff --git a/examples/README b/examples/README
new file mode 100644
index 00000000..7a5ae285
--- /dev/null
+++ b/examples/README
@@ -0,0 +1,17 @@
+Building files in this directory depends on several files being already
+built in the ../lib directory. So to build files here, the ./configure
+needs to be executed in the parent directory followed by changing
+directory to the lib directory and calling 'make' there.
+Another way is to do a top level 'make' after the ./configure which
+will make the libraries followed by all the utilities in the src/
+directory. To make them in FreeBSD use 'make -f Makefile.freebsd' .
+
+There is an brief explanation of each example in the README file in
+the main (i.e. this directory's parent) directory. There are also
+some notes at the top of each source file.
+
+Some files that were previously in this directory have been moved to
+the 'testing' directory.
+
+Douglas Gilbert
+19th January 2018
diff --git a/examples/reassign_addr.txt b/examples/reassign_addr.txt
new file mode 100644
index 00000000..9d48d005
--- /dev/null
+++ b/examples/reassign_addr.txt
@@ -0,0 +1,11 @@
+# This file is an example for the sg_reassign utility.
+# That utility can take one or more logical block addresses from stdin when
+# either the '--address=-" or "-a -" option is given on the command line.
+
+# To see logical block addresses placed in the command parameter block
+# without executing them command try something like:
+# 'sg_reassign --address=- --dummy -vv /dev/sda < reassign_addr.txt
+
+1,34,0x33,0X444 0x89abcde 0xdeadbeef # 6 lba's
+
+# dpg 20070130
diff --git a/examples/scsi_inquiry.c b/examples/scsi_inquiry.c
new file mode 100644
index 00000000..99497490
--- /dev/null
+++ b/examples/scsi_inquiry.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 1999-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
+ * device driver.
+ * This program does a SCSI inquiry command on the given device and
+ * outputs some of the result. This program highlights the use of the
+ * SCSI_IOCTL_SEND_COMMAND ioctl. This should be able to be applied to
+ * any SCSI device file descriptor (not just one related to sg). [Whether
+ * this is a good idea on a disk while it is mounted is debatable.
+ * No detrimental effects when this was tested ...]
+ *
+ * Version 0.16 20181207
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <scsi/scsi.h>
+/* #include <scsi/scsi_ioctl.h> */ /* glibc hides this file sometimes */
+
+typedef struct my_scsi_ioctl_command {
+ unsigned int inlen; /* _excluding_ scsi command length */
+ unsigned int outlen;
+ unsigned char data[1]; /* was 0 but that's not ISO C!! */
+ /* on input, scsi command starts here then opt. data */
+} My_Scsi_Ioctl_Command;
+
+#define OFF (2 * sizeof(unsigned int))
+
+#ifndef SCSI_IOCTL_SEND_COMMAND
+#define SCSI_IOCTL_SEND_COMMAND 1
+#endif
+
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define INQUIRY_REPLY_LEN 96
+
+
+int main(int argc, char * argv[])
+{
+ int s_fd, res, k, to;
+ unsigned char inq_cdb [INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0,
+ INQUIRY_REPLY_LEN, 0};
+ unsigned char * inqBuff = (unsigned char *)
+ malloc(OFF + sizeof(inq_cdb) + 512);
+ unsigned char * buffp = inqBuff + OFF;
+ My_Scsi_Ioctl_Command * ishp = (My_Scsi_Ioctl_Command *)inqBuff;
+ char * file_name = 0;
+ int do_nonblock = 0;
+ int oflags = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp(argv[k], "-n"))
+ do_nonblock = 1;
+ else if (*argv[k] != '-')
+ file_name = argv[k];
+ else {
+ printf("Unrecognized argument '%s'\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'scsi_inquiry [-n] <scsi_device>'\n");
+ printf(" where: -n open device in non-blocking mode\n");
+ printf(" Examples: scsi_inquiry /dev/sda\n");
+ printf(" scsi_inquiry /dev/sg0\n");
+ printf(" scsi_inquiry -n /dev/scd0\n");
+ return 1;
+ }
+
+ if (do_nonblock)
+ oflags = O_NONBLOCK;
+ s_fd = open(file_name, oflags | O_RDWR);
+ if (s_fd < 0) {
+ if ((EROFS == errno) || (EACCES == errno)) {
+ s_fd = open(file_name, oflags | O_RDONLY);
+ if (s_fd < 0) {
+ perror("scsi_inquiry: open error");
+ return 1;
+ }
+ }
+ else {
+ perror("scsi_inquiry: open error");
+ return 1;
+ }
+ }
+ /* Don't worry, being very careful not to write to a none-scsi file ... */
+ res = ioctl(s_fd, SCSI_IOCTL_GET_BUS_NUMBER, &to);
+ if (res < 0) {
+ /* perror("ioctl on scsi device, error"); */
+ printf("scsi_inquiry: not a scsi device\n");
+ return 1;
+ }
+
+ ishp->inlen = 0;
+ ishp->outlen = INQUIRY_REPLY_LEN;
+ memcpy(buffp, inq_cdb, INQUIRY_CMDLEN);
+ res = ioctl(s_fd, SCSI_IOCTL_SEND_COMMAND, inqBuff);
+ if (0 == res) {
+ to = (int)*(buffp + 7);
+ printf(" %.8s %.16s %.4s, byte_7=0x%x\n", buffp + 8,
+ buffp + 16, buffp + 32, to);
+ }
+ else if (res < 0)
+ perror("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND err");
+ else
+ printf("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND status=0x%x\n", res);
+
+ res = close(s_fd);
+ if (res < 0) {
+ perror("scsi_inquiry: close error");
+ return 1;
+ }
+ return 0;
+}
diff --git a/examples/sdiag_sas_p0_cjtpat.txt b/examples/sdiag_sas_p0_cjtpat.txt
new file mode 100644
index 00000000..72810920
--- /dev/null
+++ b/examples/sdiag_sas_p0_cjtpat.txt
@@ -0,0 +1,12 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to put phy identifier 0 of the
+# given device into CJTPAT (jitter pattern) generation mode.
+# Physical transmission speed is 3 Gbps
+# N.B. This will turn the receiver off on phy id 0.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,0,1,2,9,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sdiag_sas_p0_prbs9.txt b/examples/sdiag_sas_p0_prbs9.txt
new file mode 100644
index 00000000..1b96f99d
--- /dev/null
+++ b/examples/sdiag_sas_p0_prbs9.txt
@@ -0,0 +1,12 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to put phy identifier 0 of the
+# given device into PRBS9 (jitter pattern) generation mode.
+# Physical transmission speed is 22.5 Gbps
+# N.B. This will turn the receiver off on phy id 0.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,0,1,3,c,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sdiag_sas_p1_cjtpat.txt b/examples/sdiag_sas_p1_cjtpat.txt
new file mode 100644
index 00000000..c82a558e
--- /dev/null
+++ b/examples/sdiag_sas_p1_cjtpat.txt
@@ -0,0 +1,13 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to put phy identifier 1 of the
+# given device into CJTPAT (jitter pattern) generation mode.
+# Physical transmission speed is 3 Gbps
+# See sdiag_sas_p1_stop.txt to turn off this test pattern.
+# N.B. This will turn the receiver off on phy id 1.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,1,1,2,9,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sdiag_sas_p1_idle.txt b/examples/sdiag_sas_p1_idle.txt
new file mode 100644
index 00000000..39091ced
--- /dev/null
+++ b/examples/sdiag_sas_p1_idle.txt
@@ -0,0 +1,14 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to put phy identifier 1 of the
+# given device into IDLE (continuously transmit idle dwords) mode.
+# Physical transmission speed is 3 Gbps (last number on first
+# active line can be 8 for 1.5Gbps, 9 for 3Gbps and 10 for 6Gbps).
+# See sdiag_sas_p1_stop.txt to turn off this test pattern.
+# N.B. This will turn the receiver off on phy id 1.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,1,1,12,9,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sdiag_sas_p1_prbs15.txt b/examples/sdiag_sas_p1_prbs15.txt
new file mode 100644
index 00000000..1248ab3a
--- /dev/null
+++ b/examples/sdiag_sas_p1_prbs15.txt
@@ -0,0 +1,12 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to put phy identifier 1 of the
+# given device into PRBS15 (jitter pattern) generation mode.
+# Physical transmission speed is 22.5 Gbps
+# N.B. This will turn the receiver off on phy id 1.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,1,1,4,c,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sdiag_sas_p1_stop.txt b/examples/sdiag_sas_p1_stop.txt
new file mode 100644
index 00000000..55b95c34
--- /dev/null
+++ b/examples/sdiag_sas_p1_stop.txt
@@ -0,0 +1,11 @@
+# This is the hex for a SAS protocol specific diagnostic
+# page. It will attempt to stop phy identifier 1 of the
+# given device producing a test pattern.
+# N.B. This should make phy id 1 usable for SAS protocols again.
+#
+# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}'
+#
+3f,6,0,1c,1,0,2,9,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0
diff --git a/examples/sg__sat_identify.c b/examples/sg__sat_identify.c
new file mode 100644
index 00000000..c70eec06
--- /dev/null
+++ b/examples/sg__sat_identify.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2006-2018 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This program uses a ATA PASS-THROUGH (16) SCSI command to package
+ an ATA IDENTIFY DEVICE (A1h) command. If the '-p' option is given,
+ it will package an ATA IDENTIFY PACKET DEVICE (Ech) instead (for
+ ATAPI device like cd/dvd drives) See http://www.t10.org
+ SAT draft at time of writing: sat-r08a.pdf
+
+ Invocation: sg__sat_identify [-p] [-v] [-V] <device>
+
+ With SAT, the user can find out whether a device is an ATA disk or
+ an ATAPI device. The ATA Information VPD page contains a "command
+ code" field in byte 56. Its values are either ECh for a (s/p)ATA
+ disk, A1h for a (s/p)ATAPI device, or 0 for unknown.
+
+*/
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+
+#define ATA_IDENTIFY_DEVICE 0xec
+#define ATA_IDENTIFY_PACKET_DEVICE 0xa1
+#define ID_RESPONSE_LEN 512
+
+#define EBUFF_SZ 256
+
+static char * version_str = "1.04 20180220";
+
+static void usage()
+{
+ fprintf(stderr, "Usage: "
+ "sg__sat_identify [-p] [-v] [-V] <device>\n"
+ " where: -p do IDENTIFY PACKET DEVICE (def: IDENTIFY "
+ "DEVICE) command\n"
+ " -v increase verbosity\n"
+ " -V print version string and exit\n\n"
+ "Performs a IDENTIFY (PACKET) DEVICE ATA command via a SAT "
+ "pass through\n");
+}
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t inBuff[ID_RESPONSE_LEN];
+ uint8_t sense_buffer[32];
+ int do_packet = 0;
+ int verbose = 0;
+ int extend = 0;
+ int chk_cond = 0; /* set to 1 to read register(s) back */
+ int protocol = 4; /* PIO data-in */
+ int t_dir = 1; /* 0 -> to device, 1 -> from device */
+ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ const uint8_t * cucp;
+
+ memset(inBuff, 0, sizeof(inBuff));
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp(argv[k], "-p"))
+ ++do_packet;
+ else if (0 == strcmp(argv[k], "-v"))
+ ++verbose;
+ else if (0 == strcmp(argv[k], "-vv"))
+ verbose += 2;
+ else if (0 == strcmp(argv[k], "-vvv"))
+ verbose += 3;
+ else if (0 == strcmp(argv[k], "-V")) {
+ fprintf(stderr, "version: %s\n", version_str);
+ exit(0);
+ } else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ usage();
+ return 1;
+ }
+
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg__sat_identify: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[6] = 1; /* sector count */
+ apt_cdb[14] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE :
+ ATA_IDENTIFY_DEVICE);
+ apt_cdb[1] = (protocol << 1) | extend;
+ apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) |
+ (byte_block << 2) | t_length;
+ if (verbose) {
+ fprintf(stderr, " ata pass through(16) cdb: ");
+ for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k)
+ fprintf(stderr, "%02x ", apt_cdb[k]);
+ fprintf(stderr, "\n");
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(apt_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = ID_RESPONSE_LEN;
+ io_hdr.dxferp = inBuff;
+ io_hdr.cmdp = apt_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg__sat_identify: SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ if (verbose)
+ sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1);
+ /* check for ATA Return Descriptor */
+ cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr,
+ SAT_ATA_RETURN_DESC);
+ if (cucp && (cucp[3])) {
+ if (cucp[3] & 0x4) {
+ printf("error in returned FIS: aborted command\n");
+ printf(" try again with%s '-p' option\n",
+ (do_packet ? "out" : ""));
+ break;
+ }
+ }
+ ok = 1; /* not sure what is happening so output response */
+ if (0 == verbose) {
+ printf(">>> Recovered error on ATA_16, may have failed\n");
+ printf(" Add '-v' for more information\n");
+ }
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ printf("Response for IDENTIFY %sDEVICE ATA command:\n",
+ (do_packet ? "PACKET " : ""));
+ dWordHex((const unsigned short *)inBuff, 256, 0,
+ sg_is_big_endian());
+ }
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg__sat_phy_event.c b/examples/sg__sat_phy_event.c
new file mode 100644
index 00000000..40f38e19
--- /dev/null
+++ b/examples/sg__sat_phy_event.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2006-2018 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This program uses a ATA PASS-THROUGH (16) SCSI command defined
+ by SAT to package an ATA READ LOG EXT (2Fh) command to fetch
+ log page 11h. That page contains SATA phy event counters.
+ For SAT see http://www.t10.org [draft prior to standard: sat-r09.pdf]
+ For ATA READ LOG EXT command see ATA-8/ACS at www.t13.org .
+ For SATA phy counter definitions see SATA 2.5 .
+
+ Invocation: sg_sat_phy_event [-v] [-V] <device>
+
+*/
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return Descriptor */
+
+#define ATA_READ_LOG_EXT 0x2f
+#define SATA_PHY_EVENT_LPAGE 0x11
+#define READ_LOG_EXT_RESPONSE_LEN 512
+
+#define EBUFF_SZ 256
+
+static const char * version_str = "1.03 20180220";
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"ignore", no_argument, 0, 'i'},
+ {"raw", no_argument, 0, 'r'},
+ {"reset", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void usage()
+{
+ fprintf(stderr, "Usage: "
+ "sg_sat_phy_event [--help] [--hex] [--raw] [--reset] [--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --help|-h print this usage message then exit\n"
+ " --hex|-H output response in hex bytes, use twice for\n"
+ " hex words\n"
+ " --ignore|-i ignore identifier names, output id value "
+ "instead\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --reset|-R reset counters (after read)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n\n"
+ "Sends an ATA READ LOG EXT command via a SAT pass through to "
+ "fetch\nlog page 11h which contains SATA phy event counters\n");
+}
+
+struct phy_event_t {
+ int id;
+ const char * desc;
+};
+
+static struct phy_event_t phy_event_arr[] = {
+ {0x1, "Command failed and ICRC error bit set in Error register"},
+ {0x2, "R_ERR(p) response for data FIS"},
+ {0x3, "R_ERR(p) response for device-to-host data FIS"},
+ {0x4, "R_ERR(p) response for host-to-device data FIS"},
+ {0x5, "R_ERR(p) response for non-data FIS"},
+ {0x6, "R_ERR(p) response for device-to-host non-data FIS"},
+ {0x7, "R_ERR(p) response for host-to-device non-data FIS"},
+ {0x8, "Device-to-host non-data FIS retries"},
+ {0x9, "Transition from drive PHYRDY to drive PHYRDYn"},
+ {0xa, "Signature device-to-host register FISes due to COMRESET"},
+ {0xb, "CRC errors within host-to-device FIS"},
+ {0xd, "non CRC errors within host-to-device FIS"},
+ {0xf, "R_ERR(p) response for host-to-device data FIS, CRC"},
+ {0x10, "R_ERR(p) response for host-to-device data FIS, non-CRC"},
+ {0x12, "R_ERR(p) response for host-to-device non-data FIS, CRC"},
+ {0x13, "R_ERR(p) response for host-to-device non-data FIS, non-CRC"},
+ {0xc00, "PM: host-to-device non-data FIS, R_ERR(p) due to collision"},
+ {0xc01, "PM: signature register - device-to-host FISes"},
+ {0xc02, "PM: corrupts CRC propagation of device-to-host FISes"},
+ {0x0, NULL},
+};
+
+static const char * find_phy_desc(int id)
+{
+ const struct phy_event_t * pep;
+
+ for (pep = phy_event_arr; pep->desc; ++pep) {
+ if ((id & 0xfff) == pep->id)
+ return pep->desc;
+ }
+ return NULL;
+}
+
+static void dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0 ; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, c, k, j, ok, res, id, len, vendor;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char * device_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t inBuff[READ_LOG_EXT_RESPONSE_LEN];
+ uint8_t sense_buffer[64];
+ int hex = 0;
+ int ignore = 0;
+ int raw = 0;
+ int reset = 0;
+ int verbose = 0;
+ int extend = 0;
+ int chk_cond = 0; /* set to 1 to read register(s) back */
+ int protocol = 4; /* PIO data-in */
+ int t_dir = 1; /* 0 -> to device, 1 -> from device */
+ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ const uint8_t * cucp;
+ int ret = 0;
+ uint64_t ull;
+ const char * cp;
+
+ memset(inBuff, 0, sizeof(inBuff));
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hHirRvV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage();
+ exit(0);
+ case 'H':
+ ++hex;
+ break;
+ case 'i':
+ ++ignore;
+ break;
+ case 'r':
+ ++raw;
+ break;
+ case 'R':
+ ++reset;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ fprintf(stderr, "version: %s\n", version_str);
+ exit(0);
+ default:
+ fprintf(stderr, "unrecognised option code %c [0x%x]\n", c, c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (0 == device_name) {
+ fprintf(stderr, "no DEVICE name detected\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if ((sg_fd = open(device_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_sat_phy_event: error opening file: %s", device_name);
+ perror(ebuff);
+ return SG_LIB_FILE_ERROR;
+ }
+
+ /* Prepare SCSI ATA PASS-THROUGH COMMAND (16) command */
+ if (reset > 0)
+ apt_cdb[4] = 1; /* features (7:0) */
+ apt_cdb[6] = 1; /* sector count */
+ apt_cdb[8] = SATA_PHY_EVENT_LPAGE; /* lba_low (7:0) */
+ apt_cdb[14] = ATA_READ_LOG_EXT; /* command */
+ apt_cdb[1] = (protocol << 1) | extend;
+ apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) |
+ t_length;
+ if (verbose) {
+ fprintf(stderr, " ata pass through(16) cdb: ");
+ for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k)
+ fprintf(stderr, "%02x ", apt_cdb[k]);
+ fprintf(stderr, "\n");
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(apt_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = READ_LOG_EXT_RESPONSE_LEN;
+ io_hdr.dxferp = inBuff;
+ io_hdr.cmdp = apt_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_sat_phy_event: SG_IO ioctl error");
+ close(sg_fd);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ ret = sg_err_category3(&io_hdr);
+ switch (ret) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ if (verbose)
+ sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1);
+ /* check for ATA Return Descriptor */
+ cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr,
+ SAT_ATA_RETURN_DESC);
+ if (cucp && (cucp[3])) {
+ if (cucp[3] & 0x4) {
+ fprintf(stderr, "error in returned FIS: aborted command\n");
+ break;
+ }
+ }
+ ret = 0;
+ ok = 1; /* not sure what is happening so output response */
+ if (0 == verbose) {
+ fprintf(stderr, ">>> Recovered error on ATA_16, may have "
+ "failed\n");
+ fprintf(stderr, " Add '-v' for more information\n");
+ }
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ if (raw > 0)
+ dStrRaw(inBuff, 512);
+ else {
+ if (verbose && hex)
+ fprintf(stderr, "Response to READ LOG EXT (page=11h):\n");
+ if (1 == hex)
+ hex2stdout(inBuff, 512, 0);
+ else if (hex > 1)
+ dWordHex((const unsigned short *)inBuff, 256, 0,
+ sg_is_big_endian());
+ else {
+ printf("SATA phy event counters:\n");
+ for (k = 4; k < 512; k += (len + 2)) {
+ id = (inBuff[k + 1] << 8) + inBuff[k];
+ if (0 == id)
+ break;
+ len = ((id >> 12) & 0x7) * 2;
+ vendor = !!(id & 0x8000);
+ id = id & 0xfff;
+ ull = 0;
+ for (j = len - 1; j >= 0; --j) {
+ if (j < (len - 1))
+ ull <<= 8;
+ ull |= inBuff[k + 2 + j];
+ }
+ cp = NULL;
+ if ((0 == vendor) && (0 == ignore))
+ cp = find_phy_desc(id);
+ if (cp)
+ printf(" %s: %" PRIu64 "\n", cp, ull);
+ else
+ printf(" id=0x%x, vendor=%d, data_len=%d, "
+ "val=%" PRIu64 "\n", id, vendor, len, ull);
+ }
+ }
+ }
+ }
+ res = close(sg_fd);
+ if (res < 0) {
+ fprintf(stderr, "close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ return SG_LIB_FILE_ERROR;
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/examples/sg__sat_set_features.c b/examples/sg__sat_set_features.c
new file mode 100644
index 00000000..0b200477
--- /dev/null
+++ b/examples/sg__sat_set_features.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2006-2020 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This program performs a ATA PASS-THROUGH (16) SCSI command in order
+ to perform an ATA SET FEATURES command. See http://www.t10.org
+ SAT draft at time of writing: sat-r09.pdf
+
+ Invocation:
+ sg_sat_set_features [-c <n>] [-f <n>] [-h] [-L <n>] [-v] [-V] <device>
+
+*/
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+
+#define ATA_SET_FEATURES 0xef
+
+#define EBUFF_SZ 512
+
+static char * version_str = "1.06 20201125";
+
+static struct option long_options[] = {
+ {"count", required_argument, 0, 'c'},
+ {"chk_cond", no_argument, 0, 'C'},
+ {"feature", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"lba", required_argument, 0, 'L'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+void usage()
+{
+ fprintf(stderr, "Usage: "
+ "sg_sat_set_features [--count=C] [--chk_cond] [--feature=F] "
+ "[--help]\n"
+ " [-lba=LBA] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --count=C|-c C count field contents (def: 0)\n"
+ " --chk_cond|-C set chk_cond field in pass-through "
+ "(def: 0)\n"
+ " --feature=F|-f F feature field contents (def: 0)\n"
+ " --help|-h output this usage message\n"
+ " --lba=LBA| -L LBA LBA field contents (def: 0)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Sends an ATA SET FEATURES command via a SAT pass through.\n"
+ "Primary feature code is placed in '--feature=F' with '--count=C' "
+ "and\n"
+ "'--lba=LBA' being auxiliaries for some features. The arguments C, "
+ "F and LBA\n"
+ "are decimal unless prefixed by '0x' or have a trailing 'h'.\n"
+ "Example enabling write cache: 'sg_sat_set_feature --feature=2 "
+ "/dev/sdc'\n");
+}
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, c, k;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char device_name[256];
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[64];
+ int count = 0;
+ int feature = 0;
+ int lba = 0;
+ int verbose = 0;
+ int extend = 0;
+ int chk_cond = 0; /* set to 1 to read register(s) back */
+ int protocol = 3; /* non-data data-in */
+ int t_dir = 1; /* 0 -> to device, 1 -> from device */
+ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
+ int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */
+ const uint8_t * bp = NULL;
+
+ memset(device_name, 0, sizeof(device_name));
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:Cf:hL:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ count = sg_get_num(optarg);
+ if ((count < 0) || (count > 255)) {
+ fprintf(stderr, "bad argument for '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'C':
+ chk_cond = 1;
+ break;
+ case 'f':
+ feature = sg_get_num(optarg);
+ if ((feature < 0) || (feature > 255)) {
+ fprintf(stderr, "bad argument for '--feature'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'L':
+ lba = sg_get_num(optarg);
+ if ((lba < 0) || (lba > 255)) {
+ fprintf(stderr, "bad argument for '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ fprintf(stderr, "version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if ('\0' == device_name[0]) {
+ strncpy(device_name, argv[optind], sizeof(device_name) - 1);
+ device_name[sizeof(device_name) - 1] = '\0';
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if ('\0' == device_name[0]) {
+ fprintf(stderr, "missing device name!\n");
+ usage();
+ return 1;
+ }
+
+ if ((sg_fd = open(device_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_sat_set_features: error opening file: %s", device_name);
+ perror(ebuff);
+ return 1;
+ }
+
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[14] = ATA_SET_FEATURES;
+ apt_cdb[1] = (protocol << 1) | extend;
+ apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) |
+ t_length;
+ apt_cdb[4] = feature;
+ apt_cdb[6] = count;
+ apt_cdb[8] = lba & 0xff;
+ apt_cdb[10] = (lba >> 8) & 0xff;
+ apt_cdb[12] = (lba >> 16) & 0xff;
+ if (verbose) {
+ fprintf(stderr, " ata pass through(16) cdb: ");
+ for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k)
+ fprintf(stderr, "%02x ", apt_cdb[k]);
+ fprintf(stderr, "\n");
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(apt_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.dxfer_len = 0;
+ io_hdr.dxferp = NULL;
+ io_hdr.cmdp = apt_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_sat_set_features: SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* error processing: N.B. expect check condition, no sense ... !! */
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED: /* sat-r09 uses this sk */
+ case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */
+ bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer),
+ SAT_ATA_RETURN_DESC);
+ if (NULL == bp) {
+ if (verbose > 1)
+ printf("ATA Return Descriptor expected in sense but not "
+ "found\n");
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ } else if (verbose)
+ sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1);
+ if (bp && bp[3]) {
+ if (bp[3] & 0x4)
+ printf("error in returned FIS: aborted command\n");
+ else
+ printf("error=0x%x, status=0x%x\n", bp[3], bp[13]);
+ }
+ break;
+ default:
+ fprintf(stderr, "unexpected SCSI sense category\n");
+ bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer),
+ SAT_ATA_RETURN_DESC);
+ if (NULL == bp)
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ else if (verbose)
+ sg_chk_n_print3("ATA Return Descriptor, as expected",
+ &io_hdr, 1);
+ if (bp && bp[3]) {
+ if (bp[3] & 0x4)
+ printf("error in returned FIS: aborted command\n");
+ else
+ printf("error=0x%x, status=0x%x\n", bp[3], bp[13]);
+ }
+ break;
+ }
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_compare_and_write.txt b/examples/sg_compare_and_write.txt
new file mode 100644
index 00000000..86b166b3
--- /dev/null
+++ b/examples/sg_compare_and_write.txt
@@ -0,0 +1,67 @@
+# sg_compare_and_write.txt
+# This file provides a usage example of sg_compare_and_write.
+# sg_compare_and_write accepts a buffer containing 2 logical instances:
+# - the verify instance: used to match the current content of the LBA range
+# - the write instance: used to write to the LBA if the verify succeeds
+#
+# In case of failure to verify the data, the command will return with check
+# condition with the sense code set to MISCOMPARE DURING VERIFY OPERATION.
+#
+# The following example shows initialization, successful and unsuccessful
+# compare and write using sg3_utils. I am using caw_buf_zero2one and
+# caw_buf_one2zero as shown below.
+
+$ hexdump /tmp/caw_buf_zero2one
+0000000 0000 0000 0000 0000 0000 0000 0000 0000
+*
+0000200 1111 1111 1111 1111 1111 1111 1111 1111
+*
+0000400
+
+$ hexdump /tmp/caw_buf_one2zero
+0000000 1111 1111 1111 1111 1111 1111 1111 1111
+*
+0000200 0000 0000 0000 0000 0000 0000 0000 0000
+*
+0000400
+
+$ sg_map -i -x
+/dev/sg0 0 0 0 0 0 /dev/sda ATA ST3320613AS CC2H
+/dev/sg1 3 0 0 0 5 /dev/scd0 HL-DT-ST DVD-RAM GH22NS30 1.01
+/dev/sg2 5 0 0 0 0 /dev/sdb KMNRIO K2 0000
+/dev/sg3 5 0 0 1 0 /dev/sdc KMNRIO K2 0000
+
+# First I zero out the volume to make sure that the first compare and write
+# will succeed
+$ sg_write_same --16 -i /dev/zero -n 0x200000 -x 512 /dev/sdc
+
+$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump
+0000000 0000 0000 0000 0000 0000 0000 0000 0000
+*
+0000200
+
+$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc
+
+# contents of LBA 100 are a block of ones
+$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump
+0000000 1111 1111 1111 1111 1111 1111 1111 1111
+*
+0000200
+
+# We repeat the same compare and write command (zero2one input buffer).
+# compare and write fails since the verify failed (compared the zero block to
+# the actual 1 block in LBA 100
+$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc
+COMPARE AND WRITE: Fixed format, current; Sense key: Miscompare
+ Additional sense: Miscompare during verify operation
+sg_compare_and_write: SCSI COMPARE AND WRITE failed
+
+# Now we use the second buffer (one2zero)
+$ ./sg_compare_and_write --in=/tmp/caw_buf_one2zero --lba=100 --xferlen=1024 /dev/sdc
+
+# operation succeeded, contents of LBA 100 are back to zero
+$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump
+0000000 0000 0000 0000 0000 0000 0000 0000 0000
+*
+0000200
+
diff --git a/examples/sg_excl.c b/examples/sg_excl.c
new file mode 100644
index 00000000..7e589b23
--- /dev/null
+++ b/examples/sg_excl.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2003-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This is a simple program that tests the O_EXCL flag in sg while
+ * executing a SCSI INQUIRY command and a
+ * TEST UNIT READY command using the SCSI generic (sg) driver
+ *
+ * Invocation: sg_excl [-x] <sg_device>
+ *
+ * Version 3.62 (20181227)
+ *
+ * 6 byte INQUIRY command:
+ * [0x12][ |lu][pg cde][res ][al len][cntrl ]
+ *
+ * 6 byte TEST UNIT READY command:
+ * [0x00][ |lu][res ][res ][res ][res ]
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define EBUFF_SZ 256
+
+#define ME "sg_excl: "
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok /*, sg_fd2 */;
+ uint8_t inq_cdb [INQ_CMD_LEN] = {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t tur_cdb [TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0};
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[32];
+ int do_extra = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-x", argv[k], 2))
+ do_extra = 1;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_excl [-x] <sg_device>'\n");
+ return 1;
+ }
+
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if ((sg_fd = open(file_name, O_RDWR | O_EXCL | O_NONBLOCK)) < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ printf(ME "%s doesn't seem to be an new sg device\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+#if 0
+ if ((sg_fd2 = open(file_name, O_RDWR | O_EXCL)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "error opening file: %s a second time", file_name);
+ perror(ebuff);
+ return 1;
+ } else {
+ printf(ME "second open of %s in violation of O_EXCL\n", file_name);
+ close(sg_fd2);
+ }
+#endif
+
+ /* Prepare INQUIRY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_LEN;
+ io_hdr.dxferp = inqBuff;
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror(ME "Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBuff;
+ int f = (int)*(p + 7);
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
+ printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n",
+ !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1));
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+ }
+
+
+ /* Prepare TEST UNIT READY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(tur_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = tur_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror(ME "Test Unit Ready SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on Test Unit Ready, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok)
+ printf("Test Unit Ready successful so unit is ready!\n");
+ else
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+
+ if (do_extra)
+ printf("TEST UNIT READY duration=%u millisecs, resid=%d, "
+ "msg_status=%d\n", io_hdr.duration, io_hdr.resid,
+ (int)io_hdr.msg_status);
+
+ printf("Wait for 60 seconds with O_EXCL help on %s\n", file_name);
+ sleep(60);
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_persist_tst.sh b/examples/sg_persist_tst.sh
new file mode 100755
index 00000000..a75f9973
--- /dev/null
+++ b/examples/sg_persist_tst.sh
@@ -0,0 +1,130 @@
+#!/bin/sh
+# This script is meant as an example of using the sg_persist utility
+# in the sg3_utils package. This script works as expected on the
+# author's Fujitsu MAM3184, Seagate ST373455 and ST9146803SS disks.
+#
+# Version 2.0 20171104
+
+# N.B. make sure the device name is correct for your environment.
+
+key="123abc"
+key2="333aaa"
+kk=${key}
+rtype="1"
+verbose=""
+
+usage()
+{
+ echo "Usage: sg_persist_tst.sh [-e] [-h] [-s] [-v] <device>"
+ echo " where:"
+ echo " -e, --exclusive exclusive access (def: write " \
+ "exclusive)"
+ echo " -h, --help print usage message"
+ echo " -s, --second use second key"
+ echo " -v, --verbose more verbose output"
+ echo " -vv even more verbose output"
+ echo " -vvv even more verbose output"
+ echo ""
+ echo "Test SCSI Persistent Reservations with sg_persist utility."
+ echo "Default key is ${key} and alternate, second key is ${key2} ."
+ echo "Should be harmless (unless one of those keys is already in use)."
+ echo "The APTPL bit is not set in the PR register so a power cycle"
+ echo "on the device will clear the reservation if this script stops"
+ echo "(or is stopped) before clearing it. Tape drives only seem to "
+ echo "support 'exclusive access' type (so use '-e')."
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ e|-exclusive) rtype="3" ;;
+ h|-help) usage ; exit 0 ;;
+ s|-second) kk=${key2} ;;
+ vvv) verbose="-vvv" ;;
+ vv) verbose="-vv" ;;
+ v|-verbose) verbose="-v" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+echo ">>> try to report capabilities:"
+sg_persist -c ${verbose} "$1"
+res=$?
+case "$res" in
+ 0) ;;
+ 1) echo " syntax error" ;;
+ 2) echo " not ready" ;;
+ 3) echo " medium error" ;;
+ 5) echo " illegal request, report capabilities not supported?" ;;
+ 6) echo " unit attention" ;;
+ 9) echo " illegal request, Persistent Reserve (In) not supported" ;;
+ 11) echo " aborted command" ;;
+ 15) echo " file error with $1 " ;;
+ 20) echo " no sense" ;;
+ 21) echo " recovered error" ;;
+ 33) echo " timeout" ;;
+ 97) echo " response fails sanity" ;;
+ 98) echo " other SCSI error" ;;
+ 99) echo " other error" ;;
+ *) echo " unknown exit status for sg_persist: $res" ;;
+esac
+echo ""
+sleep 1
+
+echo ">>> check if any keys are registered:"
+sg_persist --no-inquiry --read-keys ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> register a key:"
+sg_persist -n --out --register --param-sark=${kk} ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> now key ${kk} should be registered:"
+sg_persist -n --read-keys ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> reserve the device (based on key ${kk}):"
+sg_persist -n --out --reserve --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> check if the device is reserved (it should be now):"
+sg_persist -n --read-reservation ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> try to 'read full status' (may not be supported):"
+sg_persist -n --read-full-status ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> now release reservation:"
+sg_persist -n --out --release --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> check if the device is reserved (it should _not_ be now):"
+sg_persist -n --read-reservation ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> unregister key ${kk}:"
+sg_persist -n --out --register --param-rk=${kk} ${verbose} "$1"
+sleep 1
+
+echo
+echo ">>> now key ${kk} should not be registered:"
+sg_persist -n -k ${verbose} "$1"
+sleep 1
diff --git a/examples/sg_sat_chk_power.c b/examples/sg_sat_chk_power.c
new file mode 100644
index 00000000..ca24f665
--- /dev/null
+++ b/examples/sg_sat_chk_power.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2006-2018 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+#include "sg_io_linux.h"
+
+/* This program performs a ATA PASS-THROUGH (16) SCSI command in order
+ to perform an ATA CHECK POWER MODE command. See http://www.t10.org
+ SAT draft at time of writing: sat-r09.pdf
+
+ Invocation: sg_sat_chk_power [-v] [-V] <device>
+
+*/
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
+
+#define ATA_CHECK_POWER_MODE 0xe5
+
+#define EBUFF_SZ 256
+
+static const char * version_str = "1.08 20181207";
+
+
+#if 0
+/* Returns length of decoded fixed format sense for SAT ATA pass-through
+ * command, else returns 0. If returns 0 (expected sense data not found)
+ * then '\0' placed in first byte of bp. */
+static int
+sg_sat_decode_fixed_sense(const uint8_t * sp, int slen, char * bp,
+ int max_blen, int verbose)
+{
+ int n;
+
+ if ((NULL == bp) || (NULL == sp) || (max_blen < 1) || (slen < 14))
+ return 0;
+ bp[0] = '\0';
+ if ((0x70 != (0x7f & sp[0])) ||
+ (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) ||
+ (0 != sp[12]) || (ASCQ_ATA_PT_INFO_AVAILABLE != sp[13]))
+ return 0;
+ n = sg_scnpr(bp, max_blen, "error=0x%x, status=0x%x, device=0x%x, "
+ "sector_count(7:0)=0x%x%c\n", sp[3], sp[4], sp[5], sp[6],
+ ((0x40 & sp[8]) ? '+' : ' '));
+ if (n >= max_blen)
+ return max_blen - 1;
+ n += sg_scnpr(bp + n, max_blen - n, "extend=%d, log_index=0x%x, "
+ "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n",
+ (!!(0x80 & sp[8])), (0xf & sp[8]), sp[9], sp[10], sp[11],
+ ((0x20 & sp[8]) ? '+' : ' '));
+ if (n >= max_blen)
+ return max_blen - 1;
+ if (verbose)
+ n += sg_scnpr(bp + n, max_blen - n, " sector_count_upper_nonzero="
+ "%d, lba_upper_nonzero=%d\n", !!(0x40 & sp[8]),
+ !!(0x20 & sp[8]));
+ return (n >= max_blen) ? max_blen - 1 : n;
+}
+#endif
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[64];
+ int verbose = 0;
+ int extend = 0;
+ int chk_cond = 1; /* set to 1 to read register(s) back */
+ int protocol = 3; /* non-dat data-in */
+ int t_dir = 1; /* 0 -> to device, 1 -> from device */
+ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
+ int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */
+ const uint8_t * bp = NULL;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp(argv[k], "-v"))
+ ++verbose;
+ else if (0 == strcmp(argv[k], "-vv"))
+ verbose += 2;
+ else if (0 == strcmp(argv[k], "-vvv"))
+ verbose += 3;
+ else if (0 == strcmp(argv[k], "-V")) {
+ fprintf(stderr, "version: %s\n", version_str);
+ exit(0);
+ } else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_sat_chk_power [-v] [-V] <device>'\n");
+ return 1;
+ }
+
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_sat_chk_power: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[14] = ATA_CHECK_POWER_MODE;
+ apt_cdb[1] = (protocol << 1) | extend;
+ apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) |
+ (byte_block << 2) | t_length;
+ if (verbose) {
+ fprintf(stderr, " ata pass through(16) cdb: ");
+ for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k)
+ fprintf(stderr, "%02x ", apt_cdb[k]);
+ fprintf(stderr, "\n");
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(apt_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.dxfer_len = 0;
+ io_hdr.dxferp = NULL;
+ io_hdr.cmdp = apt_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_sat_chk_power: SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* error processing: N.B. expect check condition, no sense ... !! */
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED: /* sat-r09 (latest) uses this sk */
+ case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */
+ /* XXX: Until the spec decides which one to go with. 20060607 */
+ bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer),
+ SAT_ATA_RETURN_DESC);
+ if (NULL == bp) {
+ if (verbose > 1)
+ printf("ATA Return Descriptor expected in sense but not "
+ "found\n");
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ } else if (verbose)
+ sg_chk_n_print3("ATA Return Descriptor, as expected",
+ &io_hdr, 1);
+ if (bp && bp[3]) {
+ if (bp[3] & 0x4)
+ printf("error in returned FIS: aborted command\n");
+ else
+ printf("error=0x%x, status=0x%x\n", bp[3], bp[13]);
+ }
+ break;
+ default:
+ fprintf(stderr, "unexpected SCSI sense category\n");
+ bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer),
+ SAT_ATA_RETURN_DESC);
+ if (NULL == bp)
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ else if (verbose)
+ sg_chk_n_print3("ATA Return Descriptor, as expected",
+ &io_hdr, 1);
+ if (bp && bp[3]) {
+ if (bp[3] & 0x4)
+ printf("error in returned FIS: aborted command\n");
+ else
+ printf("error=0x%x, status=0x%x\n", bp[3], bp[13]);
+ }
+ break;
+ }
+
+ if (bp) {
+ switch (bp[5]) { /* sector_count (7:0) */
+ case 0xff:
+ printf("In active mode or idle mode\n");
+ break;
+ case 0x80:
+ printf("In idle mode\n");
+ break;
+ case 0x41:
+ printf("In NV power mode and spindle is spun or spinning up\n");
+ break;
+ case 0x40:
+ printf("In NV power mode and spindle is spun or spinning down\n");
+ break;
+ case 0x0:
+ printf("In standby mode\n");
+ break;
+ default:
+ printf("unknown power mode (sector count) value=0x%x\n", bp[5]);
+ break;
+ }
+ } else
+ fprintf(stderr, "Expecting a ATA Return Descriptor in sense and "
+ "didn't receive it\n");
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_sat_smart_rd_data.c b/examples/sg_sat_smart_rd_data.c
new file mode 100644
index 00000000..223da2a7
--- /dev/null
+++ b/examples/sg_sat_smart_rd_data.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2006-2018 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This program performs a ATA PASS-THROUGH (16) SCSI command in order
+ to perform an ATA SMART/READ DATA command. See http://www.t10.org
+ SAT draft at time of writing: sat-r08.pdf
+
+ Invocation: sg_sat_smart_rd_data [-v] [-V] <device>
+
+*/
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+
+#define ATA_SMART 0xb0
+#define ATA_SMART_READ_DATA 0xd0
+#define SMART_READ_DATA_RESPONSE_LEN 512
+
+#define EBUFF_SZ 256
+
+static char * version_str = "1.05 20181207";
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t inBuff[SMART_READ_DATA_RESPONSE_LEN];
+ uint8_t sense_buffer[32];
+ int verbose = 0;
+ int extend = 0;
+ int chk_cond = 0; /* set to 1 to read register(s) back */
+ int protocol = 4; /* PIO data-in */
+ int t_dir = 1; /* 0 -> to device, 1 -> from device */
+ int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ const uint8_t * bp = NULL;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp(argv[k], "-v"))
+ ++verbose;
+ else if (0 == strcmp(argv[k], "-vv"))
+ verbose += 2;
+ else if (0 == strcmp(argv[k], "-vvv"))
+ verbose += 3;
+ else if (0 == strcmp(argv[k], "-V")) {
+ fprintf(stderr, "version: %s\n", version_str);
+ exit(0);
+ } else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_sat_smart_rd_data [-v] [-V] <device>'\n");
+ return 1;
+ }
+
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_sat_smart_rd_data: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[4] = ATA_SMART_READ_DATA; /* feature (7:0) */
+ apt_cdb[6] = 1; /* number of block (sector count) */
+ apt_cdb[10] = 0x4f; /* lba_mid (7:0) */
+ apt_cdb[12] = 0xc2; /* lba_high (7:0) */
+ apt_cdb[14] = ATA_SMART;
+ apt_cdb[1] = (protocol << 1) | extend;
+ apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) |
+ t_length;
+ if (verbose) {
+ fprintf(stderr, " ata pass through(16) cdb: ");
+ for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k)
+ fprintf(stderr, "%02x ", apt_cdb[k]);
+ fprintf(stderr, "\n");
+ }
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(apt_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = SMART_READ_DATA_RESPONSE_LEN;
+ io_hdr.dxferp = inBuff;
+ io_hdr.cmdp = apt_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_sat_smart_rd_data: SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer),
+ SAT_ATA_RETURN_DESC);
+ if (NULL == bp) {
+ if (verbose > 1)
+ printf("ATA Return Descriptor expected in sense but not "
+ "found\n");
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ } else if (verbose)
+ sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1);
+ if (bp && bp[3])
+ printf("error=0x%x, status=0x%x\n", bp[3], bp[13]);
+ else
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("ATA_16 command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ printf("Response:\n");
+ dWordHex((const unsigned short *)inBuff, 256, 0,
+ sg_is_big_endian());
+ }
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple1.c b/examples/sg_simple1.c
new file mode 100644
index 00000000..b8ef3491
--- /dev/null
+++ b/examples/sg_simple1.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 1999-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This is a simple program executing a SCSI INQUIRY command and a
+ * TEST UNIT READY command using the SCSI generic (sg) driver
+ * There is another variant of this program called "sg_simple2"
+ * which does not include the sg_lib.h header and logic and so has
+ * simpler but more primitive error processing.
+ * In the lk 2.6 series devices nodes such as /dev/sda also support
+ * the SG_IO ioctl.
+ *
+ * Invocation: sg_simple1 [-x] <scsi_device>
+ *
+ * Version 3.60 (20181207)
+ *
+ * 6 byte INQUIRY command:
+ * [0x12][ |lu][pg cde][res ][al len][cntrl ]
+ *
+ * 6 byte TEST UNIT READY command:
+ * [0x00][ |lu][res ][res ][res ][res ]
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define EBUFF_SZ 256
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ unsigned char inq_cdb [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char tur_cdb [TUR_CMD_LEN] =
+ {0x00, 0, 0, 0, 0, 0};
+ unsigned char inqBuff[INQ_REPLY_LEN];
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ unsigned char sense_buffer[32];
+ int do_extra = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-x", argv[k], 2))
+ do_extra = 1;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple1 [-x] <sg_device>'\n");
+ return 1;
+ }
+
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if ((sg_fd = open(file_name, O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_simple1: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ printf("sg_simple1: %s doesn't seem to be an new sg device\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+
+ /* Prepare INQUIRY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_LEN;
+ io_hdr.dxferp = inqBuff;
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple1: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBuff;
+ int f = (int)*(p + 7);
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
+ printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n",
+ !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1));
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+ }
+
+
+ /* Prepare TEST UNIT READY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(tur_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = tur_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple1: Test Unit Ready SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on Test Unit Ready, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok)
+ printf("Test Unit Ready successful so unit is ready!\n");
+ else
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+
+ if (do_extra)
+ printf("TEST UNIT READY duration=%u millisecs, resid=%d, "
+ "msg_status=%d\n", io_hdr.duration, io_hdr.resid,
+ (int)io_hdr.msg_status);
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple16.c b/examples/sg_simple16.c
new file mode 100644
index 00000000..e119ca34
--- /dev/null
+++ b/examples/sg_simple16.c
@@ -0,0 +1,122 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This program performs a READ_16 command as scsi mid-level support
+ 16 byte commands from lk 2.4.15
+
+* Copyright (C) 2001-2018 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: sg_simple16 <scsi_device>
+
+ Version 1.04 (20180218)
+
+*/
+
+#define READ16_REPLY_LEN 512
+#define READ16_CMD_LEN 16
+
+#define EBUFF_SZ 256
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ uint8_t r16_cdb [READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t inBuff[READ16_REPLY_LEN];
+ uint8_t sense_buffer[32];
+
+ for (k = 1; k < argc; ++k) {
+ if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple16 <sg_device>'\n");
+ return 1;
+ }
+
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_simple16: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ printf("sg_simple16: %s doesn't seem to be an new sg device\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+
+ /* Prepare READ_16 command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(r16_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = READ16_REPLY_LEN;
+ io_hdr.dxferp = inBuff;
+ io_hdr.cmdp = r16_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple16: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on READ_16, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ_16 command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ printf("READ_16 duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+ }
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple2.c b/examples/sg_simple2.c
new file mode 100644
index 00000000..a0710be5
--- /dev/null
+++ b/examples/sg_simple2.c
@@ -0,0 +1,197 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_linux_inc.h"
+
+/* This is a simple program executing a SCSI INQUIRY command and a
+ TEST UNIT READY command using the SCSI generic (sg) driver.
+ There is another variant of this program called "sg_simple1"
+ which includes the sg_lib.h header and logic and so has more
+ advanced error processing.
+ This version demonstrates the "sg3" interface.
+ In the lk 2.6 series devices nodes such as /dev/sda also support
+ the SG_IO ioctl.
+
+* Copyright (C) 1999-2016 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: sg_simple2 [-x] <scsi_device>
+
+ Version 03.59 (20160528)
+
+6 byte INQUIRY command:
+[0x12][ |lu][pg cde][res ][al len][cntrl ]
+
+6 byte TEST UNIT READY command:
+[0x00][ |lu][res ][res ][res ][res ]
+
+*/
+
+#define INQ_REPLY_LEN 96 /* logic assumes >= sizeof(inq_cdb) */
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define EBUFF_SZ 256
+
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k;
+ unsigned char inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char tur_cdb[TUR_CMD_LEN] =
+ {0x00, 0, 0, 0, 0, 0};
+ unsigned char inqBuff[INQ_REPLY_LEN];
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ unsigned char sense_buffer[32];
+ int do_extra = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-x", argv[k], 2))
+ do_extra = 1;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple2 [-x] <sg_device>'\n");
+ return 1;
+ }
+
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if ((sg_fd = open(file_name, O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_simple2: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ printf("sg_simple2: %s doesn't seem to be an new sg device\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+
+ /* Prepare INQUIRY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_LEN;
+ io_hdr.dxferp = inqBuff;
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple2: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
+ if (io_hdr.sb_len_wr > 0) {
+ printf("INQUIRY sense data: ");
+ for (k = 0; k < io_hdr.sb_len_wr; ++k) {
+ if ((k > 0) && (0 == (k % 10)))
+ printf("\n ");
+ printf("0x%02x ", sense_buffer[k]);
+ }
+ printf("\n");
+ }
+ if (io_hdr.masked_status)
+ printf("INQUIRY SCSI status=0x%x\n", io_hdr.status);
+ if (io_hdr.host_status)
+ printf("INQUIRY host_status=0x%x\n", io_hdr.host_status);
+ if (io_hdr.driver_status)
+ printf("INQUIRY driver_status=0x%x\n", io_hdr.driver_status);
+ }
+ else { /* output result if it is available */
+ char * p = (char *)inqBuff;
+ int f = (int)*(p + 7);
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
+ printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n",
+ !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1));
+ }
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+
+ /* Prepare TEST UNIT READY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(tur_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = tur_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple2: Test Unit Ready SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
+ if (io_hdr.sb_len_wr > 0) {
+ printf("TEST UNIT READY sense data: ");
+ for (k = 0; k < io_hdr.sb_len_wr; ++k) {
+ if ((k > 0) && (0 == (k % 10)))
+ printf("\n ");
+ printf("0x%02x ", sense_buffer[k]);
+ }
+ printf("\n");
+ }
+ else if (io_hdr.masked_status)
+ printf("TEST UNIT READY SCSI status=0x%x\n", io_hdr.status);
+ else if (io_hdr.host_status)
+ printf("TEST UNIT READY host_status=0x%x\n", io_hdr.host_status);
+ else if (io_hdr.driver_status)
+ printf("TEST UNIT READY driver_status=0x%x\n",
+ io_hdr.driver_status);
+ else
+ printf("TEST UNIT READY unexpected error\n");
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+ }
+ else
+ printf("Test Unit Ready successful so unit is ready!\n");
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("TEST UNIT READY duration=%u millisecs, resid=%d, "
+ "msg_status=%d\n", io_hdr.duration, io_hdr.resid,
+ (int)io_hdr.msg_status);
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple3.c b/examples/sg_simple3.c
new file mode 100644
index 00000000..4927a923
--- /dev/null
+++ b/examples/sg_simple3.c
@@ -0,0 +1,205 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This is a simple program executing a SCSI INQUIRY command and a
+ TEST UNIT READY command using the SCSI generic (sg) driver.
+ There is another variant of this program called "sg_simple1".
+ This variant demonstrates using the scatter gather facility in
+ the sg_io_hdr interface to break an INQUIRY response into its
+ component parts.
+
+* Copyright (C) 1999-2016 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: sg_simple3 [-x] <sg_device>
+
+ Version 03.59 (20160528)
+
+6 byte INQUIRY command:
+[0x12][ |lu][pg cde][res ][al len][cntrl ]
+
+6 byte TEST UNIT READY command:
+[0x00][ |lu][res ][res ][res ][res ]
+
+*/
+
+#define INQ_REPLY_BASE_LEN 8
+#define INQ_REPLY_VID_LEN 8
+#define INQ_REPLY_PID_LEN 16
+#define INQ_REPLY_PREV_LEN 4
+#define INQ_REPLY_IOVEC_COUNT 4
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define EBUFF_SZ 256
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ unsigned char inq_cdb[INQ_CMD_LEN] = {0x12, 0, 0, 0,
+ INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN +
+ INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN, 0};
+ unsigned char tur_cdb[TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0};
+ sg_iovec_t iovec[INQ_REPLY_IOVEC_COUNT];
+ unsigned char inqBaseBuff[INQ_REPLY_BASE_LEN];
+ char inqVidBuff[INQ_REPLY_VID_LEN];
+ char inqPidBuff[INQ_REPLY_PID_LEN];
+ char inqPRevBuff[INQ_REPLY_PREV_LEN];
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ unsigned char sense_buffer[32];
+ int do_extra = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-x", argv[k], 2))
+ do_extra = 1;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple3 [-x] <sg_device>'\n");
+ return 1;
+ }
+
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if ((sg_fd = open(file_name, O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_simple3: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
+ printf("sg_simple3: %s doesn't seem to be an new sg device\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+
+ /* Prepare INQUIRY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ io_hdr.iovec_count = INQ_REPLY_IOVEC_COUNT;
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN +
+ INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN;
+ iovec[0].iov_base = inqBaseBuff;
+ iovec[0].iov_len = INQ_REPLY_BASE_LEN;
+ iovec[1].iov_base = inqVidBuff;
+ iovec[1].iov_len = INQ_REPLY_VID_LEN;
+ iovec[2].iov_base = inqPidBuff;
+ iovec[2].iov_len = INQ_REPLY_PID_LEN;
+ iovec[3].iov_base = inqPRevBuff;
+ iovec[3].iov_len = INQ_REPLY_PREV_LEN;
+ io_hdr.dxferp = iovec;
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple3: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBaseBuff;
+ int f = (int)*(p + 7);
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s ", inqVidBuff, inqPidBuff, inqPRevBuff);
+ printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n",
+ !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1));
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+ }
+
+
+ /* Prepare TEST UNIT READY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(tur_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = tur_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple3: Test Unit Ready SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on Test Unit Ready, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok)
+ printf("Test Unit Ready successful so unit is ready!\n");
+ else
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+
+ if (do_extra)
+ printf("TEST UNIT READY duration=%u millisecs, resid=%d, "
+ "msg_status=%d\n", io_hdr.duration, io_hdr.resid,
+ (int)io_hdr.msg_status);
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple4.c b/examples/sg_simple4.c
new file mode 100644
index 00000000..95a5023b
--- /dev/null
+++ b/examples/sg_simple4.c
@@ -0,0 +1,238 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+
+/* This is a simple program executing a SCSI INQUIRY command and a
+ TEST UNIT READY command using the SCSI generic (sg) driver
+ This variant shows mmap-ed IO being used to read the data returned
+ by the INQUIRY command.
+
+* Copyright (C) 2001-2016 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: sg_simple4 [-x] <sg_device>
+
+ Version 1.02 (20160528)
+
+6 byte INQUIRY command:
+[0x12][ |lu][pg cde][res ][al len][cntrl ]
+
+6 byte TEST UNIT READY command:
+[0x00][ |lu][res ][res ][res ][res ]
+
+*/
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif /* since /usr/include/scsi/sg.h doesn't know about this yet */
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define EBUFF_SZ 256
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok;
+ unsigned char inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char tur_cdb[TUR_CMD_LEN] =
+ {0x00, 0, 0, 0, 0, 0};
+ unsigned char * inqBuff;
+ unsigned char * inqBuff2;
+ sg_io_hdr_t io_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ unsigned char sense_buffer[32];
+ int do_extra = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-x", argv[k], 2))
+ do_extra = 1;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple4 [-x] <sg_device>'\n");
+ return 1;
+ }
+
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_simple4: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Just to be safe, check we have a new sg device by trying an ioctl */
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30122)) {
+ printf("sg_simple4: %s needs sg driver version >= 3.1.22\n",
+ file_name);
+ close(sg_fd);
+ return 1;
+ }
+
+ /* since I know this program will only read from inqBuff then I use
+ PROT_READ rather than PROT_READ | PROT_WRITE */
+ inqBuff = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE,
+ MAP_SHARED, sg_fd, 0);
+ if (MAP_FAILED == inqBuff) {
+ snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() on "
+ "file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ if (inqBuff[0])
+ printf("non-null char at inqBuff[0]\n");
+ if (inqBuff[5000])
+ printf("non-null char at inqBuff[5000]\n");
+
+ /* Prepare INQUIRY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ /* io_hdr.iovec_count = 0; */ /* memset takes care of this */
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_LEN;
+ /* io_hdr.dxferp = inqBuff; // ignored in mmap-ed IO */
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr.flags = SG_FLAG_MMAP_IO;
+ /* io_hdr.pack_id = 0; */
+ /* io_hdr.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple4: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBuff;
+ int f = (int)*(p + 7);
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
+ printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n",
+ !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1));
+ /* Extra info, not necessary to look at */
+ if (do_extra)
+ printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+ }
+
+
+ /* Prepare TEST UNIT READY command */
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(tur_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.cmdp = tur_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("sg_simple4: Test Unit Ready SG_IO ioctl error");
+ close(sg_fd);
+ return 1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error on Test Unit Ready, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1);
+ break;
+ }
+
+ if (ok)
+ printf("Test Unit Ready successful so unit is ready!\n");
+ else
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+
+ if (do_extra)
+ printf("TEST UNIT READY duration=%u millisecs, resid=%d, "
+ "msg_status=%d\n",
+ io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status);
+
+ /* munmap(inqBuff, 8000); */
+ /* could call munmap(inqBuff, INQ_REPLY_LEN) here but following close()
+ causes this too happen anyway */
+#if 1
+ inqBuff2 = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE,
+ MAP_SHARED, sg_fd, 0);
+ if (MAP_FAILED == inqBuff2) {
+ snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() 2 on "
+ "file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ if (inqBuff2[0])
+ printf("non-null char at inqBuff2[0]\n");
+ if (inqBuff2[5000])
+ printf("non-null char at inqBuff2[5000]\n");
+ {
+ pid_t pid;
+ pid = fork();
+ if (pid) {
+ inqBuff2[5000] = 33;
+ munmap(inqBuff, 8000);
+ sleep(3);
+ }
+ else {
+ inqBuff[5000] = 0xaa;
+ munmap(inqBuff, 8000);
+ sleep(1);
+ }
+ }
+#endif
+ close(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_simple5.c b/examples/sg_simple5.c
new file mode 100644
index 00000000..47bf7a05
--- /dev/null
+++ b/examples/sg_simple5.c
@@ -0,0 +1,236 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+
+/* This is a simple program executing a SCSI INQUIRY command and a
+ TEST UNIT READY command using the SCSI generic pass through
+ interface. This allows this example program to be ported to
+ OSes other than linux.
+
+* Copyright (C) 2006-20018 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: sg_simple5 [-x] <scsi_device>
+
+ Version 1.03 (20180220)
+
+*/
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define TUR_CMD_LEN 6
+
+#define CMD_TIMEOUT_SECS 60
+
+
+int main(int argc, char * argv[])
+{
+ int sg_fd, k, ok, dsize, res, duration, resid, cat, got, slen;
+ uint8_t inq_cdb [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t tur_cdb [TUR_CMD_LEN] =
+ {0x00, 0, 0, 0, 0, 0};
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ char * file_name = 0;
+ char b[512];
+ uint8_t sense_b[32];
+ int verbose = 0;
+ struct sg_pt_base * ptvp;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp("-v", argv[k]))
+ verbose = 1;
+ else if (0 == strcmp("-vv", argv[k]))
+ verbose = 2;
+ else if (0 == strcmp("-vvv", argv[k]))
+ verbose = 3;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_simple5 [-v|-vv|-vvv] <device>'\n");
+ return 1;
+ }
+
+ sg_fd = scsi_pt_open_device(file_name, 1 /* ro */, 0);
+ /* N.B. An access mode of O_RDWR is required for some SCSI commands */
+ if (sg_fd < 0) {
+ fprintf(stderr, "error opening file: %s: %s\n",
+ file_name, safe_strerror(-sg_fd));
+ return 1;
+ }
+
+ dsize = sizeof(inqBuff);
+ ok = 0;
+
+ ptvp = construct_scsi_pt_obj(); /* one object per command */
+ if (NULL == ptvp) {
+ fprintf(stderr, "sg_simple5: out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, inqBuff, dsize);
+ res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose);
+ if (res < 0) {
+ fprintf(stderr, " pass through os error: %s\n",
+ safe_strerror(-res));
+ goto finish_inq;
+ } else if (SCSI_PT_DO_BAD_PARAMS == res) {
+ fprintf(stderr, " bad pass through setup\n");
+ goto finish_inq;
+ } else if (SCSI_PT_DO_TIMEOUT == res) {
+ fprintf(stderr, " pass through timeout\n");
+ goto finish_inq;
+ }
+ if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+ fprintf(stderr, " duration=%d ms\n", duration);
+ resid = get_scsi_pt_resid(ptvp);
+ switch ((cat = get_scsi_pt_result_category(ptvp))) {
+ case SCSI_PT_RESULT_GOOD:
+ got = dsize - resid;
+ if (verbose && (resid > 0))
+ fprintf(stderr, " requested %d bytes but "
+ "got %d bytes)\n", dsize, got);
+ break;
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ if (verbose) {
+ sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp),
+ sizeof(b), b);
+ fprintf(stderr, " scsi status: %s\n", b);
+ }
+ goto finish_inq;
+ case SCSI_PT_RESULT_SENSE:
+ slen = get_scsi_pt_sense_len(ptvp);
+ if (verbose) {
+ sg_get_sense_str("", sense_b, slen, (verbose > 1),
+ sizeof(b), b);
+ fprintf(stderr, "%s", b);
+ }
+ if (verbose && (resid > 0)) {
+ got = dsize - resid;
+ if ((verbose) || (got > 0))
+ fprintf(stderr, " requested %d bytes but "
+ "got %d bytes\n", dsize, got);
+ }
+ goto finish_inq;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ if (verbose) {
+ get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+ fprintf(stderr, " transport: %s", b);
+ }
+ goto finish_inq;
+ case SCSI_PT_RESULT_OS_ERR:
+ if (verbose) {
+ get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+ fprintf(stderr, " os: %s", b);
+ }
+ goto finish_inq;
+ default:
+ fprintf(stderr, " unknown pass through result "
+ "category (%d)\n", cat);
+ goto finish_inq;
+ }
+
+ ok = 1;
+finish_inq:
+ destruct_scsi_pt_obj(ptvp);
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBuff;
+
+ printf("Some of the INQUIRY command's results:\n");
+ printf(" %.8s %.16s %.4s\n", p + 8, p + 16, p + 32);
+ }
+ ok = 0;
+
+
+ /* Now prepare TEST UNIT READY command */
+ ptvp = construct_scsi_pt_obj(); /* one object per command */
+ if (NULL == ptvp) {
+ fprintf(stderr, "sg_simple5: out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ /* no data in or out */
+ res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose);
+ if (res < 0) {
+ fprintf(stderr, " pass through os error: %s\n",
+ safe_strerror(-res));
+ goto finish_inq;
+ } else if (SCSI_PT_DO_BAD_PARAMS == res) {
+ fprintf(stderr, " bad pass through setup\n");
+ goto finish_inq;
+ } else if (SCSI_PT_DO_TIMEOUT == res) {
+ fprintf(stderr, " pass through timeout\n");
+ goto finish_inq;
+ }
+ if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+ fprintf(stderr, " duration=%d ms\n", duration);
+ resid = get_scsi_pt_resid(ptvp);
+ switch ((cat = get_scsi_pt_result_category(ptvp))) {
+ case SCSI_PT_RESULT_GOOD:
+ break;
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ if (verbose) {
+ sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp),
+ sizeof(b), b);
+ fprintf(stderr, " scsi status: %s\n", b);
+ }
+ goto finish_tur;
+ case SCSI_PT_RESULT_SENSE:
+ slen = get_scsi_pt_sense_len(ptvp);
+ if (verbose) {
+ sg_get_sense_str("", sense_b, slen, (verbose > 1),
+ sizeof(b), b);
+ fprintf(stderr, "%s", b);
+ }
+ goto finish_tur;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ if (verbose) {
+ get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+ fprintf(stderr, " transport: %s", b);
+ }
+ goto finish_tur;
+ case SCSI_PT_RESULT_OS_ERR:
+ if (verbose) {
+ get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+ fprintf(stderr, " os: %s", b);
+ }
+ goto finish_tur;
+ default:
+ fprintf(stderr, " unknown pass through result "
+ "category (%d)\n", cat);
+ goto finish_tur;
+ }
+
+ ok = 1;
+finish_tur:
+ destruct_scsi_pt_obj(ptvp);
+
+ if (ok)
+ printf("Test Unit Ready successful so unit is ready!\n");
+ else
+ printf("Test Unit Ready failed so unit may _not_ be ready!\n");
+
+ scsi_pt_close_device(sg_fd);
+ return 0;
+}
diff --git a/examples/sg_unmap_example.txt b/examples/sg_unmap_example.txt
new file mode 100644
index 00000000..2b07dbae
--- /dev/null
+++ b/examples/sg_unmap_example.txt
@@ -0,0 +1,40 @@
+# sg_unmap_example.txt
+# This is an example of the contents of a file that can be given to sg_unmap
+# For example, assume the /dev/sdc is a scratch disk (e.g. one made by the
+# scsi_debug kernel module) then:
+# sg_unmap --in=sg_unmap_example.txt /dev/sdc
+
+0x12345677,1 # unmap LBA 0x12345677
+0x12345678 2 # unmap LBA 0x12345678 and 0x12345679
+0x12340000 3333 # unmap 3333 blocks starting at LBA 0x12340000
+
+0X5a5a5a5a5a 0 # unmaps 0 blocks (i.e. does nothing)
+
+ a5a5a5h
+7 # unmap 7 blocks starting at LBA 0xa5a5a5
+
+# Note that there can be leading and trailing whitespace and whitespace
+# (plus comma) can be a separator.
+#
+# Example invocation:
+# $ sg_unmap --in=../examples/sg_unmap_example.txt -vv /dev/sdc
+# open /dev/sg2 with flags=0x802
+# unmap cdb: 42 00 00 00 00 00 00 00 58 00
+# unmap parameter list:
+# 00 56 00 50 00 00 00 00 00 00 00 00 12 34 56 77
+# 00 00 00 01 00 00 00 00 00 00 00 00 12 34 56 78
+# 00 00 00 02 00 00 00 00 00 00 00 00 12 34 00 00
+# 00 00 0d 05 00 00 00 00 00 00 00 5a 5a 5a 5a 5a
+# 00 00 00 00 00 00 00 00 00 00 00 00 00 a5 a5 a5
+# 00 00 00 07 00 00 00 00
+# sg_cmds_process_resp: slen=18
+# unmap: Fixed format, current; Sense key: Illegal Request
+# Additional sense: Invalid command operation code
+# Raw sense data (in hex):
+# 70 00 05 00 00 00 00 0a 00 00 00 00 20 00 00 00
+# 00 00
+# UNMAP not supported
+#
+# --------------------------------------------------------
+# Notice the 8 byte header then 5 descriptors in the parameter
+# list
diff --git a/examples/sgq_dd.c b/examples/sgq_dd.c
new file mode 100644
index 00000000..80e8817a
--- /dev/null
+++ b/examples/sgq_dd.c
@@ -0,0 +1,1230 @@
+/*
+ * A utility program for the Linux OS SCSI generic ("sg") device driver.
+ * Copyright (C) 1999-2010 D. Gilbert and P. Allworth
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a specialization of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device or a raw
+ * device. A block size ('bs') is assumed to be 512 if not given. This
+ * program complains if 'ibs' or 'obs' are given with some other value
+ * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If
+ * 'of' is not given or 'of=-' then stdout assumed. Multipliers:
+ * 'c','C' *1 'b','B' *512 'k' *1024 'K' *1000
+ * 'm' *(1024^2) 'M' *(1000^2) 'g' *(1024^3) 'G' *(1000^3)
+ *
+ * A non-standard argument "bpt" (blocks per transfer) is added to control
+ * the maximum number of blocks in each transfer. The default value is 128.
+ * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16KB
+ * in this case) are transferred to or from the sg device in a single SCSI
+ * command.
+ *
+ * This version should compile with Linux sg drivers with version numbers
+ * >= 30000 . This version uses queuing within the Linux sg driver.
+ */
+
+#define _XOPEN_SOURCE 500
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <linux/major.h>
+#include <sys/time.h>
+typedef uint8_t u_char; /* horrible, for scsi.h */
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+
+
+static char * version_str = "0.63 20190324";
+/* resurrected from "0.55 20020509" */
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+
+
+#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+#define S_RW_LEN 10 /* Use SCSI READ(10) and WRITE(10) */
+
+#define SGP_READ10 0x28
+#define SGP_WRITE10 0x2a
+#define DEF_NUM_THREADS 4 /* actually degree of concurrency */
+#define MAX_NUM_THREADS 1024
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define FT_OTHER 0 /* filetype other than sg or raw device */
+#define FT_SG 1 /* filetype is sg char device */
+#define FT_RAW 2 /* filetype is raw char device */
+
+#define QS_IDLE 0 /* ready to start a copy cycle */
+#define QS_IN_STARTED 1 /* commenced read */
+#define QS_IN_FINISHED 2 /* finished read, ready for write */
+#define QS_OUT_STARTED 3 /* commenced write */
+
+#define QS_IN_POLL 11
+#define QS_OUT_POLL 12
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+#define EBUFF_SZ 512
+
+
+struct request_element;
+
+typedef struct request_collection
+{ /* one instance visible to all threads */
+ int infd;
+ int skip;
+ int in_type;
+ int in_scsi_type;
+ int in_blk; /* next block address to read */
+ int in_count; /* blocks remaining for next read */
+ int in_done_count; /* count of completed in blocks */
+ int in_partial;
+ int outfd;
+ int seek;
+ int out_type;
+ int out_scsi_type;
+ int out_blk; /* next block address to write */
+ int out_count; /* blocks remaining for next write */
+ int out_done_count; /* count of completed out blocks */
+ int out_partial;
+ int bs;
+ int bpt;
+ int dio;
+ int dio_incomplete;
+ int sum_of_resids;
+ int coe;
+ int debug;
+ int num_rq_elems;
+ struct request_element * req_arr;
+} Rq_coll;
+
+typedef struct request_element
+{ /* one instance per worker thread */
+ int qstate; /* "QS" state */
+ int infd;
+ int outfd;
+ int wr;
+ int blk;
+ int num_blks;
+ uint8_t * buffp;
+ uint8_t * alloc_bp;
+ sg_io_hdr_t io_hdr;
+ uint8_t cmd[S_RW_LEN];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int bs;
+ int dio;
+ int dio_incomplete;
+ int resid;
+ int in_scsi_type;
+ int out_scsi_type;
+ int debug;
+} Rq_elem;
+
+static Rq_coll rcoll;
+static struct pollfd in_pollfd_arr[MAX_NUM_THREADS];
+static struct pollfd out_pollfd_arr[MAX_NUM_THREADS];
+static int dd_count = -1;
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+static int sg_finish_io(int wr, Rq_elem * rep);
+
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+static void
+install_handler (int sig_num, void (*sig_handler) (int sig))
+{
+ struct sigaction sigact;
+ sigaction (sig_num, NULL, &sigact);
+ if (sigact.sa_handler != SIG_IGN)
+ {
+ sigact.sa_handler = sig_handler;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig_num, &sigact, NULL);
+ }
+}
+
+static void
+print_stats()
+{
+ int infull, outfull;
+
+ if (0 != rcoll.out_count)
+ fprintf(stderr, " remaining block count=%d\n", rcoll.out_count);
+ infull = dd_count - rcoll.in_done_count - rcoll.in_partial;
+ fprintf(stderr, "%d+%d records in\n", infull, rcoll.in_partial);
+ outfull = dd_count - rcoll.out_done_count - rcoll.out_partial;
+ fprintf(stderr, "%d+%d records out\n", outfull, rcoll.out_partial);
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig, &sigact, NULL);
+ fprintf(stderr, "Interrupted by signal,");
+ print_stats ();
+ kill (getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ fprintf(stderr, "Progress report, continuing ...\n");
+ print_stats ();
+ if (sig) { } /* suppress unused warning */
+}
+
+static int
+dd_filetype(const char * filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0)
+ return FT_OTHER;
+ if (S_ISCHR(st.st_mode)) {
+ if (RAW_MAJOR == major(st.st_rdev))
+ return FT_RAW;
+ else if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ }
+ return FT_OTHER;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: "
+ "sgq_dd [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>] "
+ "[bs=<num>]\n"
+ " [bpt=<num>] [count=<n>] [dio=0|1] [thr=<n>] "
+ "[coe=0|1] [gen=<n>]\n"
+ " [time=0|1] [deb=<n>] [--version]\n"
+ " usually either 'if' or 'of' is a sg or raw device\n"
+ " 'bpt' is blocks_per_transfer (default is 128)\n"
+ " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n"
+ " 'thr' is number of queues, must be > 0, default 4, max 1024\n");
+ fprintf(stderr, " 'coe' continue on sg error, 0->exit (def), "
+ "1->zero + continue\n"
+ " 'time' 0->no timing(def), 1->time plus calculate throughput\n"
+ " 'gen' 0-> 1 file is special(def), 1-> any files allowed\n"
+ " 'deb' is debug, 0->none (def), > 0->varying degrees of debug\n");
+}
+
+/* Returns -1 for error, 0 for nothing found, QS_IN_POLL or QS_OUT_POLL */
+static int
+do_poll(Rq_coll * clp, int timeout, int * req_indexp)
+{
+ int k, res;
+
+ if (FT_SG == clp->out_type) {
+ while (((res = poll(out_pollfd_arr, clp->num_rq_elems, timeout)) < 0)
+ && (EINTR == errno))
+ ;
+ if (res < 0) {
+ perror("poll error on output fds");
+ return -1;
+ }
+ else if (res > 0) {
+ for (k = 0; k < clp->num_rq_elems; ++k) {
+ if (out_pollfd_arr[k].revents & POLLIN) {
+ if (req_indexp)
+ *req_indexp = k;
+ return QS_OUT_POLL;
+ }
+ }
+ }
+ }
+ if (FT_SG == clp->in_type) {
+ while (((res = poll(in_pollfd_arr, clp->num_rq_elems, timeout)) < 0)
+ && (EINTR == errno))
+ ;
+ if (res < 0) {
+ perror("poll error on input fds");
+ return -1;
+ }
+ else if (res > 0) {
+ for (k = 0; k < clp->num_rq_elems; ++k) {
+ if (in_pollfd_arr[k].revents & POLLIN) {
+ if (req_indexp)
+ *req_indexp = k;
+ return QS_IN_POLL;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+/* Return of 0 -> success, -1 -> failure, 2 -> try again */
+static int
+read_capacity(int sg_fd, int * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rc_cdb [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t rcBuff[64];
+ uint8_t sense_b[64];
+ sg_io_hdr_t io_hdr;
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rc_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_b);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = sizeof(rcBuff);
+ io_hdr.dxferp = rcBuff;
+ io_hdr.cmdp = rc_cdb;
+ io_hdr.sbp = sense_b;
+ io_hdr.timeout = DEF_TIMEOUT;
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("read_capacity (SG_IO) error");
+ return -1;
+ }
+ res = sg_err_category3(&io_hdr);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res)
+ return 2; /* probably have another go ... */
+ else if (SG_LIB_CAT_CLEAN != res) {
+ sg_chk_n_print3("read capacity", &io_hdr, 1);
+ return -1;
+ }
+ *num_sect = 1 + sg_get_unaligned_be32(rcBuff + 0);
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+#ifdef DEBUG
+ fprintf(stderr, "number of sectors=%d, sector size=%d\n",
+ *num_sect, *sect_sz);
+#endif
+ return 0;
+}
+
+/* 0 -> ok, 1 -> short read, -1 -> error */
+static int
+normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks)
+{
+ int res;
+ int stop_after_write = 0;
+
+ rep->qstate = QS_IN_STARTED;
+ if (rep->debug > 8)
+ fprintf(stderr, "normal_in_operation: start blk=%d num_blks=%d\n",
+ rep->blk, rep->num_blks);
+ while (((res = read(rep->infd, rep->buffp,
+ blocks * rep->bs)) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ fprintf(stderr, "sgq_dd: reading, in_blk=%d, errno=%d\n", rep->blk,
+ errno);
+ return -1;
+ }
+ if (res < blocks * rep->bs) {
+ int o_blocks = blocks;
+ stop_after_write = 1;
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->in_partial++;
+ }
+ /* Reverse out + re-apply blocks on clp */
+ clp->in_blk -= o_blocks;
+ clp->in_count += o_blocks;
+ rep->num_blks = blocks;
+ clp->in_blk += blocks;
+ clp->in_count -= blocks;
+ }
+ clp->in_done_count -= blocks;
+ rep->qstate = QS_IN_FINISHED;
+ return stop_after_write;
+}
+
+/* 0 -> ok, -1 -> error */
+static int
+normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks)
+{
+ int res;
+
+ rep->qstate = QS_OUT_STARTED;
+ if (rep->debug > 8)
+ fprintf(stderr, "normal_out_operation: start blk=%d num_blks=%d\n",
+ rep->blk, rep->num_blks);
+ while (((res = write(rep->outfd, rep->buffp,
+ rep->num_blks * rep->bs)) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ fprintf(stderr, "sgq_dd: output, out_blk=%d, errno=%d\n", rep->blk,
+ errno);
+ return -1;
+ }
+ if (res < blocks * rep->bs) {
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->out_partial++;
+ }
+ rep->num_blks = blocks;
+ }
+ clp->out_done_count -= blocks;
+ rep->qstate = QS_IDLE;
+ return 0;
+}
+
+/* Returns 1 for retryable, 0 for ok, -ve for error */
+static int
+sg_fin_in_operation(Rq_coll * clp, Rq_elem * rep)
+{
+ int res;
+
+ rep->qstate = QS_IN_FINISHED;
+ res = sg_finish_io(rep->wr, rep);
+ if (res < 0) {
+ if (clp->coe) {
+ memset(rep->buffp, 0, rep->num_blks * rep->bs);
+ fprintf(stderr, ">> substituted zeros for in blk=%d for "
+ "%d bytes\n", rep->blk, rep->num_blks * rep->bs);
+ res = 0;
+ }
+ else {
+ fprintf(stderr, "error finishing sg in command\n");
+ return res;
+ }
+ }
+ if (0 == res) { /* looks good, going to return */
+ if (rep->dio_incomplete || rep->resid) {
+ clp->dio_incomplete += rep->dio_incomplete;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->in_done_count -= rep->num_blks;
+ }
+ return res;
+}
+
+/* Returns 1 for retryable, 0 for ok, -ve for error */
+static int
+sg_fin_out_operation(Rq_coll * clp, Rq_elem * rep)
+{
+ int res;
+
+ rep->qstate = QS_IDLE;
+ res = sg_finish_io(rep->wr, rep);
+ if (res < 0) {
+ if (clp->coe) {
+ fprintf(stderr, ">> ignored error for out blk=%d for "
+ "%d bytes\n", rep->blk, rep->num_blks * rep->bs);
+ res = 0;
+ }
+ else {
+ fprintf(stderr, "error finishing sg out command\n");
+ return res;
+ }
+ }
+ if (0 == res) {
+ if (rep->dio_incomplete || rep->resid) {
+ clp->dio_incomplete += rep->dio_incomplete;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->out_done_count -= rep->num_blks;
+ }
+ return res;
+}
+
+static int
+sg_start_io(Rq_elem * rep)
+{
+ sg_io_hdr_t * hp = &rep->io_hdr;
+ int res;
+
+ rep->qstate = rep->wr ? QS_OUT_STARTED : QS_IN_STARTED;
+ memset(rep->cmd, 0, sizeof(rep->cmd));
+ rep->cmd[0] = rep->wr ? SGP_WRITE10 : SGP_READ10;
+ sg_put_unaligned_be32((uint32_t)rep->blk, rep->cmd + 2);
+ sg_put_unaligned_be16((uint16_t)rep->num_blks, rep->cmd + 7);
+ memset(hp, 0, sizeof(sg_io_hdr_t));
+ hp->interface_id = 'S';
+ hp->cmd_len = sizeof(rep->cmd);
+ hp->cmdp = rep->cmd;
+ hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ hp->dxfer_len = rep->bs * rep->num_blks;
+ hp->dxferp = rep->buffp;
+ hp->mx_sb_len = sizeof(rep->sb);
+ hp->sbp = rep->sb;
+ hp->timeout = DEF_TIMEOUT;
+ hp->usr_ptr = rep;
+ hp->pack_id = rep->blk;
+ if (rep->dio)
+ hp->flags |= SG_FLAG_DIRECT_IO;
+ if (rep->debug > 8) {
+ fprintf(stderr, "sg_start_io: SCSI %s, blk=%d num_blks=%d\n",
+ rep->wr ? "WRITE" : "READ", rep->blk, rep->num_blks);
+ sg_print_command(hp->cmdp);
+ fprintf(stderr, " len=%d, dxfrp=%p, cmd_len=%d\n",
+ hp->dxfer_len, hp->dxferp, hp->cmd_len);
+ }
+
+ while (((res = write(rep->wr ? rep->outfd : rep->infd, hp,
+ sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return 1;
+ return res;
+ }
+ return 0;
+}
+
+/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */
+static int
+sg_finish_io(int wr, Rq_elem * rep)
+{
+ int res;
+ sg_io_hdr_t io_hdr;
+ sg_io_hdr_t * hp;
+#if 0
+ static int testing = 0; /* thread dubious! */
+#endif
+
+ memset(&io_hdr, 0 , sizeof(sg_io_hdr_t));
+ /* FORCE_PACK_ID active set only read packet with matching pack_id */
+ io_hdr.interface_id = 'S';
+ io_hdr.dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ io_hdr.pack_id = rep->blk;
+
+ while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr,
+ sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ perror("finishing io on sg device, error");
+ return -1;
+ }
+ if (rep != (Rq_elem *)io_hdr.usr_ptr) {
+ fprintf(stderr,
+ "sg_finish_io: bad usr_ptr, request-response mismatch\n");
+ exit(1);
+ }
+ memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t));
+ hp = &rep->io_hdr;
+
+ switch (sg_err_category3(hp)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error on block=%d, num=%d\n",
+ rep->blk, rep->num_blks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ return 1;
+ default:
+ {
+ char ebuff[EBUFF_SZ];
+ snprintf(ebuff, EBUFF_SZ, "%s blk=%d",
+ rep->wr ? "writing": "reading", rep->blk);
+ sg_chk_n_print3(ebuff, hp, 1);
+ return -1;
+ }
+ }
+#if 0
+ if (0 == (++testing % 100)) return -1;
+#endif
+ if (rep->dio &&
+ ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ rep->dio_incomplete = 1; /* count dios done as indirect IO */
+ else
+ rep->dio_incomplete = 0;
+ rep->resid = hp->resid;
+ if (rep->debug > 8)
+ fprintf(stderr, "sg_finish_io: completed %s, blk=%d\n",
+ wr ? "WRITE" : "READ", rep->blk);
+ return 0;
+}
+
+/* Returns scsi_type or -1 for error */
+static int
+sg_prepare(int fd, int sz)
+{
+ int res, t;
+ struct sg_scsi_id info;
+
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ fprintf(stderr, "sgq_dd: sg driver prior to 3.x.y\n");
+ return -1;
+ }
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &sz);
+ if (res < 0)
+ perror("sgq_dd: SG_SET_RESERVED_SIZE error");
+#if 0
+ t = 1;
+ res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
+ if (res < 0)
+ perror("sgq_dd: SG_SET_FORCE_PACK_ID error");
+#endif
+ res = ioctl(fd, SG_GET_SCSI_ID, &info);
+ if (res < 0) {
+ perror("sgq_dd: SG_SET_SCSI_ID error");
+ return -1;
+ }
+ else
+ return info.scsi_type;
+}
+
+/* Return 0 for ok, anything else for errors */
+static int
+prepare_rq_elems(Rq_coll * clp, const char * inf, const char * outf)
+{
+ int k;
+ Rq_elem * rep;
+ size_t psz;
+ char ebuff[EBUFF_SZ];
+ int sz = clp->bpt * clp->bs;
+ int scsi_type;
+
+ clp->req_arr = malloc(sizeof(Rq_elem) * clp->num_rq_elems);
+ if (NULL == clp->req_arr)
+ return 1;
+ for (k = 0; k < clp->num_rq_elems; ++k) {
+ rep = &clp->req_arr[k];
+ memset(rep, 0, sizeof(Rq_elem));
+ psz = getpagesize();
+ if (NULL == (rep->alloc_bp = malloc(sz + psz)))
+ return 1;
+ rep->buffp = (uint8_t *)
+ (((unsigned long)rep->alloc_bp + psz - 1) & (~(psz - 1)));
+ rep->qstate = QS_IDLE;
+ rep->bs = clp->bs;
+ rep->dio = clp->dio;
+ rep->debug = clp->debug;
+ rep->out_scsi_type = clp->out_scsi_type;
+ if (FT_SG == clp->in_type) {
+ if (0 == k)
+ rep->infd = clp->infd;
+ else {
+ if ((rep->infd = open(inf, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for sg reading", inf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ in_pollfd_arr[k].fd = rep->infd;
+ in_pollfd_arr[k].events = POLLIN;
+ if ((scsi_type = sg_prepare(rep->infd, sz)) < 0)
+ return 1;
+ if (0 == k)
+ clp->in_scsi_type = scsi_type;
+ rep->in_scsi_type = clp->in_scsi_type;
+ }
+ else
+ rep->infd = clp->infd;
+
+ if (FT_SG == clp->out_type) {
+ if (0 == k)
+ rep->outfd = clp->outfd;
+ else {
+ if ((rep->outfd = open(outf, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for sg writing", outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ out_pollfd_arr[k].fd = rep->outfd;
+ out_pollfd_arr[k].events = POLLIN;
+ if ((scsi_type = sg_prepare(rep->outfd, sz)) < 0)
+ return 1;
+ if (0 == k)
+ clp->out_scsi_type = scsi_type;
+ rep->out_scsi_type = clp->out_scsi_type;
+ }
+ else
+ rep->outfd = clp->outfd;
+ }
+ return 0;
+}
+
+/* Returns a "QS" code and req index, or QS_IDLE and position of first idle
+ (-1 if no idle position). Returns -1 on poll error. */
+static int
+decider(Rq_coll * clp, int first_xfer, int * req_indexp)
+{
+ int k, res;
+ Rq_elem * rep;
+ int first_idle_index = -1;
+ int lowest_blk_index = -1;
+ int times;
+ int try_poll = 0;
+ int lowest_blk = INT_MAX;
+
+ times = first_xfer ? 1 : clp->num_rq_elems;
+ for (k = 0; k < times; ++k) {
+ rep = &clp->req_arr[k];
+ if ((QS_IN_STARTED == rep->qstate) ||
+ (QS_OUT_STARTED == rep->qstate))
+ try_poll = 1;
+ else if ((QS_IN_FINISHED == rep->qstate) && (rep->blk < lowest_blk)) {
+ lowest_blk = rep->blk;
+ lowest_blk_index = k;
+ }
+ else if ((QS_IDLE == rep->qstate) && (first_idle_index < 0))
+ first_idle_index = k;
+ }
+ if (try_poll) {
+ res = do_poll(clp, 0, req_indexp);
+ if (0 != res)
+ return res;
+ }
+
+ if (lowest_blk_index >= 0) {
+ if (req_indexp)
+ *req_indexp = lowest_blk_index;
+ return QS_IN_FINISHED;
+ }
+#if 0
+ if (try_poll) {
+ res = do_poll(clp, 2, req_indexp);
+ if (0 != res)
+ return res;
+ }
+#endif
+ if (req_indexp)
+ *req_indexp = first_idle_index;
+ return QS_IDLE;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ int skip = 0;
+ int seek = 0;
+ int ibs = 0;
+ int obs = 0;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ char inf[INOUTF_SZ];
+ char outf[INOUTF_SZ];
+ int res, k, n, keylen;
+ int in_num_sect = 0;
+ int out_num_sect = 0;
+ int num_threads = DEF_NUM_THREADS;
+ int gen = 0;
+ int do_time = 0;
+ int in_sect_sz, out_sect_sz, first_xfer, qstate, req_index, seek_skip;
+ int blocks, stop_after_write, terminate;
+ char ebuff[EBUFF_SZ];
+ Rq_elem * rep;
+ struct timeval start_tm, end_tm;
+
+ memset(&rcoll, 0, sizeof(Rq_coll));
+ rcoll.bpt = DEF_BLOCKS_PER_TRANSFER;
+ rcoll.in_type = FT_OTHER;
+ rcoll.out_type = FT_OTHER;
+ inf[0] = '\0';
+ outf[0] = '\0';
+
+ for(k = 1; k < argc; k++) {
+ if (argv[k])
+ strncpy(str, argv[k], STR_SZ);
+ else
+ continue;
+ for(key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (strcmp(key,"if") == 0)
+ strncpy(inf, buf, INOUTF_SZ);
+ else if (strcmp(key,"of") == 0)
+ strncpy(outf, buf, INOUTF_SZ);
+ else if (0 == strcmp(key,"ibs"))
+ ibs = sg_get_num(buf);
+ else if (0 == strcmp(key,"obs"))
+ obs = sg_get_num(buf);
+ else if (0 == strcmp(key,"bs"))
+ rcoll.bs = sg_get_num(buf);
+ else if (0 == strcmp(key,"bpt"))
+ rcoll.bpt = sg_get_num(buf);
+ else if (0 == strcmp(key,"skip"))
+ skip = sg_get_num(buf);
+ else if (0 == strcmp(key,"seek"))
+ seek = sg_get_num(buf);
+ else if (0 == strcmp(key,"count"))
+ dd_count = sg_get_num(buf);
+ else if (0 == strcmp(key,"dio"))
+ rcoll.dio = sg_get_num(buf);
+ else if (0 == strcmp(key,"thr"))
+ num_threads = sg_get_num(buf);
+ else if (0 == strcmp(key,"coe"))
+ rcoll.coe = sg_get_num(buf);
+ else if (0 == strcmp(key,"gen"))
+ gen = sg_get_num(buf);
+ else if ((0 == strncmp(key,"deb", 3)) || (0 == strncmp(key,"verb", 4)))
+ rcoll.debug = sg_get_num(buf);
+ else if (0 == strcmp(key,"time"))
+ do_time = sg_get_num(buf);
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ if (n > 0) {
+ usage();
+ return 0;
+ }
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ rcoll.debug += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ if (res < (keylen - 1)) {
+ fprintf(stderr, "Unrecognised short option in '%s', try "
+ "'--help'\n", key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp(key, "--help", 6)) {
+ usage();
+ return 0;
+ } else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++rcoll.debug;
+ } else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ fprintf(stderr, "Unrecognized argument '%s'\n", key);
+ usage();
+ return 1;
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, "In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ fprintf(stderr, "but override: '-vV' given, zero verbose and "
+ "continue\n");
+ verbose_given = false;
+ version_given = false;
+ rcoll.debug = 0;
+ } else if (! verbose_given) {
+ fprintf(stderr, "set '-vv'\n");
+ rcoll.debug = 2;
+ } else
+ fprintf(stderr, "keep verbose=%d\n", rcoll.debug);
+#else
+ if (verbose_given && version_given)
+ fprintf(stderr, "Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ fprintf(stderr, "sgq_dd for sg version 3 driver: %s\n",
+ version_str);
+ return 0;
+ return 0;
+ }
+
+ if (argc < 2) {
+ usage();
+ return 1;
+ }
+ if (rcoll.bs <= 0) {
+ rcoll.bs = DEF_BLOCK_SIZE;
+ fprintf(stderr, "Assume default 'bs' (block size) of %d bytes\n",
+ rcoll.bs);
+ }
+ if ((ibs && (ibs != rcoll.bs)) || (obs && (obs != rcoll.bs))) {
+ fprintf(stderr, "If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage();
+ return 1;
+ }
+ if ((skip < 0) || (seek < 0)) {
+ fprintf(stderr, "skip and seek cannot be negative\n");
+ return 1;
+ }
+ if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) {
+ fprintf(stderr, "too few or too many threads requested\n");
+ usage();
+ return 1;
+ }
+ if (rcoll.debug)
+ fprintf(stderr, "sgq_dd: if=%s skip=%d of=%s seek=%d count=%d\n",
+ inf, skip, outf, seek, dd_count);
+ install_handler (SIGINT, interrupt_handler);
+ install_handler (SIGQUIT, interrupt_handler);
+ install_handler (SIGPIPE, interrupt_handler);
+ install_handler (SIGUSR1, siginfo_handler);
+
+ rcoll.infd = STDIN_FILENO;
+ rcoll.outfd = STDOUT_FILENO;
+ if (inf[0] && ('-' != inf[0])) {
+ rcoll.in_type = dd_filetype(inf);
+
+ if (FT_SG == rcoll.in_type) {
+ if ((rcoll.infd = open(inf, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for sg reading", inf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ if (FT_SG != rcoll.in_type) {
+ if ((rcoll.infd = open(inf, O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for reading", inf);
+ perror(ebuff);
+ return 1;
+ }
+ else if (skip > 0) {
+ loff_t offset = skip;
+
+ offset *= rcoll.bs; /* could exceed 32 here! */
+ if (lseek(rcoll.infd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: couldn't skip to required position on %s", inf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ }
+ }
+ if (outf[0] && ('-' != outf[0])) {
+ rcoll.out_type = dd_filetype(outf);
+
+ if (FT_SG == rcoll.out_type) {
+ if ((rcoll.outfd = open(outf, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for sg writing", outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ else {
+ if (FT_OTHER == rcoll.out_type) {
+ if ((rcoll.outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for writing", outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ else {
+ if ((rcoll.outfd = open(outf, O_WRONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: could not open %s for raw writing", outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ if (seek > 0) {
+ loff_t offset = seek;
+
+ offset *= rcoll.bs; /* could exceed 32 bits here! */
+ if (lseek(rcoll.outfd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sgq_dd: couldn't seek to required position on %s", outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ }
+ }
+ if ((STDIN_FILENO == rcoll.infd) && (STDOUT_FILENO == rcoll.outfd)) {
+ fprintf(stderr, "Disallow both if and of to be stdin and stdout\n");
+ return 1;
+ }
+ if ((FT_OTHER == rcoll.in_type) && (FT_OTHER == rcoll.out_type) && !gen) {
+ fprintf(stderr, "Either 'if' or 'of' must be a sg or raw device\n");
+ return 1;
+ }
+ if (0 == dd_count)
+ return 0;
+ else if (dd_count < 0) {
+ if (FT_SG == rcoll.in_type) {
+ res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ fprintf(stderr, "Unit attention, media changed(in), repeat\n");
+ res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz);
+ }
+ if (0 != res) {
+ fprintf(stderr, "Unable to read capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ else {
+ if (in_num_sect > skip)
+ in_num_sect -= skip;
+ }
+ }
+ if (FT_SG == rcoll.out_type) {
+ res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ fprintf(stderr, "Unit attention, media changed(out), "
+ "repeat\n");
+ res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz);
+ }
+ if (0 != res) {
+ fprintf(stderr, "Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ }
+ else {
+ if (out_num_sect > seek)
+ out_num_sect -= seek;
+ }
+ }
+ if (in_num_sect > 0) {
+ if (out_num_sect > 0)
+ dd_count = (in_num_sect > out_num_sect) ? out_num_sect :
+ in_num_sect;
+ else
+ dd_count = in_num_sect;
+ }
+ else
+ dd_count = out_num_sect;
+ }
+ if (rcoll.debug > 1)
+ fprintf(stderr, "Start of loop, count=%d, in_num_sect=%d, "
+ "out_num_sect=%d\n", dd_count, in_num_sect, out_num_sect);
+ if (dd_count <= 0) {
+ fprintf(stderr, "Couldn't calculate count, please give one\n");
+ return 1;
+ }
+
+ rcoll.in_count = dd_count;
+ rcoll.in_done_count = dd_count;
+ rcoll.skip = skip;
+ rcoll.in_blk = skip;
+ rcoll.out_count = dd_count;
+ rcoll.out_done_count = dd_count;
+ rcoll.seek = seek;
+ rcoll.out_blk = seek;
+
+ if ((FT_SG == rcoll.in_type) || (FT_SG == rcoll.out_type))
+ rcoll.num_rq_elems = num_threads;
+ else
+ rcoll.num_rq_elems = 1;
+ if (prepare_rq_elems(&rcoll, inf, outf)) {
+ fprintf(stderr, "Setup failure, perhaps no memory\n");
+ return 1;
+ }
+
+ first_xfer = 1;
+ stop_after_write = 0;
+ terminate = 0;
+ seek_skip = rcoll.seek - rcoll.skip;
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+ while (rcoll.out_done_count > 0) { /* >>>>>>>>> main loop */
+ req_index = -1;
+ qstate = decider(&rcoll, first_xfer, &req_index);
+ rep = (req_index < 0) ? NULL : (rcoll.req_arr + req_index);
+ switch (qstate) {
+ case QS_IDLE:
+ if ((NULL == rep) || (rcoll.in_count <= 0)) {
+ /* usleep(1000); */
+ /* do_poll(&rcoll, 10, NULL); */
+ /* do_poll(&rcoll, 0, NULL); */
+ break;
+ }
+ if (rcoll.debug > 8)
+ fprintf(stderr, " sgq_dd: non-sleeping QS_IDLE state, "
+ "req_index=%d\n", req_index);
+ if (first_xfer >= 2)
+ first_xfer = 0;
+ else if (1 == first_xfer)
+ ++first_xfer;
+ if (stop_after_write) {
+ terminate = 1;
+ break;
+ }
+ blocks = (rcoll.in_count > rcoll.bpt) ? rcoll.bpt : rcoll.in_count;
+ rep->wr = 0;
+ rep->blk = rcoll.in_blk;
+ rep->num_blks = blocks;
+ rcoll.in_blk += blocks;
+ rcoll.in_count -= blocks;
+
+ if (FT_SG == rcoll.in_type) {
+ res = sg_start_io(rep);
+ if (0 != res) {
+ if (1 == res)
+ fprintf(stderr, "Out of memory starting sg io\n");
+ terminate = 1;
+ }
+ }
+ else {
+ res = normal_in_operation(&rcoll, rep, blocks);
+ if (res < 0)
+ terminate = 1;
+ else if (res > 0)
+ stop_after_write = 1;
+ }
+ break;
+ case QS_IN_FINISHED:
+ if (rcoll.debug > 8)
+ fprintf(stderr, " sgq_dd: state is QS_IN_FINISHED, "
+ "req_index=%d\n", req_index);
+ if ((rep->blk + seek_skip) != rcoll.out_blk) {
+ /* if write would be out of sequence then wait */
+ if (rcoll.debug > 4)
+ fprintf(stderr, " sgq_dd: QS_IN_FINISHED, "
+ "out of sequence\n");
+ usleep(200);
+ break;
+ }
+ rep->wr = 1;
+ rep->blk = rcoll.out_blk;
+ blocks = rep->num_blks;
+ rcoll.out_blk += blocks;
+ rcoll.out_count -= blocks;
+
+ if (FT_SG == rcoll.out_type) {
+ res = sg_start_io(rep);
+ if (0 != res) {
+ if (1 == res)
+ fprintf(stderr, "Out of memory starting sg io\n");
+ terminate = 1;
+ }
+ }
+ else {
+ if (normal_out_operation(&rcoll, rep, blocks) < 0)
+ terminate = 1;
+ }
+ break;
+ case QS_IN_POLL:
+ if (rcoll.debug > 8)
+ fprintf(stderr, " sgq_dd: state is QS_IN_POLL, "
+ "req_index=%d\n", req_index);
+ res = sg_fin_in_operation(&rcoll, rep);
+ if (res < 0)
+ terminate = 1;
+ else if (res > 1) {
+ if (first_xfer) {
+ /* only retry on first xfer */
+ if (0 != sg_start_io(rep))
+ terminate = 1;
+ }
+ else
+ terminate = 1;
+ }
+ break;
+ case QS_OUT_POLL:
+ if (rcoll.debug > 8)
+ fprintf(stderr, " sgq_dd: state is QS_OUT_POLL, "
+ "req_index=%d\n", req_index);
+ res = sg_fin_out_operation(&rcoll, rep);
+ if (res < 0)
+ terminate = 1;
+ else if (res > 1) {
+ if (first_xfer) {
+ /* only retry on first xfer */
+ if (0 != sg_start_io(rep))
+ terminate = 1;
+ }
+ else
+ terminate = 1;
+ }
+ break;
+ default:
+ if (rcoll.debug > 8)
+ fprintf(stderr, " sgq_dd: state is ?????\n");
+ terminate = 1;
+ break;
+ }
+ if (terminate)
+ break;
+ } /* >>>>>>>>>>>>> end of main loop */
+
+ if ((do_time) && (start_tm.tv_sec || start_tm.tv_usec)) {
+ struct timeval res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+ b = (double)rcoll.bs * (dd_count - rcoll.out_done_count);
+ printf("time to transfer data was %d.%06d secs",
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ printf(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ printf("\n");
+ }
+
+ if (STDIN_FILENO != rcoll.infd)
+ close(rcoll.infd);
+ if (STDOUT_FILENO != rcoll.outfd)
+ close(rcoll.outfd);
+ res = 0;
+ if (0 != rcoll.out_count) {
+ fprintf(stderr, ">>>> Some error occurred,\n");
+ res = 2;
+ }
+ print_stats();
+ if (rcoll.dio_incomplete) {
+ int fd;
+ char c;
+
+ fprintf(stderr, ">> Direct IO requested but incomplete %d times\n",
+ rcoll.dio_incomplete);
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ fprintf(stderr, ">>> %s set to '0' but should be set "
+ "to '1' for direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (rcoll.sum_of_resids)
+ fprintf(stderr, ">> Non-zero sum of residual counts=%d\n",
+ rcoll.sum_of_resids);
+ return res;
+}
diff --git a/examples/transport_ids.txt b/examples/transport_ids.txt
new file mode 100644
index 00000000..2657af25
--- /dev/null
+++ b/examples/transport_ids.txt
@@ -0,0 +1,31 @@
+# This file is an example for the sg_persist utility.
+# It discusses using "TransportID"s which are defined (most recently)
+# in SPC-4 revision 20 section 7.5.4 titled: "TransportID identifiers".
+#
+# The sg_persist utility can take one or more "transportID"s from stdin when
+# either the '--transport-id=-" or "-X -" option is given on the command
+# line.
+
+# To see transport IDs decoded after they have been read in (e.g. to check
+# they are well formed) use the verbose flag 3 times (i.e. "... -vvv ...").
+
+# Here is a simple example (for SPI) of a comma separated hex list:
+1,0,0,7,0,0,0,1 # SPI, initiator address=7, relative_port_num=1
+
+# and here is the transport specific format for the same thing:
+# spi,1,7
+
+# An example for SAS follows, first as a hex string, then in transport
+# specific format (incremented by 1)
+6,0,0,0,50,6,5,b0,0,6,f2,60
+sas,500605b00006f261
+
+# For iSCSI the hex list form is awkward, better to use the transport
+# specific format. [The leading spaces are ignored.]
+ iqn.5886.com.acme.diskarrays-sn-a8675309
+
+
+ # Leading spaces and tabs before a '#' are ok.
+
+
+# dpg 20090824
diff --git a/getopt_long/getopt.h b/getopt_long/getopt.h
new file mode 100644
index 00000000..4d6543b4
--- /dev/null
+++ b/getopt_long/getopt.h
@@ -0,0 +1,86 @@
+/* $NetBSD: getopt.h,v 1.7 2005/02/03 04:39:32 perry Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * modified May 12, 2005 by Jim Basney <jbasney@ncsa.uiuc.edu>
+ *
+ * removed #include of non-POSIX <sys/cdefs.h> and <sys/featuretest.h>
+ * removed references to _NETBSD_SOURCE and HAVE_NBTOOL_CONFIG_H
+ * added #if !HAVE_GETOPT_LONG
+ * removed __BEGIN_DECLS and __END_DECLS
+ */
+
+#ifndef _MYPROXY_GETOPT_H_
+#define _MYPROXY_GETOPT_H_
+
+#if !HAVE_GETOPT_LONG
+
+#include <unistd.h>
+
+/*
+ * Gnu like getopt_long() and BSD4.4 getsubopt()/optreset extensions
+ */
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+extern char *optarg;
+extern int optind;
+extern int optopt;
+extern int opterr;
+
+struct option {
+ /* name of long option */
+ const char *name;
+ /*
+ * one of no_argument, required_argument, and optional_argument:
+ * whether option takes an argument
+ */
+ int has_arg;
+ /* if not NULL, set *flag to val when option found */
+ int *flag;
+ /* if flag not NULL, value to set *flag to; else return value */
+ int val;
+};
+
+int getopt_long(int, char * const *, const char *,
+ const struct option *, int *);
+
+#endif /* !HAVE_GETOPT_LONG */
+
+#endif /* !_MYPROXY_GETOPT_H_ */
diff --git a/getopt_long/getopt_long.c b/getopt_long/getopt_long.c
new file mode 100644
index 00000000..f94dcae2
--- /dev/null
+++ b/getopt_long/getopt_long.c
@@ -0,0 +1,434 @@
+/* $NetBSD: getopt_long.c,v 1.17 2004/06/20 22:20:15 jmc Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * modified May 12, 2005 by Jim Basney <jbasney@ncsa.uiuc.edu>
+ *
+ * removed #include of non-POSIX <sys/cdefs.h> <err.h>
+ * removed #include of "namespace.h"
+ * use local "port_getopt.h" instead of <getopt.h>
+ * removed REPLACE_GETOPT and HAVE_NBTOOL_CONFIG_H sections
+ * removed __P() from function declarations
+ * use ANSI C function parameter lists
+ * removed optreset support
+ * replace _DIAGASSERT() with assert()
+ * replace non-POSIX warnx(...) with fprintf(stderr, ...)
+ * added extern declarations for optarg, optind, opterr, and optopt
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: getopt_long.c,v 1.17 2004/06/20 22:20:15 jmc Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <assert.h>
+#include <errno.h>
+#include "getopt.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __weak_alias
+__weak_alias(getopt_long,_getopt_long)
+#endif
+
+#if !HAVE_GETOPT_LONG
+#define IGNORE_FIRST (*options == '-' || *options == '+')
+#define PRINT_ERROR ((opterr) && ((*options != ':') \
+ || (IGNORE_FIRST && options[1] != ':')))
+#define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL)
+#define PERMUTE (!IS_POSIXLY_CORRECT && !IGNORE_FIRST)
+/* XXX: GNU ignores PC if *options == '-' */
+#define IN_ORDER (!IS_POSIXLY_CORRECT && *options == '-')
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((IGNORE_FIRST && options[1] == ':') \
+ || (*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#define EMSG ""
+
+extern char *optarg;
+extern int optind, opterr, optopt;
+
+static int getopt_internal (int, char * const *, const char *);
+static int gcd (int, int);
+static void permute_args (int, int, int, char * const *);
+
+static char *place = EMSG; /* option letter processing */
+
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1; /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptchar[] = "unknown option -- %c";
+static const char illoptstring[] = "unknown option -- %s";
+
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(int a, int b)
+{
+ int c;
+
+ c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+
+ return b;
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(int panonopt_start, int panonopt_end, int opt_end,
+ char * const *nargv)
+{
+ int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+ char *swap;
+
+ assert(nargv != NULL);
+
+ /*
+ * compute lengths of blocks and number and size of cycles
+ */
+ nnonopts = panonopt_end - panonopt_start;
+ nopts = opt_end - panonopt_end;
+ ncycle = gcd(nnonopts, nopts);
+ cyclelen = (opt_end - panonopt_start) / ncycle;
+
+ for (i = 0; i < ncycle; i++) {
+ cstart = panonopt_end+i;
+ pos = cstart;
+ for (j = 0; j < cyclelen; j++) {
+ if (pos >= panonopt_end)
+ pos -= nnonopts;
+ else
+ pos += nopts;
+ swap = nargv[pos];
+ /* LINTED const cast */
+ ((char **) nargv)[pos] = nargv[cstart];
+ /* LINTED const cast */
+ ((char **)nargv)[cstart] = swap;
+ }
+ }
+}
+
+/*
+ * getopt_internal --
+ * Parse argc/argv argument vector. Called by user level routines.
+ * Returns -2 if -- is found (can be long option or end of options marker).
+ */
+static int
+getopt_internal(int nargc, char * const *nargv, const char *options)
+{
+ char *oli; /* option letter list index */
+ int optchar;
+
+ assert(nargv != NULL);
+ assert(options != NULL);
+
+ optarg = NULL;
+
+ /*
+ * XXX Some programs (like rsyncd) expect to be able to
+ * XXX re-initialize optind to 0 and have getopt_long(3)
+ * XXX properly function again. Work around this braindamage.
+ */
+ if (optind == 0)
+ optind = 1;
+
+start:
+ if (!*place) { /* update scanning pointer */
+ if (optind >= nargc) { /* end of argument vector */
+ place = EMSG;
+ if (nonopt_end != -1) {
+ /* do permutation, if we have to */
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ else if (nonopt_start != -1) {
+ /*
+ * If we skipped non-options, set optind
+ * to the first of them.
+ */
+ optind = nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return -1;
+ }
+ if ((*(place = nargv[optind]) != '-')
+ || (place[1] == '\0')) { /* found non-option */
+ place = EMSG;
+ if (IN_ORDER) {
+ /*
+ * GNU extension:
+ * return non-option as argument to option 1
+ */
+ optarg = nargv[optind++];
+ return INORDER;
+ }
+ if (!PERMUTE) {
+ /*
+ * if no permutation wanted, stop parsing
+ * at first non-option
+ */
+ return -1;
+ }
+ /* do permutation */
+ if (nonopt_start == -1)
+ nonopt_start = optind;
+ else if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ nonopt_start = optind -
+ (nonopt_end - nonopt_start);
+ nonopt_end = -1;
+ }
+ optind++;
+ /* process next argument */
+ goto start;
+ }
+ if (nonopt_start != -1 && nonopt_end == -1)
+ nonopt_end = optind;
+ if (place[1] && *++place == '-') { /* found "--" */
+ place++;
+ return -2;
+ }
+ }
+ if ((optchar = (int)*place++) == (int)':' ||
+ (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) {
+ /* option letter unknown or ':' */
+ if (!*place)
+ ++optind;
+ if (PRINT_ERROR)
+ fprintf(stderr, illoptchar, optchar);
+ optopt = optchar;
+ return BADCH;
+ }
+ if (optchar == 'W' && oli[1] == ';') { /* -W long-option */
+ /* XXX: what if no long options provided (called by getopt)? */
+ if (*place)
+ return -2;
+
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ fprintf(stderr, recargchar, optchar);
+ optopt = optchar;
+ return BADARG;
+ } else /* white space */
+ place = nargv[optind];
+ /*
+ * Handle -W arg the same as --arg (which causes getopt to
+ * stop parsing).
+ */
+ return -2;
+ }
+ if (*++oli != ':') { /* doesn't take argument */
+ if (!*place)
+ ++optind;
+ } else { /* takes (optional) argument */
+ optarg = NULL;
+ if (*place) /* no white space */
+ optarg = place;
+ /* XXX: disable test for :: if PC? (GNU doesn't) */
+ else if (oli[1] != ':') { /* arg not optional */
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ fprintf(stderr, recargchar, optchar);
+ optopt = optchar;
+ return BADARG;
+ } else
+ optarg = nargv[optind];
+ }
+ place = EMSG;
+ ++optind;
+ }
+ /* dump back option letter */
+ return optchar;
+}
+
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+ int retval;
+
+ assert(nargv != NULL);
+ assert(options != NULL);
+ assert(long_options != NULL);
+ /* idx may be NULL */
+
+ if ((retval = getopt_internal(nargc, nargv, options)) == -2) {
+ char *current_argv, *has_equal;
+ size_t current_argv_len;
+ int i, match;
+
+ current_argv = place;
+ match = -1;
+
+ optind++;
+ place = EMSG;
+
+ if (*current_argv == '\0') { /* found "--" */
+ /*
+ * We found an option (--), so if we skipped
+ * non-options, we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return -1;
+ }
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ /* argument found (--option=arg) */
+ current_argv_len = has_equal - current_argv;
+ has_equal++;
+ } else
+ current_argv_len = strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ /* find matching long option */
+ if (strncmp(current_argv, long_options[i].name,
+ current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) ==
+ (unsigned)current_argv_len) {
+ /* exact match */
+ match = i;
+ break;
+ }
+ if (match == -1) /* partial match */
+ match = i;
+ else {
+ /* ambiguous abbreviation */
+ if (PRINT_ERROR)
+ fprintf(stderr, ambig, (int)current_argv_len,
+ current_argv);
+ optopt = 0;
+ return BADCH;
+ }
+ }
+ if (match != -1) { /* option found */
+ if (long_options[match].has_arg == no_argument
+ && has_equal) {
+ if (PRINT_ERROR)
+ fprintf(stderr, noarg, (int)current_argv_len,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of
+ * flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ return BADARG;
+ }
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else if (long_options[match].has_arg ==
+ required_argument) {
+ /*
+ * optional argument doesn't use
+ * next nargv
+ */
+ optarg = nargv[optind++];
+ }
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument; leading ':'
+ * indicates no error should be generated
+ */
+ if (PRINT_ERROR)
+ fprintf(stderr, recargstring, current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless
+ * of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ --optind;
+ return BADARG;
+ }
+ } else { /* unknown option */
+ if (PRINT_ERROR)
+ fprintf(stderr, illoptstring, current_argv);
+ optopt = 0;
+ return BADCH;
+ }
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ retval = 0;
+ } else
+ retval = long_options[match].val;
+ if (idx)
+ *idx = match;
+ }
+ return retval;
+}
+#endif /* !GETOPT_LONG */
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 00000000..4cf82f15
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,63 @@
+
+scsiincludedir = $(includedir)/scsi
+
+scsiinclude_HEADERS = \
+ sg_lib.h \
+ sg_lib_data.h \
+ sg_lib_names.h \
+ sg_cmds.h \
+ sg_cmds_basic.h \
+ sg_cmds_extra.h \
+ sg_cmds_mmc.h \
+ sg_pr2serr.h \
+ sg_unaligned.h \
+ sg_pt.h \
+ sg_pt_nvme.h
+
+if OS_LINUX
+scsiinclude_HEADERS += \
+ sg_linux_inc.h \
+ sg_io_linux.h \
+ sg_pt_linux.h
+
+noinst_HEADERS = \
+ sg_pt_win32.h
+endif
+
+if OS_WIN32_MINGW
+scsiinclude_HEADERS += sg_pt_win32.h
+
+noinst_HEADERS = \
+ sg_linux_inc.h \
+ sg_io_linux.h
+endif
+
+if OS_WIN32_CYGWIN
+scsiinclude_HEADERS += sg_pt_win32.h
+
+noinst_HEADERS = \
+ sg_linux_inc.h \
+ sg_io_linux.h
+endif
+
+if OS_FREEBSD
+noinst_HEADERS = \
+ sg_linux_inc.h \
+ sg_io_linux.h \
+ sg_pt_win32.h
+endif
+
+if OS_SOLARIS
+noinst_HEADERS = \
+ sg_linux_inc.h \
+ sg_io_linux.h \
+ sg_pt_win32.h
+endif
+
+if OS_OSF
+noinst_HEADERS = \
+ sg_linux_inc.h \
+ sg_io_linux.h \
+ sg_pt_win32.h
+endif
+
diff --git a/include/Makefile.in b/include/Makefile.in
new file mode 100644
index 00000000..6067757a
--- /dev/null
+++ b/include/Makefile.in
@@ -0,0 +1,606 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@OS_LINUX_TRUE@am__append_1 = \
+@OS_LINUX_TRUE@ sg_linux_inc.h \
+@OS_LINUX_TRUE@ sg_io_linux.h \
+@OS_LINUX_TRUE@ sg_pt_linux.h
+
+@OS_WIN32_MINGW_TRUE@am__append_2 = sg_pt_win32.h
+@OS_WIN32_CYGWIN_TRUE@am__append_3 = sg_pt_win32.h
+subdir = include
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__noinst_HEADERS_DIST) \
+ $(am__scsiinclude_HEADERS_DIST) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__noinst_HEADERS_DIST = sg_linux_inc.h sg_io_linux.h sg_pt_win32.h
+am__scsiinclude_HEADERS_DIST = sg_lib.h sg_lib_data.h sg_lib_names.h \
+ sg_cmds.h sg_cmds_basic.h sg_cmds_extra.h sg_cmds_mmc.h \
+ sg_pr2serr.h sg_unaligned.h sg_pt.h sg_pt_nvme.h \
+ sg_linux_inc.h sg_io_linux.h sg_pt_linux.h sg_pt_win32.h
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(scsiincludedir)"
+HEADERS = $(noinst_HEADERS) $(scsiinclude_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+scsiincludedir = $(includedir)/scsi
+scsiinclude_HEADERS = sg_lib.h sg_lib_data.h sg_lib_names.h sg_cmds.h \
+ sg_cmds_basic.h sg_cmds_extra.h sg_cmds_mmc.h sg_pr2serr.h \
+ sg_unaligned.h sg_pt.h sg_pt_nvme.h $(am__append_1) \
+ $(am__append_2) $(am__append_3)
+@OS_FREEBSD_TRUE@noinst_HEADERS = \
+@OS_FREEBSD_TRUE@ sg_linux_inc.h \
+@OS_FREEBSD_TRUE@ sg_io_linux.h \
+@OS_FREEBSD_TRUE@ sg_pt_win32.h
+
+@OS_LINUX_TRUE@noinst_HEADERS = \
+@OS_LINUX_TRUE@ sg_pt_win32.h
+
+@OS_OSF_TRUE@noinst_HEADERS = \
+@OS_OSF_TRUE@ sg_linux_inc.h \
+@OS_OSF_TRUE@ sg_io_linux.h \
+@OS_OSF_TRUE@ sg_pt_win32.h
+
+@OS_SOLARIS_TRUE@noinst_HEADERS = \
+@OS_SOLARIS_TRUE@ sg_linux_inc.h \
+@OS_SOLARIS_TRUE@ sg_io_linux.h \
+@OS_SOLARIS_TRUE@ sg_pt_win32.h
+
+@OS_WIN32_CYGWIN_TRUE@noinst_HEADERS = \
+@OS_WIN32_CYGWIN_TRUE@ sg_linux_inc.h \
+@OS_WIN32_CYGWIN_TRUE@ sg_io_linux.h
+
+@OS_WIN32_MINGW_TRUE@noinst_HEADERS = \
+@OS_WIN32_MINGW_TRUE@ sg_linux_inc.h \
+@OS_WIN32_MINGW_TRUE@ sg_io_linux.h
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign include/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-scsiincludeHEADERS: $(scsiinclude_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(scsiinclude_HEADERS)'; test -n "$(scsiincludedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(scsiincludedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(scsiincludedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(scsiincludedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(scsiincludedir)" || exit $$?; \
+ done
+
+uninstall-scsiincludeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(scsiinclude_HEADERS)'; test -n "$(scsiincludedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(scsiincludedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(scsiincludedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-scsiincludeHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-scsiincludeHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+ clean-libtool cscopelist-am ctags ctags-am distclean \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am \
+ install-scsiincludeHEADERS install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-scsiincludeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/include/freebsd_nvme_ioctl.h b/include/freebsd_nvme_ioctl.h
new file mode 100644
index 00000000..0b79d851
--- /dev/null
+++ b/include/freebsd_nvme_ioctl.h
@@ -0,0 +1,175 @@
+#ifndef FREEBSD_NVME_IOCTL_H
+#define FREEBSD_NVME_IOCTL_H
+
+/*-
+ * Copyright (C) 2012-2013 Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+
+#include <sys/param.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NVME_PASSTHROUGH_CMD _IOWR('n', 0, struct nvme_pt_command)
+
+#if __FreeBSD_version < 1100110
+
+#define NVME_STATUS_GET_SC(st) (st.sc)
+#define NVME_STATUS_GET_SCT(st) (st.sct)
+
+
+struct nvme_command
+{
+ /* dword 0 */
+ uint16_t opc : 8; /* opcode */
+ uint16_t fuse : 2; /* fused operation */
+ uint16_t rsvd1 : 6;
+ uint16_t cid; /* command identifier */
+
+ /* dword 1 */
+ uint32_t nsid; /* namespace identifier */
+
+ /* dword 2-3 */
+ uint32_t rsvd2;
+ uint32_t rsvd3;
+
+ /* dword 4-5 */
+ uint64_t mptr; /* metadata pointer */
+
+ /* dword 6-7 */
+ uint64_t prp1; /* prp entry 1 */
+
+ /* dword 8-9 */
+ uint64_t prp2; /* prp entry 2 */
+
+ /* dword 10-15 */
+ uint32_t cdw10; /* command-specific */
+ uint32_t cdw11; /* command-specific */
+ uint32_t cdw12; /* command-specific */
+ uint32_t cdw13; /* command-specific */
+ uint32_t cdw14; /* command-specific */
+ uint32_t cdw15; /* command-specific */
+} __packed;
+
+struct nvme_status {
+
+ uint16_t p : 1; /* phase tag */
+ uint16_t sc : 8; /* status code */
+ uint16_t sct : 3; /* status code type */
+ uint16_t rsvd2 : 2;
+ uint16_t m : 1; /* more */
+ uint16_t dnr : 1; /* do not retry */
+} __packed;
+
+struct nvme_completion {
+
+ /* dword 0 */
+ uint32_t cdw0; /* command-specific */
+
+ /* dword 1 */
+ uint32_t rsvd1;
+
+ /* dword 2 */
+ uint16_t sqhd; /* submission queue head pointer */
+ uint16_t sqid; /* submission queue identifier */
+
+ /* dword 3 */
+ uint16_t cid; /* command identifier */
+ struct nvme_status status;
+} __packed;
+
+struct nvme_pt_command {
+
+ /*
+ * cmd is used to specify a passthrough command to a controller or
+ * namespace.
+ *
+ * The following fields from cmd may be specified by the caller:
+ * * opc (opcode)
+ * * nsid (namespace id) - for admin commands only
+ * * cdw10-cdw15
+ *
+ * Remaining fields must be set to 0 by the caller.
+ */
+ struct nvme_command cmd;
+
+ /*
+ * cpl returns completion status for the passthrough command
+ * specified by cmd.
+ *
+ * The following fields will be filled out by the driver, for
+ * consumption by the caller:
+ * * cdw0
+ * * status (except for phase)
+ *
+ * Remaining fields will be set to 0 by the driver.
+ */
+ struct nvme_completion cpl;
+
+ /* buf is the data buffer associated with this passthrough command. */
+ void * buf;
+
+ /*
+ * len is the length of the data buffer associated with this
+ * passthrough command.
+ */
+ uint32_t len;
+
+ /*
+ * is_read = 1 if the passthrough command will read data into the
+ * supplied buffer from the controller.
+ *
+ * is_read = 0 if the passthrough command will write data from the
+ * supplied buffer to the controller.
+ */
+ uint32_t is_read;
+
+ /*
+ * driver_lock is used by the driver only. It must be set to 0
+ * by the caller.
+ */
+ struct mtx * driver_lock;
+};
+#else /* not __FreeBSD_version < 1100110 */
+#include <dev/nvme/nvme.h>
+#endif /* __FreeBSD_version < 1100110 */
+
+#ifndef nvme_completion_is_error
+#define nvme_completion_is_error(cpl) \
+ ((cpl)->status.sc != 0 || (cpl)->status.sct != 0)
+#endif
+
+#define NVME_CTRLR_PREFIX "/dev/nvme"
+#define NVME_NS_PREFIX "ns"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* for FREEBSD_NVME_IOCTL_H */
diff --git a/include/sg_cmds.h b/include/sg_cmds.h
new file mode 100644
index 00000000..690f53ab
--- /dev/null
+++ b/include/sg_cmds.h
@@ -0,0 +1,21 @@
+#ifndef SG_CMDS_H
+#define SG_CMDS_H
+
+/********************************************************************
+ * This header did contain wrapper declarations for many SCSI commands
+ * up until sg3_utils version 1.22 . In that version, the command
+ * wrappers were broken into two groups, the 'basic' ones found in the
+ * "sg_cmds_basic.h" header and the 'extra' ones found in the
+ * "sg_cmds_extra.h" header. This header now simply includes those two
+ * headers.
+ * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and
+ * contains some MMC specific commands.
+ * The corresponding function definitions are found in the sg_cmds_basic.c,
+ * sg_cmds_extra.c and sg_cmds_mmc.c files.
+ ********************************************************************/
+
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_cmds_mmc.h"
+
+#endif
diff --git a/include/sg_cmds_basic.h b/include/sg_cmds_basic.h
new file mode 100644
index 00000000..48aabc9a
--- /dev/null
+++ b/include/sg_cmds_basic.h
@@ -0,0 +1,364 @@
+#ifndef SG_CMDS_BASIC_H
+#define SG_CMDS_BASIC_H
+
+/*
+ * Copyright (c) 2004-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Error, warning and verbose output is sent to the file pointed to by
+ * sg_warnings_strm which is declared in sg_lib.h and can be set with
+ * the sg_set_warnings_strm() function. If not given sg_warnings_strm
+ * defaults to stderr.
+ * If 'noisy' is false and 'verbose' is zero then following functions should
+ * not output anything to sg_warnings_strm. If 'noisy' is true and 'verbose'
+ * is zero then Unit Attention, Recovered, Medium and Hardware errors (sense
+ * keys) send output to sg_warnings_strm. Increasing values of 'verbose'
+ * send increasing amounts of (debug) output to sg_warnings_strm.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Functions with the "_pt" suffix take a pointer to an object (derived from)
+ * sg_pt_base rather than an open file descriptor as their first argument.
+ * That object is assumed to be constructed and have a device file descriptor
+ * associated with it. clear_scsi_pt_obj() is called at the start of each
+ * "_pt" function. Caller is responsible for lifetime of ptp.
+ * If the sense buffer is accessed outside the "_pt" function then the caller
+ * must invoke set_scsi_pt_sense() _prior_ to the "_pt" function. Otherwise
+ * a sense buffer local to "_pt" function is used.
+ * Usually the cdb pointer will be NULL going into the "_pt" functions but
+ * could be given by the caller in which case it will used rather than a
+ * locally generated one. */
+
+struct sg_pt_base;
+
+
+/* Invokes a SCSI INQUIRY command and yields the response
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ABORTED_COMMAND, a negated errno or -1 -> other errors */
+int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values, negated error or -1
+ * for other errors. The CMDDT field is obsolete in the INQUIRY cdb (since
+ * spc3r16 in 2003) so * an argument to set it has been removed (use the
+ * REPORT SUPPORTED OPERATION CODES command instead). Adds the ability to
+ * set the command abort timeout and the ability to report the residual
+ * count. If timeout_secs is zero or less the default command abort timeout
+ * (60 seconds) is used. If residp is non-NULL then the residual value is
+ * written where residp points. A residual value of 0 implies mx_resp_len
+ * bytes have be written where resp points. If the residual value equals
+ * mx_resp_len then no bytes have been written. */
+int sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose);
+
+/* Similar to sg_ll_inquiry_v2(). See note above about "_pt" suffix. */
+int sg_ll_inquiry_pt(struct sg_pt_base * ptp, bool evpd, int pg_op,
+ void * resp, int mx_resp_len, int timeout_secs,
+ int * residp, bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Select not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+ int subpg_code, uint8_t * paramp, int param_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Log Sense 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_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+ int subpg_code, int paramp, uint8_t * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+ int subpg_code, int paramp, uint8_t * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp,
+ int param_len, bool noisy, int verbose);
+/* v2 adds RTD (revert to defaults) bit, added in spc5r11 */
+int sg_ll_mode_select6_v2(int sg_fd, bool pf, bool rtd, bool sp,
+ void * paramp, int param_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp,
+ int param_len, bool noisy, int verbose);
+/* v2 adds RTD (revert to defaults) bit, added in spc5r11 */
+int sg_ll_mode_select10_v2(int sg_fd, bool pf, bool rtd, bool sp,
+ void * paramp, int param_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code,
+ int sub_pg_code, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ ->
+ * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+ int sub_pg_code, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+
+/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See
+ * sg_ll_inquiry_v2() for their description */
+int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc,
+ int pg_code, int sub_pg_code, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3)
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command 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_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION
+ * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP
+ * -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Luns not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_report_luns(int sg_fd, int select_report, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Similar to sg_ll_report_luns(). See note above about "_pt" suffix. */
+int sg_ll_report_luns_pt(struct sg_pt_base * ptp, int select_report,
+ void * resp, int mx_resp_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+
+/* Similar to sg_ll_request_sense(). See note above about "_pt" suffix. */
+int sg_ll_request_sense_pt(struct sg_pt_base * ptp, bool desc, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Start stop unit 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
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function. */
+int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+ int power_cond, bool noflush__fl, bool loej,
+ bool start, bool noisy, int verbose);
+
+/* Similar to sg_ll_start_stop_unit(). See note above about "_pt" suffix. */
+int sg_ll_start_stop_unit_pt(struct sg_pt_base * ptp, bool immed,
+ int pc_mod__fl_num, int power_cond,
+ bool noflush__fl, bool loej, bool start,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_INVALID_OP -> cdb not supported,
+ * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+ unsigned int lba, unsigned int count, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready,
+ * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */
+int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose);
+
+/* Similar to sg_ll_test_unit_ready(). See note above about "_pt" suffix. */
+int sg_ll_test_unit_ready_pt(struct sg_pt_base * ptp, int pack_id,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY ->
+ * device not ready, -1 -> other failure */
+int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+ bool noisy, int verbose);
+
+/* Similar to sg_ll_test_unit_ready_progress(). See note above about "_pt"
+ * suffix. */
+int sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptp, int pack_id,
+ int * progress, bool noisy, int verbose);
+
+
+struct sg_simple_inquiry_resp {
+ uint8_t peripheral_qualifier;
+ uint8_t peripheral_type;
+ uint8_t byte_1; /* was 'rmb' prior to version 1.39 */
+ /* now rmb == !!(0x80 & byte_1) */
+ uint8_t version; /* as per recent drafts: whole of byte 2 */
+ uint8_t byte_3;
+ uint8_t byte_5;
+ uint8_t byte_6;
+ uint8_t byte_7;
+ char vendor[9]; /* T10 field is 8 bytes, NUL char appended */
+ char product[17];
+ char revision[5];
+};
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * a negated errno or -1 -> other errors */
+int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+ bool noisy, int verbose);
+
+/* Similar to sg_simple_inquiry(). See note above about "_pt" suffix. */
+int sg_simple_inquiry_pt(struct sg_pt_base * ptvp,
+ struct sg_simple_inquiry_resp * inq_data, bool noisy,
+ int verbose);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int sg_mode_page_offset(const uint8_t * resp, int resp_len,
+ bool mode_sense_6, char * err_buff, int err_buff_len);
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int sg_msense_calc_length(const uint8_t * resp, int resp_len,
+ bool mode_sense_6, int * bd_lenp);
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6 is true then use MODE SENSE (6) else use MODE SENSE (10). If
+ * flexible true and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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_MALFORMED -> bad response, -1 -> other failure.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered. */
+int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code,
+ int sub_pg_code, bool dbd, bool flexible,
+ int mx_mpage_len, int * success_mask,
+ void * pcontrol_arr[], int * reported_lenp,
+ int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. Implementation calls scsi_pt_open_device(). */
+int sg_cmds_open_device(const char * device_name, bool read_only, int verbose);
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. Implementation calls scsi_pt_open_flags(). */
+int sg_cmds_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. If error in Unix returns negated errno.
+ Implementation calls scsi_pt_close_device(). */
+int sg_cmds_close_device(int device_fd);
+
+const char * sg_cmds_version();
+
+#define SG_NO_DATA_IN 0
+
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * sense data (may not be fatal), -1 for failed, 0, or a positive number. If
+ * 'mx_di_len > 0' then asks pass-through for resid and returns
+ * (mx_di_len - resid); otherwise returns 0. So for data-in it should return
+ * the actual number of bytes received. For data-out (to device) or no data
+ * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category
+ * output via 'o_sense_cat' pointer (if not NULL). Note that several sense
+ * categories also have data in bytes received; -2 is still returned. */
+int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+ int pt_res, bool noisy, int verbose,
+ int * o_sense_cat);
+
+/* NVMe devices use a different command set. This function will return true
+ * if the device associated with 'pvtp' is a NVME device, else it will
+ * return false (e.g. for SCSI devices). */
+bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_cmds_extra.h b/include/sg_cmds_extra.h
new file mode 100644
index 00000000..6e24c6e0
--- /dev/null
+++ b/include/sg_cmds_extra.h
@@ -0,0 +1,391 @@
+#ifndef SG_CMDS_EXTRA_H
+#define SG_CMDS_EXTRA_H
+
+/*
+ * Copyright (c) 2004-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Note: all functions that have an 'int timeout_secs' argument will use
+ * that value if it is > 0. Otherwise they will set an internal default
+ * which is currently 60 seconds. This timeout is typically applied in the
+ * SCSI stack above the initiator. If it goes off then the SCSI command is
+ * aborted and there can be other unwelcome side effects. Note that some
+ * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take
+ * a lot longer than the default timeout. */
+
+/* Functions with the "_pt" suffix ^^^ take a pointer to an object (derived
+ * from) sg_pt_base rather than an open file descriptor as their first
+ * argument. That object is assumed to be constructed and have a device file
+ * descriptor * associated with it. Caller is responsible for lifetime of
+ * ptp.
+ * ^^^ apart from sg_ll_ata_pt() as 'pass-through' is part of its name. */
+
+struct sg_pt_base;
+
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int sg_ll_ata_pt(int sg_fd, const uint8_t * cdbp, int cdb_len,
+ int timeout_secs, void * dinp, void * doutp, int dlen,
+ uint8_t * sensep, int max_sense_len, uint8_t * ata_return_dp,
+ int max_ata_return_len, int * residp, int verbose);
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Format unit 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. Note that sg_ll_format_unit2() and
+ * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */
+int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplist, int dlist_format, int timeout_secs,
+ void * paramp, int param_len, bool noisy, int verbose);
+int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplist, int dlist_format, int ffmt,
+ int timeout_secs, void * paramp, int param_len,
+ bool noisy, int verbose);
+int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplist, int dlist_format, int ffmt,
+ int timeout_secs, void * paramp, int param_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC).
+ * Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure.
+ * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */
+int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+ int alloc_len, bool noisy, int verbose);
+int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+ void * resp, int alloc_len, bool noisy,
+ int verbose);
+int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+ uint32_t element_id, uint8_t rt,
+ void * resp, int alloc_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, SG_LIB_CAT_INVALID_OP if command not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+ unsigned int rq_type, void * paramp,
+ int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */
+int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+int sg_ll_read_block_limits_v2(int sg_fd, bool mloi, void * resp,
+ int mx_resp_len, int * residp, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+ void * resp, int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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_read_defect10(int sg_fd, bool req_plist, bool req_glist,
+ int dl_format, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+ void * resp, int xfer_len, int * offsetp, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+ void * resp, int xfer_len, int * offsetp, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Read media serial number 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_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */
+int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist,
+ void * paramp, int param_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive diagnostic 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_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Same as sg_ll_receive_diag() but with added timeout_secs and residp
+ * arguments. Adds the ability to set the command abort timeout
+ * and the ability to report the residual count. If timeout_secs is zero
+ * or less the default command abort timeout (60 seconds) is used.
+ * If residp is non-NULL then the residual value is written where residp
+ * points. A residual value of 0 implies mx_resp_len bytes have be written
+ * where resp points. If the residual value equals mx_resp_len then no
+ * bytes have been written. */
+int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose);
+
+int sg_ll_receive_diag_pt(struct sg_pt_base * ptp, bool pcv, int pg_code,
+ void * resp, int mx_resp_len, int timeout_secs,
+ int * residp, bool noisy, int verbose);
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report identifying information 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_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+ bool noisy, int 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,
+ bool noisy, int verbose);
+int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+ bool extended, bool 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,
+ * 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_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Report Referrals 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_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+ void * resp, int mx_resp_len, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can
+ * take a long time, if so set long_duration flag in which case the timeout
+ * is set to 7200 seconds; if the value of long_duration is > 7200 then that
+ * value is taken as the timeout value in seconds. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Send diagnostic 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_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+ bool devofl_bit, bool unitofl_bit, int long_duration,
+ void * paramp, int param_len, bool noisy, int verbose);
+
+int sg_ll_send_diag_pt(struct sg_pt_base * ptp, int st_code, bool pf_bit,
+ bool st_bit, bool devofl_bit, bool unitofl_bit,
+ int long_duration, void * paramp, int param_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set identifying information 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_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> command 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_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+ int param_len, bool noisy, int verbose);
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+ void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk,
+ unsigned int lba, int veri_len, void * data_out,
+ int data_out_len, unsigned int * infop, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI VERIFY (16) command (SBC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */
+int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk,
+ uint64_t llba, int veri_len, int group_num,
+ void * data_out, int data_out_len, uint64_t * infop,
+ bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+ void * paramp, int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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. Adds mode specific field (spc4r32) and timeout
+ * to command abort to override default of 60 seconds. If timeout_secs is
+ * 0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+ uint32_t buffer_offset, void * paramp,
+ uint32_t param_len, int timeout_secs, bool noisy,
+ int verbose);
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+ unsigned int lba, void * data_out, int xfer_len,
+ int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
+ * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info
+ * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+ uint64_t llba, void * data_out, int xfer_len,
+ int * offsetp, bool noisy, int verbose);
+
+/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function
+ * supports all service action variants of the THIRD-PARTY COPY IN opcode.
+ * 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, bool noisy, int verbose);
+
+/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4)
+ * including POPULATE TOKEN and WRITE USING TOKEN use
+ * sg_ll_3party_copy_out(). 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 */
+int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+ int verbose);
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4),
+ * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> opcode 0x83 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_3party_copy_out(int sg_fd, int sa, unsigned int list_id,
+ int group_num, int timeout_secs, void * paramp,
+ int param_len, bool noisy, int verbose);
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+ uint64_t lba, uint32_t num_blocks, int group_num,
+ int timeout_secs, bool noisy, int verbose);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_cmds_mmc.h b/include/sg_cmds_mmc.h
new file mode 100644
index 00000000..c9b7d73d
--- /dev/null
+++ b/include/sg_cmds_mmc.h
@@ -0,0 +1,54 @@
+#ifndef SG_CMDS_MMC_H
+#define SG_CMDS_MMC_H
+
+/*
+ * Copyright (c) 2008-2017 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+ int max_num_desc, int type, void * resp,
+ int mx_resp_len, bool noisy, int verbose);
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command 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_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+ int drv_write_speed, bool noisy, int verbose);
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+ bool noisy, int verbose);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_io_linux.h b/include/sg_io_linux.h
new file mode 100644
index 00000000..7b3567cd
--- /dev/null
+++ b/include/sg_io_linux.h
@@ -0,0 +1,202 @@
+#ifndef SG_IO_LINUX_H
+#define SG_IO_LINUX_H
+
+/*
+ * Copyright (c) 2004-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Version 1.08 [20201102]
+ */
+
+/*
+ * This header file contains Linux specific information related to the SCSI
+ * command pass through in the SCSI generic (sg) driver and the Linux
+ * block layer.
+ */
+
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* host_bytes: DID_* are Linux SCSI result (a 32 bit variable) bits 16:23 */
+#ifndef DID_OK
+#define DID_OK 0x00
+#endif
+#ifndef DID_NO_CONNECT
+#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03 /* Timed out for some other reason */
+#define DID_BAD_TARGET 0x04 /* Bad target (id?) */
+#define DID_ABORT 0x05 /* Told to abort for some other reason */
+#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */
+#define DID_ERROR 0x07 /* Internal error */
+#define DID_RESET 0x08 /* Reset by somebody */
+#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */
+#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */
+#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */
+#endif
+#ifndef DID_IMM_RETRY
+#define DID_IMM_RETRY 0x0c /* Retry without decrementing retry count */
+#endif
+#ifndef DID_REQUEUE
+#define DID_REQUEUE 0x0d /* Requeue command (no immediate retry) also
+ * without decrementing the retry count */
+#endif
+#ifndef DID_TRANSPORT_DISRUPTED
+#define DID_TRANSPORT_DISRUPTED 0xe
+#endif
+#ifndef DID_TRANSPORT_FAILFAST
+#define DID_TRANSPORT_FAILFAST 0xf
+#endif
+#ifndef DID_TARGET_FAILURE
+#define DID_TARGET_FAILURE 0x10
+#endif
+#ifndef DID_NEXUS_FAILURE
+#define DID_NEXUS_FAILURE 0x11
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DID_OK DID_OK
+#define SG_LIB_DID_NO_CONNECT DID_NO_CONNECT
+#define SG_LIB_DID_BUS_BUSY DID_BUS_BUSY
+#define SG_LIB_DID_TIME_OUT DID_TIME_OUT
+#define SG_LIB_DID_BAD_TARGET DID_BAD_TARGET
+#define SG_LIB_DID_ABORT DID_ABORT
+#define SG_LIB_DID_PARITY DID_PARITY
+#define SG_LIB_DID_ERROR DID_ERROR
+#define SG_LIB_DID_RESET DID_RESET
+#define SG_LIB_DID_BAD_INTR DID_BAD_INTR
+#define SG_LIB_DID_PASSTHROUGH DID_PASSTHROUGH
+#define SG_LIB_DID_SOFT_ERROR DID_SOFT_ERROR
+#define SG_LIB_DID_IMM_RETRY DID_IMM_RETRY
+#define SG_LIB_DID_REQUEUE DID_REQUEUE
+#define SG_LIB_TRANSPORT_DISRUPTED DID_TRANSPORT_DISRUPTED
+#define SG_LIB_DID_TRANSPORT_FAILFAST DID_TRANSPORT_FAILFAST
+#define SG_LIB_DID_TARGET_FAILURE DID_TARGET_FAILURE
+#define SG_LIB_DID_NEXUS_FAILURE DID_NEXUS_FAILURE
+
+/* DRIVER_* are Linux SCSI result (a 32 bit variable) bits 24:27 */
+#ifndef DRIVER_OK
+#define DRIVER_OK 0x00
+#endif
+#ifndef DRIVER_BUSY
+#define DRIVER_BUSY 0x01
+#define DRIVER_SOFT 0x02
+#define DRIVER_MEDIA 0x03
+#define DRIVER_ERROR 0x04
+#define DRIVER_INVALID 0x05
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_HARD 0x07
+#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */
+
+/* SUGGEST_* are Linux SCSI result (a 32 bit variable) bits 28:31 */
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages.
+ * Following "suggests" are "or-ed" with one of previous 8 entries */
+#define SUGGEST_RETRY 0x10
+#define SUGGEST_ABORT 0x20
+#define SUGGEST_REMAP 0x30
+#define SUGGEST_DIE 0x40
+#define SUGGEST_SENSE 0x80
+#define SUGGEST_IS_OK 0xff
+#endif
+
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+
+/* These defines are to isolate applications from kernel define changes */
+#define SG_LIB_DRIVER_OK DRIVER_OK
+#define SG_LIB_DRIVER_BUSY DRIVER_BUSY
+#define SG_LIB_DRIVER_SOFT DRIVER_SOFT
+#define SG_LIB_DRIVER_MEDIA DRIVER_MEDIA
+#define SG_LIB_DRIVER_ERROR DRIVER_ERROR
+#define SG_LIB_DRIVER_INVALID DRIVER_INVALID
+#define SG_LIB_DRIVER_TIMEOUT DRIVER_TIMEOUT
+#define SG_LIB_DRIVER_HARD DRIVER_HARD
+#define SG_LIB_DRIVER_SENSE DRIVER_SENSE
+
+
+/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept
+ * to stop compilation breakages. */
+#define SG_LIB_SUGGEST_RETRY SUGGEST_RETRY
+#define SG_LIB_SUGGEST_ABORT SUGGEST_ABORT
+#define SG_LIB_SUGGEST_REMAP SUGGEST_REMAP
+#define SG_LIB_SUGGEST_DIE SUGGEST_DIE
+#define SG_LIB_SUGGEST_SENSE SUGGEST_SENSE
+#define SG_LIB_SUGGEST_IS_OK SUGGEST_IS_OK
+#define SG_LIB_DRIVER_MASK DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK SUGGEST_MASK
+
+void sg_print_masked_status(int masked_status);
+void sg_print_host_status(int host_status);
+void sg_print_driver_status(int driver_status);
+
+/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings
+ * else it prints errors/warnings (prefixed by 'leadin') to
+ * 'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the
+ * raw sense buffer (in ASCII hex) should be printed. */
+int sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+ int driver_status, const uint8_t * sense_buffer,
+ int sb_len, bool raw_sinfo);
+
+/* The following function declaration is for the sg version 3 driver. */
+struct sg_io_hdr;
+
+/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings;
+ * else it prints errors/warnings (prefixed by 'leadin') to
+ * 'sg_warnings_fd' and returns 0. For sg_io_v4 interface use
+ * sg_linux_sense_print() instead. */
+int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+ bool raw_sinfo);
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+ * prints error/warning (prefix by 'leadin') to stderr (pr2ws) and
+ * returns 0. */
+int sg_linux_sense_print(const char * leadin, int scsi_status,
+ int host_status, int driver_status,
+ const uint8_t * sense_buffer, int sb_len,
+ bool raw_sinfo);
+
+/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and
+ * its length from the struct sg_io_hdr pointer. If these cannot be
+ * obtained, false is returned. For sg_io_v4 interface use
+ * sg_scsi_normalize_sense() function instead [see sg_lib.h]. */
+bool sg_normalize_sense(const struct sg_io_hdr * hp,
+ struct sg_scsi_sense_hdr * sshp);
+
+/* Returns SG_LIB_CAT_* value. */
+int sg_err_category(int masked_status, int host_status, int driver_status,
+ const uint8_t * sense_buffer, int sb_len);
+
+/* Returns SG_LIB_CAT_* value. */
+int sg_err_category_new(int scsi_status, int host_status, int driver_status,
+ const uint8_t * sense_buffer, int sb_len);
+
+/* The following function declaration is for the sg version 3 driver. for
+ * sg_io_v4 interface use sg_err_category_new() function instead */
+int sg_err_category3(struct sg_io_hdr * hp);
+
+
+/* Note about SCSI status codes found in older versions of Linux.
+ * Linux has traditionally used a 1 bit right shifted and masked
+ * version of SCSI standard status codes. Now CHECK_CONDITION
+ * and friends (in <scsi/scsi.h>) are deprecated. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_lib.h b/include/sg_lib.h
new file mode 100644
index 00000000..41545ee9
--- /dev/null
+++ b/include/sg_lib.h
@@ -0,0 +1,813 @@
+#ifndef SG_LIB_H
+#define SG_LIB_H
+
+/*
+ * Copyright (c) 2004-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ *
+ * On 5th October 2004 a FreeBSD license was added to this file.
+ * The intention is to keep this file and the related sg_lib.c file
+ * as open source and encourage their unencumbered use.
+ *
+ * Current version number of this library is in the sg_lib_data.c file and
+ * can be accessed with the sg_lib_version() function.
+ */
+
+
+/*
+ * This header file contains defines and function declarations that may
+ * be useful to applications that communicate with devices that use a
+ * SCSI command set. These command sets have names like SPC-4, SBC-3,
+ * SSC-3, SES-2 and draft standards defining them can be found at
+ * https://www.t10.org . Virtually all devices in the Linux SCSI subsystem
+ * utilize SCSI command sets. Many devices in other Linux device subsystems
+ * utilize SCSI command sets either natively or via emulation (e.g. a
+ * SATA disk in a USB enclosure).
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SCSI Peripheral Device Types (PDT) [5 bit field] */
+#define PDT_DISK 0x0 /* direct access block device (disk) */
+#define PDT_TAPE 0x1 /* sequential access device (magnetic tape) */
+#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */
+#define PDT_PROCESSOR 0x3 /* processor device (e.g. SAFTE device) */
+#define PDT_WO 0x4 /* write once device (some optical disks) */
+#define PDT_MMC 0x5 /* CD/DVD/BD (multi-media) */
+#define PDT_SCANNER 0x6 /* obsolete */
+#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */
+#define PDT_MCHANGER 0x8 /* media changer device (e.g. tape robot) */
+#define PDT_COMMS 0x9 /* communications device (obsolete) */
+#define PDT_SAC 0xc /* storage array controller device */
+#define PDT_SES 0xd /* SCSI Enclosure Services (SES) device */
+#define PDT_RBC 0xe /* Reduced Block Commands (simplified PDT_DISK) */
+#define PDT_OCRW 0xf /* optical card read/write device */
+#define PDT_BCC 0x10 /* bridge controller commands */
+#define PDT_OSD 0x11 /* Object Storage Device (OSD) */
+#define PDT_ADC 0x12 /* Automation/drive commands (ADC) */
+#define PDT_SMD 0x13 /* Security Manager Device (SMD) */
+#define PDT_ZBC 0x14 /* Zoned Block Commands (ZBC) */
+#define PDT_WLUN 0x1e /* Well known logical unit (WLUN) */
+#define PDT_UNKNOWN 0x1f /* Unknown or no device type */
+#define PDT_MASK 0x1f /* For byte 0 of INQUIRY response */
+#define PDT_MAX 0x1f
+
+#define GRPNUM_MASK 0x3f
+
+/* ZBC disks use either PDT_ZBC (if 'host managed') or PDT_DISK .
+ * So squeeze two PDTs into one integer. Use sg_pdt_s_eq() to compare.
+ * N.B. Must not use PDT_DISK as upper */
+#define PDT_DISK_ZBC (PDT_DISK | (PDT_ZBC << 8))
+#define PDT_ALL (-1) /* for common to all PDTs */
+#define PDT_LOWER_MASK 0xff
+#define PDT_UPPER_MASK (~PDT_LOWER_MASK)
+
+#ifndef SAM_STAT_GOOD
+/* The SCSI status codes as found in SAM-4 at www.t10.org */
+#define SAM_STAT_GOOD 0x0
+#define SAM_STAT_CHECK_CONDITION 0x2
+#define SAM_STAT_CONDITION_MET 0x4 /* this is not an error */
+#define SAM_STAT_BUSY 0x8
+#define SAM_STAT_INTERMEDIATE 0x10 /* obsolete in SAM-4 */
+#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 /* obsolete in SAM-4 */
+#define SAM_STAT_RESERVATION_CONFLICT 0x18
+#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */
+#define SAM_STAT_TASK_SET_FULL 0x28
+#define SAM_STAT_ACA_ACTIVE 0x30
+#define SAM_STAT_TASK_ABORTED 0x40
+#endif
+
+/* The SCSI sense key codes as found in SPC-4 at www.t10.org */
+#define SPC_SK_NO_SENSE 0x0
+#define SPC_SK_RECOVERED_ERROR 0x1
+#define SPC_SK_NOT_READY 0x2
+#define SPC_SK_MEDIUM_ERROR 0x3
+#define SPC_SK_HARDWARE_ERROR 0x4
+#define SPC_SK_ILLEGAL_REQUEST 0x5
+#define SPC_SK_UNIT_ATTENTION 0x6
+#define SPC_SK_DATA_PROTECT 0x7
+#define SPC_SK_BLANK_CHECK 0x8
+#define SPC_SK_VENDOR_SPECIFIC 0x9
+#define SPC_SK_COPY_ABORTED 0xa
+#define SPC_SK_ABORTED_COMMAND 0xb
+#define SPC_SK_RESERVED 0xc
+#define SPC_SK_VOLUME_OVERFLOW 0xd
+#define SPC_SK_MISCOMPARE 0xe
+#define SPC_SK_COMPLETED 0xf
+
+/* Transport protocol identifiers or just Protocol identifiers */
+#define TPROTO_FCP 0
+#define TPROTO_SPI 1
+#define TPROTO_SSA 2
+#define TPROTO_1394 3
+#define TPROTO_SRP 4 /* SCSI over RDMA */
+#define TPROTO_ISCSI 5
+#define TPROTO_SAS 6
+#define TPROTO_ADT 7
+#define TPROTO_ATA 8
+#define TPROTO_UAS 9 /* USB attached SCSI */
+#define TPROTO_SOP 0xa /* SCSI over PCIe */
+#define TPROTO_PCIE 0xb /* includes NVMe */
+#define TPROTO_NONE 0xf
+
+/* SCSI Feature Sets (sfs) */
+#define SCSI_FS_SPC_DISCOVERY_2016 0x1
+#define SCSI_FS_SBC_BASE_2010 0x102
+#define SCSI_FS_SBC_BASE_2016 0x101
+#define SCSI_FS_SBC_BASIC_PROV_2016 0x103
+#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104
+#define SCSI_FS_ZBC_HOST_AWARE_2020 0x300
+#define SCSI_FS_ZBC_HOST_MANAGED_2020 0x301
+#define SCSI_FS_ZBC_DOMAINS_REALMS_2020 0x302
+
+/* Often SCSI responses use the highest integer that can fit in a field
+ * to indicate "unbounded" or limit does not apply. Sometimes represented
+ * in output as "-1" for brevity */
+#define SG_LIB_UNBOUNDED_16BIT 0xffff
+#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU
+#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL
+
+#if (__STDC_VERSION__ >= 199901L) /* C99 or later */
+ typedef uintptr_t sg_uintptr_t;
+#else
+ typedef unsigned long sg_uintptr_t;
+#endif
+
+/* Borrowed from Linux kernel; no check that 'arr' actually is one */
+#define SG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* Doesn't seem to be a common C and C++ technique for clearing an
+ * aggregrate (e.g. a struct instance) on the stack. Hence this hack: */
+#ifdef __cplusplus
+#define SG_C_CPP_ZERO_INIT {}
+#else
+#define SG_C_CPP_ZERO_INIT ={0}
+#endif
+
+
+/* The format of the version string is like this: "2.26 20170906" */
+const char * sg_lib_version();
+
+/* Returns length of SCSI command given the opcode (first byte).
+ * Yields the wrong answer for variable length commands (opcode=0x7f)
+ * and potentially some vendor specific commands. */
+int sg_get_command_size(uint8_t cdb_byte0);
+
+/* Command name given pointer to the cdb. Certain command names
+ * depend on peripheral type (give 0 or -1 if unknown). Places command
+ * name into buff and will write no more than buff_len bytes. */
+void sg_get_command_name(const uint8_t * cdbp, int peri_type, int buff_len,
+ char * buff);
+
+/* Command name given only the first byte (byte 0) of a cdb and
+ * peripheral type (give 0 or -1 if unknown). */
+void sg_get_opcode_name(uint8_t cdb_byte0, int peri_type, int buff_len,
+ char * buff);
+
+/* Command name given opcode (byte 0), service action and peripheral type.
+ * If no service action give 0, if unknown peripheral type give 0 or -1 . */
+void sg_get_opcode_sa_name(uint8_t cdb_byte0, int service_action,
+ int peri_type, int buff_len, char * buff);
+
+/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte
+ * command) of command. Gets Admin NVMe command name if 'admin' is true
+ * (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name
+ * (e.g. opcode=0 -> Flush). Returns 'buff'. */
+char * sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len,
+ char * buff);
+
+/* Fetch scsi status string. */
+void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff);
+
+/* This is a slightly stretched SCSI sense "descriptor" format header.
+ * The addition is to allow the 0x70 and 0x71 response codes. The idea
+ * is to place the salient data of both "fixed" and "descriptor" sense
+ * format into one structure to ease application processing.
+ * The original sense buffer should be kept around for those cases
+ * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */
+struct sg_scsi_sense_hdr {
+ uint8_t response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */
+ uint8_t sense_key;
+ uint8_t asc;
+ uint8_t ascq;
+ uint8_t byte4; /* descriptor: SDAT_OVFL; fixed: lower three ... */
+ uint8_t byte5; /* ... bytes of INFO field */
+ uint8_t byte6;
+ uint8_t additional_length; /* zero for fixed format sense data */
+};
+
+/* The '_is_good()' returns true when status is SAM_STAT_GOOD or
+ * SAM_STAT_CONDITION_MET, returns false otherwise. Ignores bit 0. The
+ * '_is_bad() variant is the logical inverse. */
+bool sg_scsi_status_is_good(int sstatus);
+bool sg_scsi_status_is_bad(int sstatus);
+
+/* Maps the salient data from a sense buffer which is in either fixed or
+ * descriptor format into a structure mimicking a descriptor format
+ * header (i.e. the first 8 bytes of sense descriptor format).
+ * If zero response code returns false. Otherwise returns true and if 'sshp'
+ * is non-NULL then zero all fields and then set the appropriate fields in
+ * that structure. sshp::additional_length is always 0 for response
+ * codes 0x70 and 0x71 (fixed format). */
+bool sg_scsi_normalize_sense(const uint8_t * sensep, int sense_len,
+ struct sg_scsi_sense_hdr * sshp);
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const uint8_t * sg_scsi_sense_desc_find(const uint8_t * sensep, int sense_len,
+ int desc_type);
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int sg_get_sense_key(const uint8_t * sensep, int sense_len);
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff);
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. Prefixes
+ * any valid additional sense found with "Additional sense: ". */
+char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff);
+
+/* Same as sg_get_asc_ascq_str() when add_sense_leadin is true. When it is
+ * false this function does _not_ prefix any valid additional sense found
+ * with "Additional sense: ". */
+char * sg_get_additional_sense_str(int asc, int ascq, bool add_sense_leadin,
+ int buff_len, char * buff);
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_info_fld(const uint8_t * sensep, int sb_len,
+ uint64_t * info_outp);
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool sg_get_sense_cmd_spec_fld(const uint8_t * sensep, int sb_len,
+ uint64_t * cmd_spec_outp);
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool sg_get_sense_filemark_eom_ili(const uint8_t * sensep, int sb_len,
+ bool * filemark_p, bool * eom_p,
+ bool * ili_p);
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false. Handles both fixed and descriptor
+ * sense formats. N.B. App should multiply by 100 and divide by 65536
+ * to get percentage completion from given value. */
+bool sg_get_sense_progress_fld(const uint8_t * sensep, int sb_len,
+ int * progress_outp);
+
+/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'.
+ * Usually multiline with multiple '\n' including one trailing. If
+ * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended
+ * to each line written to 'buff', NULL treated as "". Returns the number of
+ * bytes written to 'buff' excluding the trailing '\0'.
+ * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first
+ * line output. Also this function returned type void. */
+int sg_get_sense_str(const char * leadin, const uint8_t * sense_buffer,
+ int sb_len, bool raw_sinfo, int buff_len, char * buff);
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format). 'leadin' is string prepended to each line written
+ * to 'b', NULL treated as "". Returns the number of bytes written to 'b'
+ * excluding the trailing '\0'. If problem, returns 0. */
+int sg_get_sense_descriptors_str(const char * leadin,
+ const uint8_t * sense_buffer,
+ int sb_len, int blen, char * b);
+
+/* Decodes a designation descriptor (e.g. as found in the Device
+ * Identification VPD page (0x83)) into string 'b' whose maximum length is
+ * blen. 'leadin' is string prepended to each line written to 'b', NULL
+ * treated as "". Returns the number of bytes written to 'b' excluding the
+ * trailing '\0'. */
+int sg_get_designation_descriptor_str(const char * leadin,
+ const uint8_t * ddp, int dd_len,
+ bool print_assoc, bool do_long,
+ int blen, char * b);
+
+/* Expects a T10 UUID designator (as found in the Device Identification VPD
+ * page) pointed to by 'dp'. To not produce an error string in 'b', c_set
+ * should be 1 (binary) and dlen should be 18. Currently T10 only supports
+ * locally assigned UUIDs. Writes output to string 'b' of no more than blen
+ * bytes and returns the number of bytes actually written to 'b' but doesn't
+ * count the trailing null character it always appends (if blen > 0). 'lip'
+ * is lead-in string (on each line) than may be NULL. skip_prefix avoids
+ * outputting: ' Locally assigned UUID: ' before the UUID. */
+int sg_t10_uuid_desig2str(const uint8_t * dp, int dlen, int c_set,
+ bool do_long, bool skip_prefix,
+ const char * lip, int blen, char * b);
+
+/* Yield string associated with peripheral device type (pdt). Returns
+ * 'buff'. If 'pdt' out of range yields "bad pdt" string. */
+char * sg_get_pdt_str(int pdt, int buff_len, char * buff);
+
+/* Some lesser used PDTs share a lot in common with a more used PDT.
+ * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK.
+ * If such a lesser used 'dev_pdt' is given to this function, then it will
+ * return the more used PDT (i.e. "decays to"); otherwise 'dev_pdt' is
+ * returned. Valid for 'pdt' 0 to 31, for other values returns 0. */
+int sg_lib_pdt_decay(int dev_pdt);
+
+/* Yield string associated with transport protocol identifier (tpi). Returns
+ * 'buff'. If 'tpi' out of range yields "bad tpi" string. */
+char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff);
+
+/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded
+ * string output in 'buff' which is also the return value. Each new line
+ * is prefixed by 'leadin'. If leadin NULL treat as "". */
+char * sg_decode_transportid_str(const char * leadin, uint8_t * bp, int bplen,
+ bool only_one, int buff_len, char * buff);
+
+/* Returns a designator's type string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_type_str(int val);
+
+/* Returns a designator's code_set string given 'val' (0 to 15 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_code_set_str(int val);
+
+/* Returns a designator's association string given 'val' (0 to 3 inclusive),
+ * otherwise returns NULL. */
+const char * sg_get_desig_assoc_str(int val);
+
+/* Yield string associated with zone type (see ZBC and ZBC-2) [e.g. REPORT
+ * ZONES command response]. Returns 'buff' unless buff_len < 1 in which
+ * NULL is returned. */
+char * sg_get_zone_type_str(uint8_t zt, int buff_len, char * buff);
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ * char b[64];
+ * ...
+ * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len,
+ char * buff, bool * foundp, int verbose);
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length are the Variable
+ * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e).
+ * Both have an inbuilt length field which can be cross checked with clen.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The 'FIS register' structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopefully there is a low
+ * probability this function will yield true in that case. */
+bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen);
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff);
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI
+ * status, sense_key, asc and ascq tuple. If successful returns true and
+ * writes to non-NULL pointer arguments; otherwise returns false. */
+bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+ uint8_t * asc_p, uint8_t * ascq_p);
+
+/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status
+ * field. Assumes descriptor (i.e. not fixed) sense. Assume sbp has room. */
+void sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc);
+
+/* Build minimum sense buffer, either descriptor type (desc=true) or fixed
+ * type (desc=false). Assume sbp has enough room (8 or 14 bytes
+ * respectively). sbp should have room for 32 or 18 bytes respectively */
+void sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey,
+ uint8_t asc, uint8_t ascq);
+
+/* Returns true if left argument is "equal" to the right argument. l_pdt_s
+ * is a compound PDT (SCSI Peripheral Device Type) or a negative number
+ * which represents a wildcard (i.e. match anything). r_pdt_s has a similar
+ * form. PDT values are 5 bits long (0 to 31) and a compound pdt_s is
+ * formed by shifting the second (upper) PDT by eight bits to the left and
+ * OR-ing it with the first PDT. The pdt_s values must be defined so
+ * PDT_DISK (0) is _not_ the upper value in a compound pdt_s. */
+bool sg_pdt_s_eq(int l_pdt_s, int r_pdt_s);
+
+extern FILE * sg_warnings_strm;
+
+void sg_set_warnings_strm(FILE * warnings_strm);
+
+/* Given a SCSI command pointed to by cdbp of sz bytes this function forms a
+ * SCSI command in ASCII hex surrounded by square brackets in 'b'. 'b' is at
+ * least blen bytes long. If cmd_name is true then the command is prefixed
+ * by its SCSI command name (e.g. "VERIFY(10) [2f ...]". The command is
+ * shown as spaced separated pairs of hexadecimal digits (i.e. 0-9, a-f).
+ * Each pair represents byte. The leftmost pair of digits is cdbp[0] . If
+ * sz <= 0 then this function tries to guess the length of the command. */
+char *
+sg_get_command_str(const uint8_t * cdbp, int sz, bool cmd_name, int blen,
+ char * b);
+
+/* The following "print" functions send ASCII to 'sg_warnings_strm' file
+ * descriptor (default value is stderr). 'leadin' is string prepended to
+ * each line printed out, NULL treated as "". */
+void sg_print_command_len(const uint8_t * command, int len);
+void sg_print_command(const uint8_t * command);
+void sg_print_scsi_status(int scsi_status);
+
+/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'. Reads
+ * environment variable SG3_UTILS_DSENSE. Only (currently) used in SNTL. */
+bool sg_get_initial_dsense(void);
+
+/* 'leadin' is string prepended to each line printed out, NULL treated as
+ * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the
+ * first line printed. */
+void sg_print_sense(const char * leadin, const uint8_t * sense_buffer,
+ int sb_len, bool raw_info);
+
+/* This examines exit_status and if an error message is known it is output
+ * to stdout/stderr and true is returned. If no error message is
+ * available nothing is output and false is returned. If exit_status is
+ * zero (no error) nothing is output and true is returned. If exit_status
+ * is negative then nothing is output and false is returned. If leadin is
+ * non-NULL then it is printed before the error message. All messages are
+ * a single line with a trailing LF. */
+bool sg_if_can2stdout(const char * leadin, int exit_status);
+bool sg_if_can2stderr(const char * leadin, int exit_status);
+
+/* This examines exit_status and if an error message is known it is output
+ * as a string to 'b' and true is returned. If 'longer' is true and extra
+ * information is available then it is added to the output. If no error
+ * message is available a null character is output and false is returned.
+ * If exit_status is zero (no error) and 'longer' is true then the string
+ * 'No errors' is output; if 'longer' is false then a null character is
+ * output; in both cases true is returned. If exit_status is negative then
+ * a null character is output and false is returned. All messages are a
+ * single line (less than 80 characters) with no trailing LF. The output
+ * string including the trailing null character is no longer than b_len. */
+bool sg_exit2str(int exit_status, bool longer, int b_len, char * b);
+
+/* Utilities can use these exit status values for syntax errors and
+ * file (device node) problems (e.g. not found or permissions). */
+#define SG_LIB_SYNTAX_ERROR 1 /* command line syntax problem */
+
+/* The sg_err_category_sense() function returns one of the following.
+ * These may be used as exit status values (from a process). Notice that
+ * some of the lower values correspond to SCSI sense key values. */
+#define SG_LIB_CAT_CLEAN 0 /* No errors or other information */
+#define SG_LIB_OK_TRUE SG_LIB_CAT_CLEAN /* No error, reporting true */
+/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */
+#define SG_LIB_CAT_NOT_READY 2 /* sense key: not ready, see 12 and 13
+ * [sk,asc,ascq: 0x2,<most>,<most>] */
+#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check
+ * [sk,asc,ascq: 0x3/0x4/0x8,*,*] */
+#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid
+ * opcode): [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed
+ * [sk,asc,ascq: 0x6,*,*] */
+ /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */
+#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected?
+ * [sk,asc,ascq: 0x7,*,*] */
+#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode:
+ * [sk,asc,ascq: 0x5,0x20,0x0] */
+#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred
+ * [sk,asc,ascq: 0xa,*,*] */
+#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer
+ * [sk,asc,ascq: 0xb,! 0x10,*] */
+#define SG_LIB_CAT_STANDBY 12 /* sense key: not ready, special case
+ * [sk,asc, ascq: 0x2, 0x4, 0xb] */
+#define SG_LIB_CAT_UNAVAILABLE 13 /* sense key: not ready, special case
+ * [sk,asc, ascq: 0x2, 0x4, 0xc] */
+#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify
+ * [sk,asc,ascq: 0xe,*,*] */
+#define SG_LIB_FILE_ERROR 15 /* device or other file problem */
+/* for 17 and 18, see below */
+#define SG_LIB_CAT_NO_SENSE 20 /* sense data with key of "no sense"
+ * [sk,asc,ascq: 0x0,*,*] */
+#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err
+ * [sk,asc,ascq: 0x1,*,*] */
+#define SG_LIB_LBA_OUT_OF_RANGE 22 /* Illegal request, LBA Out Of Range
+ * [sk,asc,ascq: 0x5,0x21,0x0] */
+#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT
+ /* 24: this is a SCSI status, not sense.
+ * It indicates reservation by another
+ * machine blocks this command */
+#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key.
+ * Only from PRE-FETCH (SBC-4) */
+#define SG_LIB_CAT_BUSY 26 /* SCSI status, not sense. Invites retry */
+#define SG_LIB_CAT_TS_FULL 27 /* SCSI status, not sense. Wait then retry */
+#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */
+#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */
+#define SG_LIB_CONTRADICT 31 /* error involving two or more cl options */
+#define SG_LIB_LOGIC_ERROR 32 /* unexpected situation in code */
+/* for 33 see SG_LIB_CAT_TIMEOUT below */
+#define SG_LIB_WINDOWS_ERR 34 /* Windows error number don't fit in 7 bits so
+ * map to a single value for exit statuses */
+#define SG_LIB_TRANSPORT_ERROR 35 /* driver or interconnect */
+#define SG_LIB_OK_FALSE 36 /* no error, reporting false (cf. no error,
+ * reporting true is SG_LIB_OK_TRUE(0) ) */
+#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF)
+ * [sk,asc,ascq: 0xb,0x10,*] */
+/* 47: flock error used in ddpt utility */
+#define SG_LIB_NVME_STATUS 48 /* NVMe Status Field (SF) other than 0 */
+#define SG_LIB_WILD_RESID 49 /* Residual value for data-in transfer of a
+ * SCSI command is nonsensical */
+#define SG_LIB_OS_BASE_ERR 50 /* in Linux: values found in:
+ * include/uapi/asm-generic/errno-base.h
+ * Example: ENOMEM reported as 62 (=50+12)
+ * if errno > 46 then use this value */
+/* 51-->96 set aside for Unix errno values shifted by SG_LIB_OS_BASE_ERR */
+#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */
+#define SG_LIB_CAT_SENSE 98 /* Something else is in the sense buffer */
+#define SG_LIB_CAT_OTHER 99 /* Some other error/warning has occurred
+ * (e.g. a transport or driver error) */
+/* 100 to 120 (inclusive) used by ddpt utility */
+#define SG_LIB_UNUSED_ABOVE 120 /* Put extra errors in holes below this */
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less
+ * common sense key then return SG_LIB_CAT_SENSE .*/
+int sg_err_category_sense(const uint8_t * sense_buffer, int sb_len);
+
+/* Here are some additional sense data categories that are not returned
+ * by sg_err_category_sense() but are returned by some related functions. */
+#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */
+ /* invalid opcode) plus 'info' field: */
+ /* [sk,asc,ascq: 0x5,*,*] */
+#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */
+ /* sense key plus 'info' field: */
+ /* [sk,asc,ascq: 0x3/0x4,*,*] */
+#define SG_LIB_CAT_TIMEOUT 33 /* SCSI command timeout */
+#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */
+ /* protection plus 'info' field: */
+ /* [sk,asc,ascq: 0xb,0x10,*] */
+
+/* Yield string associated with sense category. Returns 'buff' (or pointer
+ * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat)val>" string. The original 'sense
+ * category' concept has been expanded to most detected errors and is
+ * returned by these utilities as their exit status value (an (unsigned)
+ * 8 bit value where 0 means good (i.e. no errors)). Uses the
+ * sg_exit2str() function. */
+const char * sg_get_category_sense_str(int sense_cat, int buff_len,
+ char * buff, int verbose);
+
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int sg_vpd_dev_id_iter(const uint8_t * initial_desig_desc, int page_len,
+ int * off, int m_assoc, int m_desig_type,
+ int m_code_set);
+
+
+/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */
+
+/* Always returns valid string even if errnum is wild (or library problem).
+ * If errnum is negative, flip its sign. */
+char * safe_strerror(int errnum);
+
+/* Not all platforms support the Unix sleep(seconds) function. */
+void sg_sleep_secs(int num_secs);
+
+/* There are several SCSI commands that are very destructive for the user
+ * data stored on a device. The FORMAT UNIT command is the prime example
+ * but there are an increasing number of newer SCSI commands that remove or
+ * destroy some or all of the user's data. This function takes 15 seconds,
+ * divided into three parts, saying that 'cmd_name' will be executed on
+ * 'dev_name' and then waits for 5 seconds inviting the user to press
+ * control-C to abort the operation. After three such prompts the function
+ * returns and the utility start to execute the "dangerous" SCSI command,
+ * Utilities that use this function usually have a --quick option to bypass
+ * this call. That may be appropriate if the utility in question is called
+ * from a script or in background processing. If 'stress_all' is true then
+ * state "ALL data" will be lost, if false drop the "ALL". */
+void
+sg_warn_and_wait(const char * cmd_name, const char * dev_name,
+ bool stress_all);
+
+
+/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation.
+ * Each line is prefixed with an address, starting at 0 for str[0]..str[15].
+ * All output numbers are in hex.
+ * 'no_ascii' selects on of 3 output format types:
+ * > 0 each line has address then up to 16 ASCII-hex bytes
+ * = 0 in addition, the bytes are listed in ASCII to the right
+ * < 0 only the ASCII-hex bytes are listed (i.e. without address)
+*/
+void dStrHex(const char * str, int len, int no_ascii);
+
+/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per
+ * line optionally followed at right by its ASCII interpretation. Same
+ * logic as dStrHex() with different output stream (i.e. stderr). */
+void dStrHexErr(const char * str, int len, int no_ascii);
+
+/* Read binary starting at 'str' for 'len' bytes and output as ASCII
+ * hexadecimal into file pointer (fp). 16 bytes per line are output with an
+ * additional space between 8th and 9th byte on each line (for readability).
+ * 'no_ascii' selects one of 3 output format types as shown in dStrHex() . */
+void dStrHexFp(const char* str, int len, int no_ascii, FILE * fp);
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space separated)
+ * to 'b' not to exceed 'b_len' characters. Each line starts with 'leadin'
+ * (NULL for no leadin) and there are 16 bytes per line with an extra space
+ * between the 8th and 9th bytes. 'oformat' is 0 for repeat in printable ASCII
+ * ('.' for non printable chars) to right of each line; 1 don't (so just
+ * output ASCII hex). If 'oformat' is 2 output same as 1 but any LFs are
+ * replaced by space (and trailing spaces are trimmed). Note that an address
+ * is _not_ printed on each line preceding the hex data. Returns number of
+ * bytes written to 'b' excluding the trailing '\0'. The only difference
+ * between dStrHexStr() and hex2str() is the type of the first argument. */
+int dStrHexStr(const char * str, int len, const char * leadin, int oformat,
+ int cb_len, char * cbp);
+int hex2str(const uint8_t * b_str, int len, const char * leadin, int oformat,
+ int cb_len, char * cbp);
+
+/* Similar to hex2str() but outputs to file pointed to be fp */
+void hex2fp(const uint8_t * b_str, int len, const char * leadin, int oformat,
+ FILE * fp);
+
+/* The following 2 functions are equivalent to dStrHex() and dStrHexErr()
+ * respectively. The difference is only the type of the first of argument:
+ * uint8_t instead of char. The name of the argument is changed to b_str to
+ * stress it is a pointer to the start of a binary string. */
+void hex2stdout(const uint8_t * b_str, int len, int no_ascii);
+void hex2stderr(const uint8_t * b_str, int len, int no_ascii);
+
+/* Read ASCII hex bytes or binary from fname (a file named '-' taken as
+ * stdin). If reading ASCII hex then there should be either one entry per
+ * line or a comma, space, hyphen or tab separated list of bytes. If no_space
+ * is set then a string of ACSII hex digits is expected, 2 per byte.
+ * Everything from and including a '#' on a line is ignored. Returns 0 if ok,
+ * or an error code. If the error code is SG_LIB_LBA_OUT_OF_RANGE then mp_arr
+ * would be exceeded and both mp_arr and mp_arr_len are written to.
+ * The max_arr_len_and argument may carry extra information: when it is
+ * negative its absolute value is used for the maximum number of bytes to
+ * write to mp_arr _and_ the first hexadecimal value on each line is skipped.
+ * Many hexadecimal output programs place a running address (index) as the
+ * first field on each line. When as_binary and/or no_space are true, the
+ * absolute value of max_arr_len_and is used. */
+int sg_f2hex_arr(const char * fname, bool as_binary, bool no_space,
+ uint8_t * mp_arr, int * mp_arr_len, int max_arr_len_and);
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool sg_is_big_endian();
+
+/* Returns true if byte sequence starting at bp with a length of b_len is
+ * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs());
+ * otherwise returns false. If bp is NULL or b_len <= 0 returns false. */
+bool sg_all_zeros(const uint8_t * bp, int b_len);
+bool sg_all_ffs(const uint8_t * bp, int b_len);
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+ int num_words, bool is_big_endian, char * ochars);
+
+/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally
+ * followed at the right hand side of the line with an ASCII interpretation
+ * (pairs of ASCII characters in big endian order (upper first)).
+ * Each line is prefixed with an address, starting at 0.
+ * All output numbers are in hex. 'no_ascii' allows for 3 output types:
+ * > 0 each line has address then up to 8 ASCII-hex words
+ * = 0 in addition, the words are listed in ASCII pairs to the right
+ * = -1 only the ASCII-hex words are listed (i.e. without address)
+ * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin"
+ * < -2 same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines.
+*/
+void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and
+ * tabs; accept comma, hyphen, space, tab and hash as terminator.
+ * Handles zero and positive values up to 2**31-1 .
+ * Experimental: left argument (must in with hexadecimal digit) added
+ * to, or multiplied, by right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0xf+0x3'. */
+int sg_get_num(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int sg_get_num_nomult(const char * buf);
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X), hex suffix
+ * (h or H), or a decimal multiplier suffix (as per GNU's dd (since 2002:
+ * SI and IEC 60027-2)). Main (SI) multipliers supported: K, M, G, T, P
+ * and E. Ignore leading spaces and tabs; accept comma, hyphen, space, tab
+ * and hash as terminator. Handles zero and positive values up to 2**63-1 .
+ * Experimental: the left argument (must end in with hexadecimal digit)
+ * added to, or multiplied by, the right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0xf+0x3'. */
+int64_t sg_get_llnum(const char * buf);
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int64_t sg_get_llnum_nomult(const char * buf);
+
+/* Returns pointer to heap (or NULL) that is aligned to a align_to byte
+ * boundary. Sends back *buff_to_free pointer in third argument that may be
+ * different from the return value. If it is different then the *buff_to_free
+ * pointer should be freed (rather than the returned value) when the heap is
+ * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all
+ * returned heap to zeros. If num_bytes is 0 then set to page size. */
+uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to,
+ uint8_t ** buff_to_free, bool vb);
+
+/* Returns OS page size in bytes. If uncertain returns 4096. */
+uint32_t sg_get_page_size(void);
+
+/* If byte_count is 0 or less then the OS page size is used as denominator.
+ * Returns true if the remainder of ((unsigned)pointer % byte_count) is 0,
+ * else returns false. */
+bool sg_is_aligned(const void * pointer, int byte_count);
+
+/* Does similar job to sg_get_unaligned_be*() but this function starts at
+ * a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit)
+ * offset. Maximum number of num_bits is 64. For example, these two
+ * invocations are equivalent (and should yield the same result);
+ * sg_get_big_endian(from_bp, 7, 16)
+ * sg_get_unaligned_be16(from_bp) */
+uint64_t sg_get_big_endian(const uint8_t * from_bp,
+ int start_bit /* 0 to 7 */,
+ int num_bits /* 1 to 64 */);
+
+/* Does similar job to sg_put_unaligned_be*() but this function starts at
+ * a given start_bit offset. Maximum number of num_bits is 64. Preserves
+ * residual bits in partially written bytes. start_bit 7 is MSb. */
+void sg_set_big_endian(uint64_t val, uint8_t * to, int start_bit /* 0 to 7 */,
+ int num_bits /* 1 to 64 */);
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR is returned. If
+ * os_err_num is 0 then 0 is returned. */
+int sg_convert_errno(int os_err_num);
+
+
+/* <<< Architectural support functions [is there a better place?] >>> */
+
+/* Non Unix OSes distinguish between text and binary files.
+ * Set text mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_text_mode(int fd);
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+ * failure. */
+int sg_set_binary_mode(int fd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_LIB_H */
diff --git a/include/sg_lib_data.h b/include/sg_lib_data.h
new file mode 100644
index 00000000..b27b7c8d
--- /dev/null
+++ b/include/sg_lib_data.h
@@ -0,0 +1,145 @@
+#ifndef SG_LIB_DATA_H
+#define SG_LIB_DATA_H
+
+/*
+ * Copyright (c) 2007-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * This header file contains some structure declarations and array name
+ * declarations which are defined in the sg_lib_data.c .
+ * Typically this header does not need to be exposed to users of the
+ * sg_lib interface declared in sg_libs.h .
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Operation codes with associated service actions that change or qualify
+ * the command name */
+#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */
+#define SG_RECEIVE_COPY 0x84 /* since spc4r34 became next entry */
+#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */
+#define SG_MAINTENANCE_IN 0xa3
+#define SG_MAINTENANCE_OUT 0xa4
+#define SG_PERSISTENT_RESERVE_IN 0x5e
+#define SG_PERSISTENT_RESERVE_OUT 0x5f
+#define SG_READ_ATTRIBUTE 0x8c
+#define SG_READ_BUFFER 0x3c /* now READ BUFFER(10) */
+#define SG_READ_BUFFER_16 0x9b
+#define SG_READ_POSITION 0x34 /* SSC command with service actions */
+#define SG_SANITIZE 0x48
+#define SG_SERVICE_ACTION_BIDI 0x9d
+#define SG_SERVICE_ACTION_IN_12 0xab
+#define SG_SERVICE_ACTION_IN_16 0x9e
+#define SG_SERVICE_ACTION_OUT_12 0xa9
+#define SG_SERVICE_ACTION_OUT_16 0x9f
+#define SG_VARIABLE_LENGTH_CMD 0x7f
+#define SG_WRITE_BUFFER 0x3b
+#define SG_ZONING_OUT 0x94
+#define SG_ZBC_OUT SG_ZONING_OUT /* as SPC calls them */
+#define SG_ZONING_IN 0x95
+#define SG_ZBC_IN SG_ZONING_IN /* as SPC calls them */
+
+
+
+struct sg_lib_simple_value_name_t {
+ int value;
+ const char * name;
+};
+
+struct sg_lib_value_name_t {
+ int value;
+ int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */
+ const char * name;
+};
+
+struct sg_value_2names_t {
+ int value;
+ const char * name;
+ const char * name2;
+};
+
+struct sg_lib_asc_ascq_t {
+ uint8_t asc; /* additional sense code */
+ uint8_t ascq; /* additional sense code qualifier */
+ const char * text;
+};
+
+struct sg_lib_asc_ascq_range_t {
+ uint8_t asc; /* additional sense code (ASC) */
+ uint8_t ascq_min; /* ASCQ minimum in range */
+ uint8_t ascq_max; /* ASCQ maximum in range */
+ const char * text;
+};
+
+/* First use: SCSI status, sense_key, asc, ascq tuple */
+struct sg_lib_4tuple_u8 {
+ uint8_t t1;
+ uint8_t t2;
+ uint8_t t3;
+ uint8_t t4;
+};
+
+struct sg_cmd_response_t {
+ int din_len;
+ int dout_len;
+ int resid;
+ int resid2;
+ const uint8_t * sbp;
+};
+
+
+extern const char * sg_lib_version_str;
+
+extern struct sg_lib_value_name_t sg_lib_normal_opcodes[];
+extern struct sg_lib_value_name_t sg_lib_read_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_write_buff_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_maint_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_pr_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[];
+extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[];
+extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[];
+extern struct sg_lib_value_name_t sg_lib_variable_length_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[];
+extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_attr_arr[];
+extern struct sg_lib_value_name_t sg_lib_read_pos_arr[];
+extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[];
+extern struct sg_lib_simple_value_name_t sg_lib_sstatus_str_arr[];
+extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[];
+extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[];
+extern const char * sg_lib_sense_key_desc[];
+extern const char * sg_lib_pdt_strs[];
+extern const char * sg_lib_transport_proto_strs[];
+extern const char * sg_lib_tapealert_strs[];
+extern int sg_lib_pdt_decay_arr[];
+
+extern struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[];
+extern struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[];
+extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[];
+extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[];
+
+extern struct sg_value_2names_t sg_exit_str_arr[];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_lib_names.h b/include/sg_lib_names.h
new file mode 100644
index 00000000..df997edd
--- /dev/null
+++ b/include/sg_lib_names.h
@@ -0,0 +1,31 @@
+#ifndef SG_LIB_NAMES_H
+#define SG_LIB_NAMES_H
+
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdint.h>
+
+#include "sg_lib_data.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern struct sg_lib_simple_value_name_t sg_lib_names_mode_arr[];
+extern struct sg_lib_simple_value_name_t sg_lib_names_vpd_arr[];
+
+extern const size_t sg_lib_names_mode_len;
+extern const size_t sg_lib_names_vpd_len;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* end of SG_LIB_NAMES */
diff --git a/include/sg_linux_inc.h b/include/sg_linux_inc.h
new file mode 100644
index 00000000..e6f6b523
--- /dev/null
+++ b/include/sg_linux_inc.h
@@ -0,0 +1,58 @@
+#ifndef SG_LINUX_INC_H
+#define SG_LINUX_INC_H
+
+#ifdef SG_KERNEL_INCLUDES
+ #include <stdint.h> /* C99 header for exact integer types */
+ #define __user
+ typedef uint8_t u8;
+ #include "/usr/src/linux/include/scsi/sg.h"
+ #include "/usr/src/linux/include/scsi/scsi.h"
+#else
+ #ifdef SG_TRICK_GNU_INCLUDES
+ #include <linux/../scsi/sg.h>
+ #include <linux/../scsi/scsi.h>
+ #else
+ #define __user
+ #include <scsi/sg.h>
+ #include <scsi/scsi.h>
+ #endif
+#endif
+
+#ifdef BLKGETSIZE64
+ #ifndef u64
+ #include <stdint.h> /* C99 header for exact integer types */
+ typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */
+ #endif
+#endif
+
+/*
+ Getting the correct include files for the sg interface can be an ordeal.
+ In a perfect world, one would just write:
+ #include <scsi/sg.h>
+ #include <scsi/scsi.h>
+ This would include the files found in the /usr/include/scsi directory.
+ Those files are maintained with the GNU library which may or may not
+ agree with the kernel and version of sg driver that is running. Any
+ many cases this will not matter. However in some it might, for example
+ glibc 2.1's include files match the sg driver found in the lk 2.2
+ series. Hence if glibc 2.1 is used with lk 2.4 then the additional
+ sg v3 interface will not be visible.
+ If this is a problem then defining SG_KERNEL_INCLUDES will access the
+ kernel supplied header files (assuming they are in the normal place).
+ The GNU library maintainers and various kernel people don't like
+ this approach (but it does work).
+ The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and
+ was used) prior to glibc 2.2 . Prior to that version /usr/include/linux
+ was a symbolic link to /usr/src/linux/include/linux .
+
+ There are other approaches if this include "mixup" causes pain. These
+ would involve include files being copied or symbolic links being
+ introduced.
+
+ Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES
+ nor SG_TRICK_GNU_INCLUDES is defined.
+
+ dpg 20010415, 20030522
+*/
+
+#endif
diff --git a/include/sg_pr2serr.h b/include/sg_pr2serr.h
new file mode 100644
index 00000000..4a57d838
--- /dev/null
+++ b/include/sg_pr2serr.h
@@ -0,0 +1,376 @@
+#ifndef SG_PR2SERR_H
+#define SG_PR2SERR_H
+
+/*
+ * Copyright (c) 2004-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* pr2serr and pr2ws are convenience functions that replace the somewhat
+ * long-winded fprintf(stderr, ....). The second form (i.e. pr2ws() ) is for
+ * internal library use and may place its output somewhere other than stderr;
+ * it depends on the external variable sg_warnings_strm which can be set
+ * with sg_set_warnings_strm(). By default it uses stderr. */
+
+#if __USE_MINGW_ANSI_STDIO -0 == 1
+#define __printf(a, b) __attribute__((__format__(gnu_printf, a, b)))
+#elif defined(__GNUC__) || defined(__clang__)
+#define __printf(a, b) __attribute__((__format__(printf, a, b)))
+#else
+#define __printf(a, b)
+#endif
+
+int pr2serr(const char * fmt, ...) __printf(1, 2);
+
+int pr2ws(const char * fmt, ...) __printf(1, 2);
+
+/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
+ * functions that can be called multiple times. Returns number of chars
+ * placed in cp excluding the trailing null char. So for cp_max_len > 0 the
+ * return value is always < cp_max_len; for cp_max_len <= 1 the return value
+ * is 0 and no chars are written to cp. Note this means that when
+ * cp_max_len = 1, this function assumes that cp[0] is the null character
+ * and does nothing (and returns 0). Linux kernel has a similar function
+ * called scnprintf(). */
+int sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...) __printf(3, 4);
+
+/* JSON support functions and structures follow. The prefix "sgj_" is used
+ * for sg3_utils JSON functions, types and values. */
+
+enum sgj_separator_t {
+ SGJ_SEP_NONE = 0,
+ SGJ_SEP_SPACE_1,
+ SGJ_SEP_SPACE_2,
+ SGJ_SEP_SPACE_3,
+ SGJ_SEP_SPACE_4,
+ SGJ_SEP_EQUAL_NO_SPACE,
+ SGJ_SEP_EQUAL_1_SPACE,
+ SGJ_SEP_COLON_NO_SPACE,
+ SGJ_SEP_COLON_1_SPACE,
+};
+
+typedef void * sgj_opaque_p;
+
+/* Apart from the state information at the end of this structure, the earlier
+ * fields are initialized from the command line argument given to the
+ * --json= option. If there is no argument then they initialized as shown. */
+typedef struct sgj_state_t {
+ /* the following set by default, the SG3_UTILS_JSON_OPTS environment
+ * variable or command line argument to --json option, in that order. */
+ bool pr_as_json; /* = false (def: is human readable output) */
+ bool pr_exit_status; /* 'e' (def: true) */
+ bool pr_hex; /* 'h' (def: false) */
+ bool pr_leadin; /* 'l' (def: true) */
+ bool pr_name_ex; /* 'n' name_extra (information) (def: false) */
+ bool pr_out_hr; /* 'o' (def: false) */
+ bool pr_packed; /* 'k' (def: false) only when !pr_pretty */
+ bool pr_pretty; /* 'p' (def: true) */
+ bool pr_string; /* 's' (def: true) */
+ char pr_format; /* (def: '\0') */
+ int pr_indent_size; /* digit (def: 4) */
+ int verbose; /* 'v' (def: 0) incremented each appearance */
+
+ /* the following hold state information */
+ int first_bad_char; /* = '\0' */
+ sgj_opaque_p basep; /* base JSON object pointer */
+ sgj_opaque_p out_hrp; /* JSON array pointer when pr_out_hr set */
+ sgj_opaque_p userp; /* for temporary usage */
+} sgj_state;
+
+/* This function tries to convert the in_name C string to the "snake_case"
+ * convention so the output sname only contains lower case ASCII letters,
+ * numerals and "_" as a separator. Any leading or trailing underscores
+ * are removed as are repeated underscores (e.g. "_Snake __ case" becomes
+ * "snake_case"). Parentheses and the characters between them are removed.
+ * Returns sname (i.e. the pointer to the output buffer).
+ * Note: strlen(in_name) should be <= max_sname_len . */
+char * sgj_convert_to_snake_name(const char * in_name, char * sname,
+ int max_sname_len);
+bool sgj_is_snake_name(const char * in_name);
+
+/* There are many variants of JSON supporting functions below and some
+ * abbreviations are used to shorten their function names:
+ * sgj_ - prefix of all the functions related to (non-)JSON output
+ * hr - human readable form (as it was before JSON)
+ * js - JSON only output
+ * haj - human readable and JSON output, hr goes in 'output' array
+ * pr - has printf() like variadic arguments
+ * _r - suffix indicating the return value should/must be used
+ * nv - adds a name-value JSON field (or several)
+ * o - value is the provided JSON object (or array)
+ * i - value is a JSON integer object (int64_t or uint64_t)
+ * b - value is a JSON boolean object
+ * s - value is a JSON string object
+ * str - same as s
+ * hex - value is hexadecimal in a JSON string object
+ * _nex - extra 'name_extra' JSON string object about name
+ * new - object that needs sgj_free_unattached() if not attached
+ *
+ * */
+
+/* If jsp in non-NULL and jsp->pr_as_json is true then this call is ignored
+ * unless jsp->pr_out_hrp is true. Otherwise this function prints to stdout
+ * like printf(fmt, ...); note that no LF is added. In the jsp->pr_out_hrp is
+ * true case, nothing is printed to stdout but instead is placed into a JSON
+ * array (jsp->out_hrp) after some preprocessing. That preprocessing involves
+ * removing a leading LF from 'fmt' (if present) and up to two trailing LF
+ * characters. */
+void sgj_pr_hr(sgj_state * jsp, const char * fmt, ...) __printf(2, 3);
+
+/* Initializes the state object pointed to by jsp based on the argument
+ * given to the right of --json= pointed to by j_optarg. If it is NULL
+ * then state object gets its default values. Returns true if argument
+ * to --json= is decoded properly, else returns false and places the
+ * first "bad" character in jsp->first_bad_char . Note that no JSON
+ * in-core tree needs to exist when this function is called. */
+bool sgj_init_state(sgj_state * jsp, const char * j_optarg);
+
+/* sgj_start() creates a JSON in-core tree and returns a pointer to it (or
+ * NULL if the associated heap allocation fails). It should be paired with
+ * sgj_finish() to clean up (i.e. remove all heap allocations) all the
+ * elements (i.e. JSON objects and arrays) that have been placed in that
+ * in-core tree. If jsp is NULL nothing further happens. Otherwise the pointer
+ * to be returned is placed in jsp->basep. If jsp->pr_leadin is true and
+ * util_name is non-NULL then a "utility_invoked" JSON object is made with
+ * "name", and "version_date" object fields. If the jsp->pr_out_hr field is
+ * true a named array called "output" is added to the "utility_invoked" object
+ * (creating it in the case when jsp->pr_leadin is false) and a pointer to
+ * that array object is placed in jsp->objectp . The returned pointer is not
+ * usually needed but if it is NULL then a heap allocation has failed. */
+sgj_opaque_p sgj_start_r(const char * util_name, const char * ver_str,
+ int argc, char *argv[], sgj_state * jsp);
+
+/* These are low level functions returning a pointer to a newly created JSON
+ * object or array. If jsp is NULL or jsp->pr_as_json is false nothing happens
+ * and NULL is returned. Note that this JSON object is _not_ placed in the
+ * in-core tree controlled by jsp (jsp->basep); it may be added later as the
+ * fourth argument to sgj_js_nv_o(), for example. */
+sgj_opaque_p sgj_new_unattached_object_r(sgj_state * jsp);
+sgj_opaque_p sgj_new_unattached_array_r(sgj_state * jsp);
+
+/* If jsp is NULL or jsp->pr_as_json is false nothing happens and NULL is
+ * returned. Otherwise it creates a new named object (whose name is what
+ * 'name' points to) at 'jop' with an empty object as its value; a pointer
+ * to that empty object is returned. If 'jop' is NULL then jsp->basep is
+ * used instead. The returned value should always be checked (for NULL)
+ * and if not, used. */
+sgj_opaque_p sgj_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name);
+sgj_opaque_p sgj_snake_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * conv2sname);
+
+/* If jsp is NULL or jsp->pr_as_json is false nothing happens and NULL is
+ * returned. Otherwise it creates a new named object (whose name is what
+ * 'name' points to) at 'jop' with an empty array as its value; a pointer
+ * to that empty array is returned. If 'jop' is NULL then jsp->basep is
+ * used instead. The returned value should always * be checked (for NULL)
+ * and if not, used. */
+sgj_opaque_p sgj_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name);
+sgj_opaque_p sgj_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * conv2sname);
+
+/* If either jsp or value is NULL or jsp->pr_as_json is false then nothing
+ * happens and NULL is returned. The insertion point is at jop but if it is
+ * NULL jsp->basep is used. If 'name' is non-NULL a new named JSON object is
+ * added using 'name' and the associated value is a JSON string formed from
+ * 'value'. If 'name' is NULL then 'jop' is assumed to be a JSON array and
+ * a JSON string formed from 'value' is added. Note that the jsp->pr_string
+ * setting is ignored by this function. If successful returns a * a pointer
+ * newly formed JSON string. */
+sgj_opaque_p sgj_js_nv_s(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, const char * value);
+sgj_opaque_p sgj_js_nv_s_len(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name,
+ const char * value, int slen);
+
+/* If either jsp is NULL or jsp->pr_as_json is false then nothing happens and
+ * NULL is returned. The insertion point is at jop but if it is NULL
+ * jsp->basep is used. If 'name' is non-NULL a new named JSON object is
+ * added using 'name' and the associated value is a JSON integer formed from
+ * 'value'. If 'name' is NULL then 'jop' is assumed to be a JSON array and
+ * a JSON integer formed from 'value' is added. If successful returns a
+ * a pointer newly formed JSON integer. */
+sgj_opaque_p sgj_js_nv_i(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, int64_t value);
+
+/* If either jsp is NULL or jsp->pr_as_json is false then nothing happens and
+ * NULL is returned. The insertion point is at jop but if it is NULL
+ * jsp->basep is used. If 'name' is non-NULL a new named JSON object is
+ * added using 'name' and the associated value is a JSON boolean formed from
+ * 'value'. If 'name' is NULL then 'jop' is assumed to be a JSON array and
+ * a JSON boolean formed from 'value' is added. If successful returns a
+ * a pointer newly formed JSON boolean. */
+sgj_opaque_p sgj_js_nv_b(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, bool value);
+
+/* If jsp is NULL, jsp->pr_as_json is false or ua_jop is NULL nothing then
+ * happens and NULL is returned. 'jop' is the insertion point but if it is
+ * NULL jsp->basep is used instead. If 'name' is non-NULL a new named JSON
+ * object is added using 'name' and the associated value is ua_jop. If 'name'
+ * is NULL then 'jop' is assumed to be a JSON array and ua_jop is added to
+ * it. If successful returns ua_jop . The "ua_" prefix stands for unattached.
+ * That should be the case before invocation and it will be attached to jop
+ * after a successful invocation. This means that ua_jop must have been
+ * created by sgj_new_unattached_object_r() or similar. */
+sgj_opaque_p sgj_js_nv_o(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, sgj_opaque_p ua_jop);
+
+/* This function only produces JSON output if jsp is non-NULL and
+ * jsp->pr_as_json is true. It adds a named object at 'jop' (or jop->basep
+ * if jop is NULL) along with a value. If jsp->pr_hex is true then that
+ * value is two sub-objects, one named 'i' with a 'value' as a JSON integer,
+ * the other one named 'hex' with 'value' rendered as hex in a JSON string.
+ * If jsp->pr_hex is false then there are no sub-objects and the 'value' is
+ * rendered as JSON integer. */
+void sgj_js_nv_ihex(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, uint64_t value);
+
+/* This function only produces JSON output if jsp is non-NULL and
+ * jsp->pr_as_json is true. It adds a named object at 'jop' (or jop->basep
+ * if jop is NULL) along with a value. If jsp->pr_string is true then that
+ * value is two sub-objects, one named 'i' with a 'val_i' as a JSON integer,
+ * the other one named str_name with val_s rendered as a JSON string. If
+ * str_name is NULL then "meaning" will be used. If jsp->pr_string is false
+ * then there are no sub-objects and the 'val_i' is rendered as a JSON
+ * integer. */
+void sgj_js_nv_istr(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, int64_t val_i,
+ const char * str_name, const char * val_s);
+
+/* Similar to sgj_js_nv_istr(). The hex output is conditional jsp->pr_hex . */
+void sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, int64_t val_i,
+ const char * str_name, const char * val_s);
+
+/* This function only produces JSON output if jsp is non-NULL and
+ * jsp->pr_as_json is true. It adds a named object at 'jop' (or jop->basep
+ * if jop is NULL) along with a value. If jsp->pr_name_ex is true then that
+ * value is two sub-objects, one named 'i' with a 'val_i' as a JSON integer,
+ * the other one named "abbreviated_name_expansion" with value nex_s rendered
+ * as a JSON string. If jsp->pr_hex and 'hex_as_well' are true, then a
+ * sub-object named 'hex' with a value rendered as a hex string equal to
+ * val_i. If jsp->pr_name_ex is false and either jsp->pr_hex or hex_as_well are
+ * false then there are no sub-objects and the 'val_i' is rendered as a JSON
+ * integer. */
+void sgj_js_nv_ihex_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t val_i, bool hex_as_well, const char * nex_s);
+
+void sgj_js_nv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, int64_t val_i, bool hex_as_well,
+ const char * str_name, const char * val_s,
+ const char * nex_s);
+
+/* Add named field whose value is a (large) JSON string made up of num_bytes
+ * ASCII hexadecimal bytes (each two hex digits separated by a space) starting
+ * at byte_arr. The heap is used for intermediate storage so num_bytes can
+ * be arbitrarily large. */
+void sgj_js_nv_hex_bytes(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const uint8_t * byte_arr, int num_bytes);
+
+/* The '_haj_' refers to generating output both for human readable and/or
+ * JSON with a single invocation. If jsp is non-NULL and jsp->pr_out_hr is
+ * true then both JSON and human readable output is formed (and the latter is
+ * placed in the jsp->out_hrp JSON array). The human readable form will have
+ * leadin_sp spaces followed by 'name' then a separator, then 'value' with a
+ * trailing LF. If 'name' is NULL then it and the separator are ignored. If
+ * there is JSON output, then leadin_sp and sep are ignored. If 'jop' is NULL
+ * then basep->basep is used. If 'name' is NULL then a JSON string object,
+ * made from 'value' is added to the JSON array pointed to by 'jop'.
+ * Otherwise a 'name'-d JSON object whose value is a JSON string object made
+ * from 'value' is added at 'jop'. */
+void sgj_haj_vs(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ const char * value);
+
+/* Similar to sgj_haj_vs()'s description with 'JSON string object'
+ * replaced by 'JSON integer object'. */
+void sgj_haj_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well);
+void sgj_haj_vistr(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well, const char * val_s);
+
+/* The '_nex' refers to a "name_extra" (information) sub-object (a JSON
+ * string) which explains a bit more about the 'name' entry. This is useful
+ * when T10 specifies the name as an abbreviation (e.g. SYSV). Whether this
+ * sub-object is shown in the JSON output is controlled by the 'n' control
+ * character. */
+void sgj_haj_vi_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well, const char * nex_s);
+void sgj_haj_vistr_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well,
+ const char * val_s, const char * nex_s);
+
+/* Similar to above '_haj_' calls but a named sub-object is always formed
+ * containing a JSON integer object named "i" whose value is 'value'. The
+ * returned pointer is to that sub-object. */
+sgj_opaque_p sgj_haj_subo_r(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well);
+
+/* Similar to sgj_haj_vs()'s description with 'JSON string object' replaced
+ * by 'JSON boolean object'. */
+void sgj_haj_vb(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, bool value);
+
+/* Breaks up the string pointed to by 'sp' into lines and adds them to the
+ * jsp->out_hrp array. Treat '\n' in sp as line breaks. Consumes characters
+ * from sp until either a '\0' is found or slen is exhausted. Add each line
+ * to jsp->out_hrp JSON array (if conditions met). */
+void sgj_js_str_out(sgj_state * jsp, const char * sp, int slen);
+
+/* This function only produces JSON output if jsp is non-NULL and
+ * jsp->pr_as_json is true. 'sbp' is assumed to point to sense data as
+ * defined by T10 with a length of 'sb_len' bytes. Returns false if an
+ * issue is detected, else it returns true. */
+bool sgj_js_sense(sgj_state * jsp, sgj_opaque_p jop, const uint8_t * sbp,
+ int sb_len);
+
+bool sgj_js_designation_descriptor(sgj_state * jsp, sgj_opaque_p jop,
+ const uint8_t * ddp, int dd_len);
+
+/* Nothing in the in-core JSON tree is actually printed to 'fp' (typically
+ * stdout) until this call is made. If jsp is NULL, jsp->pr_as_json is false
+ * or jsp->basep is NULL then this function does nothing. If jsp->exit_status
+ * is true then a new JSON object named "exit_status" and the 'exit_status'
+ * value rendered as a JSON integer is appended to jsp->basep. The in-core
+ * JSON tree with jsp->basep as its root is streamed to 'fp'. */
+void sgj_js2file(sgj_state * jsp, sgj_opaque_p jop, int exit_status,
+ FILE * fp);
+
+/* This function is only needed if the pointer returned from either
+ * sgj_new_unattached_object_r() or sgj_new_unattached_array_r() has not
+ * been attached into the in-core JSON tree whose root is jsp->basep . */
+void sgj_free_unattached(sgj_opaque_p jop);
+
+/* If jsp is NULL or jsp->basep is NULL then this function does nothing.
+ * This function does bottom up, heap freeing of all the in-core JSON
+ * objects and arrays attached to the root JSON object assumed to be
+ * found at jsp->basep . After this call jsp->basep, jsp->out_hrp and
+ * jsp->userp will all be set to NULL. */
+void sgj_finish(sgj_state * jsp);
+
+char * sg_json_usage(int char_if_not_j, char * b, int blen);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sg_pt.h b/include/sg_pt.h
new file mode 100644
index 00000000..a1017f50
--- /dev/null
+++ b/include/sg_pt.h
@@ -0,0 +1,292 @@
+#ifndef SG_PT_H
+#define SG_PT_H
+
+/*
+ * Copyright (c) 2005-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This declaration hides the fact that each implementation has its own
+ * structure "derived" (using a C++ term) from this one. It compiles
+ * because 'struct sg_pt_base' is only referenced (by pointer: 'objp')
+ * in this interface. An instance of this structure represents the
+ * context of one synchronous SCSI (or NVME) command and the context
+ * can be re-used. If an instance of sg_pt_base is shared across several
+ * threads then it is up to the application to take care of multi-threaded
+ * issues with that instance. */
+struct sg_pt_base;
+
+
+/* The format of the version string is like this: "3.04 20180213".
+ * The leading digit will be incremented if this interface changes
+ * in a way that may impact backward compatibility. */
+const char * scsi_pt_version();
+const char * sg_pt_version(); /* both functions give same result */
+
+
+/* Returns file descriptor or file handle and is >= 0 if successful.
+ * If error in Unix returns negated errno. */
+int scsi_pt_open_device(const char * device_name, bool read_only,
+ int verbose);
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. Returns valid file descriptor or handle ( >= 0 ) if successful,
+ * otherwise returns -1 or a negated errno.
+ * In Win32 O_EXCL translated to equivalent. */
+int scsi_pt_open_flags(const char * device_name, int flags, int verbose);
+
+/* Returns 0 if successful. 'device_fd' should be a value that was previously
+ * returned by scsi_pt_open_device() or scsi_pt_open_flags() that has not
+ * already been closed. If error in Unix returns negated errno. */
+int scsi_pt_close_device(int device_fd);
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), 5 is also a NVMe device (FreeBSD CAM NVMe (e.g. /dev/nda0)) or 0
+ * if something else (e.g. ATA block device) or dev_fd < 0.
+ * The return value differs somewhat by OS.
+ * If error, returns negated errno (operating system) value. */
+int check_pt_file_handle(int dev_fd, const char * device_name, int verbose);
+
+
+/* Creates an object that can be used to issue one or more SCSI commands
+ * (or task management functions). Returns NULL if problem.
+ * Once this object has been created it should be destroyed with
+ * destruct_scsi_pt_obj() when it is no longer needed. */
+struct sg_pt_base * construct_scsi_pt_obj(void);
+
+/* An alternate and preferred way to create an object that can be used to
+ * issue one or more SCSI (or NVMe) commands (or task management functions).
+ * This variant associates a device file descriptor (handle) with the object
+ * and a verbose argument that causes messages to be written to stderr if
+ * errors occur. The reason for this is to optionally allow the detection of
+ * NVMe devices that will cause pt_device_is_nvme() to return true. Set
+ * dev_fd to -1 if no open device file descriptor is available. Caller
+ * should additionally call get_scsi_pt_os_err() after this call to check
+ * for errors. The dev_fd argument may be -1 to indicate no device file
+ * descriptor. */
+struct sg_pt_base *
+ construct_scsi_pt_obj_with_fd(int dev_fd, int verbose);
+
+/* Forget any previous dev_fd and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_fd should be >= 0 for a valid file handle or -1 . */
+int set_pt_file_handle(struct sg_pt_base * objp, int dev_fd, int verbose);
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int get_pt_file_handle(const struct sg_pt_base * objp);
+
+/* Clear state information held in *objp . This allows this object to be
+ * used to issue more than one SCSI command. The dev_fd is remembered.
+ * Use set_pt_file_handle() to change dev_fd. */
+void clear_scsi_pt_obj(struct sg_pt_base * objp);
+
+/* Partially clear state information held in *objp . Any error settings and
+ * the data-in and data-out settings are cleared. So dev_fd, cdb and sense
+ * settings are kept. */
+void partial_clear_scsi_pt_obj(struct sg_pt_base * objp);
+
+/* Set the CDB (command descriptor block). May also be a NVMe Admin command
+ * which will be 64 bytes long.
+ *
+ * Note that the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be
+ * called after this function to "guess" which command set the given command
+ * belongs to. It is valid to supply a cdb value of NULL. */
+void set_scsi_pt_cdb(struct sg_pt_base * objp, const uint8_t * cdb,
+ int cdb_len);
+
+/* Set the sense buffer and the maximum length of that buffer. For NVMe
+ * commands this "sense" buffer will receive the 4 DWORDs of from the
+ * completion queue. It is valid to supply a sense value of NULL. */
+void set_scsi_pt_sense(struct sg_pt_base * objp, uint8_t * sense,
+ int max_sense_len);
+
+/* Set a pointer and length to be used for data transferred from device */
+void set_scsi_pt_data_in(struct sg_pt_base * objp, /* from device */
+ uint8_t * dxferp, int dxfer_ilen);
+
+/* Set a pointer and length to be used for data transferred to device */
+void set_scsi_pt_data_out(struct sg_pt_base * objp, /* to device */
+ const uint8_t * dxferp, int dxfer_olen);
+
+/* Set a pointer and length to be used for metadata transferred to
+ * (out_true=true) or from (out_true=false) device (NVMe only) */
+void set_pt_metadata_xfer(struct sg_pt_base * objp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true);
+
+/* The following "set_"s implementations may be dummies */
+void set_scsi_pt_packet_id(struct sg_pt_base * objp, int pack_id);
+void set_scsi_pt_tag(struct sg_pt_base * objp, uint64_t tag);
+void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code);
+void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute,
+ int priority);
+
+/* Following is a guard which is defined when set_scsi_pt_flags() is
+ * present. Older versions of this library may not have this function. */
+#define SCSI_PT_FLAGS_FUNCTION 1
+/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both
+ * are given, use the pass-through default. */
+#define SCSI_PT_FLAGS_QUEUE_AT_TAIL 0x10
+#define SCSI_PT_FLAGS_QUEUE_AT_HEAD 0x20
+/* Set (potentially OS dependent) flags for pass-through mechanism.
+ * Apart from contradictions, flags can be OR-ed together. */
+void set_scsi_pt_flags(struct sg_pt_base * objp, int flags);
+
+#define SCSI_PT_DO_START_OK 0
+#define SCSI_PT_DO_BAD_PARAMS 1
+#define SCSI_PT_DO_TIMEOUT 2
+#define SCSI_PT_DO_NOT_SUPPORTED 4
+#define SCSI_PT_DO_NVME_STATUS 48 /* == SG_LIB_NVME_STATUS */
+/* If OS error prior to or during command submission then returns negated
+ * error value (e.g. Unix '-errno'). This includes interrupted system calls
+ * (e.g. by a signal) in which case -EINTR would be returned. Note that
+ * system call errors also can be fetched with get_scsi_pt_os_err().
+ * Return 0 if okay (i.e. at the very least: command sent). Positive
+ * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor
+ * has already been provided by construct_scsi_pt_obj_with_fd() then the
+ * given 'fd' can be -1 or the same value as given to the constructor. */
+int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs,
+ int verbose);
+
+/* NVMe Admin commands can be sent directly to do_scsi_pt(). Unfortunately
+ * NVMe has at least one other command set: "NVM" to access user data and
+ * the opcodes in the NVM command set overlap with the Admin command set.
+ * So NVMe Admin commands should be sent do_scsi_pt() while NVMe "NVM"
+ * commands should be sent to this function. No SCSI commands should be
+ * sent to this function. Currently submq is not implemented and all
+ * submitted NVM commands are sent on queue 0, the same queue use for
+ * Admin commands. The return values follow the same pattern as do_scsi_pt(),
+ * with 0 returned being good. The NVMe device file descriptor must either
+ * be given to the obj constructor, or a prior set_pt_file_handle() call. */
+int do_nvm_pt(struct sg_pt_base * objp, int submq, int timeout_secs,
+ int verbose);
+
+#define SCSI_PT_RESULT_GOOD 0
+#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */
+#define SCSI_PT_RESULT_SENSE 2
+#define SCSI_PT_RESULT_TRANSPORT_ERR 3
+#define SCSI_PT_RESULT_OS_ERR 4
+/* This function, called soon after do_scsi_pt(), returns one of the above
+ * result categories. The highest numbered applicable category is returned.
+ *
+ * Note that the sg_cmds_process_resp() function found in sg_cmds_basic.h
+ * is useful for processing SCSI command responses.
+ * And the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be called
+ * after set_scsi_pt_cdb() to "guess" which command set the given command
+ * belongs to. */
+int get_scsi_pt_result_category(const struct sg_pt_base * objp);
+
+/* If not available return 0 which implies there is no residual value. If
+ * supported it is the number of bytes requested to transfer less the
+ * number actually transferred. This it typically important for data-in
+ * transfers. For data-out (only) transfers, the 'dout_req_len -
+ * dout_act_len' is returned. For bidi transfer the data-in residual is
+ * returned. */
+int get_scsi_pt_resid(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value (from device that received the command). If an
+ * NVMe command was issued directly (i.e. through do_scsi_pt() then return
+ * NVMe status (i.e. ((SCT << 8) | SC)). If problem returns -1. */
+int get_scsi_pt_status_response(const struct sg_pt_base * objp);
+
+/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(),
+ * then returns NVMe result (i.e. DWord(0) from completion queue). If
+ * 'objp' is NULL then returns 0xffffffff. */
+uint32_t get_pt_result(const struct sg_pt_base * objp);
+
+/* These two get functions should just echo what has been given to
+ * set_scsi_pt_cdb(). If it has not been called or clear_scsi_pt_obj()
+ * has been called then they return 0 and NULL respectively. */
+int get_scsi_pt_cdb_len(const struct sg_pt_base * objp);
+uint8_t * get_scsi_pt_cdb_buf(const struct sg_pt_base * objp);
+
+/* Actual sense length returned. If sense data is present but
+ actual sense length is not known, return 'max_sense_len' */
+int get_scsi_pt_sense_len(const struct sg_pt_base * objp);
+uint8_t * get_scsi_pt_sense_buf(const struct sg_pt_base * objp);
+
+/* If not available return 0 (for success). */
+int get_scsi_pt_os_err(const struct sg_pt_base * objp);
+char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len,
+ char * b);
+
+/* If not available return 0 (for success) */
+int get_scsi_pt_transport_err(const struct sg_pt_base * objp);
+void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err);
+char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp,
+ int max_b_len, char * b);
+
+/* If not available return -1 otherwise return number of milliseconds
+ * that the lower layers (and hardware) took to execute the previous
+ * command. */
+int get_scsi_pt_duration_ms(const struct sg_pt_base * objp);
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t get_pt_duration_ns(const struct sg_pt_base * objp);
+
+/* The two functions yield requested and actual data transfer lengths in
+ * bytes. The second argument is a pointer to the data-in length; the third
+ * argument is a pointer to the data-out length. The pointers may be NULL.
+ * The _actual_ values are related to resid (residual count from DMA) */
+void get_pt_req_lengths(const struct sg_pt_base * objp, int * req_dinp,
+ int * req_doutp);
+void get_pt_actual_lengths(const struct sg_pt_base * objp, int * act_dinp,
+ int * act_doutp);
+
+/* Return true if device associated with 'objp' uses NVMe command set. To
+ * be useful (in modifying the type of command sent (SCSI or NVMe) then
+ * construct_scsi_pt_obj_with_fd() should be used followed by an invocation
+ * of this function. */
+bool pt_device_is_nvme(const struct sg_pt_base * objp);
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'objp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t get_pt_nvme_nsid(const struct sg_pt_base * objp);
+
+
+/* Should be invoked once per objp after other processing is complete in
+ * order to clean up resources. For ever successful construct_scsi_pt_obj()
+ * call there should be one destruct_scsi_pt_obj(). If the
+ * construct_scsi_pt_obj_with_fd() function was used to create this object
+ * then the dev_fd provided to that constructor is not altered by this
+ * destructor. So the user should still close dev_fd (perhaps with
+ * scsi_pt_close_device() ). */
+void destruct_scsi_pt_obj(struct sg_pt_base * objp);
+
+#ifdef SG_LIB_WIN32
+#define SG_LIB_WIN32_DIRECT 1
+
+/* Request SPT direct interface when state_direct is 1, state_direct set
+ * to 0 for the SPT indirect interface. Default setting selected by build
+ * (i.e. library compile time) and is usually indirect. */
+void scsi_pt_win32_direct(int state_direct);
+
+/* Returns current SPT interface state, 1 for direct, 0 for indirect */
+int scsi_pt_win32_spt_state(void);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_PT_H */
diff --git a/include/sg_pt_linux.h b/include/sg_pt_linux.h
new file mode 100644
index 00000000..548a7634
--- /dev/null
+++ b/include/sg_pt_linux.h
@@ -0,0 +1,202 @@
+#ifndef SG_PT_LINUX_H
+#define SG_PT_LINUX_H
+
+/*
+ * Copyright (c) 2017-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <linux/types.h>
+
+#include "sg_pt_nvme.h"
+
+/* This header is for internal use by the sg3_utils library (libsgutils)
+ * and is Linux specific. Best not to include it directly in code that
+ * is meant to be OS independent. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_LINUX_BSG_H
+
+#define BSG_PROTOCOL_SCSI 0
+
+#define BSG_SUB_PROTOCOL_SCSI_CMD 0
+#define BSG_SUB_PROTOCOL_SCSI_TMF 1
+#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2
+
+/*
+ * For flag constants below:
+ * sg.h sg_io_hdr also has bits defined for it's flags member. These
+ * two flag values (0x10 and 0x20) have the same meaning in sg.h . For
+ * bsg the BSG_FLAG_Q_AT_HEAD flag is ignored since it is the default.
+ */
+#define BSG_FLAG_Q_AT_TAIL 0x10 /* default is Q_AT_HEAD */
+#define BSG_FLAG_Q_AT_HEAD 0x20
+
+#ifndef SGV4_FLAG_YIELD_TAG
+#define SGV4_FLAG_YIELD_TAG 0x8
+#endif
+#ifndef SGV4_FLAG_FIND_BY_TAG
+#define SGV4_FLAG_FIND_BY_TAG 0x100
+#endif
+#ifndef SGV4_FLAG_IMMED
+#define SGV4_FLAG_IMMED 0x400
+#endif
+#ifndef SGV4_FLAG_IMMED
+#define SGV4_FLAG_IMMED 0x400
+#endif
+#ifndef SGV4_FLAG_DEV_SCOPE
+#define SGV4_FLAG_DEV_SCOPE 0x1000
+#endif
+#ifndef SGV4_FLAG_SHARE
+#define SGV4_FLAG_SHARE 0x2000
+#endif
+
+struct sg_io_v4 {
+ __s32 guard; /* [i] 'Q' to differentiate from v3 */
+ __u32 protocol; /* [i] 0 -> SCSI , .... */
+ __u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task
+ management function, .... */
+
+ __u32 request_len; /* [i] in bytes */
+ __u64 request; /* [i], [*i] {SCSI: cdb} */
+ __u64 request_tag; /* [i] {in sg 4.0+ this is out parameter} */
+ __u32 request_attr; /* [i] {SCSI: task attribute} */
+ __u32 request_priority; /* [i] {SCSI: task priority} */
+ __u32 request_extra; /* [i] {used for pack_id} */
+ __u32 max_response_len; /* [i] in bytes */
+ __u64 response; /* [i], [*o] {SCSI: (auto)sense data} */
+
+ /* "dout_": data out (to device); "din_": data in (from device) */
+ __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else
+ dout_xfer points to array of iovec */
+ __u32 dout_xfer_len; /* [i] bytes to be transferred to device */
+ __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */
+ __u32 din_xfer_len; /* [i] bytes to be transferred from device */
+ __u64 dout_xferp; /* [i], [*i] */
+ __u64 din_xferp; /* [i], [*o] */
+
+ __u32 timeout; /* [i] units: millisecond */
+ __u32 flags; /* [i] bit mask */
+ __u64 usr_ptr; /* [i->o] unused internally */
+ __u32 spare_in; /* [i] */
+
+ __u32 driver_status; /* [o] 0 -> ok */
+ __u32 transport_status; /* [o] 0 -> ok */
+ __u32 device_status; /* [o] {SCSI: command completion status} */
+ __u32 retry_delay; /* [o] {SCSI: status auxiliary information} */
+ __u32 info; /* [o] additional information */
+ __u32 duration; /* [o] time to complete, in milliseconds */
+ __u32 response_len; /* [o] bytes of response actually written */
+ __s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */
+ __s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */
+ __u64 generated_tag; /* [o] {SCSI: transport generated task tag} */
+ __u32 spare_out; /* [o] */
+
+ __u32 padding;
+};
+
+#else
+
+#include <linux/bsg.h>
+
+#endif
+
+
+struct sg_pt_linux_scsi {
+ struct sg_io_v4 io_hdr; /* use v4 header as it is more general */
+ /* Leave io_hdr in first place of this structure */
+ bool is_sg;
+ bool is_bsg;
+ bool is_nvme; /* OS device type, if false ignore nvme_our_sntl */
+ bool nvme_our_sntl; /* true: our SNTL; false: received NVMe command */
+ bool nvme_stat_dnr; /* Do No Retry, part of completion status field */
+ bool nvme_stat_more; /* More, part of completion status field */
+ bool mdxfer_out; /* direction of metadata xfer, true->data-out */
+ int dev_fd; /* -1 if not given (yet) */
+ int in_err;
+ int os_err;
+ int sg_version; /* for deciding whether to use v3 or v4 interface */
+ uint32_t nvme_nsid; /* 1 to 0xfffffffe are possibly valid, 0
+ * implies dev_fd is not a NVMe device
+ * (is_nvme=false) or it is a NVMe char
+ * device (e.g. /dev/nvme0 ) */
+ uint32_t nvme_result; /* DW0 from completion queue */
+ uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue,
+ * note: the DNR+More bit are not there.
+ * The whole 16 byte completion q entry is
+ * sent back as sense data */
+ uint32_t mdxfer_len;
+ struct sg_sntl_dev_state_t dev_stat;
+ void * mdxferp;
+ uint8_t * nvme_id_ctlp; /* cached response to controller IDENTIFY */
+ uint8_t * free_nvme_id_ctlp;
+ uint8_t tmf_request[4];
+};
+
+struct sg_pt_base {
+ struct sg_pt_linux_scsi impl;
+};
+
+
+#ifndef sg_nvme_admin_cmd
+#define sg_nvme_admin_cmd sg_nvme_passthru_cmd
+#endif
+
+/* Linux NVMe related ioctls */
+#ifndef NVME_IOCTL_ID
+#define NVME_IOCTL_ID _IO('N', 0x40)
+#endif
+#ifndef NVME_IOCTL_ADMIN_CMD
+#define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct sg_nvme_admin_cmd)
+#endif
+#ifndef NVME_IOCTL_SUBMIT_IO
+#define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct sg_nvme_user_io)
+#endif
+#ifndef NVME_IOCTL_IO_CMD
+#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct sg_nvme_passthru_cmd)
+#endif
+#ifndef NVME_IOCTL_RESET
+#define NVME_IOCTL_RESET _IO('N', 0x44)
+#endif
+#ifndef NVME_IOCTL_SUBSYS_RESET
+#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45)
+#endif
+#ifndef NVME_IOCTL_RESCAN
+#define NVME_IOCTL_RESCAN _IO('N', 0x46)
+#endif
+#if 0
+#define NVME_IOCTL_ADMIN64_CMD _IOWR('N', 0x47, struct nvme_passthru_cmd64)
+#define NVME_IOCTL_IO64_CMD _IOWR('N', 0x48, struct nvme_passthru_cmd64)
+#endif
+
+extern bool sg_bsg_nvme_char_major_checked;
+extern int sg_bsg_major;
+extern volatile int sg_nvme_char_major;
+extern long sg_lin_page_size;
+
+void sg_find_bsg_nvme_char_major(int verbose);
+int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb);
+int sg_linux_get_sg_version(const struct sg_pt_base * vp);
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+ char * b);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* end of SG_PT_LINUX_H */
diff --git a/include/sg_pt_nvme.h b/include/sg_pt_nvme.h
new file mode 100644
index 00000000..06f8f0f4
--- /dev/null
+++ b/include/sg_pt_nvme.h
@@ -0,0 +1,224 @@
+#ifndef SG_PT_NVME_H
+#define SG_PT_NVME_H
+
+/*
+ * Copyright (c) 2017-2019 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* structures copied and slightly modified from <linux/nvme_ioctl.h> which
+ * is Copyright (c) 2011-2014, Intel Corporation. */
+
+
+/* Note that the command input structure is in (packed) "cpu" format. That
+ * means, for example, if the CPU is little endian (most are) then so is the
+ * structure. However what comes out in the data-in buffer (e.g. for the
+ * Admin Identify command response) is almost all little endian following ATA
+ * (but no SCSI and IP which are big endian) and Intel's preference. There
+ * are exceptions, for example the EUI-64 identifiers in the Admin Identify
+ * response are big endian.
+ *
+ * Code online (e.g. nvme-cli at github.com) seems to favour packed
+ * structures, while the author prefers byte offset plus a range of unaligned
+ * integer builders such as those in sg_unaligned.h .
+ */
+
+#ifdef __GNUC__
+#ifndef __clang__
+ struct __attribute__((__packed__)) sg_nvme_user_io
+#else
+ struct sg_nvme_user_io
+#endif
+#else
+struct sg_nvme_user_io
+#endif
+{
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t control;
+ uint16_t nblocks;
+ uint16_t rsvd;
+ uint64_t metadata;
+ uint64_t addr;
+ uint64_t slba;
+ uint32_t dsmgmt;
+ uint32_t reftag;
+ uint16_t apptag;
+ uint16_t appmask;
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#endif
+
+/* Using byte offsets and unaligned be/le copies safer than packed
+ * structures. These are for sg_nvme_user_io . */
+#define SG_NVME_IO_OPCODE 0
+#define SG_NVME_IO_FLAGS 1
+#define SG_NVME_IO_CONTROL 2
+#define SG_NVME_IO_NBLOCKS 4
+#define SG_NVME_IO_RSVD 6
+#define SG_NVME_IO_METADATA 8
+#define SG_NVME_IO_ADDR 16
+#define SG_NVME_IO_SLBA 24
+#define SG_NVME_IO_DSMGMT 32
+#define SG_NVME_IO_REFTAG 36
+#define SG_NVME_IO_APPTAG 40
+#define SG_NVME_IO_APPMASK 42
+
+#ifdef __GNUC__
+#ifndef __clang__
+ struct __attribute__((__packed__)) sg_nvme_passthru_cmd
+#else
+ struct sg_nvme_passthru_cmd
+#endif
+#else
+struct sg_nvme_passthru_cmd
+#endif
+{
+ uint8_t opcode;
+ uint8_t flags;
+ uint16_t rsvd1;
+ uint32_t nsid;
+ uint32_t cdw2;
+ uint32_t cdw3;
+ uint64_t metadata;
+ uint64_t addr;
+ uint32_t metadata_len;
+ uint32_t data_len;
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+#ifdef SG_LIB_LINUX
+ uint32_t timeout_ms;
+ uint32_t result; /* out: DWord(0) from completion queue */
+#endif
+}
+#ifdef SG_LIB_FREEBSD
+__packed;
+#else
+;
+#endif
+
+struct sg_sntl_dev_state_t {
+ uint8_t scsi_dsense;
+ uint8_t enclosure_override; /* ENC_OV in sdparm */
+ uint8_t pdt; /* 6 bit value in INQUIRY response */
+ uint8_t enc_serv; /* single bit in INQUIRY response */
+ uint8_t id_ctl253; /* NVMSR field of Identify controller (byte 253) */
+ bool wce; /* Write Cache Enable (WCE) setting */
+ bool wce_changed; /* WCE setting has been changed */
+};
+
+struct sg_sntl_result_t {
+ uint8_t sstatus;
+ uint8_t sk;
+ uint8_t asc;
+ uint8_t ascq;
+ uint8_t in_byte;
+ uint8_t in_bit; /* use 255 for 'no bit position given' */
+};
+
+struct sg_opcode_info_t {
+ uint8_t opcode;
+ uint16_t sa; /* service action, 0 for none */
+ uint32_t flags; /* OR-ed set of F_* flags */
+ uint8_t len_mask[16]; /* len=len_mask[0], then mask for cdb[1]... */
+ /* ignore cdb bytes after position 15 */
+};
+
+/* Using byte offsets and unaligned be/le copies safer than packed
+ * structures. These are for sg_nvme_passthru_cmd . */
+#define SG_NVME_PT_OPCODE 0 /* length: 1 byte */
+#define SG_NVME_PT_FLAGS 1 /* length: 1 byte */
+#define SG_NVME_PT_RSVD1 2 /* length: 2 bytes */
+#define SG_NVME_PT_NSID 4 /* length: 4 bytes */
+#define SG_NVME_PT_CDW2 8 /* length: 4 bytes */
+#define SG_NVME_PT_CDW3 12 /* length: 4 bytes */
+#define SG_NVME_PT_METADATA 16 /* length: 8 bytes */
+#define SG_NVME_PT_ADDR 24 /* length: 8 bytes */
+#define SG_NVME_PT_METADATA_LEN 32 /* length: 4 bytes */
+#define SG_NVME_PT_DATA_LEN 36 /* length: 4 bytes */
+#define SG_NVME_PT_CDW10 40 /* length: 4 bytes */
+#define SG_NVME_PT_CDW11 44 /* length: 4 bytes */
+#define SG_NVME_PT_CDW12 48 /* length: 4 bytes */
+#define SG_NVME_PT_CDW13 52 /* length: 4 bytes */
+#define SG_NVME_PT_CDW14 56 /* length: 4 bytes */
+#define SG_NVME_PT_CDW15 60 /* length: 4 bytes */
+
+#ifdef SG_LIB_LINUX
+/* General references state that "all NVMe commands are 64 bytes long". If
+ * so then the following are add-ons by Linux, go to the OS and not the
+ * the NVMe device. */
+#define SG_NVME_PT_TIMEOUT_MS 64 /* length: 4 bytes */
+#define SG_NVME_PT_RESULT 68 /* length: 4 bytes */
+#endif
+
+/* Byte offset of Result and Status (plus phase bit) in CQ */
+#define SG_NVME_PT_CQ_RESULT 0 /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW0 0 /* CDW0, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW1 4 /* CDW1, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW2 8 /* CDW2, length: 4 bytes */
+#define SG_NVME_PT_CQ_DW3 12 /* CDW3, length: 4 bytes */
+#define SG_NVME_PT_CQ_STATUS_P 14 /* CDW3 31:16, length: 2 bytes */
+
+
+/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */
+#define SG_NVME_BROADCAST_NSID 0xffffffff /* all namespaces */
+#define SG_NVME_CTL_NSID 0x0 /* the "controller's" namespace */
+
+/* Vendor specific (sg3_utils) VPD pages */
+#define SG_NVME_VPD_NICR 0xde /* NVME Identify controller response */
+
+
+/* Given the NVMe Identify Controller response and optionally the NVMe
+ * Identify Namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+ const uint8_t * nvme_id_ns_p, int pdt,
+ int tproto, uint8_t * dop, int max_do_len);
+
+/* Initialize dev_stat pointed to by dsp */
+void sntl_init_dev_stat(struct sg_sntl_dev_state_t * dsp);
+
+/* Internal function (common to all OSes) to support the SNTL SCSI MODE
+ * SENSE(10) command. Has a vendor specific Unit Attention mpage which
+ * has only one field currently: ENC_OV (enclosure override) */
+int sntl_resp_mode_sense10(const struct sg_sntl_dev_state_t * dsp,
+ const uint8_t * cdbp, uint8_t * dip, int mx_di_len,
+ struct sg_sntl_result_t * resp);
+
+/* Internal function (common to all OSes) to support the SNTL SCSI MODE
+ * SELECT(10) command. */
+int sntl_resp_mode_select10(struct sg_sntl_dev_state_t * dsp,
+ const uint8_t * cdbp, const uint8_t * dop,
+ int do_len, struct sg_sntl_result_t * resp);
+
+/* Returns pointer to array of struct sg_opcode_info_t of SCSI commands
+ * translated to NVMe. */
+const struct sg_opcode_info_t * sg_get_opcode_translation(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_PT_NVME_H */
diff --git a/include/sg_pt_win32.h b/include/sg_pt_win32.h
new file mode 100644
index 00000000..a19485d4
--- /dev/null
+++ b/include/sg_pt_win32.h
@@ -0,0 +1,473 @@
+#ifndef SG_PT_WIN32_H
+#define SG_PT_WIN32_H
+/*
+ * The information in this file was obtained from scsi-wnt.h by
+ * Richard Stemmer, rs@epost.de . He in turn gives credit to
+ * Jay A. Key (for scsipt.c).
+ * The plscsi program (by Pat LaVarre <p.lavarre@ieee.org>) has
+ * also been used as a reference.
+ * Much of the information in this header can also be obtained
+ * from msdn.microsoft.com .
+ * Updated for cygwin version 1.7.17 changes 20121026
+ */
+
+/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of <winioctl.h> */
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SCSI_MAX_SENSE_LEN 64
+#define SCSI_MAX_CDB_LEN 16
+#define SCSI_MAX_INDIRECT_DATA 16384
+
+typedef struct {
+ USHORT Length;
+ UCHAR ScsiStatus;
+ UCHAR PathId;
+ UCHAR TargetId;
+ UCHAR Lun;
+ UCHAR CdbLength;
+ UCHAR SenseInfoLength;
+ UCHAR DataIn;
+ ULONG DataTransferLength;
+ ULONG TimeOutValue;
+ ULONG_PTR DataBufferOffset; /* was ULONG; problem in 64 bit */
+ ULONG SenseInfoOffset;
+ UCHAR Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;
+
+
+typedef struct {
+ USHORT Length;
+ UCHAR ScsiStatus;
+ UCHAR PathId;
+ UCHAR TargetId;
+ UCHAR Lun;
+ UCHAR CdbLength;
+ UCHAR SenseInfoLength;
+ UCHAR DataIn;
+ ULONG DataTransferLength;
+ ULONG TimeOutValue;
+ PVOID DataBuffer;
+ ULONG SenseInfoOffset;
+ UCHAR Cdb[SCSI_MAX_CDB_LEN];
+} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
+
+
+typedef struct {
+ SCSI_PASS_THROUGH spt;
+ /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */
+ ULONG Filler;
+ UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN];
+ UCHAR ucDataBuf[SCSI_MAX_INDIRECT_DATA];
+} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS;
+
+
+typedef struct {
+ SCSI_PASS_THROUGH_DIRECT spt;
+ ULONG Filler;
+ UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN];
+} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER;
+
+
+
+typedef struct {
+ UCHAR NumberOfLogicalUnits;
+ UCHAR InitiatorBusId;
+ ULONG InquiryDataOffset;
+} SCSI_BUS_DATA, *PSCSI_BUS_DATA;
+
+
+typedef struct {
+ UCHAR NumberOfBusses;
+ SCSI_BUS_DATA BusData[1];
+} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO;
+
+
+typedef struct {
+ UCHAR PathId;
+ UCHAR TargetId;
+ UCHAR Lun;
+ BOOLEAN DeviceClaimed;
+ ULONG InquiryDataLength;
+ ULONG NextInquiryDataOffset;
+ UCHAR InquiryData[1];
+} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA;
+
+
+typedef struct {
+ ULONG Length;
+ UCHAR PortNumber;
+ UCHAR PathId;
+ UCHAR TargetId;
+ UCHAR Lun;
+} SCSI_ADDRESS, *PSCSI_ADDRESS;
+
+/*
+ * Standard IOCTL define
+ */
+#ifndef CTL_CODE
+#define CTL_CODE(DevType, Function, Method, Access) \
+ (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
+#endif
+
+/*
+ * file access values
+ */
+#ifndef FILE_ANY_ACCESS
+#define FILE_ANY_ACCESS 0
+#endif
+#ifndef FILE_READ_ACCESS
+#define FILE_READ_ACCESS 0x0001
+#endif
+#ifndef FILE_WRITE_ACCESS
+#define FILE_WRITE_ACCESS 0x0002
+#endif
+
+// IOCTL_STORAGE_QUERY_PROPERTY
+
+#define FILE_DEVICE_MASS_STORAGE 0x0000002d
+#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE
+#define FILE_ANY_ACCESS 0
+
+// #define METHOD_BUFFERED 0
+
+#define IOCTL_STORAGE_QUERY_PROPERTY \
+ CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_BUS_TYPE {
+ BusTypeUnknown = 0x00,
+ BusTypeScsi = 0x01,
+ BusTypeAtapi = 0x02,
+ BusTypeAta = 0x03,
+ BusType1394 = 0x04,
+ BusTypeSsa = 0x05,
+ BusTypeFibre = 0x06,
+ BusTypeUsb = 0x07,
+ BusTypeRAID = 0x08,
+ BusTypeiScsi = 0x09,
+ BusTypeSas = 0x0A,
+ BusTypeSata = 0x0B,
+ BusTypeSd = 0x0C,
+ BusTypeMmc = 0x0D,
+ BusTypeVirtual = 0xE,
+ BusTypeFileBackedVirtual = 0xF,
+ BusTypeSpaces = 0x10,
+ BusTypeNvme = 0x11,
+ BusTypeSCM = 0x12,
+ BusTypeUfs = 0x13,
+ BusTypeMax = 0x14,
+ BusTypeMaxReserved = 0x7F
+} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_TYPE {
+ ProtocolTypeUnknown = 0,
+ ProtocolTypeScsi,
+ ProtocolTypeAta,
+ ProtocolTypeNvme,
+ ProtocolTypeSd
+} STORAGE_PROTOCOL_TYPE;
+
+typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE {
+ NVMeDataTypeUnknown = 0,
+ NVMeDataTypeIdentify,
+ NVMeDataTypeLogPage,
+ NVMeDataTypeFeature
+} STORAGE_PROTOCOL_NVME_DATA_TYPE;
+
+typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {
+ STORAGE_PROTOCOL_TYPE ProtocolType;
+ ULONG DataType;
+ ULONG ProtocolDataRequestValue;
+ ULONG ProtocolDataRequestSubValue;
+ ULONG ProtocolDataOffset;
+ ULONG ProtocolDataLength;
+ ULONG FixedProtocolReturnData;
+ ULONG Reserved[3];
+} STORAGE_PROTOCOL_SPECIFIC_DATA;
+
+
+typedef struct _STORAGE_DEVICE_DESCRIPTOR {
+ ULONG Version;
+ ULONG Size;
+ UCHAR DeviceType;
+ UCHAR DeviceTypeModifier;
+ BOOLEAN RemovableMedia;
+ BOOLEAN CommandQueueing;
+ ULONG VendorIdOffset; /* 0 if not available */
+ ULONG ProductIdOffset; /* 0 if not available */
+ ULONG ProductRevisionOffset;/* 0 if not available */
+ ULONG SerialNumberOffset; /* -1 if not available ?? */
+ STORAGE_BUS_TYPE BusType;
+ ULONG RawPropertiesLength;
+ UCHAR RawDeviceProperties[1];
+} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
+
+#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1
+
+#define IOCTL_STORAGE_PROTOCOL_COMMAND \
+ CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \
+ FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+typedef struct _STORAGE_PROTOCOL_COMMAND {
+ DWORD Version; /* STORAGE_PROTOCOL_STRUCTURE_VERSION */
+ DWORD Length;
+ STORAGE_PROTOCOL_TYPE ProtocolType;
+ DWORD Flags;
+ DWORD ReturnStatus;
+ DWORD ErrorCode;
+ DWORD CommandLength;
+ DWORD ErrorInfoLength;
+ DWORD DataToDeviceTransferLength;
+ DWORD DataFromDeviceTransferLength;
+ DWORD TimeOutValue;
+ DWORD ErrorInfoOffset;
+ DWORD DataToDeviceBufferOffset;
+ DWORD DataFromDeviceBufferOffset;
+ DWORD CommandSpecific;
+ DWORD Reserved0;
+ DWORD FixedProtocolReturnData;
+ DWORD Reserved1[3];
+ BYTE Command[1]; /* has CommandLength elements */
+} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;
+
+#endif /* _DEVIOCTL_ */
+
+typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER {
+ ULONG Version;
+ ULONG Size;
+ ULONG StorageDeviceIdOffset;
+ ULONG StorageDeviceOffset;
+ ULONG DriveLayoutSignatureOffset;
+} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER;
+
+// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2)
+// to test for equality
+
+#ifndef _DEVIOCTL_
+typedef enum _STORAGE_QUERY_TYPE {
+ PropertyStandardQuery = 0,
+ PropertyExistsQuery,
+ PropertyMaskQuery,
+ PropertyQueryMaxDefined
+} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
+
+typedef enum _STORAGE_PROPERTY_ID {
+ StorageDeviceProperty = 0,
+ StorageAdapterProperty,
+ StorageDeviceIdProperty,
+ StorageDeviceUniqueIdProperty,
+ StorageDeviceWriteCacheProperty,
+ StorageMiniportProperty,
+ StorageAccessAlignmentProperty,
+ /* Identify controller goes to adapter; Identify namespace to device */
+ StorageAdapterProtocolSpecificProperty = 49,
+ StorageDeviceProtocolSpecificProperty = 50
+} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
+
+typedef struct _STORAGE_PROPERTY_QUERY {
+ STORAGE_PROPERTY_ID PropertyId;
+ STORAGE_QUERY_TYPE QueryType;
+ UCHAR AdditionalParameters[1];
+} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
+
+typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR {
+ DWORD Version;
+ DWORD Size;
+ STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData;
+} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR;
+
+// Command completion status
+// The "Phase Tag" field and "Status Field" are separated in spec. We define
+// them in the same data structure to ease the memory access from software.
+//
+typedef union {
+ struct {
+ USHORT P : 1; // Phase Tag (P)
+
+ USHORT SC : 8; // Status Code (SC)
+ USHORT SCT : 3; // Status Code Type (SCT)
+ USHORT Reserved : 2;
+ USHORT M : 1; // More (M)
+ USHORT DNR : 1; // Do Not Retry (DNR)
+ } DUMMYSTRUCTNAME;
+ USHORT AsUshort;
+} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS;
+
+// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes
+//
+typedef struct {
+ ULONGLONG ErrorCount;
+ USHORT SQID; // Submission Queue ID
+ USHORT CMDID; // Command ID
+ NVME_COMMAND_STATUS Status; // Status Field: This field indicates the
+ // Status Field for the command that
+ // completed. The Status Field is located in
+ // bits 15:01, bit 00 corresponds to the Phase
+ // Tag posted for the command.
+ struct {
+ USHORT Byte : 8; // Byte in command that contained error
+ USHORT Bit : 3; // Bit in command that contained error
+ USHORT Reserved : 5;
+ } ParameterErrorLocation;
+
+ ULONGLONG Lba; // LBA: This field indicates the first LBA
+ // that experienced the error condition, if
+ // applicable.
+ ULONG NameSpace; // Namespace: This field indicates the nsid
+ // that the error is associated with, if
+ // applicable.
+ UCHAR VendorInfoAvailable; // Vendor Specific Information Available
+ UCHAR Reserved0[3];
+ ULONGLONG CommandSpecificInfo; // This field contains command specific
+ // information. If used, the command
+ // definition specifies the information
+ // returned.
+ UCHAR Reserved1[24];
+} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG;
+
+typedef struct {
+
+ ULONG DW0;
+ ULONG Reserved;
+
+ union {
+ struct {
+ USHORT SQHD; // SQ Head Pointer (SQHD)
+ USHORT SQID; // SQ Identifier (SQID)
+ } DUMMYSTRUCTNAME;
+
+ ULONG AsUlong;
+ } DW2;
+
+ union {
+ struct {
+ USHORT CID; // Command Identifier (CID)
+ NVME_COMMAND_STATUS Status;
+ } DUMMYSTRUCTNAME;
+
+ ULONG AsUlong;
+ } DW3;
+
+} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY;
+
+
+// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field.
+//
+// Flag indicates the request targeting to adapter instead of device.
+#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST 0x80000000
+
+//
+// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field.
+//
+#define STORAGE_PROTOCOL_STATUS_PENDING 0x0
+#define STORAGE_PROTOCOL_STATUS_SUCCESS 0x1
+#define STORAGE_PROTOCOL_STATUS_ERROR 0x2
+#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST 0x3
+#define STORAGE_PROTOCOL_STATUS_NO_DEVICE 0x4
+#define STORAGE_PROTOCOL_STATUS_BUSY 0x5
+#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN 0x6
+#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES 0x7
+
+#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED 0xFF
+
+// Command Length for Storage Protocols.
+//
+// NVMe commands are always 64 bytes.
+#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME 0x40
+
+// Command Specific Information for Storage Protocols - CommandSpecific field
+//
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND 0x01
+#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02
+
+#endif /* _DEVIOCTL_ */
+
+
+// NVME_PASS_THROUGH
+
+#ifndef STB_IO_CONTROL
+typedef struct _SRB_IO_CONTROL {
+ ULONG HeaderLength;
+ UCHAR Signature[8];
+ ULONG Timeout;
+ ULONG ControlCode;
+ ULONG ReturnCode;
+ ULONG Length;
+} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
+#endif
+
+#ifndef NVME_PASS_THROUGH_SRB_IO_CODE
+
+#define NVME_SIG_STR "NvmeMini"
+#define NVME_STORPORT_DRIVER 0xe000
+
+#define NVME_PASS_THROUGH_SRB_IO_CODE \
+ CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#pragma pack(1)
+
+/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT),
+ * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure
+ * pass-through. Win10 also has "Protocol specific queries" for things like
+ * Identify and Get feature. */
+typedef struct _NVME_PASS_THROUGH_IOCTL
+{
+ SRB_IO_CONTROL SrbIoCtrl;
+ ULONG VendorSpecific[6];
+ ULONG NVMeCmd[16]; /* Command DW[0...15] */
+ ULONG CplEntry[4]; /* Completion DW[0...3] */
+ ULONG Direction; /* 0=None, 1=Out, 2=In, 3=I/O */
+ ULONG QueueId; /* 0=AdminQ */
+ ULONG DataBufferLen; /* sizeof(DataBuffer) if Data In */
+ ULONG MetaDataLen;
+ ULONG ReturnBufferLen; /* offsetof(DataBuffer), plus
+ * sizeof(DataBuffer) if Data Out */
+ UCHAR DataBuffer[1];
+} NVME_PASS_THROUGH_IOCTL;
+#pragma pack()
+
+#endif // NVME_PASS_THROUGH_SRB_IO_CODE
+
+
+/*
+ * method codes
+ */
+#define METHOD_BUFFERED 0
+#define METHOD_IN_DIRECT 1
+#define METHOD_OUT_DIRECT 2
+#define METHOD_NEITHER 3
+
+
+#define IOCTL_SCSI_BASE 0x00000004
+
+/*
+ * constants for DataIn member of SCSI_PASS_THROUGH* structures
+ */
+#define SCSI_IOCTL_DATA_OUT 0
+#define SCSI_IOCTL_DATA_IN 1
+#define SCSI_IOCTL_DATA_UNSPECIFIED 2
+
+#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \
+ METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_MINIPORT CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \
+ METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \
+ METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \
+ METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \
+ METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_SCSI_GET_ADDRESS CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \
+ METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_PT_WIN32_H */
diff --git a/include/sg_unaligned.h b/include/sg_unaligned.h
new file mode 100644
index 00000000..0a65b391
--- /dev/null
+++ b/include/sg_unaligned.h
@@ -0,0 +1,491 @@
+#ifndef SG_UNALIGNED_H
+#define SG_UNALIGNED_H
+
+/*
+ * Copyright (c) 2014-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdbool.h>
+#include <stdint.h> /* for uint8_t and friends */
+#include <string.h> /* for memcpy */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* These inline functions convert integers (always unsigned) to byte streams
+ * and vice versa. They have two goals:
+ * - change the byte ordering of integers between host order and big
+ * endian ("_be") or little endian ("_le")
+ * - copy the big or little endian byte stream so it complies with any
+ * alignment that host integers require
+ *
+ * Host integer to given endian byte stream is a "_put_" function taking
+ * two arguments (integer and pointer to byte stream) returning void.
+ * Given endian byte stream to host integer is a "_get_" function that takes
+ * one argument and returns an integer of appropriate size (uint32_t for 24
+ * bit operations, uint64_t for 48 bit operations).
+ *
+ * Big endian byte format "on the wire" is the default used by SCSI
+ * standards (www.t10.org). Big endian is also the network byte order.
+ * Little endian is used by ATA, PCI and NVMe.
+ */
+
+/* The generic form of these routines was borrowed from the Linux kernel,
+ * via mhvtl. There is a specialised version of the main functions for
+ * little endian or big endian provided that not-quite-standard defines for
+ * endianness are available from the compiler and the <byteswap.h> header
+ * (a GNU extension) has been detected by ./configure . To force the
+ * generic version, use './configure --disable-fast-lebe ' . */
+
+/* Note: Assumes that the source and destination locations do not overlap.
+ * An example of overlapping source and destination:
+ * sg_put_unaligned_le64(j, ((uint8_t *)&j) + 1);
+ * Best not to do things like that.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h" /* need this to see if HAVE_BYTESWAP_H */
+#endif
+
+#undef GOT_UNALIGNED_SPECIALS /* just in case */
+
+#if defined(__BYTE_ORDER__) && defined(HAVE_BYTESWAP_H) && \
+ ! defined(IGNORE_FAST_LEBE)
+
+#if defined(__LITTLE_ENDIAN__) || (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+
+#define GOT_UNALIGNED_SPECIALS 1
+
+#include <byteswap.h> /* for bswap_16(), bswap_32() and bswap_64() */
+
+// #warning ">>>>>> Doing Little endian special unaligneds"
+
+static inline uint16_t sg_get_unaligned_be16(const void *p)
+{
+ uint16_t u;
+
+ memcpy(&u, p, 2);
+ return bswap_16(u);
+}
+
+static inline uint32_t sg_get_unaligned_be32(const void *p)
+{
+ uint32_t u;
+
+ memcpy(&u, p, 4);
+ return bswap_32(u);
+}
+
+static inline uint64_t sg_get_unaligned_be64(const void *p)
+{
+ uint64_t u;
+
+ memcpy(&u, p, 8);
+ return bswap_64(u);
+}
+
+static inline void sg_put_unaligned_be16(uint16_t val, void *p)
+{
+ uint16_t u = bswap_16(val);
+
+ memcpy(p, &u, 2);
+}
+
+static inline void sg_put_unaligned_be32(uint32_t val, void *p)
+{
+ uint32_t u = bswap_32(val);
+
+ memcpy(p, &u, 4);
+}
+
+static inline void sg_put_unaligned_be64(uint64_t val, void *p)
+{
+ uint64_t u = bswap_64(val);
+
+ memcpy(p, &u, 8);
+}
+
+static inline uint16_t sg_get_unaligned_le16(const void *p)
+{
+ uint16_t u;
+
+ memcpy(&u, p, 2);
+ return u;
+}
+
+static inline uint32_t sg_get_unaligned_le32(const void *p)
+{
+ uint32_t u;
+
+ memcpy(&u, p, 4);
+ return u;
+}
+
+static inline uint64_t sg_get_unaligned_le64(const void *p)
+{
+ uint64_t u;
+
+ memcpy(&u, p, 8);
+ return u;
+}
+
+static inline void sg_put_unaligned_le16(uint16_t val, void *p)
+{
+ memcpy(p, &val, 2);
+}
+
+static inline void sg_put_unaligned_le32(uint32_t val, void *p)
+{
+ memcpy(p, &val, 4);
+}
+
+static inline void sg_put_unaligned_le64(uint64_t val, void *p)
+{
+ memcpy(p, &val, 8);
+}
+
+#elif defined(__BIG_ENDIAN__) || (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+
+#define GOT_UNALIGNED_SPECIALS 1
+
+#include <byteswap.h>
+
+// #warning ">>>>>> Doing BIG endian special unaligneds"
+
+static inline uint16_t sg_get_unaligned_le16(const void *p)
+{
+ uint16_t u;
+
+ memcpy(&u, p, 2);
+ return bswap_16(u);
+}
+
+static inline uint32_t sg_get_unaligned_le32(const void *p)
+{
+ uint32_t u;
+
+ memcpy(&u, p, 4);
+ return bswap_32(u);
+}
+
+static inline uint64_t sg_get_unaligned_le64(const void *p)
+{
+ uint64_t u;
+
+ memcpy(&u, p, 8);
+ return bswap_64(u);
+}
+
+static inline void sg_put_unaligned_le16(uint16_t val, void *p)
+{
+ uint16_t u = bswap_16(val);
+
+ memcpy(p, &u, 2);
+}
+
+static inline void sg_put_unaligned_le32(uint32_t val, void *p)
+{
+ uint32_t u = bswap_32(val);
+
+ memcpy(p, &u, 4);
+}
+
+static inline void sg_put_unaligned_le64(uint64_t val, void *p)
+{
+ uint64_t u = bswap_64(val);
+
+ memcpy(p, &u, 8);
+}
+
+static inline uint16_t sg_get_unaligned_be16(const void *p)
+{
+ uint16_t u;
+
+ memcpy(&u, p, 2);
+ return u;
+}
+
+static inline uint32_t sg_get_unaligned_be32(const void *p)
+{
+ uint32_t u;
+
+ memcpy(&u, p, 4);
+ return u;
+}
+
+static inline uint64_t sg_get_unaligned_be64(const void *p)
+{
+ uint64_t u;
+
+ memcpy(&u, p, 8);
+ return u;
+}
+
+static inline void sg_put_unaligned_be16(uint16_t val, void *p)
+{
+ memcpy(p, &val, 2);
+}
+
+static inline void sg_put_unaligned_be32(uint32_t val, void *p)
+{
+ memcpy(p, &val, 4);
+}
+
+static inline void sg_put_unaligned_be64(uint64_t val, void *p)
+{
+ memcpy(p, &val, 8);
+}
+
+#endif /* __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ */
+#endif /* #if defined __BYTE_ORDER__ && defined <byteswap.h> &&
+ * ! defined IGNORE_FAST_LEBE */
+
+
+#ifndef GOT_UNALIGNED_SPECIALS
+
+/* Now we have no tricks left, so use the only way this can be done
+ * correctly in C safely: lots of shifts. */
+
+// #warning ">>>>>> Doing GENERIC unaligneds"
+
+static inline uint16_t sg_get_unaligned_be16(const void *p)
+{
+ return ((const uint8_t *)p)[0] << 8 | ((const uint8_t *)p)[1];
+}
+
+static inline uint32_t sg_get_unaligned_be32(const void *p)
+{
+ return ((const uint8_t *)p)[0] << 24 | ((const uint8_t *)p)[1] << 16 |
+ ((const uint8_t *)p)[2] << 8 | ((const uint8_t *)p)[3];
+}
+
+static inline uint64_t sg_get_unaligned_be64(const void *p)
+{
+ return (uint64_t)sg_get_unaligned_be32(p) << 32 |
+ sg_get_unaligned_be32((const uint8_t *)p + 4);
+}
+
+static inline void sg_put_unaligned_be16(uint16_t val, void *p)
+{
+ ((uint8_t *)p)[0] = (uint8_t)(val >> 8);
+ ((uint8_t *)p)[1] = (uint8_t)val;
+}
+
+static inline void sg_put_unaligned_be32(uint32_t val, void *p)
+{
+ sg_put_unaligned_be16(val >> 16, p);
+ sg_put_unaligned_be16(val, (uint8_t *)p + 2);
+}
+
+static inline void sg_put_unaligned_be64(uint64_t val, void *p)
+{
+ sg_put_unaligned_be32(val >> 32, p);
+ sg_put_unaligned_be32(val, (uint8_t *)p + 4);
+}
+
+
+static inline uint16_t sg_get_unaligned_le16(const void *p)
+{
+ return ((const uint8_t *)p)[1] << 8 | ((const uint8_t *)p)[0];
+}
+
+static inline uint32_t sg_get_unaligned_le32(const void *p)
+{
+ return ((const uint8_t *)p)[3] << 24 | ((const uint8_t *)p)[2] << 16 |
+ ((const uint8_t *)p)[1] << 8 | ((const uint8_t *)p)[0];
+}
+
+static inline uint64_t sg_get_unaligned_le64(const void *p)
+{
+ return (uint64_t)sg_get_unaligned_le32((const uint8_t *)p + 4) << 32 |
+ sg_get_unaligned_le32(p);
+}
+
+static inline void sg_put_unaligned_le16(uint16_t val, void *p)
+{
+ ((uint8_t *)p)[0] = val & 0xff;
+ ((uint8_t *)p)[1] = val >> 8;
+}
+
+static inline void sg_put_unaligned_le32(uint32_t val, void *p)
+{
+ sg_put_unaligned_le16(val >> 16, (uint8_t *)p + 2);
+ sg_put_unaligned_le16(val, p);
+}
+
+static inline void sg_put_unaligned_le64(uint64_t val, void *p)
+{
+ sg_put_unaligned_le32(val >> 32, (uint8_t *)p + 4);
+ sg_put_unaligned_le32(val, p);
+}
+
+#endif /* #ifndef GOT_UNALIGNED_SPECIALS */
+
+/* Following are lesser used conversions that don't have specializations
+ * for endianness; big endian first. In summary these are the 24, 48 bit and
+ * given-length conversions plus the "nz" conditional put conversions. */
+
+/* Now big endian, get 24+48 then put 24+48 */
+static inline uint32_t sg_get_unaligned_be24(const void *p)
+{
+ return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 |
+ ((const uint8_t *)p)[2];
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_be48(const void *p)
+{
+ return (uint64_t)sg_get_unaligned_be16(p) << 32 |
+ sg_get_unaligned_be32((const uint8_t *)p + 2);
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p)
+{
+ if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+ return 0;
+ else {
+ const uint8_t * xp = (const uint8_t *)p;
+ uint64_t res = *xp;
+
+ for (++xp; num_bytes > 1; ++xp, --num_bytes)
+ res = (res << 8) | *xp;
+ return res;
+ }
+}
+
+static inline void sg_put_unaligned_be24(uint32_t val, void *p)
+{
+ ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+ ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+ ((uint8_t *)p)[2] = val & 0xff;
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_be48(uint64_t val, void *p)
+{
+ sg_put_unaligned_be16(val >> 32, p);
+ sg_put_unaligned_be32(val, (uint8_t *)p + 2);
+}
+
+/* Now little endian, get 24+48 then put 24+48 */
+static inline uint32_t sg_get_unaligned_le24(const void *p)
+{
+ return (uint32_t)sg_get_unaligned_le16(p) |
+ ((const uint8_t *)p)[2] << 16;
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline uint64_t sg_get_unaligned_le48(const void *p)
+{
+ return (uint64_t)sg_get_unaligned_le16((const uint8_t *)p + 4) << 32 |
+ sg_get_unaligned_le32(p);
+}
+
+static inline void sg_put_unaligned_le24(uint32_t val, void *p)
+{
+ ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+ ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+ ((uint8_t *)p)[0] = val & 0xff;
+}
+
+/* Assume 48 bit value placed in uint64_t */
+static inline void sg_put_unaligned_le48(uint64_t val, void *p)
+{
+ ((uint8_t *)p)[5] = (val >> 40) & 0xff;
+ ((uint8_t *)p)[4] = (val >> 32) & 0xff;
+ ((uint8_t *)p)[3] = (val >> 24) & 0xff;
+ ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+ ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+ ((uint8_t *)p)[0] = val & 0xff;
+}
+
+/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than
+ * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is
+ * an 8 byte unsigned integer. */
+static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p)
+{
+ if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t)))
+ return 0;
+ else {
+ const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1);
+ uint64_t res = *xp;
+
+ for (--xp; num_bytes > 1; --xp, --num_bytes)
+ res = (res << 8) | *xp;
+ return res;
+ }
+}
+
+/* Since cdb and parameter blocks are often memset to zero before these
+ * unaligned function partially fill them, then check for a val of zero
+ * and ignore if it is with these variants. First big endian, then little */
+static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_be16(val, p);
+}
+
+static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p)
+{
+ if (val) {
+ ((uint8_t *)p)[0] = (val >> 16) & 0xff;
+ ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+ ((uint8_t *)p)[2] = val & 0xff;
+ }
+}
+
+static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_be32(val, p);
+}
+
+static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_be64(val, p);
+}
+
+static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_le16(val, p);
+}
+
+static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p)
+{
+ if (val) {
+ ((uint8_t *)p)[2] = (val >> 16) & 0xff;
+ ((uint8_t *)p)[1] = (val >> 8) & 0xff;
+ ((uint8_t *)p)[0] = val & 0xff;
+ }
+}
+
+static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_le32(val, p);
+}
+
+static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p)
+{
+ if (val)
+ sg_put_unaligned_le64(val, p);
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_UNALIGNED_H */
diff --git a/inhex/README b/inhex/README
new file mode 100644
index 00000000..77557a05
--- /dev/null
+++ b/inhex/README
@@ -0,0 +1,94 @@
+ Hex data for various sg3_utils utilities
+ ========================================
+
+The files in this folder contain hexadecimal data (in ASCII) and associated
+comments (prefixed with the hash mark symbol: '#' ). Files containing
+hexadecimal data have the extension ".hex". There is at least one file
+containing binary data and it has the extension ".raw".
+
+The utility that each hex file is associated with can be determined in most
+case by prepending "sg_" to these filenames. Then go to the 'src' folder (a
+sibling folder to this one) and look for a match or partial match on
+the name.
+
+For example:
+ vpd_dev_id.hex
+after prepending 'sg_' becomes:
+ sg_vpd_dev_id.hex
+which is a partial match on the sg_vpd utility.
+The remaining 'dev_id.hex' is meant to suggest the 'device identifier'
+VPD page which is a mandatory VPD page for SCSI devices..
+
+Assuming sg3_utils is installed, it can be tested like this:
+ sg_vpd --inhex=<folder_holding_sg3_utils>/inhex/vpd_dev_id.hex
+
+And should output this:
+
+Device Identification VPD page:
+ Addressed logical unit:
+ designator type: NAA, code set: Binary
+ 0x5000c5003011cb2b
+ Target port:
+ designator type: NAA, code set: Binary
+ transport: Serial Attached SCSI Protocol (SPL-4)
+ 0x5000c5003011cb29
+ designator type: Relative target port, code set: Binary
+ transport: Serial Attached SCSI Protocol (SPL-4)
+ Relative target port: 0x1
+ Target device that contains addressed lu:
+ designator type: NAA, code set: Binary
+ transport: Serial Attached SCSI Protocol (SPL-4)
+ 0x5000c5003011cb28
+ designator type: SCSI name string, code set: UTF-8
+ SCSI name string:
+ naa.5000C5003011CB28
+
+Not all the hex files follow the "prepend sg_" pattern. Those hex files
+starting with 'nvme_' are examples of invoking NVMe commands with the
+sg_raw utility.
+
+Binary <--> Hexadecimal
+-----------------------
+The vpd_zbdc.raw file is binary and was created by:
+ sg_decode_sense --inhex=vpd_zbdc.hex --nodecode --write=vpd_zbdc.raw
+as an example of converting a file in ASCII hexadecimal byte oriented
+format to binary.
+
+Turning binary output into hexadecimal can be done several ways. For
+viewing in byte oriented ASCII hex these Unix commands can be used:
+ od -t x1 vpd_zbdc.raw
+ hexdump -C vpd_zbdc.raw
+
+Each line starting with a "input offset" which is a running count of
+bytes, starting at zero. The hexdump examples shows an ASCII rendering
+of those 16 bytes to the right of each line. The sg_decode_sense utility
+may also be used:
+ sg_decode_sense --binary=vpd_zbdc.raw -H
+ sg_decode_sense --binary=vpd_zbdc.raw -HH
+
+The second form of sg_decode_sense appends an ASCII rendering of the 16
+bytes to the right of each line.
+
+When ASCII hexadecimal is being used as input to a utility in this
+package, the "input offset" at the start of each line (and the optional
+ASCII rendering to the right of each line) must not be given.
+That can be done with hexdump:
+ hexdump -An -C -v vpd_zbdc.raw
+ ^^^
+That is a syntax error, there is no 'A' option <<<<<<<< check
+
+And the sg_decode_sense utility can do it with (with the --nodecode option):
+ sg_decode_sense -N --binary=vpd_zbdc.raw -HHH
+That will print suitable lines of hexadecimal (16 bytes per line) to the
+console (stdout) To go the other way (i.e. hexadecimal to binary):
+ sg_decode_sense -N --inhex=vpd_zbdc.hex --write=vpd_zbdc.bin
+
+
+Conclusion
+----------
+Users are encouraged to send the author any ASCII hex files for utilities
+that support --inhex and don't have hex data already. Special cases are
+also welcome. They help the author test this code.
+
+Douglas Gilbert
+18th July 2022
diff --git a/inhex/descriptor_sense.hex b/inhex/descriptor_sense.hex
new file mode 100644
index 00000000..c0fcba97
--- /dev/null
+++ b/inhex/descriptor_sense.hex
@@ -0,0 +1,30 @@
+# Test descriptor format sense data. Values are in hex.
+# Invocation: 'sg_decode_sense -f descriptor_sense.hex' [dpg 20220626]
+
+
+# unrec_err, excessive_writes, sdat_ovfl, additional_len=?
+72 01 03 02 80 00 00 4c
+
+# Information: 0x11223344556677bb
+00 0a 80 00 11 22 33 44 55 66 77 bb
+
+# command specific: 0x3344556677bbccff
+01 0a 00 00 33 44 55 66 77 bb cc ff
+
+# sense key specific: SKSV=1, actual_count=257 (hex: 0x101)
+02 06 00 00 80 01 01 00
+
+# field replaceable code=0x45
+03 02 00 45
+
+# another progress report indicator
+0a 06 02 01 02 00 32 01
+
+# incorrect length indicator (ILI)
+05 02 00 20
+
+# user data segment referral
+0b 1a 01 00
+00 00 00 01 01 02 03 04 05 06 07 08
+01 02 03 04 55 06 07 08
+02 00 12 34
diff --git a/inhex/fixed_sense.hex b/inhex/fixed_sense.hex
new file mode 100644
index 00000000..1c6464b4
--- /dev/null
+++ b/inhex/fixed_sense.hex
@@ -0,0 +1,5 @@
+# Test fixed format sense data. Values are in hex.
+# Invocation: 'sg_decode_sense -f fixed_sense.hex' [dpg 20220622]
+
+f0 00 03 00 00 12 34 0a 00 00 00 00 11 00 77 80
+01 98
diff --git a/inhex/forwarded_sense.hex b/inhex/forwarded_sense.hex
new file mode 100644
index 00000000..968eb2b9
--- /dev/null
+++ b/inhex/forwarded_sense.hex
@@ -0,0 +1,16 @@
+# Test forwarded sense data. Values are in hex.
+# Invocation: 'sg_decode_sense -f forwarded_sense.hex' [dpg 20210330]
+
+# descriptor header
+72 6 18 7 0 0 0 1c
+
+# Forwarded sense [len=12]
+c a 1 2
+72 6 18 7 0 0 0 0
+
+# Information [len=12]
+0 a 80 0 11 22 33 44 55 66 77 88
+
+# FRU [len=4]
+3 2 0
+ 99
diff --git a/inhex/get_elem_status.hex b/inhex/get_elem_status.hex
new file mode 100644
index 00000000..5e72149e
--- /dev/null
+++ b/inhex/get_elem_status.hex
@@ -0,0 +1,51 @@
+#
+# The is a real response to the SCSI GET PHYSICAL ELEMENT STATUS command.
+# The storage elements are associated with heads on this hard disk and
+# one of them (element_id: 10) is "out of spec". The same output was
+# obtained with --report-type=1 (report only storage elements) as this
+# invocation (where --report-type defaults to 0: report all physical
+# elements):
+# sg_get_elem_status -HHH <dev>
+#
+# The hard disk had 9 platters and thus had 18 heads as both side of each
+# platter are used.
+
+00 00 00 12 00 00 00 12 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 02 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 03 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 04 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 05 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 06 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 07 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 08 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 09 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0a 00 00 00 00 00 00 01 65
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0b 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0c 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0d 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0e 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 0f 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 10 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 11 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 01
+ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00
+
diff --git a/inhex/get_lba_status.hex b/inhex/get_lba_status.hex
new file mode 100644
index 00000000..97934fa6
--- /dev/null
+++ b/inhex/get_lba_status.hex
@@ -0,0 +1,14 @@
+
+
+0 0 0 24
+0 0 0 6
+
+0 0 0 0 0 0 0 0
+11 22 33 0
+
+0 0 0 0
+
+0 0 0 0 11 22 33 0
+0 0 0 44
+
+1 1 0 0
diff --git a/inhex/inq_standard.hex b/inhex/inq_standard.hex
new file mode 100644
index 00000000..73c80f37
--- /dev/null
+++ b/inhex/inq_standard.hex
@@ -0,0 +1,23 @@
+# This file contains the ASCII hex of a SCSI INQUIRY command
+# 'standard' response. In this case non-standard responses refers
+# to responses contain VPD page that are also fetched with the
+# SCSI INQUIRY command.
+
+# The response in this file can be decoded with:
+# sg_inq --inhex=inq_standard.hex
+# or
+# sg_vpd --inhex=inq_standard.hex --page=sinq
+#
+# The sg_inq utility defaults to the 'standard' INQUIRY while the
+# sg_vpd utility defaults to the "Supported VPD pages" VPD page.
+# Hence sg_vpd needs the extra option '--page=sinq' which says the
+# VPD page is the standard inquiry. Strictly speaking the standard
+# INQUIRY is not a VPD page but probably would be if SCSI was not
+# 40 years old and highly values backward compatibility.
+
+00 00 07 02 5b 00 10 0a 4c 69 6e 75 78 20 20 20
+73 63 73 69 5f 64 65 62 75 67 20 20 20 20 20 20
+30 31 39 31 32 30 32 31 30 35 32 30 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 c0 05 c0 06 00
+21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
diff --git a/inhex/logs_last_n.hex b/inhex/logs_last_n.hex
new file mode 100644
index 00000000..b6384d17
--- /dev/null
+++ b/inhex/logs_last_n.hex
@@ -0,0 +1,41 @@
+# This file contains the ASCII hex of a SCSI LOG SENSE command responses
+# for the various "Last n" log (sub)pages concaternated together.
+
+# The response in this file can be decoded with:
+# sg_logs --inhex=logs_last_n.hex
+# or
+# sg_logs --inhex=logs_last_n.hex --brief
+# or
+# sg_logs --inhex=logs_last_n.hex --exclude
+
+# Last n mode page data changed log subpage
+4b 02 00 28
+00 00 03 0c 00 00 00 04 00 00 00 02 00 00 00 01
+00 01 03 04 0a 00 00 00
+00 02 03 04 5a 01 00 00
+00 03 03 04 5c 02 00 00
+
+# Last n INQUIRY data changed log subpage
+4b 01 00 28
+00 00 03 0c 00 00 00 01 00 00 00 03 00 00 00 02
+00 01 03 04 00 00 00 00
+00 02 03 04 01 80 00 00
+00 03 03 04 01 83 00 00
+
+# Last n deferred errors or asynchronous events log subpage
+0b 00 00 5a
+00 00 03 40
+73,0,0,0,0,0,0 38
+b,36,1,0
+0,0,0,2,11,11,11,11,22,22,22,22,55,55,55,55,66,66,66,66 1,0,0,7, 2,0,0,8
+0,0,0,1,77,77,77,77,77,77,77,77,88,88,88,88,88,88,88,88, 3,0,0,5
+00 01 03 12
+f1 00 03 00 00 12 34 0a 00 00 00 00 11 00 00 00 00 00
+
+# Last n error events log page
+07 00 00 31
+00 00 01 0c
+6d 65 64 69 75 6d 20 65 72 72 6f 72
+00 01 01 1d
+55 41 3a 20 63 61 70 61 63 69 74 79 20 64 61 74
+61 20 68 61 73 20 63 68 61 6e 67 65 64
diff --git a/inhex/nvme_dev_self_test.hex b/inhex/nvme_dev_self_test.hex
new file mode 100644
index 00000000..1ef87c62
--- /dev/null
+++ b/inhex/nvme_dev_self_test.hex
@@ -0,0 +1,20 @@
+# 64 byte NVMe Device Self Test command (an Admin command) that is suitable
+# for:
+# sg_raw --cmdfile=<this_file_name> <nvme_device>
+#
+# There is no data-in or data-out associated with this command. This command
+# is optional so check the Identify controller command response to see if
+# it is supported.
+#
+# The following invocation will self test the controller and all its
+# namespaces (since nsid=0xffffffff) and does a "short" self test on each
+# one (since CDW10 is 0x1).
+
+14 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# A typical invocation in Linux and FreeBSD would look like this:
+# sg_raw --cmdfile=nvme_dev_self_test.hex /dev/nvme0
+#
diff --git a/inhex/nvme_identify_ctl.hex b/inhex/nvme_identify_ctl.hex
new file mode 100644
index 00000000..f22141e2
--- /dev/null
+++ b/inhex/nvme_identify_ctl.hex
@@ -0,0 +1,27 @@
+# 64 byte NVMe Identify controller command (an Admin command) that is
+# suitable for:
+# sg_raw --cmdfile=<this_file_name> --request=4096 <nvme_device>
+#
+# The address field (at byte offset 24, 8 bytes and little endian) gives
+# special meaning to the highest address pointers:
+# ffffffff fffffffe use address of data-in buffer
+# ffffffff fffffffd use address of data-out buffer
+#
+# The data length field (at byte offset 36, 4 bytes and little endian)
+# gives special meaning to the highest block counts:
+# fffffffe use byte length of data-in buffer
+# fffffffd use byte length of data-out buffer
+#
+# Since The Identify command reads data "in" from the device, then the
+# data-in buffer is appropriate.
+
+06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff
+00 00 00 00 fe ff ff ff 01 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# A typical invocation in Linux and FreeBSD would look like this:
+# sg_raw --cmdfile=nvme_identify_ctl.hex -r 4k /dev/nvme0
+#
+# NVMe likes "4k" (4096 bytes) buffer size, preferably aligned to
+# a 4096 byte (or "page") boundary.
diff --git a/inhex/nvme_read_ctl.hex b/inhex/nvme_read_ctl.hex
new file mode 100644
index 00000000..7996a1ae
--- /dev/null
+++ b/inhex/nvme_read_ctl.hex
@@ -0,0 +1,39 @@
+# 64 byte NVMe, Read command (a NVM command) that is suitable for:
+# sg_raw --cmdfile=<this_file_name> --nvm --request=2048 <nvme_device>
+#
+# The address field (at byte offset 24, 8 bytes and little endian) gives
+# special meaning to the highest address pointers:
+# ffffffff fffffffe use address of data-in buffer
+# ffffffff fffffffd use address of data-out buffer
+#
+# The data length field (at byte offset 36, 4 bytes and little endian)
+# gives special meaning to the highest block counts:
+# fffffffe use byte length of data-in buffer
+# fffffffd use byte length of data-out buffer
+#
+# 512 byte logical block size is assumed. Read 4 blocks hence 2048 bytes.
+# The first LBA read is 0x12345 and the namespace is 1. If successful
+# the four blocks will be read into the data-in buffer. Submission queue
+# 0 is used (the same queue that Admin commands use). The NVM opcode for
+# the Read command is 0x2 and appears in the first command byte.
+
+02 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff
+00 00 00 00 fe ff ff ff 45 23 01 00 00 00 00 00
+03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Notice NVMe uses its quirky "0's based" number of blocks so
+# 03 appears at byte offset 48 to mean "read 4 blocks".
+#
+# A typical invocation in Linux and FreeBSD would look like this:
+# sg_raw --cmdfile=nvme_read_ctl.hex --nvm -r 2048
+# --outfile=t.bin /dev/nvme0n1
+# In FreeBSD the device name would be /dev/nvme0ns1
+#
+# Notice the '--nvm' option which is needed to distinguish a NVM
+# command from an Admin command as Admin commands are the default
+# in this utility.
+#
+# This utility (and most others in the package) aligns data-in and
+# data-out buffers to the beginning of pages which are 4096 bytes
+# long at a minimum. This is the way NVMe likes things as well.
diff --git a/inhex/nvme_read_oob_ctl.hex b/inhex/nvme_read_oob_ctl.hex
new file mode 100644
index 00000000..81d7a305
--- /dev/null
+++ b/inhex/nvme_read_oob_ctl.hex
@@ -0,0 +1,47 @@
+# 64 byte NVMe, Read command (a NVM command) which what should be an
+# Out-of-Bounds LBA (around 377 TB with 512 byte sectors. This file is
+# suitable for:
+# sg_raw --cmdfile=<this_file_name> --nvm --request=2048 <nvme_device>
+#
+# The address field (at byte offset 24, 8 bytes and little endian) gives
+# special meaning to the highest address pointers:
+# ffffffff fffffffe use address of data-in buffer
+# ffffffff fffffffd use address of data-out buffer
+#
+# The data length field (at byte offset 36, 4 bytes and little endian)
+# gives special meaning to the highest block counts:
+# fffffffe use byte length of data-in buffer
+# fffffffd use byte length of data-out buffer
+#
+# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+# This NVMe (NVM) Read command purposely has a very large starting LBA
+# in order to get a "Attempted write to read only range" error. This is
+# to test error reporting.
+# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+#
+# 512 byte logical block size is assumed. Read 4 blocks hence 2048 bytes.
+# The first LBA read is 0xabcd012345 and the namespace is 1. If successful
+# the four blocks will be read into the data-in buffer. Submission queue
+# 0 is used (the same queue that Admin commands use). The NVM opcode for
+# the Read command is 0x2 and appears in the first command byte.
+
+02 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff
+00 00 00 00 fe ff ff ff 45 23 01 cd ab 00 00 00
+03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Notice NVMe uses its quirky "0's based" number of blocks so
+# 03 appears at byte offset 48 to mean "read 4 blocks".
+#
+# A typical invocation in Linux and FreeBSD would look like this:
+# sg_raw --cmdfile=nvme_read_oob_ctl.hex --nvm -r 2048
+# --outfile=t.bin /dev/nvme0n1
+# In FreeBSD the device name would be /dev/nvme0ns1
+#
+# Notice the '--nvm' option which is needed to distinguish a NVM
+# command from an Admin command as Admin commands are the default
+# in this utility.
+#
+# This utility (and most others in the package) aligns data-in and
+# data-out buffers to the beginning of pages which are 4096 bytes
+# long at a minimum. This is the way NVMe likes things as well.
diff --git a/inhex/nvme_write_ctl.hex b/inhex/nvme_write_ctl.hex
new file mode 100644
index 00000000..9e6c1123
--- /dev/null
+++ b/inhex/nvme_write_ctl.hex
@@ -0,0 +1,38 @@
+# 64 byte NVMe, Write command (a NVM command) that is suitable for:
+# sg_raw --cmdfile=<this_file_name> --nvm --request=2048 <nvme_device>
+#
+# The address field (at byte offset 24, 8 bytes and little endian) gives
+# special meaning to the highest address pointers:
+# ffffffff fffffffe use address of data-in buffer
+# ffffffff fffffffd use address of data-out buffer
+#
+# The data length field (at byte offset 36, 4 bytes and little endian)
+# gives special meaning to the highest block counts:
+# fffffffe use byte length of data-in buffer
+# fffffffd use byte length of data-out buffer
+#
+# 512 byte logical block size is assumed. Write 4 blocks hence 2048 bytes.
+# The first LBA written is 0x12345 and the namespace is 1. If successful the
+# four blocks will be written out of the data-out buffer. Submission queue
+# is used (the same queue that Admin commands use). The NVM opcode for the
+# Write command is 0x1 and appears in the first command byte.
+
+01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 fd ff ff ff ff ff ff ff
+00 00 00 00 fd ff ff ff 45 23 01 00 00 00 00 00
+03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Notice NVMe uses its quirky "0's based" number of blocks so
+# 03 appears at byte offset 48 to mean "write 4 blocks".
+#
+# A typical invocation in Linux and FreeBSD would look like this:
+# sg_raw --cmdfile=nvme_write_ctl.hex --nvm -s 2048
+# --infile=t.bin /dev/nvme0
+#
+# Notice the '--nvm' option which is needed to distinguish a NVM
+# command from an Admin command as Admin commands are the default
+# in this utility.
+#
+# This utility (and most others in the package) aligns data-in and
+# data-out buffers to the beginning of pages which are 4096 bytes
+# long at a minimum. This is the way NVMe likes things as well.
diff --git a/inhex/opcodes.hex b/inhex/opcodes.hex
new file mode 100644
index 00000000..8833ab0b
--- /dev/null
+++ b/inhex/opcodes.hex
@@ -0,0 +1,27 @@
+00 00 01 a0 12 00 00 00 00 00 00 06 a0 00 00 00
+00 00 00 0c 03 00 00 00 00 00 00 06 00 00 00 00
+00 00 00 06 5a 00 00 00 00 00 00 0a 1a 00 00 00
+00 00 00 06 55 00 00 00 00 00 00 0a 15 00 00 00
+00 00 00 06 4d 00 00 00 00 00 00 0a 25 00 00 00
+00 00 00 0a 88 00 00 00 00 00 00 10 28 00 00 00
+00 00 00 0a 08 00 00 00 00 00 00 06 a8 00 00 00
+00 00 00 0c 8a 00 00 00 00 00 00 10 2a 00 00 00
+00 00 00 0a 0a 00 00 00 00 00 00 06 aa 00 00 00
+00 00 00 0c 1b 00 00 00 00 00 00 06 9e 00 00 10
+00 01 00 10 9e 00 00 12 00 01 00 10 9f 00 00 12
+00 01 00 10 a3 00 00 0a 00 01 00 0c a3 00 00 0c
+00 01 00 0c a3 00 00 0d 00 01 00 0c 8f 00 00 00
+00 00 00 10 2f 00 00 00 00 00 00 0a 7f 00 00 09
+00 01 00 20 7f 00 00 0b 00 01 00 20 7f 00 00 11
+00 01 00 20 56 00 00 00 00 00 00 0a 16 00 00 00
+00 00 00 06 57 00 00 00 00 00 00 0a 17 00 00 00
+00 00 00 06 1e 00 00 00 00 00 00 06 01 00 00 00
+00 00 00 06 1d 00 00 02 00 00 00 06 42 00 00 00
+00 00 00 0a 3b 00 00 00 00 00 00 0a 41 00 00 00
+00 00 00 0a 93 00 00 00 00 00 00 10 35 00 00 00
+00 00 00 0a 91 00 00 00 00 00 00 10 89 00 00 00
+00 00 00 10 34 00 00 00 00 00 00 0a 90 00 00 00
+00 00 00 10 94 00 00 03 00 01 00 10 94 00 00 01
+00 01 00 10 94 00 00 02 00 01 00 10 94 00 00 04
+00 01 00 10 95 00 00 00 00 01 00 10 95 00 00 06
+00 01 00 10
diff --git a/inhex/ref_sense.hex b/inhex/ref_sense.hex
new file mode 100644
index 00000000..e6a6a374
--- /dev/null
+++ b/inhex/ref_sense.hex
@@ -0,0 +1,7 @@
+# Test User data segment referral sense data. Values are in hex.
+# Invocation: 'sg_decode_sense -f ref_sense.hex' [dpg 20210330]
+
+72,0,0,0,0,0,0 38
+b,36,1,0
+0,0,0,2,11,11,11,11,22,22,22,22,55,55,55,55,66,66,66,66 1,0,0,7, 2,0,0,8
+0,0,0,1,77,77,77,77,77,77,77,77,88,88,88,88,88,88,88,88, 3,0,0,5
diff --git a/inhex/rep_density.hex b/inhex/rep_density.hex
new file mode 100644
index 00000000..0aba1b50
--- /dev/null
+++ b/inhex/rep_density.hex
@@ -0,0 +1,18 @@
+#
+# This file contains the response to SCSI REPORT DENSITY SUPPORTED command
+# using the sg_rep_density utility. This file was generated with:
+# sg_rep_density -HHH /dev/sg4 > rep_density.hex
+# where /dev/sg4 was a LTO-4 tape drive. To decode that file containing
+# hexadecimal in ASCII use:
+# sg_rep_density --inhex=rep_density.hex
+
+00 9e 00 00 44 44 00 00 00 00 25 a6 00 7f 02 c0
+00 06 1a 80 4c 54 4f 2d 43 56 45 20 55 2d 33 31
+36 20 20 20 55 6c 74 72 69 75 6d 20 33 2f 31 36
+54 20 20 20 20 20 20 20 46 46 80 00 00 00 31 b5
+00 7f 03 80 00 0c 35 00 4c 54 4f 2d 43 56 45 20
+55 2d 34 31 36 20 20 20 55 6c 74 72 69 75 6d 20
+34 2f 31 36 54 20 20 20 20 20 20 20 58 58 a0 00
+00 00 3b 26 00 7f 05 00 00 16 e3 60 4c 54 4f 2d
+43 56 45 20 55 2d 35 31 36 20 20 20 55 6c 74 72
+69 75 6d 20 35 2f 31 36 54 20 20 20 20 20 20 20
diff --git a/inhex/rep_density_media.hex b/inhex/rep_density_media.hex
new file mode 100644
index 00000000..22312fdf
--- /dev/null
+++ b/inhex/rep_density_media.hex
@@ -0,0 +1,13 @@
+#
+# This file contains the response to SCSI REPORT DENSITY SUPPORTED command
+# using the sg_rep_density utility. This file was generated with:
+# sg_rep_density --media -HHH /dev/sg4 > rep_density_media.hex
+# where /dev/sg4 was a LTO-4 tape drive. To decode that file containing
+# hexadecimal in ASCII use:
+# sg_rep_density --inhex=rep_density_media.hex
+# The --media option is not needed in the decode invocation.
+
+00 36 00 00 58 58 a0 00 00 00 3b 26 00 7f 05 00
+00 17 85 3e 4c 54 4f 2d 43 56 45 20 55 2d 35 31
+36 20 20 20 55 6c 74 72 69 75 6d 20 35 2f 31 36
+54 20 20 20 20 20 20 20
diff --git a/inhex/rep_density_media_typem.hex b/inhex/rep_density_media_typem.hex
new file mode 100644
index 00000000..57918fe8
--- /dev/null
+++ b/inhex/rep_density_media_typem.hex
@@ -0,0 +1,13 @@
+#
+# This file contains the response to SCSI REPORT DENSITY SUPPORTED command
+# using the sg_rep_density utility. This file was generated with:
+# sg_rep_density -M -t -HHH /dev/sg4 > rep_density_media_typem.hex
+# where /dev/sg4 was a LTO-4 tape drive. To decode that file containing
+# hexadecimal in ASCII use:
+# sg_rep_density --typem -i rep_density_media_typem.hex
+# The --typem option is required in the decode invocation.
+
+00 3a 00 00 00 00 00 34 01 58 00 00 00 00 00 00
+00 00 00 7f 03 4e 00 00 48 50 20 20 20 20 20 20
+4c 54 4f 35 44 61 74 61 55 6c 74 72 69 75 6d 20
+35 20 44 61 74 61 20 54 61 70 65 20
diff --git a/inhex/rep_density_typem.hex b/inhex/rep_density_typem.hex
new file mode 100644
index 00000000..315d6907
--- /dev/null
+++ b/inhex/rep_density_typem.hex
@@ -0,0 +1,31 @@
+#
+# This file contains the response to SCSI REPORT DENSITY SUPPORTED command
+# using the sg_rep_density utility. This file was generated with:
+# sg_rep_density --typem -HHH /dev/sg4 > rep_density_typem.hex
+# where /dev/sg4 was a LTO-4 tape drive. To decode that file containing
+# hexadecimal in ASCII use:
+# sg_rep_density --typem -i rep_density_typem.hex
+# The --typem option is required in the decode invocation.
+
+01 52 00 00 00 00 00 34 01 44 00 00 00 00 00 00
+00 00 00 7f 02 a8 00 00 48 50 20 20 20 20 20 20
+4c 54 4f 33 44 61 74 61 55 6c 74 72 69 75 6d 20
+33 20 44 61 74 61 20 54 61 70 65 20 00 00 00 34
+01 46 00 00 00 00 00 00 00 00 00 7f 03 34 00 00
+48 50 20 20 20 20 20 20 4c 54 4f 34 44 61 74 61
+55 6c 74 72 69 75 6d 20 34 20 44 61 74 61 20 54
+61 70 65 20 00 00 00 34 01 58 00 00 00 00 00 00
+00 00 00 7f 03 4e 00 00 48 50 20 20 20 20 20 20
+4c 54 4f 35 44 61 74 61 55 6c 74 72 69 75 6d 20
+35 20 44 61 74 61 20 54 61 70 65 20 01 00 00 34
+01 44 00 00 00 00 00 00 00 00 00 7f 02 a8 00 00
+48 50 20 20 20 20 20 20 4c 54 4f 33 57 4f 52 4d
+55 6c 74 72 69 75 6d 20 33 20 57 4f 52 4d 20 54
+61 70 65 20 01 00 00 34 01 46 00 00 00 00 00 00
+00 00 00 7f 03 34 00 00 48 50 20 20 20 20 20 20
+4c 54 4f 34 57 4f 52 4d 55 6c 74 72 69 75 6d 20
+34 20 57 4f 52 4d 20 54 61 70 65 20 01 00 00 34
+01 58 00 00 00 00 00 00 00 00 00 7f 03 4e 00 00
+48 50 20 20 20 20 20 20 4c 54 4f 35 57 4f 52 4d
+55 6c 74 72 69 75 6d 20 35 20 57 4f 52 4d 20 54
+61 70 65 20
diff --git a/inhex/rep_realms.hex b/inhex/rep_realms.hex
new file mode 100644
index 00000000..08df66b9
--- /dev/null
+++ b/inhex/rep_realms.hex
@@ -0,0 +1,35 @@
+# This is the output (in hex) of the SCSI REPORT REALMS command.
+# This page is constructed from the command description in zbc2r10
+# with three realms, and two zone domains
+
+# A typical example:
+# sg_rep_zones --realm --inhex=rep_realms.hex
+
+
+# parameter data header (64 bytes)
+00 00 00 90 00 00 00 03 00 00 00 30 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# first zone domain descriptor
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+# first realm descriptor, zone domain 0 start,last
+00 00 00 00 00 00 00 00 00 00 00 00 00 03 ff ff
+# first realm descriptor, zone domain 1 start,last
+00 00 00 00 00 04 00 00 00 00 00 00 00 07 ff ff
+
+# second zone domain descriptor
+00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00
+# second realm descriptor, zone domain 0 start,last
+00 00 00 00 00 08 00 00 00 00 00 00 00 0b ff ff
+# second realm descriptor, zone domain 1 start,last
+00 00 00 00 00 0c 00 00 00 00 00 00 00 0f ff ff
+
+# third realm descriptor
+00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00
+# second realm descriptor, zone domain 0 start,last
+00 00 00 00 00 10 00 00 00 00 00 00 00 13 ff ff
+# second realm descriptor, zone domain 1 start,last
+00 00 00 00 00 14 00 00 00 00 00 00 00 17 ff ff
+
diff --git a/inhex/rep_zdomains.hex b/inhex/rep_zdomains.hex
new file mode 100644
index 00000000..346f2c0b
--- /dev/null
+++ b/inhex/rep_zdomains.hex
@@ -0,0 +1,29 @@
+# This is the output (in hex) of the SCSI REPORT ZONE DOMAINS command.
+# This page is constructed from the command description in zbc2r10
+# with two zone domains.
+
+# A typical example:
+# sg_rep_zones --domain --inhex=rep_zdomains.hex
+
+# parameter data header (64 bytes)
+00 00 00 c0 00 00 00 c0 02 02 00 30 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# first zone domain
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00
+00 00 00 00 00 13 ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# second zone domain
+01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 03 00 00 00 00 00 04 00 00
+00 00 00 00 00 17 ff ff 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
diff --git a/inhex/rep_zones.hex b/inhex/rep_zones.hex
new file mode 100644
index 00000000..702c93c5
--- /dev/null
+++ b/inhex/rep_zones.hex
@@ -0,0 +1,39 @@
+# This is the output (in hex) of the SCSI REPORT ZONES command
+# from a simulated ZBC device from the scsi_debug driver in Linux.
+# The parameters to the scsi_debug driver given to modprobe were:
+# dev_size_mb=512 zbc=managed zone_size_mb=128 zone_nr_conv=1
+#
+# The hex bytes in this file were generated by:
+# sg_rep_zones /dev/sg1 -HHH > /tmp/rep_zones.hex
+# where /dev/sg1 was a scsi_debug device.
+
+# An example invocation:
+# sg_rep_zones --inhex=rep_zones.hex
+
+
+# parameter data header (64 bytes)
+00 00 01 00 00 00 00 00 00 00 00 00 00 0f ff ff
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# first zone descriptor, zone type: conventional
+01 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00
+00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+# second zone descriptor, zone type: sequential write required
+02 10 00 00 00 00 00 00 00 00 00 00 00 04 00 00
+00 00 00 00 00 04 00 00 00 00 00 00 00 04 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+# third zone descriptor, zone type: sequential write required
+02 10 00 00 00 00 00 00 00 00 00 00 00 04 00 00
+00 00 00 00 00 08 00 00 00 00 00 00 00 08 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+# fourth and last zone descriptor, zone type: sequential write required
+02 10 00 00 00 00 00 00 00 00 00 00 00 04 00 00
+00 00 00 00 00 0c 00 00 00 00 00 00 00 0c 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
diff --git a/inhex/ses_areca_all.hex b/inhex/ses_areca_all.hex
new file mode 100644
index 00000000..b744321c
--- /dev/null
+++ b/inhex/ses_areca_all.hex
@@ -0,0 +1,195 @@
+#
+# This file was generated by something like:
+# sg_ses --page=all -HHHH /dev/sg5 > ses_areca_all.hex
+# where /dev/sg5 was an Areca 8028 SAS-3 expander
+
+# An example invocation to decode the hex data below:
+# sg_ses --all --status --inhex=ses_areca_all.hex
+#
+# vvvvvvvvvv generated part of file below vvvvvvvvvvvvvv
+
+# Supported Diagnostic Pages dpage:
+00 00 00 0b 00 01 02 04 05 07 0a 0d 0e 0f 3f
+
+# Configuration (SES) dpage:
+01 00 01 28 00 00 00 00 11 00 09 2c d5 b4 01 50
+3f c0 ec 16 41 72 65 63 61 20 20 20 41 52 43 2d
+38 30 32 38 30 31 2e 33 33 2e 36 33 30 31 33 33
+11 22 33 44 55 00 00 00 17 18 00 18 0e 01 00 1c
+18 01 00 0c 03 05 00 1a 04 02 00 17 12 02 00 1a
+19 03 00 16 02 02 00 17 06 01 00 18 41 72 72 61
+79 44 65 76 69 63 65 73 49 6e 53 75 62 45 6e 63
+6c 73 72 30 45 6e 63 6c 6f 73 75 72 65 45 6c 65
+6d 65 6e 74 49 6e 53 75 62 45 6e 63 6c 73 72 30
+53 41 53 20 45 78 70 61 6e 64 65 72 43 6f 6f 6c
+69 6e 67 45 6c 65 6d 65 6e 74 49 6e 53 75 62 45
+6e 63 6c 73 72 30 54 65 6d 70 53 65 6e 73 6f 72
+73 49 6e 53 75 62 45 6e 63 6c 73 72 30 56 6f 6c
+74 61 67 65 53 65 6e 73 6f 72 73 49 6e 53 75 62
+45 6e 63 6c 73 72 30 43 6f 6e 6e 65 63 74 6f 72
+73 49 6e 53 75 62 45 6e 63 6c 73 72 30 50 6f 77
+65 72 53 75 70 70 6c 79 49 6e 53 75 62 45 6e 63
+6c 73 72 30 41 75 64 69 62 6c 65 41 6c 61 72 6d
+49 6e 53 75 62 45 6e 63 6c 73 72 30
+
+# Enclosure Status (SES) dpage:
+02 02 00 cc 00 00 00 00 00 00 00 00 05 00 00 00
+05 00 00 00 05 00 00 00 05 00 00 00 05 00 00 00
+05 00 00 00 05 00 00 00 05 00 00 00 05 00 00 00
+05 00 00 00 05 00 00 00 05 00 00 00 05 00 00 00
+05 00 00 00 05 00 00 00 05 00 00 00 05 00 00 00
+05 00 00 00 01 00 00 00 05 00 00 00 05 00 00 00
+05 00 00 00 05 00 00 00 05 00 00 00 00 00 00 00
+01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+05 00 00 10 05 00 00 10 05 00 00 10 05 00 00 10
+01 02 ee 07 00 00 00 00 01 00 45 00 01 00 56 00
+00 00 00 00 01 00 00 5e 01 00 00 b4 00 00 00 00
+01 05 00 00 01 05 00 00 01 05 00 00 00 00 00 00
+05 00 00 20 05 00 00 20 00 00 00 00 01 00 00 00
+
+# String In (SES) dpage:
+04 00 00 2e 57 65 20 68 61 76 65 20 69 6d 70 6c
+65 6d 65 6e 74 65 64 20 53 74 72 69 6e 67 20 49
+6e 20 44 69 61 67 6e 6f 73 74 69 63 20 50 61 67
+65 00
+
+# Threshold In (SES) dpage:
+05 00 00 c4 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 63 50 19 14 73 6e 19 14
+00 00 00 00 82 7f 70 6d 7a 77 69 66 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+
+# Element Descriptor (SES) dpage:
+07 00 03 0e 00 00 00 00 00 00 00 18 41 72 72 61
+79 44 65 76 69 63 65 73 49 6e 53 75 62 45 6e 63
+6c 73 72 30 00 00 00 08 53 4c 4f 54 20 30 31 00
+00 00 00 08 53 4c 4f 54 20 30 32 00 00 00 00 08
+53 4c 4f 54 20 30 33 00 00 00 00 08 53 4c 4f 54
+20 30 34 00 00 00 00 08 53 4c 4f 54 20 30 35 00
+00 00 00 08 53 4c 4f 54 20 30 36 00 00 00 00 08
+53 4c 4f 54 20 30 37 00 00 00 00 08 53 4c 4f 54
+20 30 38 00 00 00 00 08 53 4c 4f 54 20 30 39 00
+00 00 00 08 53 4c 4f 54 20 31 30 00 00 00 00 08
+53 4c 4f 54 20 31 31 00 00 00 00 08 53 4c 4f 54
+20 31 32 00 00 00 00 08 53 4c 4f 54 20 31 33 00
+00 00 00 08 53 4c 4f 54 20 31 34 00 00 00 00 08
+53 4c 4f 54 20 31 35 00 00 00 00 08 53 4c 4f 54
+20 31 36 00 00 00 00 08 53 4c 4f 54 20 31 37 00
+00 00 00 08 53 4c 4f 54 20 31 38 00 00 00 00 08
+53 4c 4f 54 20 31 39 00 00 00 00 08 53 4c 4f 54
+20 32 30 00 00 00 00 08 53 4c 4f 54 20 32 31 00
+00 00 00 08 53 4c 4f 54 20 32 32 00 00 00 00 08
+53 4c 4f 54 20 32 33 00 00 00 00 08 53 4c 4f 54
+20 32 34 00 00 00 00 1c 45 6e 63 6c 6f 73 75 72
+65 45 6c 65 6d 65 6e 74 49 6e 53 75 62 45 6e 63
+6c 73 72 30 00 00 00 12 45 6e 63 6c 6f 73 75 72
+65 45 6c 65 6d 65 6e 74 30 31 00 00 00 0c 53 41
+53 20 45 78 70 61 6e 64 65 72 00 00 00 09 45 78
+70 61 6e 64 65 72 30 00 00 00 1a 43 6f 6f 6c 69
+6e 67 45 6c 65 6d 65 6e 74 49 6e 53 75 62 45 6e
+63 6c 73 72 30 00 00 00 07 46 61 6e 20 30 31 00
+00 00 00 07 46 61 6e 20 30 32 00 00 00 00 07 46
+61 6e 20 30 33 00 00 00 00 07 46 61 6e 20 30 34
+00 00 00 00 07 43 50 55 46 61 6e 00 00 00 00 17
+54 65 6d 70 53 65 6e 73 6f 72 73 49 6e 53 75 62
+45 6e 63 6c 73 72 30 00 00 00 0c 45 4e 43 2e 20
+54 65 6d 70 20 20 00 00 00 00 0c 43 68 69 70 20
+54 65 6d 70 20 20 00 00 00 00 1a 56 6f 6c 74 61
+67 65 53 65 6e 73 6f 72 73 49 6e 53 75 62 45 6e
+63 6c 73 72 30 00 00 00 07 30 2e 39 35 56 20 00
+00 00 00 07 31 2e 38 56 20 20 00 00 00 00 16 43
+6f 6e 6e 65 63 74 6f 72 73 49 6e 53 75 62 45 6e
+63 6c 73 72 30 00 00 00 0c 43 6f 6e 6e 65 63 74
+6f 72 30 30 00 00 00 00 0c 43 6f 6e 6e 65 63 74
+6f 72 30 31 00 00 00 00 0c 43 6f 6e 6e 65 63 74
+6f 72 30 32 00 00 00 00 17 50 6f 77 65 72 53 75
+70 70 6c 79 49 6e 53 75 62 45 6e 63 6c 73 72 30
+00 00 00 0e 50 6f 77 65 72 53 75 70 70 6c 79 30
+31 00 00 00 00 0e 50 6f 77 65 72 53 75 70 70 6c
+79 30 32 00 00 00 00 18 41 75 64 69 62 6c 65 41
+6c 61 72 6d 49 6e 53 75 62 45 6e 63 6c 73 72 30
+00 00 00 0e 41 75 64 69 62 6c 65 2d 41 6c 61 72
+6d 00
+
+# Additional Element Status (SES-2) dpage:
+0a 00 03 bc 00 00 00 00 16 22 00 00 01 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 16 22 00 01
+01 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+16 22 00 02 01 00 00 02 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 16 22 00 03 01 00 00 03 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 16 22 00 04 01 00 00 04
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 16 22 00 05
+01 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+16 22 00 06 01 00 00 06 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 16 22 00 07 01 00 00 07 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 16 22 00 08 01 00 00 08
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 16 22 00 09
+01 00 00 09 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+16 22 00 0a 01 00 00 0a 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 16 22 00 0b 01 00 00 0b 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 16 22 00 0c 01 00 00 0c
+20 00 00 02 50 01 b4 d5 16 ec c0 3f 50 01 51 7e
+85 c3 ef ff 14 00 00 00 00 00 00 00 16 22 00 0d
+01 00 00 0d 20 00 00 02 50 01 b4 d5 16 ec c0 3f
+50 01 51 7e 85 c3 ef ff 15 00 00 00 00 00 00 00
+16 22 00 0e 01 00 00 0e 20 00 00 02 50 01 b4 d5
+16 ec c0 3f 50 01 51 7e 85 c3 ef ff 16 00 00 00
+00 00 00 00 16 22 00 0f 01 00 00 0f 20 00 00 02
+50 01 b4 d5 16 ec c0 3f 50 01 51 7e 85 c3 ef ff
+17 00 00 00 00 00 00 00 16 22 00 10 01 00 00 10
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 16 22 00 11
+01 00 00 11 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+16 22 00 12 01 00 00 12 10 00 00 08 50 01 b4 d5
+16 ec c0 3f 50 00 c5 00 30 11 cb 29 00 00 00 00
+00 00 00 00 16 22 00 13 01 00 00 13 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 16 22 00 14 01 00 00 14
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 16 22 00 15
+01 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+16 22 00 16 01 00 00 16 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 16 22 00 17 01 00 00 17 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 16 56 00 00 24 40 00 00
+50 01 b4 d5 16 ec c0 3f ff 0d ff 0c ff 0e ff 0f
+ff 09 ff 08 ff 0a ff 0b ff 05 ff 04 ff 06 ff 07
+ff 01 ff 00 ff 02 ff 03 02 ff 02 ff 02 ff 02 ff
+01 ff 01 ff 01 ff 01 ff 00 ff 00 ff 00 ff 00 ff
+ff 11 ff 10 ff 12 ff 13 ff 15 ff 14 ff 16 ff 17
+
+# Supported SES Diagnostic Pages (SES-2) dpage:
+0d 00 00 0c 01 02 04 05 07 0a 0d 0e 0f 00 00 00
+
+# Download Microcode (SES-2) dpage:
+0e 00 00 14 00 00 00 00 00 00 00 00 00 10 00 00
+00 00 00 00 00 00 00 00
+
+# Subenclosure Nickname (SES-2) dpage:
+0f 00 00 2c 00 00 00 00 00 00 00 00 00 00 00 00
+45 76 61 6c 20 42 6f 61 72 64 20 4e 69 63 6b 6e
+61 6d 65 20 53 69 6d 75 6c 61 74 6f 72 20 20 20
diff --git a/inhex/vpd_bdce.hex b/inhex/vpd_bdce.hex
new file mode 100644
index 00000000..d20c5304
--- /dev/null
+++ b/inhex/vpd_bdce.hex
@@ -0,0 +1,18 @@
+#
+# This is a manufactured response to an INQUIRY for the
+# Block device characteristics extension VPD page (0xb5 ["bdce"]).
+# It may have been generated by a call like this:
+# sg_vpd -p bdce /dev/sg3 -HHHH
+
+# Block device characteristics extension VPD page
+00 b5 00 7c
+00 03 05 0e 00 00 00 06 00 00 00 03
+
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
diff --git a/inhex/vpd_consistuents.hex b/inhex/vpd_consistuents.hex
new file mode 100644
index 00000000..4a67b74d
--- /dev/null
+++ b/inhex/vpd_consistuents.hex
@@ -0,0 +1,47 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_consistuents.hex
+
+
+# Device constituent VPD page header
+00 8b 00 c2
+
+# First constituent descriptor, fixed part
+00 03 00 00
+41 42 43 44 20 20 00 00
+41 42 43 44 45 46 47 48 41 42 43 44 44 44 44 44
+30 31 32 33
+00 00
+00 2a
+
+# inner constituent specific descriptor (for VPD page)
+01 00 00 10
+# ... the VPD page
+00 b3 00 0c 00 00 00 00
+00 20 00 00
+00 00 00 04
+
+# another inner constituent specific descriptor (for VPD page)
+01 00 00 12
+# ... the VPD page
+00 92 00 0e 00 00 00 00
+00 01 01 01 01 02 02 01
+09 09
+
+
+# Second constituent descriptor, fixed part
+00 03 00 00
+53 45 41 47 41 54 45 20
+53 54 32 30 30 46 4d 30 30 37 33 20 20 20 20 20
+30 30 30 37
+00 00
+00 50
+
+# inner constituent specific descriptor ("di" VPD page)
+01 00 00 4c
+# ... the VPD page
+00 83 00 48 01 03 00 08 50 00 c5 00 30 11 cb 2b
+61 93 00 08 50 00 c5 00 30 11 cb 29 61 94 00 04
+00 00 00 01 61 a3 00 08 50 00 c5 00 30 11 cb 28
+03 28 00 18 6e 61 61 2e 35 30 30 30 43 35 30 30
+33 30 31 31 43 42 32 38 00 00 00 00
diff --git a/inhex/vpd_cpr.hex b/inhex/vpd_cpr.hex
new file mode 100644
index 00000000..130c5c14
--- /dev/null
+++ b/inhex/vpd_cpr.hex
@@ -0,0 +1,18 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_cpr.hex
+
+# Dummy data for Concurrent positioning ranges VPD page
+00 b9 00 7c 00 00 00 00
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+# after 64 byte header there is the first LBA range descriptor (32 bytes)
+01 02 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 10 00 00 00
+ 00 00 00 00 00 00 00 00
+# second LBA range descriptor (32 bytes)
+02 02 00 00 00 00 00 00
+00 00 00 00 10 00 00 00
+00 00 00 00 10 00 00 00
+ 00 00 00 00 00 00 00 00
diff --git a/inhex/vpd_dev_id.hex b/inhex/vpd_dev_id.hex
new file mode 100644
index 00000000..e9cb9907
--- /dev/null
+++ b/inhex/vpd_dev_id.hex
@@ -0,0 +1,9 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_dev_id.hex
+
+00 83 00 48 01 03 00 08 50 00 c5 00 30 11 cb 2b
+61 93 00 08 50 00 c5 00 30 11 cb 29 61 94 00 04
+00 00 00 01 61 a3 00 08 50 00 c5 00 30 11 cb 28
+03 28 00 18 6e 61 61 2e 35 30 30 30 43 35 30 30
+33 30 31 31 43 42 32 38 00 00 00 00
diff --git a/inhex/vpd_di_all.hex b/inhex/vpd_di_all.hex
new file mode 100644
index 00000000..b0c93765
--- /dev/null
+++ b/inhex/vpd_di_all.hex
@@ -0,0 +1,51 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_di_all.hex
+
+00 83 01 04
+
+# Vendor specific designator
+01 00 00 16 11 22 33 44 55 66 77 88 99 aa bb cc
+dd ee ff ed cb a9 87 65 43 21
+
+# T10 vendor ID
+02 01 00 14
+41 42 43 20 20 20 20 20
+58 59 5a 31 32 33 34 35 36 37 38 39
+
+# EUI-64
+01 02 00 08 11 22 33 44 55 66 77 88
+01 02 00 0c 11 22 33 44 55 66 77 88 00 00 01 23
+01 02 00 10 01 23 45 67 89 ab cd ef 11 22 33 44 55 66 77 88
+
+# NAA
+01 03 00 08 51 22 33 44 55 66 77 88
+01 03 00 10 61 22 33 44 55 66 77 88 aa bb cc dd ee ff ee dd
+
+# Relative target port
+01 14 00 04 00 00 00 02
+
+# Target port group
+01 15 00 04 00 00 00 03
+
+# Logical unit group
+01 06 00 04 00 00 00 04
+
+# MD5 logical unitp
+01 07 00 10 ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00
+
+# SCSI name string: iqn.5886.com.acme.diskarrays-sn-a8675309
+02 28 00 28
+69 71 6e 2e 35 38 38 36 2e 63 6f 6d 2e 61 63 6d
+65 2e 64 69 73 6b 61 72 72 61 79 73 2d 73 6e 2d
+61 38 36 37 35 33 30 39
+
+# Protocol specific
+# USB
+91 99 00 04 04 00 02 00
+# PCIe
+a1 99 00 08 01 23 00 00 00 00 00 00
+
+# UUID
+01 0a 00 12 10 00 11 22 33 44 55 66 77 88 99 aa
+bb cc dd ee fe dc
diff --git a/inhex/vpd_fp.hex b/inhex/vpd_fp.hex
new file mode 100644
index 00000000..967080f5
--- /dev/null
+++ b/inhex/vpd_fp.hex
@@ -0,0 +1,31 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_fp.hex
+
+# Dummy data for Format presets VPD page
+00 b8 00 80
+
+# after 4 byte header here is the first Format preset descriptor (64 bytes)
+00 00 01 00
+01 0 0 00
+01 00 00 00
+0 0 0 0
+00 00 00 00 00 ff ff ff # last LBA
+0 0 0 0 0 0 0 0 0 0 0 0 0 0
+00 00 # FMPTINFO, Protection field usage and protection interval exp
+0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+0 0 0 0 0 0 0 0
+
+# second Format preset descriptor (64 bytes)
+00 00 01 01
+02 0 0 00
+01 00 00 00
+0 0 0 0
+00 00 00 00 01 ff ff ff # last LBA
+0 0 0 0 0 0 0 0 0 0 0 0 0 0
+00 00 # FMPTINFO, Protection field usage and protection interval exp
+# host-aware zones schema type specific information
+5 ff
+0 0 0 0 0 0 0 0 0 0
+20 00 00 00
+0 0 0 0
diff --git a/inhex/vpd_lbpro.hex b/inhex/vpd_lbpro.hex
new file mode 100644
index 00000000..188666c0
--- /dev/null
+++ b/inhex/vpd_lbpro.hex
@@ -0,0 +1,7 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_lbpro.hex
+
+01 b5 00 10 00 00 00 00
+03 01 20 e0
+07 02 10 80 00 00 00 00
diff --git a/inhex/vpd_lbpv.hex b/inhex/vpd_lbpv.hex
new file mode 100644
index 00000000..a89af17b
--- /dev/null
+++ b/inhex/vpd_lbpv.hex
@@ -0,0 +1,9 @@
+#
+# This is a manufactured response to an INQUIRY for the
+# Logical Block Provisioning VPD page (0xb2 ["lbpv"]). It may
+# have been generated by a call like this:
+# sg_vpd -p lbpv /dev/sg3 -HHHH
+
+# Logical block provisioning VPD page
+00 b2 00 10 00 01 00 00
+01 03 00 08 51 22 33 44 55 66 77 88
diff --git a/inhex/vpd_ref.hex b/inhex/vpd_ref.hex
new file mode 100644
index 00000000..618f911f
--- /dev/null
+++ b/inhex/vpd_ref.hex
@@ -0,0 +1,9 @@
+#
+# This is a manufactured response to an INQUIRY for the
+# Referrals VPD page (0xb3 ["ref"]). It may have been
+# generated by a call like this:
+# sg_vpd -p ref /dev/sg3 -HHHH
+
+# Referrals VPD page
+00 b3 00 0c 00 00 00 00
+11 22 33 44 00 00 10 00
diff --git a/inhex/vpd_sbl.hex b/inhex/vpd_sbl.hex
new file mode 100644
index 00000000..da9e9b1e
--- /dev/null
+++ b/inhex/vpd_sbl.hex
@@ -0,0 +1,10 @@
+#
+# This is a manufactured response to an INQUIRY for the
+# Supported block lengths and protection types VPD page (0xb4 ["sbl"]).
+# It may have been generated by a call like this:
+# sg_vpd -p sbl /dev/sg3 -HHHH
+
+# Supported block lengths and protection types VPD page
+00 b4 00 10
+00 00 02 00 47 07 00 00
+00 00 10 00 47 07 00 00
diff --git a/inhex/vpd_sdeb.hex b/inhex/vpd_sdeb.hex
new file mode 100644
index 00000000..2f50c896
--- /dev/null
+++ b/inhex/vpd_sdeb.hex
@@ -0,0 +1,99 @@
+#
+# The VPD responses in this file where generated from a dummy device
+# (ramdisk) associated with the Linux scsi_debug driver as follows:
+# sg_vpd -a /dev/sg3 -HHHH
+
+# Supported VPD pages VPD page
+00 00 00 0c 00 80 83 84 85 86 87 88 89 b0 b1 b2
+
+# Unit serial number VPD page
+00 80 00 04 32 30 30 30
+
+# Device identification VPD page
+00 83 00 70 02 01 00 1c 4c 69 6e 75 78 20 20 20
+73 63 73 69 5f 64 65 62 75 67 20 20 20 20 20 20
+32 30 30 30 01 03 00 08 33 33 33 30 00 00 07 d0
+61 94 00 04 00 00 00 01 61 93 00 08 32 22 22 20
+00 00 07 ce 61 95 00 04 00 00 01 00 61 a3 00 08
+32 22 22 20 00 00 07 cd 63 a8 00 18 6e 61 61 2e
+33 32 32 32 32 32 32 30 30 30 30 30 30 37 43 44
+00 00 00 00
+
+# Software interface identification VPD page
+00 84 00 12 22 22 22 00 bb 00 22 22 22 00 bb 01
+22 22 22 00 bb 02
+
+# Management network addresses VPD page
+00 85 00 44 01 00 00 20 68 74 74 70 73 3a 2f 2f
+77 77 77 2e 6b 65 72 6e 65 6c 2e 6f 72 67 2f 63
+6f 6e 66 69 67 00 00 00 04 00 00 1c 68 74 74 70
+3a 2f 2f 77 77 77 2e 6b 65 72 6e 65 6c 2e 6f 72
+67 2f 6c 6f 67 00 00 00
+
+# extended INQUIRY data VPD page
+00 86 00 3c 00 07 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Mode page policy VPD page
+00 87 00 08 02 00 80 00 18 00 82 00
+
+# SCSI Ports VPD page
+00 88 00 30 00 00 00 01 00 00 00 00 00 00 00 0c
+61 93 00 08 32 22 22 20 00 00 07 ce 00 00 00 02
+00 00 00 00 00 00 00 0c 61 93 00 08 32 22 22 20
+00 00 07 cf
+
+# ATA information VPD page
+00 89 02 38 00 00 00 00 6c 69 6e 75 78 20 20 20
+53 41 54 20 73 63 73 69 5f 64 65 62 75 67 20 20
+31 32 33 34 34 00 00 00 01 00 00 00 00 00 00 00
+01 00 00 00 00 00 00 00 ec 00 00 00 5a 0c ff 3f
+37 c8 10 00 00 00 00 00 3f 00 00 00 00 00 00 00
+58 58 58 58 58 58 58 58 20 20 20 20 20 20 20 20
+20 20 20 20 00 00 00 40 04 00 2e 33 38 31 20 20
+20 20 54 53 38 33 30 30 33 31 53 41 20 20 20 20
+20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
+20 20 20 20 20 20 20 20 20 20 10 80 00 00 00 2f
+00 00 00 02 00 02 07 00 ff ff 01 00 3f 00 c1 ff
+3e 00 10 01 b0 f8 50 09 00 00 07 00 03 00 78 00
+78 00 f0 00 78 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 02 00 00 00 00 00 00 00 7e 00 1b 00
+6b 34 01 7d 03 40 69 34 01 3c 03 40 7f 40 00 00
+00 00 fe fe 00 00 00 00 00 fe 00 00 00 00 00 00
+00 00 00 00 b0 f8 50 09 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 01 00 b0 f8
+50 09 b0 f8 50 09 20 20 02 00 b6 42 00 80 8a 00
+06 3c 0a 3c ff ff c6 07 00 01 00 08 f0 0f 00 10
+02 00 30 00 00 00 00 00 00 00 06 fe 00 00 02 00
+50 00 8a 00 4f 95 00 00 21 00 0b 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 a5 51
+
+# Block limits VPD page
+00 b0 00 3c 00 00 00 01 00 00 40 00 00 00 04 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01
+00 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Block device characteristics VPD page
+00 b1 00 3c 00 01 00 05 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# Logical block provisioning VPD page
+00 b2 00 04 00 00 00 00
diff --git a/inhex/vpd_sfs.hex b/inhex/vpd_sfs.hex
new file mode 100644
index 00000000..f4c2ab07
--- /dev/null
+++ b/inhex/vpd_sfs.hex
@@ -0,0 +1,7 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_sfs.hex
+
+00 92 00 0e 00 00 00 00
+00 01 01 01 01 02 02 01
+09 09
diff --git a/inhex/vpd_tpc.hex b/inhex/vpd_tpc.hex
new file mode 100644
index 00000000..e3e9f387
--- /dev/null
+++ b/inhex/vpd_tpc.hex
@@ -0,0 +1,43 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_tpc.hex
+
+00 8f 00 8c
+
+00 00 00 20
+00 00 00 00 00 00 01 23 00 00 00 3c
+00 00 00 1e 00 00 00 99 88 77 66 55 00 00 00 44
+55 66 77 88
+
+00 01 00 10
+0d
+00 00
+03 00
+12 00
+83 02 10 11
+84 01 07
+00 00 # pad
+
+00 04 00 1c
+00 00 00 00 00 1c 00 40 00 01 22 33
+00 99 88 77 00 00 00 00 00 00 00 00 00 00 00 00
+
+00 08 00 04
+02
+02 e9
+00 # pad
+
+00 0c 00 0c
+00 08
+00 00
+00 01
+c0 00
+ff ff
+00 00 # pad
+
+00 0d 00 14
+12
+# UUID
+10 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee
+fe dc
+00 # pad
diff --git a/inhex/vpd_zbdc.hex b/inhex/vpd_zbdc.hex
new file mode 100644
index 00000000..d8d1ad3b
--- /dev/null
+++ b/inhex/vpd_zbdc.hex
@@ -0,0 +1,29 @@
+#
+# An example invocation:
+# sg_vpd --inhex=vpd_zbdc.hex
+
+# Zoned block device characteristics VPD page [0xb6]
+# Host managed zoned block device model; pdt=0x14
+14 b6 00 3c
+# ZBD extension=0; AAORb=0; URSWRZ=0
+00 00 00 00
+
+# Optimal # of open sequential write preferred
+00 00 00 00
+
+# Optimal # of open non-sequentailly written sequential write preferred
+00 00 00 00
+
+# maximum # of open sequential write required
+00 00 00 08
+
+# Zone alignment mode=1 (constant zone lengths)
+00 00 00 01
+
+# Zone starting LBA granularity
+00 00 00 02 00 00 00 00
+
+
+# pad to total length of 64 bytes
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
diff --git a/inhex/vpd_zbdc.raw b/inhex/vpd_zbdc.raw
new file mode 100644
index 00000000..249dbd50
--- /dev/null
+++ b/inhex/vpd_zbdc.raw
Binary files differ
diff --git a/inhex/z_act_query.hex b/inhex/z_act_query.hex
new file mode 100644
index 00000000..16d094e0
--- /dev/null
+++ b/inhex/z_act_query.hex
@@ -0,0 +1,28 @@
+# This is the output (in hex) of a simulated SCSI ZONE QUERY command.
+#
+# The hex bytes in this file may be generated by:
+# sg_z_act_query /dev/sg1 -HHH > /tmp/z_act_query.hex
+# where /dev/sg1 was a SCSI device implementing ZBC-2.
+
+# An example invocation:
+# sg_z_act_query --inhex=z_act_query.hex
+
+
+# parameter data header (64 bytes)
+00 00 00 80 00 00 00 80 80 00 03 00 00 00 00 00
+00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+# first zone activation descriptor, zone type: conventional
+01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 04
+00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00
+# second zone activation descriptor, zone type: sequential write required
+02 10 02 00 00 00 00 00 00 00 00 00 00 00 00 05
+00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00
+# third zone descriptor, zone type: sequential write required
+02 10 03 00 00 00 00 00 00 00 00 00 00 00 00 06
+00 00 00 60 00 00 00 00 00 00 00 00 00 00 00 00
+# fourth and last zone activation descriptor, zone type: sequential write required
+02 10 04 00 00 00 00 00 00 00 00 00 00 00 00 07
+00 00 00 80 00 00 00 00 00 00 00 00 00 00 00 00
diff --git a/install-sh b/install-sh
new file mode 100755
index 00000000..ec298b53
--- /dev/null
+++ b/install-sh
@@ -0,0 +1,541 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2020-11-14.01; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+tab=' '
+nl='
+'
+IFS=" $tab$nl"
+
+# Set DOITPROG to "echo" to test this script.
+
+doit=${DOITPROG-}
+doit_exec=${doit:-exec}
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+# Create dirs (including intermediate dirs) using mode 755.
+# This is like GNU 'install' as of coreutils 8.32 (2020).
+mkdir_umask=22
+
+backupsuffix=
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+is_target_a_directory=possibly
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+ or: $0 [OPTION]... SRCFILES... DIRECTORY
+ or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+ or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+ --help display this help and exit.
+ --version display version info and exit.
+
+ -c (ignored)
+ -C install only if different (preserve data modification time)
+ -d create directories instead of installing files.
+ -g GROUP $chgrpprog installed files to GROUP.
+ -m MODE $chmodprog installed files to MODE.
+ -o USER $chownprog installed files to USER.
+ -p pass -p to $cpprog.
+ -s $stripprog installed files.
+ -S SUFFIX attempt to back up existing files, with suffix SUFFIX.
+ -t DIRECTORY install into DIRECTORY.
+ -T report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+ CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+ RMPROG STRIPPROG
+
+By default, rm is invoked with -f; when overridden with RMPROG,
+it's up to you to specify -f if you want it.
+
+If -S is not specified, no backups are attempted.
+
+Email bug reports to bug-automake@gnu.org.
+Automake home page: https://www.gnu.org/software/automake/
+"
+
+while test $# -ne 0; do
+ case $1 in
+ -c) ;;
+
+ -C) copy_on_change=true;;
+
+ -d) dir_arg=true;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift;;
+
+ --help) echo "$usage"; exit $?;;
+
+ -m) mode=$2
+ case $mode in
+ *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
+ echo "$0: invalid mode: $mode" >&2
+ exit 1;;
+ esac
+ shift;;
+
+ -o) chowncmd="$chownprog $2"
+ shift;;
+
+ -p) cpprog="$cpprog -p";;
+
+ -s) stripcmd=$stripprog;;
+
+ -S) backupsuffix="$2"
+ shift;;
+
+ -t)
+ is_target_a_directory=always
+ dst_arg=$2
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ shift;;
+
+ -T) is_target_a_directory=never;;
+
+ --version) echo "$0 $scriptversion"; exit $?;;
+
+ --) shift
+ break;;
+
+ -*) echo "$0: invalid option: $1" >&2
+ exit 1;;
+
+ *) break;;
+ esac
+ shift
+done
+
+# We allow the use of options -d and -T together, by making -d
+# take the precedence; this is for compatibility with GNU install.
+
+if test -n "$dir_arg"; then
+ if test -n "$dst_arg"; then
+ echo "$0: target directory not allowed when installing a directory." >&2
+ exit 1
+ fi
+fi
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+ # When -d is used, all remaining arguments are directories to create.
+ # When -t is used, the destination is already specified.
+ # Otherwise, the last argument is the destination. Remove it from $@.
+ for arg
+ do
+ if test -n "$dst_arg"; then
+ # $@ is not empty: it contains at least $arg.
+ set fnord "$@" "$dst_arg"
+ shift # fnord
+ fi
+ shift # arg
+ dst_arg=$arg
+ # Protect names problematic for 'test' and other utilities.
+ case $dst_arg in
+ -* | [=\(\)!]) dst_arg=./$dst_arg;;
+ esac
+ done
+fi
+
+if test $# -eq 0; then
+ if test -z "$dir_arg"; then
+ echo "$0: no input file specified." >&2
+ exit 1
+ fi
+ # It's OK to call 'install-sh -d' without argument.
+ # This can happen when creating conditional directories.
+ exit 0
+fi
+
+if test -z "$dir_arg"; then
+ if test $# -gt 1 || test "$is_target_a_directory" = always; then
+ if test ! -d "$dst_arg"; then
+ echo "$0: $dst_arg: Is not a directory." >&2
+ exit 1
+ fi
+ fi
+fi
+
+if test -z "$dir_arg"; then
+ do_exit='(exit $ret); exit $ret'
+ trap "ret=129; $do_exit" 1
+ trap "ret=130; $do_exit" 2
+ trap "ret=141; $do_exit" 13
+ trap "ret=143; $do_exit" 15
+
+ # Set umask so as not to create temps with too-generous modes.
+ # However, 'strip' requires both read and write access to temps.
+ case $mode in
+ # Optimize common cases.
+ *644) cp_umask=133;;
+ *755) cp_umask=22;;
+
+ *[0-7])
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw='% 200'
+ fi
+ cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+ *)
+ if test -z "$stripcmd"; then
+ u_plus_rw=
+ else
+ u_plus_rw=,u+rw
+ fi
+ cp_umask=$mode$u_plus_rw;;
+ esac
+fi
+
+for src
+do
+ # Protect names problematic for 'test' and other utilities.
+ case $src in
+ -* | [=\(\)!]) src=./$src;;
+ esac
+
+ if test -n "$dir_arg"; then
+ dst=$src
+ dstdir=$dst
+ test -d "$dstdir"
+ dstdir_status=$?
+ # Don't chown directories that already exist.
+ if test $dstdir_status = 0; then
+ chowncmd=""
+ fi
+ else
+
+ # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+ # might cause directories to be created, which would be especially bad
+ # if $src (and thus $dsttmp) contains '*'.
+ if test ! -f "$src" && test ! -d "$src"; then
+ echo "$0: $src does not exist." >&2
+ exit 1
+ fi
+
+ if test -z "$dst_arg"; then
+ echo "$0: no destination specified." >&2
+ exit 1
+ fi
+ dst=$dst_arg
+
+ # If destination is a directory, append the input filename.
+ if test -d "$dst"; then
+ if test "$is_target_a_directory" = never; then
+ echo "$0: $dst_arg: Is a directory" >&2
+ exit 1
+ fi
+ dstdir=$dst
+ dstbase=`basename "$src"`
+ case $dst in
+ */) dst=$dst$dstbase;;
+ *) dst=$dst/$dstbase;;
+ esac
+ dstdir_status=0
+ else
+ dstdir=`dirname "$dst"`
+ test -d "$dstdir"
+ dstdir_status=$?
+ fi
+ fi
+
+ case $dstdir in
+ */) dstdirslash=$dstdir;;
+ *) dstdirslash=$dstdir/;;
+ esac
+
+ obsolete_mkdir_used=false
+
+ if test $dstdir_status != 0; then
+ case $posix_mkdir in
+ '')
+ # With -d, create the new directory with the user-specified mode.
+ # Otherwise, rely on $mkdir_umask.
+ if test -n "$dir_arg"; then
+ mkdir_mode=-m$mode
+ else
+ mkdir_mode=
+ fi
+
+ posix_mkdir=false
+ # The $RANDOM variable is not portable (e.g., dash). Use it
+ # here however when possible just to lower collision chance.
+ tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+
+ trap '
+ ret=$?
+ rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null
+ exit $ret
+ ' 0
+
+ # Because "mkdir -p" follows existing symlinks and we likely work
+ # directly in world-writeable /tmp, make sure that the '$tmpdir'
+ # directory is successfully created first before we actually test
+ # 'mkdir -p'.
+ if (umask $mkdir_umask &&
+ $mkdirprog $mkdir_mode "$tmpdir" &&
+ exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
+ then
+ if test -z "$dir_arg" || {
+ # Check for POSIX incompatibilities with -m.
+ # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+ # other-writable bit of parent directory when it shouldn't.
+ # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+ test_tmpdir="$tmpdir/a"
+ ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
+ case $ls_ld_tmpdir in
+ d????-?r-*) different_mode=700;;
+ d????-?--*) different_mode=755;;
+ *) false;;
+ esac &&
+ $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
+ ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
+ test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+ }
+ }
+ then posix_mkdir=:
+ fi
+ rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
+ else
+ # Remove any dirs left behind by ancient mkdir implementations.
+ rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
+ fi
+ trap '' 0;;
+ esac
+
+ if
+ $posix_mkdir && (
+ umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+ )
+ then :
+ else
+
+ # mkdir does not conform to POSIX,
+ # or it failed possibly due to a race condition. Create the
+ # directory the slow way, step by step, checking for races as we go.
+
+ case $dstdir in
+ /*) prefix='/';;
+ [-=\(\)!]*) prefix='./';;
+ *) prefix='';;
+ esac
+
+ oIFS=$IFS
+ IFS=/
+ set -f
+ set fnord $dstdir
+ shift
+ set +f
+ IFS=$oIFS
+
+ prefixes=
+
+ for d
+ do
+ test X"$d" = X && continue
+
+ prefix=$prefix$d
+ if test -d "$prefix"; then
+ prefixes=
+ else
+ if $posix_mkdir; then
+ (umask $mkdir_umask &&
+ $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+ # Don't fail if two instances are running concurrently.
+ test -d "$prefix" || exit 1
+ else
+ case $prefix in
+ *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) qprefix=$prefix;;
+ esac
+ prefixes="$prefixes '$qprefix'"
+ fi
+ fi
+ prefix=$prefix/
+ done
+
+ if test -n "$prefixes"; then
+ # Don't fail if two instances are running concurrently.
+ (umask $mkdir_umask &&
+ eval "\$doit_exec \$mkdirprog $prefixes") ||
+ test -d "$dstdir" || exit 1
+ obsolete_mkdir_used=true
+ fi
+ fi
+ fi
+
+ if test -n "$dir_arg"; then
+ { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+ { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+ else
+
+ # Make a couple of temp file names in the proper directory.
+ dsttmp=${dstdirslash}_inst.$$_
+ rmtmp=${dstdirslash}_rm.$$_
+
+ # Trap to clean up those temp files at exit.
+ trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+ # Copy the file name to the temp name.
+ (umask $cp_umask &&
+ { test -z "$stripcmd" || {
+ # Create $dsttmp read-write so that cp doesn't create it read-only,
+ # which would cause strip to fail.
+ if test -z "$doit"; then
+ : >"$dsttmp" # No need to fork-exec 'touch'.
+ else
+ $doit touch "$dsttmp"
+ fi
+ }
+ } &&
+ $doit_exec $cpprog "$src" "$dsttmp") &&
+
+ # and set any options; do chmod last to preserve setuid bits.
+ #
+ # If any of these fail, we abort the whole thing. If we want to
+ # ignore errors from any of these, just make sure not to ignore
+ # errors from the above "$doit $cpprog $src $dsttmp" command.
+ #
+ { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+ { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+ { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+ { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+ # If -C, don't bother to copy if it wouldn't change the file.
+ if $copy_on_change &&
+ old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
+ new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
+ set -f &&
+ set X $old && old=:$2:$4:$5:$6 &&
+ set X $new && new=:$2:$4:$5:$6 &&
+ set +f &&
+ test "$old" = "$new" &&
+ $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+ then
+ rm -f "$dsttmp"
+ else
+ # If $backupsuffix is set, and the file being installed
+ # already exists, attempt a backup. Don't worry if it fails,
+ # e.g., if mv doesn't support -f.
+ if test -n "$backupsuffix" && test -f "$dst"; then
+ $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null
+ fi
+
+ # Rename the file to the real destination.
+ $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+ # The rename failed, perhaps because mv can't rename something else
+ # to itself, or perhaps because mv is so ancient that it does not
+ # support -f.
+ {
+ # Now remove or move aside any old file at destination location.
+ # We try this two ways since rm can't unlink itself on some
+ # systems and the destination file might be busy for other
+ # reasons. In this case, the final cleanup might fail but the new
+ # file should still install successfully.
+ {
+ test ! -f "$dst" ||
+ $doit $rmcmd "$dst" 2>/dev/null ||
+ { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+ { $doit $rmcmd "$rmtmp" 2>/dev/null; :; }
+ } ||
+ { echo "$0: cannot unlink or rename $dst" >&2
+ (exit 1); exit 1
+ }
+ } &&
+
+ # Now rename the file to the real destination.
+ $doit $mvcmd "$dsttmp" "$dst"
+ }
+ fi || exit 1
+
+ trap '' 0
+ fi
+done
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/lib/BSD_LICENSE b/lib/BSD_LICENSE
new file mode 100644
index 00000000..15e3da3f
--- /dev/null
+++ b/lib/BSD_LICENSE
@@ -0,0 +1,26 @@
+
+Copyright (c) 1999-2019, Douglas Gilbert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Above is the:
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 00000000..2c19b1bd
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,109 @@
+libsgutils2_la_SOURCES = \
+ sg_lib.c \
+ sg_pr2serr.c \
+ sg_lib_data.c \
+ sg_lib_names.c \
+ sg_cmds_basic.c \
+ sg_cmds_basic2.c \
+ sg_cmds_extra.c \
+ sg_cmds_mmc.c \
+ sg_pt_common.c \
+ sg_json_builder.c
+
+if OS_LINUX
+if PT_DUMMY
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+else
+libsgutils2_la_SOURCES += \
+ sg_pt_linux.c \
+ sg_io_linux.c \
+ sg_pt_linux_nvme.c
+endif
+endif
+
+if OS_WIN32_MINGW
+libsgutils2_la_SOURCES += sg_pt_win32.c
+endif
+
+if OS_WIN32_CYGWIN
+libsgutils2_la_SOURCES += sg_pt_win32.c
+endif
+
+if OS_FREEBSD
+if PT_DUMMY
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+else
+libsgutils2_la_SOURCES += sg_pt_freebsd.c
+endif
+endif
+
+if OS_SOLARIS
+libsgutils2_la_SOURCES += sg_pt_solaris.c
+endif
+
+if OS_OSF
+libsgutils2_la_SOURCES += sg_pt_osf1.c
+endif
+
+if OS_HAIKU
+if PT_DUMMY
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+else
+libsgutils2_la_SOURCES += sg_pt_haiku.c
+endif
+endif
+
+if OS_NETBSD
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+endif
+
+if OS_OPENBSD
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+endif
+
+if OS_OTHER
+libsgutils2_la_SOURCES += sg_pt_dummy.c
+endif
+
+if DEBUG
+# This is active if --enable-debug given to ./configure
+# removed -Wduplicated-branches because needs gcc-8
+DBG_CFLAGS = -Wextra -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+DBG_CPPFLAGS = -DDEBUG
+else
+DBG_CFLAGS =
+DBG_CPPFLAGS =
+endif
+
+# For C++/clang testing
+## CC = gcc-9
+## CXX = g++
+## CC = clang
+## CXX = clang++
+## CC = clang++
+## CC = powerpc64-linux-gnu-gcc
+
+# -std=<s> can be c99, c11, gnu11, etc. Default is gnu11 for C code
+# -Wall is no longer all warnings. Add -W (since renamed to -Wextra) for more
+AM_CPPFLAGS = -iquote ${top_srcdir}/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(DBG_CPPFLAGS)
+AM_CFLAGS = -Wall -W $(DBG_CFLAGS)
+# AM_CFLAGS = -Wall -W $(DBG_CFLAGS) -fanalyzer
+# AM_CFLAGS = -Wall -W -Wextra -Wmisleading-indentation -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+# AM_CFLAGS = -Wall -W -pedantic -std=c11
+# AM_CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# AM_CFLAGS = -Wall -W -pedantic -std=c++11
+# AM_CFLAGS = -Wall -W -pedantic -std=c++14
+# AM_CFLAGS = -Wall -W -pedantic -std=c++1z
+# AM_CFLAGS = -Wall -W -pedantic -std=c++20
+# AM_CFLAGS = -Wall -W -pedantic -std=c++23
+
+lib_LTLIBRARIES = libsgutils2.la
+
+libsgutils2_la_LDFLAGS = -version-info 2:0:0 -no-undefined -release ${PACKAGE_VERSION}
+
+libsgutils2_la_LIBADD = @GETOPT_O_FILES@
+libsgutils2_la_DEPENDENCIES = @GETOPT_O_FILES@
+
+EXTRA_DIST = \
+ sg_json_builder.h \
+ BSD_LICENSE
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 00000000..529c7d12
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,800 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@OS_LINUX_TRUE@@PT_DUMMY_TRUE@am__append_1 = sg_pt_dummy.c
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__append_2 = \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_pt_linux.c \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_io_linux.c \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_pt_linux_nvme.c
+
+@OS_WIN32_MINGW_TRUE@am__append_3 = sg_pt_win32.c
+@OS_WIN32_CYGWIN_TRUE@am__append_4 = sg_pt_win32.c
+@OS_FREEBSD_TRUE@@PT_DUMMY_TRUE@am__append_5 = sg_pt_dummy.c
+@OS_FREEBSD_TRUE@@PT_DUMMY_FALSE@am__append_6 = sg_pt_freebsd.c
+@OS_SOLARIS_TRUE@am__append_7 = sg_pt_solaris.c
+@OS_OSF_TRUE@am__append_8 = sg_pt_osf1.c
+@OS_HAIKU_TRUE@@PT_DUMMY_TRUE@am__append_9 = sg_pt_dummy.c
+@OS_HAIKU_TRUE@@PT_DUMMY_FALSE@am__append_10 = sg_pt_haiku.c
+@OS_NETBSD_TRUE@am__append_11 = sg_pt_dummy.c
+@OS_OPENBSD_TRUE@am__append_12 = sg_pt_dummy.c
+@OS_OTHER_TRUE@am__append_13 = sg_pt_dummy.c
+subdir = lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__libsgutils2_la_SOURCES_DIST = sg_lib.c sg_pr2serr.c sg_lib_data.c \
+ sg_lib_names.c sg_cmds_basic.c sg_cmds_basic2.c \
+ sg_cmds_extra.c sg_cmds_mmc.c sg_pt_common.c sg_json_builder.c \
+ sg_pt_dummy.c sg_pt_linux.c sg_io_linux.c sg_pt_linux_nvme.c \
+ sg_pt_win32.c sg_pt_freebsd.c sg_pt_solaris.c sg_pt_osf1.c \
+ sg_pt_haiku.c
+@OS_LINUX_TRUE@@PT_DUMMY_TRUE@am__objects_1 = sg_pt_dummy.lo
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__objects_2 = sg_pt_linux.lo \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_io_linux.lo \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_pt_linux_nvme.lo
+@OS_WIN32_MINGW_TRUE@am__objects_3 = sg_pt_win32.lo
+@OS_WIN32_CYGWIN_TRUE@am__objects_4 = sg_pt_win32.lo
+@OS_FREEBSD_TRUE@@PT_DUMMY_TRUE@am__objects_5 = sg_pt_dummy.lo
+@OS_FREEBSD_TRUE@@PT_DUMMY_FALSE@am__objects_6 = sg_pt_freebsd.lo
+@OS_SOLARIS_TRUE@am__objects_7 = sg_pt_solaris.lo
+@OS_OSF_TRUE@am__objects_8 = sg_pt_osf1.lo
+@OS_HAIKU_TRUE@@PT_DUMMY_TRUE@am__objects_9 = sg_pt_dummy.lo
+@OS_HAIKU_TRUE@@PT_DUMMY_FALSE@am__objects_10 = sg_pt_haiku.lo
+@OS_NETBSD_TRUE@am__objects_11 = sg_pt_dummy.lo
+@OS_OPENBSD_TRUE@am__objects_12 = sg_pt_dummy.lo
+@OS_OTHER_TRUE@am__objects_13 = sg_pt_dummy.lo
+am_libsgutils2_la_OBJECTS = sg_lib.lo sg_pr2serr.lo sg_lib_data.lo \
+ sg_lib_names.lo sg_cmds_basic.lo sg_cmds_basic2.lo \
+ sg_cmds_extra.lo sg_cmds_mmc.lo sg_pt_common.lo \
+ sg_json_builder.lo $(am__objects_1) $(am__objects_2) \
+ $(am__objects_3) $(am__objects_4) $(am__objects_5) \
+ $(am__objects_6) $(am__objects_7) $(am__objects_8) \
+ $(am__objects_9) $(am__objects_10) $(am__objects_11) \
+ $(am__objects_12) $(am__objects_13)
+libsgutils2_la_OBJECTS = $(am_libsgutils2_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libsgutils2_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libsgutils2_la_LDFLAGS) $(LDFLAGS) -o \
+ $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sg_cmds_basic.Plo \
+ ./$(DEPDIR)/sg_cmds_basic2.Plo ./$(DEPDIR)/sg_cmds_extra.Plo \
+ ./$(DEPDIR)/sg_cmds_mmc.Plo ./$(DEPDIR)/sg_io_linux.Plo \
+ ./$(DEPDIR)/sg_json_builder.Plo ./$(DEPDIR)/sg_lib.Plo \
+ ./$(DEPDIR)/sg_lib_data.Plo ./$(DEPDIR)/sg_lib_names.Plo \
+ ./$(DEPDIR)/sg_pr2serr.Plo ./$(DEPDIR)/sg_pt_common.Plo \
+ ./$(DEPDIR)/sg_pt_dummy.Plo ./$(DEPDIR)/sg_pt_freebsd.Plo \
+ ./$(DEPDIR)/sg_pt_haiku.Plo ./$(DEPDIR)/sg_pt_linux.Plo \
+ ./$(DEPDIR)/sg_pt_linux_nvme.Plo ./$(DEPDIR)/sg_pt_osf1.Plo \
+ ./$(DEPDIR)/sg_pt_solaris.Plo ./$(DEPDIR)/sg_pt_win32.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsgutils2_la_SOURCES)
+DIST_SOURCES = $(am__libsgutils2_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+libsgutils2_la_SOURCES = sg_lib.c sg_pr2serr.c sg_lib_data.c \
+ sg_lib_names.c sg_cmds_basic.c sg_cmds_basic2.c \
+ sg_cmds_extra.c sg_cmds_mmc.c sg_pt_common.c sg_json_builder.c \
+ $(am__append_1) $(am__append_2) $(am__append_3) \
+ $(am__append_4) $(am__append_5) $(am__append_6) \
+ $(am__append_7) $(am__append_8) $(am__append_9) \
+ $(am__append_10) $(am__append_11) $(am__append_12) \
+ $(am__append_13)
+@DEBUG_FALSE@DBG_CFLAGS =
+
+# This is active if --enable-debug given to ./configure
+# removed -Wduplicated-branches because needs gcc-8
+@DEBUG_TRUE@DBG_CFLAGS = -Wextra -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+@DEBUG_FALSE@DBG_CPPFLAGS =
+@DEBUG_TRUE@DBG_CPPFLAGS = -DDEBUG
+
+# For C++/clang testing
+
+# -std=<s> can be c99, c11, gnu11, etc. Default is gnu11 for C code
+# -Wall is no longer all warnings. Add -W (since renamed to -Wextra) for more
+AM_CPPFLAGS = -iquote ${top_srcdir}/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(DBG_CPPFLAGS)
+AM_CFLAGS = -Wall -W $(DBG_CFLAGS)
+# AM_CFLAGS = -Wall -W $(DBG_CFLAGS) -fanalyzer
+# AM_CFLAGS = -Wall -W -Wextra -Wmisleading-indentation -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+# AM_CFLAGS = -Wall -W -pedantic -std=c11
+# AM_CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# AM_CFLAGS = -Wall -W -pedantic -std=c++11
+# AM_CFLAGS = -Wall -W -pedantic -std=c++14
+# AM_CFLAGS = -Wall -W -pedantic -std=c++1z
+# AM_CFLAGS = -Wall -W -pedantic -std=c++20
+# AM_CFLAGS = -Wall -W -pedantic -std=c++23
+lib_LTLIBRARIES = libsgutils2.la
+libsgutils2_la_LDFLAGS = -version-info 2:0:0 -no-undefined -release ${PACKAGE_VERSION}
+libsgutils2_la_LIBADD = @GETOPT_O_FILES@
+libsgutils2_la_DEPENDENCIES = @GETOPT_O_FILES@
+EXTRA_DIST = \
+ sg_json_builder.h \
+ BSD_LICENSE
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsgutils2.la: $(libsgutils2_la_OBJECTS) $(libsgutils2_la_DEPENDENCIES) $(EXTRA_libsgutils2_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libsgutils2_la_LINK) -rpath $(libdir) $(libsgutils2_la_OBJECTS) $(libsgutils2_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_cmds_basic.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_cmds_basic2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_cmds_extra.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_cmds_mmc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_io_linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_json_builder.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_lib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_lib_data.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_lib_names.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pr2serr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_dummy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_freebsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_haiku.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_linux_nvme.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_osf1.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_solaris.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_win32.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sg_cmds_basic.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_basic2.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_extra.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_mmc.Plo
+ -rm -f ./$(DEPDIR)/sg_io_linux.Plo
+ -rm -f ./$(DEPDIR)/sg_json_builder.Plo
+ -rm -f ./$(DEPDIR)/sg_lib.Plo
+ -rm -f ./$(DEPDIR)/sg_lib_data.Plo
+ -rm -f ./$(DEPDIR)/sg_lib_names.Plo
+ -rm -f ./$(DEPDIR)/sg_pr2serr.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_common.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_dummy.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_freebsd.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_haiku.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_linux.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_linux_nvme.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_osf1.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_solaris.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_win32.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sg_cmds_basic.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_basic2.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_extra.Plo
+ -rm -f ./$(DEPDIR)/sg_cmds_mmc.Plo
+ -rm -f ./$(DEPDIR)/sg_io_linux.Plo
+ -rm -f ./$(DEPDIR)/sg_json_builder.Plo
+ -rm -f ./$(DEPDIR)/sg_lib.Plo
+ -rm -f ./$(DEPDIR)/sg_lib_data.Plo
+ -rm -f ./$(DEPDIR)/sg_lib_names.Plo
+ -rm -f ./$(DEPDIR)/sg_pr2serr.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_common.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_dummy.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_freebsd.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_haiku.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_linux.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_linux_nvme.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_osf1.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_solaris.Plo
+ -rm -f ./$(DEPDIR)/sg_pt_win32.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lib/sg_cmds_basic.c b/lib/sg_cmds_basic.c
new file mode 100644
index 00000000..01ca55c1
--- /dev/null
+++ b/lib/sg_cmds_basic.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (c) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * CONTENTS
+ * Some SCSI commands are executed in many contexts and hence began
+ * to appear in several sg3_utils utilities. This files centralizes
+ * some of the low level command execution code. In most cases the
+ * interpretation of the command response is left to the each
+ * utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.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_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* Needs to be after config.h */
+#ifdef SG_LIB_LINUX
+#include <errno.h>
+#endif
+
+
+static const char * const version_str = "2.00 20220118";
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */
+
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define REQUEST_SENSE_CMD 0x3
+#define REQUEST_SENSE_CMDLEN 6
+#define REPORT_LUNS_CMD 0xa0
+#define REPORT_LUNS_CMDLEN 12
+#define TUR_CMD 0x0
+#define TUR_CMDLEN 6
+
+#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */
+
+
+const char *
+sg_cmds_version()
+{
+ return version_str;
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. */
+int
+sg_cmds_open_device(const char * device_name, bool read_only, int verbose)
+{
+ return scsi_pt_open_device(device_name, read_only, verbose);
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. */
+int
+sg_cmds_open_flags(const char * device_name, int flags, int verbose)
+{
+ return scsi_pt_open_flags(device_name, flags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+sg_cmds_close_device(int device_fd)
+{
+ return scsi_pt_close_device(device_fd);
+}
+
+static const char * const pass_through_s = "pass-through";
+
+static void
+sg_cmds_resid_print(const char * leadin, bool is_din, int num_req,
+ int num_got)
+{
+ pr2ws(" %s: %s requested %d bytes (data-%s got %d "
+ "bytes%s\n", leadin, pass_through_s,num_req,
+ (is_din ? "in), got" : "out) but reported"), num_got,
+ (is_din ? "" : " sent"));
+}
+
+static int
+sg_cmds_process_helper(const char * leadin, int req_din_x, int act_din_x,
+ int req_dout_x, int act_dout_x, const uint8_t * sbp,
+ int slen, bool noisy, int verbose, int * o_sense_cat)
+{
+ int scat;
+ bool n = false;
+ bool check_data_in = false;
+
+ scat = sg_err_category_sense(sbp, slen);
+ switch (scat) {
+ case SG_LIB_CAT_NOT_READY:
+ case SG_LIB_CAT_INVALID_OP:
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ case SG_LIB_LBA_OUT_OF_RANGE:
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_COPY_ABORTED:
+ case SG_LIB_CAT_DATA_PROTECT:
+ case SG_LIB_CAT_PROTECTION:
+ case SG_LIB_CAT_NO_SENSE:
+ case SG_LIB_CAT_MISCOMPARE:
+ case SG_LIB_CAT_STANDBY:
+ case SG_LIB_CAT_UNAVAILABLE:
+ n = false;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_MEDIUM_HARD:
+ check_data_in = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ case SG_LIB_CAT_SENSE:
+ default:
+ n = noisy;
+ break;
+ }
+ if (verbose || n) {
+ char b[512];
+
+ if (leadin && (strlen(leadin) > 0))
+ pr2ws("%s:\n", leadin);
+ sg_get_sense_str(NULL, sbp, slen, (verbose > 1),
+ sizeof(b), b);
+ pr2ws("%s", b);
+ if (req_din_x > 0) {
+ if (act_din_x != req_din_x) {
+ if ((verbose > 2) || check_data_in || (act_din_x > 0))
+ sg_cmds_resid_print(leadin, true, req_din_x, act_din_x);
+ if (act_din_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't get negative bytes, say it "
+ "got none\n", leadin, pass_through_s);
+ }
+ }
+ }
+ if (req_dout_x > 0) {
+ if (act_dout_x != req_dout_x) {
+ if ((verbose > 1) && (act_dout_x > 0))
+ sg_cmds_resid_print(leadin, false, req_dout_x, act_dout_x);
+ if (act_dout_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't send negative bytes, say it "
+ "sent none\n", leadin, pass_through_s);
+ }
+ }
+ }
+ }
+ if (o_sense_cat)
+ *o_sense_cat = scat;
+ return -2;
+}
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * o_sense_cat (sense category) written which may not be fatal. Returns
+ * -1 for other types of failure. Returns 0, or a positive number. If data-in
+ * type command (or bidi) then returns actual number of bytes read
+ * (din_len - resid); otherwise returns 0. Note that several sense categories
+ * also have data in bytes received; -2 is still returned. */
+int
+sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+ int pt_res, bool noisy, int verbose, int * o_sense_cat)
+{
+ int cat, slen, sstat, req_din_x, req_dout_x;
+ int act_din_x, act_dout_x;
+ const uint8_t * sbp;
+ char b[1024];
+
+ if (NULL == leadin)
+ leadin = "";
+ if (pt_res < 0) {
+#ifdef SG_LIB_LINUX
+ if (verbose)
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+ if ((-ENXIO == pt_res) && o_sense_cat) {
+ if (verbose > 2)
+ pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n");
+ *o_sense_cat = SG_LIB_CAT_NOT_READY;
+ return -2;
+ } else if (noisy && (0 == verbose))
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+#else
+ if (noisy || verbose)
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+#endif
+ return -1;
+ } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) {
+ pr2ws("%s: bad %s setup\n", leadin, pass_through_s);
+ return -1;
+ } else if (SCSI_PT_DO_TIMEOUT == pt_res) {
+ pr2ws("%s: %s timeout\n", leadin, pass_through_s);
+ return -1;
+ }
+ if (verbose > 2) {
+ uint64_t duration = get_pt_duration_ns(ptvp);
+
+ if (duration > 0)
+ pr2ws(" duration=%" PRIu64 " ns\n", duration);
+ else {
+ int d = get_scsi_pt_duration_ms(ptvp);
+
+ if (d != -1)
+ pr2ws(" duration=%u ms\n", (uint32_t)d);
+ }
+ }
+ get_pt_req_lengths(ptvp, &req_din_x, &req_dout_x);
+ get_pt_actual_lengths(ptvp, &act_din_x, &act_dout_x);
+ slen = get_scsi_pt_sense_len(ptvp);
+ sbp = get_scsi_pt_sense_buf(ptvp);
+ switch ((cat = get_scsi_pt_result_category(ptvp))) {
+ case SCSI_PT_RESULT_GOOD:
+ if (sbp && (slen > 7)) {
+ int resp_code = sbp[0] & 0x7f;
+
+ /* SBC referrals can have status=GOOD and sense_key=COMPLETED */
+ if (resp_code >= 0x70) {
+ if (resp_code < 0x72) {
+ if (SPC_SK_NO_SENSE != (0xf & sbp[2]))
+ sg_err_category_sense(sbp, slen);
+ } else if (resp_code < 0x74) {
+ if (SPC_SK_NO_SENSE != (0xf & sbp[1]))
+ sg_err_category_sense(sbp, slen);
+ }
+ }
+ }
+ if (req_din_x > 0) {
+ if (act_din_x != req_din_x) {
+ if ((verbose > 1) && (act_din_x >= 0))
+ sg_cmds_resid_print(leadin, true, req_din_x, act_din_x);
+ if (act_din_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't get negative bytes, say it "
+ "got none\n", leadin, pass_through_s);
+ act_din_x = 0;
+ }
+ }
+ }
+ if (req_dout_x > 0) {
+ if (act_dout_x != req_dout_x) {
+ if ((verbose > 1) && (act_dout_x >= 0))
+ sg_cmds_resid_print(leadin, false, req_dout_x, act_dout_x);
+ if (act_dout_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't send negative bytes, say it "
+ "sent none\n", leadin, pass_through_s);
+ act_dout_x = 0;
+ }
+ }
+ }
+ return act_din_x;
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ sstat = get_scsi_pt_status_response(ptvp);
+ if (o_sense_cat) {
+ switch (sstat) {
+ case SAM_STAT_RESERVATION_CONFLICT:
+ *o_sense_cat = SG_LIB_CAT_RES_CONFLICT;
+ return -2;
+ case SAM_STAT_CONDITION_MET:
+ *o_sense_cat = SG_LIB_CAT_CONDITION_MET;
+ return -2;
+ case SAM_STAT_BUSY:
+ *o_sense_cat = SG_LIB_CAT_BUSY;
+ return -2;
+ case SAM_STAT_TASK_SET_FULL:
+ *o_sense_cat = SG_LIB_CAT_TS_FULL;
+ return -2;
+ case SAM_STAT_ACA_ACTIVE:
+ *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE;
+ return -2;
+ case SAM_STAT_TASK_ABORTED:
+ *o_sense_cat = SG_LIB_CAT_TASK_ABORTED;
+ return -2;
+ default:
+ break;
+ }
+ }
+ if (verbose || noisy) {
+ sg_get_scsi_status_str(sstat, sizeof(b), b);
+ pr2ws("%s: scsi status: %s\n", leadin, b);
+ }
+ return -1;
+ case SCSI_PT_RESULT_SENSE:
+ return sg_cmds_process_helper(leadin, req_din_x, act_din_x,
+ req_dout_x, act_dout_x, sbp, slen,
+ noisy, verbose, o_sense_cat);
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ if (verbose || noisy) {
+ get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+ pr2ws("%s: transport: %s\n", leadin, b);
+ }
+#ifdef SG_LIB_LINUX
+ return -1; /* DRIVER_SENSE is not passed through */
+#else
+ /* Shall we favour sense data over a transport error (given both) */
+ {
+ bool favour_sense = ((SAM_STAT_CHECK_CONDITION ==
+ get_scsi_pt_status_response(ptvp)) && (slen > 0));
+
+ if (favour_sense)
+ return sg_cmds_process_helper(leadin, req_din_x, act_din_x,
+ req_dout_x, act_dout_x, sbp,
+ slen, noisy, verbose,
+ o_sense_cat);
+ else
+ return -1;
+ }
+#endif
+ case SCSI_PT_RESULT_OS_ERR:
+ if (verbose || noisy) {
+ get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+ pr2ws("%s: os: %s\n", leadin, b);
+ }
+ return -1;
+ default:
+ pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s,
+ cat);
+ return -1;
+ }
+}
+
+bool
+sg_cmds_is_nvme(const struct sg_pt_base * ptvp)
+{
+ return pt_device_is_nvme(ptvp);
+}
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+ struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp)
+ pr2ws("%s: out of memory\n", cname);
+ return ptvp;
+}
+
+static const char * const inquiry_s = "inquiry";
+
+
+/* Returns 0 on success, while positive values are SG_LIB_CAT_* errors
+ * (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */
+static int
+sg_ll_inquiry_com(struct sg_pt_base * ptvp, int sg_fd, bool cmddt, bool evpd,
+ int pg_op, void * resp, int mx_resp_len, int timeout_secs,
+ int * residp, bool noisy, int verbose)
+{
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, sense_cat, resid;
+ uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t * up;
+
+ if (resp == NULL) {
+ if (verbose)
+ pr2ws("Got NULL `resp` pointer");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (cmddt)
+ inq_cdb[1] |= 0x2;
+ if (evpd)
+ inq_cdb[1] |= 0x1;
+ inq_cdb[2] = (uint8_t)pg_op;
+ /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", inquiry_s,
+ sg_get_command_str(inq_cdb, INQUIRY_CMDLEN, false, sizeof(b),
+ b));
+ }
+ if (mx_resp_len > 0) {
+ up = (uint8_t *)resp;
+ up[0] = 0x7f; /* defensive prefill */
+ if (mx_resp_len > 4)
+ up[4] = 0;
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, timeout_secs, verbose);
+ ret = sg_cmds_process_resp(ptvp, inquiry_s, res, noisy, verbose,
+ &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else if (ret < 4) {
+ if (verbose)
+ pr2ws("%s: got too few bytes (%d)\n", __func__, ret);
+ ret = SG_LIB_CAT_MALFORMED;
+ } else
+ ret = 0;
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2ws("%s resid (%d) should never exceed requested "
+ "len=%d\n", inquiry_s, resid, mx_resp_len);
+ if (0 == ret)
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ /* zero unfilled section of response buffer, based on resid */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+fini:
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values, negated errno or
+ * -1 -> other errors. The CMDDT field is obsolete in the INQUIRY cdb. */
+int
+sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_inquiry_com(NULL, sg_fd, cmddt, evpd, pg_op, resp,
+ mx_resp_len, 0 /* timeout_sec */, NULL, noisy,
+ verbose);
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION
+ * CODES command instead). Adds the ability to set the command abort timeout
+ * and the ability to report the residual count. If timeout_secs is zero
+ * or less the default command abort timeout (60 seconds) is used.
+ * If residp is non-NULL then the residual value is written where residp
+ * points. A residual value of 0 implies mx_resp_len bytes have be written
+ * where resp points. If the residual value equals mx_resp_len then no
+ * bytes have been written. */
+int
+sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose)
+{
+ return sg_ll_inquiry_com(NULL, sg_fd, false, evpd, pg_op, resp,
+ mx_resp_len, timeout_secs, residp, noisy,
+ verbose);
+}
+
+/* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base.
+ * That object is assumed to be constructed and have a device file descriptor
+ * associated with it. Caller is responsible for lifetime of ptp. */
+int
+sg_ll_inquiry_pt(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp, bool noisy,
+ int verbose)
+{
+ return sg_ll_inquiry_com(ptvp, -1, false, evpd, pg_op, resp, mx_resp_len,
+ timeout_secs, residp, noisy, verbose);
+}
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values, negated
+ * errno or -1 -> other errors */
+int
+sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+ bool noisy, int verbose)
+{
+ int ret;
+ uint8_t * inq_resp = NULL;
+ uint8_t * free_irp = NULL;
+
+ if (inq_data) {
+ memset(inq_data, 0, sizeof(* inq_data));
+ inq_data->peripheral_qualifier = 0x3;
+ inq_data->peripheral_type = PDT_UNKNOWN;
+ }
+ inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false);
+ if (NULL == inq_resp) {
+ pr2ws("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = sg_ll_inquiry_com(NULL, sg_fd, false, false, 0, inq_resp,
+ SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
+
+ if (inq_data && (0 == ret)) {
+ inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+ inq_data->peripheral_type = inq_resp[0] & PDT_MASK;
+ inq_data->byte_1 = inq_resp[1];
+ inq_data->version = inq_resp[2];
+ inq_data->byte_3 = inq_resp[3];
+ inq_data->byte_5 = inq_resp[5];
+ inq_data->byte_6 = inq_resp[6];
+ inq_data->byte_7 = inq_resp[7];
+ memcpy(inq_data->vendor, inq_resp + 8, 8);
+ memcpy(inq_data->product, inq_resp + 16, 16);
+ memcpy(inq_data->revision, inq_resp + 32, 4);
+ }
+ if (free_irp)
+ free(free_irp);
+ return ret;
+}
+
+/* Similar to sg_simple_inquiry() but takes pointer to pt object rather
+ * than device file descriptor. */
+int
+sg_simple_inquiry_pt(struct sg_pt_base * ptvp,
+ struct sg_simple_inquiry_resp * inq_data,
+ bool noisy, int verbose)
+{
+ int ret;
+ uint8_t * inq_resp = NULL;
+ uint8_t * free_irp = NULL;
+
+ if (inq_data) {
+ memset(inq_data, 0, sizeof(* inq_data));
+ inq_data->peripheral_qualifier = 0x3;
+ inq_data->peripheral_type = PDT_MASK;
+ }
+ inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false);
+ if (NULL == inq_resp) {
+ pr2ws("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = sg_ll_inquiry_com(ptvp, -1, false, false, 0, inq_resp,
+ SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
+
+ if (inq_data && (0 == ret)) {
+ inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+ inq_data->peripheral_type = inq_resp[0] & PDT_MASK;
+ inq_data->byte_1 = inq_resp[1];
+ inq_data->version = inq_resp[2];
+ inq_data->byte_3 = inq_resp[3];
+ inq_data->byte_5 = inq_resp[5];
+ inq_data->byte_6 = inq_resp[6];
+ inq_data->byte_7 = inq_resp[7];
+ memcpy(inq_data->vendor, inq_resp + 8, 8);
+ memcpy(inq_data->product, inq_resp + 16, 16);
+ memcpy(inq_data->revision, inq_resp + 32, 4);
+ }
+ if (free_irp)
+ free(free_irp);
+ return ret;
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * N.B. To access the sense buffer outside this routine then one be
+ * provided by the caller.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+static int
+sg_ll_test_unit_ready_com(struct sg_pt_base * ptvp, int sg_fd, int pack_id,
+ int * progress, bool noisy, int verbose)
+{
+ static const char * const tur_s = "test unit ready";
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, sense_cat;
+ uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", tur_s,
+ sg_get_command_str(tur_cdb, TUR_CMDLEN, false, sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_packet_id(ptvp, pack_id);
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, tur_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ if (progress) {
+ int slen = get_scsi_pt_sense_len(ptvp);
+
+ if (! sg_get_sense_progress_fld(sense_b, slen, progress))
+ *progress = -1;
+ }
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+int
+sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id,
+ int * progress, bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, progress, noisy,
+ verbose);
+}
+
+int
+sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+ bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, progress, noisy,
+ verbose);
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, NULL, noisy,
+ verbose);
+}
+
+int
+sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy,
+ int verbose)
+{
+ return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various
+ * SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_request_sense_com(struct sg_pt_base * ptvp, int sg_fd, bool desc,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ bool ptvp_given = false;
+ bool local_cdb = true;
+ bool local_sense = true;
+ int ret, res, sense_cat;
+ static const char * const rq_s = "request sense";
+ uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] =
+ {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (desc)
+ rs_cdb[1] |= 0x1;
+ if (mx_resp_len > 0xff) {
+ pr2ws("mx_resp_len cannot exceed 255\n");
+ return -1;
+ }
+ rs_cdb[4] = mx_resp_len & 0xff;
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", rq_s,
+ sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false;
+ else
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, rq_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((mx_resp_len >= 8) && (ret < 8)) {
+ if (verbose)
+ pr2ws(" %s: got %d bytes in response, too short\n", rq_s,
+ ret);
+ ret = -1;
+ } else
+ ret = 0;
+ }
+ if (ptvp_given) {
+ if (local_sense) /* stop caller accessing local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb) /* stop caller accessing local sense */
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_request_sense_com(NULL, sg_fd, desc, resp, mx_resp_len,
+ noisy, verbose);
+}
+
+int
+sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len,
+ noisy, verbose);
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_report_luns_com(struct sg_pt_base * ptvp, int sg_fd, int select_report,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const report_luns_s = "report luns";
+ bool ptvp_given = false;
+ bool local_cdb = true;
+ bool local_sense = true;
+ int ret, res, sense_cat;
+ uint8_t rl_cdb[REPORT_LUNS_CMDLEN] =
+ {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ rl_cdb[2] = select_report & 0xff;
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", report_luns_s,
+ sg_get_command_str(rl_cdb, REPORT_LUNS_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false;
+ else
+ set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, report_luns_s, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (ptvp_given) {
+ if (local_sense) /* stop caller accessing local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors,
+ * Expects sg_fd to be >= 0 representing an open device fd. */
+int
+sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_report_luns_com(NULL, sg_fd, select_report, resp,
+ mx_resp_len, noisy, verbose);
+}
+
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * Expects a non-NULL ptvp containing an open device fd. */
+int
+sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_report_luns_com(ptvp, -1, select_report, resp,
+ mx_resp_len, noisy, verbose);
+}
diff --git a/lib/sg_cmds_basic2.c b/lib/sg_cmds_basic2.c
new file mode 100644
index 00000000..cbc609a9
--- /dev/null
+++ b/lib/sg_cmds_basic2.c
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (c) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * CONTENTS
+ * Some SCSI commands are executed in many contexts and hence began
+ * to appear in several sg3_utils utilities. This files centralizes
+ * some of the low level command execution code. In most cases the
+ * interpretation of the command response is left to the each
+ * utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define EBUFF_SZ 256
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */
+#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */
+
+#define SYNCHRONIZE_CACHE_CMD 0x35
+#define SYNCHRONIZE_CACHE_CMDLEN 10
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define READ_CAPACITY_16_SA 0x10
+#define READ_CAPACITY_10_CMD 0x25
+#define READ_CAPACITY_10_CMDLEN 10
+#define MODE_SENSE6_CMD 0x1a
+#define MODE_SENSE6_CMDLEN 6
+#define MODE_SENSE10_CMD 0x5a
+#define MODE_SENSE10_CMDLEN 10
+#define MODE_SELECT6_CMD 0x15
+#define MODE_SELECT6_CMDLEN 6
+#define MODE_SELECT10_CMD 0x55
+#define MODE_SELECT10_CMDLEN 10
+#define LOG_SENSE_CMD 0x4d
+#define LOG_SENSE_CMDLEN 10
+#define LOG_SELECT_CMD 0x4c
+#define LOG_SELECT_CMDLEN 10
+#define START_STOP_CMD 0x1b
+#define START_STOP_CMDLEN 6
+#define PREVENT_ALLOW_CMD 0x1e
+#define PREVENT_ALLOW_CMDLEN 6
+
+#define MODE6_RESP_HDR_LEN 4
+#define MODE10_RESP_HDR_LEN 8
+#define MODE_RESP_ARB_LEN 1024
+
+#define INQUIRY_RESP_INITIAL_LEN 36
+
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+ struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp)
+ pr2ws("%s: out of memory\n", cname);
+ return ptvp;
+}
+
+/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
+ unsigned int lba, unsigned int count, bool noisy,
+ int verbose)
+{
+ static const char * const cdb_s = "synchronize cache(10)";
+ int res, ret, sense_cat;
+ uint8_t sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] =
+ {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (sync_nv)
+ sc_cdb[1] |= 4;
+ if (immed)
+ sc_cdb[1] |= 2;
+ sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2);
+ sc_cdb[6] = group & GRPNUM_MASK;
+ if (count > 0xffff) {
+ pr2ws("count too big\n");
+ return -1;
+ }
+ sg_put_unaligned_be16((int16_t)count, sc_cdb + 7);
+
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(sc_cdb, SYNCHRONIZE_CACHE_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "read capacity(16)";
+ int ret, res, sense_cat;
+ uint8_t rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+ {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (pmi) { /* lbs only valid when pmi set */
+ rc_cdb[14] |= 1;
+ sg_put_unaligned_be64(llba, rc_cdb + 2);
+ }
+ /* Allocation length, no guidance in SBC-2 rev 15b */
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rc_cdb, SERVICE_ACTION_IN_16_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "read capacity(10)";
+ int ret, res, sense_cat;
+ uint8_t rc_cdb[READ_CAPACITY_10_CMDLEN] =
+ {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (pmi) { /* lbs only valid when pmi set */
+ rc_cdb[8] |= 1;
+ sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2);
+ }
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rc_cdb, READ_CAPACITY_10_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "mode sense(6)";
+ int res, ret, sense_cat, resid;
+ uint8_t modes_cdb[MODE_SENSE6_CMDLEN] =
+ {MODE_SENSE6_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ modes_cdb[1] = (uint8_t)(dbd ? 0x8 : 0);
+ modes_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+ modes_cdb[3] = (uint8_t)(sub_pg_code & 0xff);
+ modes_cdb[4] = (uint8_t)(mx_resp_len & 0xff);
+ if (mx_resp_len > 0xff) {
+ pr2ws("mx_resp_len too big\n");
+ return -1;
+ }
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(modes_cdb, MODE_SENSE6_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((verbose > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == verbose) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+ cdb_s, resid, mx_resp_len);
+ return ret ? ret : SG_LIB_CAT_MALFORMED;
+ }
+ /* zero unfilled section of response buffer */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+ return ret;
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+ int sub_pg_code, void * resp, int mx_resp_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code,
+ resp, mx_resp_len, 0, NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * Adds the ability to set the command abort timeout
+ * and the ability to report the residual count. If timeout_secs is zero
+ * or less the default command abort timeout (60 seconds) is used.
+ * If residp is non-NULL then the residual value is written where residp
+ * points. A residual value of 0 implies mx_resp_len bytes have be written
+ * where resp points. If the residual value equals mx_resp_len then no
+ * bytes have been written. */
+int
+sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
+ int sub_pg_code, void * resp, int mx_resp_len,
+ int timeout_secs, int * residp, bool noisy, int verbose)
+{
+ int res, ret, sense_cat, resid;
+ static const char * const cdb_s = "mode sense(10)";
+ struct sg_pt_base * ptvp;
+ uint8_t modes_cdb[MODE_SENSE10_CMDLEN] =
+ {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ modes_cdb[1] = (uint8_t)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0));
+ modes_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+ modes_cdb[3] = (uint8_t)(sub_pg_code & 0xff);
+ sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7);
+ if (mx_resp_len > 0xffff) {
+ pr2ws("mx_resp_len too big\n");
+ goto gen_err;
+ }
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(modes_cdb, MODE_SENSE10_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ goto gen_err;
+ set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((verbose > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == verbose) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+ cdb_s, resid, mx_resp_len);
+ return ret ? ret : SG_LIB_CAT_MALFORMED;
+ }
+ /* zero unfilled section of response buffer */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+ return ret;
+gen_err:
+ if (residp)
+ *residp = 0;
+ return -1;
+}
+
+/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_mode_select6_v2(int sg_fd, bool pf, bool rtd, bool sp, void * paramp,
+ int param_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "mode select(6)";
+ int res, ret, sense_cat;
+ uint8_t modes_cdb[MODE_SELECT6_CMDLEN] =
+ {MODE_SELECT6_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ modes_cdb[1] = (uint8_t)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+ if (rtd)
+ modes_cdb[1] |= 0x2;
+ modes_cdb[4] = (uint8_t)(param_len & 0xff);
+ if (param_len > 0xff) {
+ pr2ws("%s: param_len too big\n", cdb_s);
+ return -1;
+ }
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(modes_cdb, MODE_SELECT6_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (verbose > 1) {
+ pr2ws(" %s parameter list\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_mode_select6_v2(sg_fd, pf, false, sp, paramp, param_len,
+ noisy, verbose);
+}
+
+/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors,
+ * v2 adds rtd (revert to defaults) bit (spc5r11). */
+int
+sg_ll_mode_select10_v2(int sg_fd, bool pf, bool rtd, bool sp, void * paramp,
+ int param_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "mode select(10)";
+ int res, ret, sense_cat;
+ uint8_t modes_cdb[MODE_SELECT10_CMDLEN] =
+ {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ modes_cdb[1] = (uint8_t)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
+ if (rtd)
+ modes_cdb[1] |= 0x2;
+ sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7);
+ if (param_len > 0xffff) {
+ pr2ws("%s: param_len too big\n", cdb_s);
+ return -1;
+ }
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(modes_cdb, MODE_SELECT10_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (verbose > 1) {
+ pr2ws(" %s parameter list\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp,
+ int param_len, bool noisy, int verbose)
+{
+ return sg_ll_mode_select10_v2(sg_fd, pf, false, sp, paramp, param_len,
+ noisy, verbose);
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. In most cases users are
+ * interested in the first mode page. This function returns the (byte)
+ * offset of the start of the first mode page. Set mode_sense_6 to true for
+ * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
+ * or -1 if failure. If there is a failure a message is written to err_buff
+ * if it is non-NULL and err_buff_len > 0. */
+int
+sg_mode_page_offset(const uint8_t * resp, int resp_len,
+ bool mode_sense_6, char * err_buff, int err_buff_len)
+{
+ int bd_len, calc_len, offset;
+ bool err_buff_ok = ((err_buff_len > 0) && err_buff);
+
+ if ((NULL == resp) || (resp_len < 4))
+ goto too_short;
+ if (mode_sense_6) {
+ calc_len = resp[0] + 1;
+ bd_len = resp[3];
+ offset = bd_len + MODE6_RESP_HDR_LEN;
+ } else { /* Mode sense(10) */
+ if (resp_len < 8)
+ goto too_short;
+ calc_len = sg_get_unaligned_be16(resp) + 2;
+ bd_len = sg_get_unaligned_be16(resp + 6);
+ /* LongLBA doesn't change this calculation */
+ offset = bd_len + MODE10_RESP_HDR_LEN;
+ }
+ if ((offset + 2) > calc_len) {
+ if (err_buff_ok)
+ snprintf(err_buff, err_buff_len, "calculated response "
+ "length too small, offset=%d calc_len=%d bd_len=%d\n",
+ offset, calc_len, bd_len);
+ offset = -1;
+ }
+ return offset;
+too_short:
+ if (err_buff_ok)
+ snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) "
+ "too short\n", (mode_sense_6 ? 6 : 10), resp_len);
+ return -1;
+}
+
+/* MODE SENSE commands yield a response that has header then zero or more
+ * block descriptors followed by mode pages. This functions returns the
+ * length (in bytes) of those three components. Note that the return value
+ * can exceed resp_len in which case the MODE SENSE command should be
+ * re-issued with a larger response buffer. If bd_lenp is non-NULL and if
+ * successful the block descriptor length (in bytes) is written to *bd_lenp.
+ * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
+ * responses. Returns -1 if there is an error (e.g. response too short). */
+int
+sg_msense_calc_length(const uint8_t * resp, int resp_len,
+ bool mode_sense_6, int * bd_lenp)
+{
+ int calc_len;
+
+ if (NULL == resp)
+ goto an_err;
+ if (mode_sense_6) {
+ if (resp_len < 4)
+ goto an_err;
+ calc_len = resp[0] + 1;
+ } else {
+ if (resp_len < 8)
+ goto an_err;
+ calc_len = sg_get_unaligned_be16(resp + 0) + 2;
+ }
+ if (bd_lenp)
+ *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6);
+ return calc_len;
+an_err:
+ if (bd_lenp)
+ *bd_lenp = 0;
+ return -1;
+}
+
+/* Fetches current, changeable, default and/or saveable modes pages as
+ * indicated by pcontrol_arr for given pg_code and sub_pg_code. If
+ * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If
+ * flexible set and mode data length seems wrong then try and
+ * fix (compensating hack for bad device or driver). pcontrol_arr
+ * should have 4 elements for output of current, changeable, default
+ * and saved values respectively. Each element should be NULL or
+ * at least mx_mpage_len bytes long.
+ * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors.
+ * If success_mask pointer is not NULL then first zeros it. Then set bits
+ * 0, 1, 2 and/or 3 if the current, changeable, default and saved values
+ * respectively have been fetched. If error on current page
+ * then stops and returns that error; otherwise continues if an error is
+ * detected but returns the first error encountered. */
+int
+sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code,
+ bool dbd, bool flexible, int mx_mpage_len,
+ int * success_mask, void * pcontrol_arr[],
+ int * reported_lenp, int verbose)
+{
+ bool resp_mode6;
+ int k, n, res, offset, calc_len, xfer_len;
+ int resid = 0;
+ const int msense10_hlen = MODE10_RESP_HDR_LEN;
+ uint8_t buff[MODE_RESP_ARB_LEN];
+ char ebuff[EBUFF_SZ];
+ int first_err = 0;
+
+ if (success_mask)
+ *success_mask = 0;
+ if (reported_lenp)
+ *reported_lenp = 0;
+ if (mx_mpage_len < 4)
+ return 0;
+ memset(ebuff, 0, sizeof(ebuff));
+ /* first try to find length of current page response */
+ memset(buff, 0, msense10_hlen);
+ if (mode6) /* want first 8 bytes just in case */
+ res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code,
+ sub_pg_code, buff, msense10_hlen, true,
+ verbose);
+ else /* MODE SENSE(10) obviously */
+ res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+ 0 /* pc */, pg_code, sub_pg_code, buff,
+ msense10_hlen, 0, &resid, true, verbose);
+ if (0 != res)
+ return res;
+ n = buff[0];
+ if (reported_lenp) {
+ int m;
+
+ m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid;
+ if (m < 0) /* Grrr, this should not happen */
+ m = 0;
+ *reported_lenp = m;
+ }
+ resp_mode6 = mode6;
+ if (flexible) {
+ if (mode6 && (n < 3)) {
+ resp_mode6 = false;
+ if (verbose)
+ pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) "
+ "response processing\n", n);
+ }
+ if ((! mode6) && (n > 5)) {
+ if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) &&
+ (0 == buff[5]) && (0 == buff[6])) {
+ buff[1] = n;
+ buff[0] = 0;
+ if (verbose)
+ pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) "
+ "response so fix length\n", n);
+ } else
+ resp_mode6 = true;
+ }
+ }
+ if (verbose && (resp_mode6 != mode6))
+ pr2ws(">>> msense(%d) but resp[0]=%d so switch response "
+ "processing\n", (mode6 ? 6 : 10), buff[0]);
+ calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL);
+ if (calc_len > MODE_RESP_ARB_LEN)
+ calc_len = MODE_RESP_ARB_LEN;
+ offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ);
+ if (offset < 0) {
+ if (('\0' != ebuff[0]) && (verbose > 0))
+ pr2ws("%s: %s\n", __func__, ebuff);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ xfer_len = calc_len - offset;
+ if (xfer_len > mx_mpage_len)
+ xfer_len = mx_mpage_len;
+
+ for (k = 0; k < 4; ++k) {
+ if (NULL == pcontrol_arr[k])
+ continue;
+ memset(pcontrol_arr[k], 0, mx_mpage_len);
+ resid = 0;
+ if (mode6)
+ res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */,
+ pg_code, sub_pg_code, buff,
+ calc_len, true, verbose);
+ else
+ res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
+ k /* pc */, pg_code, sub_pg_code,
+ buff, calc_len, 0, &resid, true,
+ verbose);
+ if (res || resid) {
+ if (0 == first_err) {
+ if (res)
+ first_err = res;
+ else {
+ first_err = -49; /* unexpected resid != 0 */
+ if (verbose)
+ pr2ws("%s: unexpected resid=%d, page=0x%x, "
+ "pcontrol=%d\n", __func__, resid, pg_code, k);
+ }
+ }
+ if (0 == k)
+ break; /* if problem on current page, it won't improve */
+ else
+ continue;
+ }
+ if (xfer_len > 0)
+ memcpy(pcontrol_arr[k], buff + offset, xfer_len);
+ if (success_mask)
+ *success_mask |= (1 << k);
+ }
+ return first_err;
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors. */
+int
+sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+ int subpg_code, int paramp, uint8_t * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code,
+ paramp, resp, mx_resp_len, 0, NULL, noisy,
+ verbose);
+}
+
+/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * Adds the ability to set the command abort timeout
+ * and the ability to report the residual count. If timeout_secs is zero
+ * or less the default command abort timeout (60 seconds) is used.
+ * If residp is non-NULL then the residual value is written where residp
+ * points. A residual value of 0 implies mx_resp_len bytes have be written
+ * where resp points. If the residual value equals mx_resp_len then no
+ * bytes have been written. */
+int
+sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
+ int subpg_code, int paramp, uint8_t * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose)
+{
+ static const char * const cdb_s = "log sense";
+ int res, ret, sense_cat, resid;
+ uint8_t logs_cdb[LOG_SENSE_CMDLEN] =
+ {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (mx_resp_len > 0xffff) {
+ pr2ws("mx_resp_len too big\n");
+ goto gen_err;
+ }
+ logs_cdb[1] = (uint8_t)((ppc ? 2 : 0) | (sp ? 1 : 0));
+ logs_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+ logs_cdb[3] = (uint8_t)(subpg_code & 0xff);
+ sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5);
+ sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(logs_cdb, LOG_SENSE_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ goto gen_err;
+ set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((mx_resp_len > 3) && (ret < 4)) {
+ /* resid indicates LOG SENSE response length bad, so zero it */
+ resp[2] = 0;
+ resp[3] = 0;
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
+ cdb_s, resid, mx_resp_len);
+ return ret ? ret : SG_LIB_CAT_MALFORMED;
+ }
+ /* zero unfilled section of response buffer */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+ return ret;
+gen_err:
+ if (residp)
+ *residp = 0;
+ return -1;
+}
+
+/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
+ int subpg_code, uint8_t * paramp, int param_len,
+ bool noisy, int verbose)
+{
+ static const char * const cdb_s = "log select";
+ int res, ret, sense_cat;
+ uint8_t logs_cdb[LOG_SELECT_CMDLEN] =
+ {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (param_len > 0xffff) {
+ pr2ws("%s: param_len too big\n", cdb_s);
+ return -1;
+ }
+ logs_cdb[1] = (uint8_t)((pcr ? 2 : 0) | (sp ? 1 : 0));
+ logs_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
+ logs_cdb[3] = (uint8_t)(subpg_code & 0xff);
+ sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(logs_cdb, LOG_SELECT_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if ((verbose > 1) && (param_len > 0)) {
+ pr2ws(" %s parameter list\n", cdb_s);
+ hex2stderr(paramp, param_len, -1);
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
+ * format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
+ * and fl(mmc) one bit field. This is the cause of the awkardly named
+ * pc_mod__fl_num and noflush__fl arguments to this function.
+ * */
+static int
+sg_ll_start_stop_unit_com(struct sg_pt_base * ptvp, int sg_fd, bool immed,
+ int pc_mod__fl_num, int power_cond, bool noflush__fl,
+ bool loej, bool start, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "start stop unit";
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, sense_cat;
+ uint8_t ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (immed)
+ ssuBlk[1] = 0x1;
+ ssuBlk[3] = pc_mod__fl_num & 0xf; /* bits 2 and 3 are reserved in MMC */
+ ssuBlk[4] = ((power_cond & 0xf) << 4);
+ if (noflush__fl)
+ ssuBlk[4] |= 0x4;
+ if (loej)
+ ssuBlk[4] |= 0x2;
+ if (start)
+ ssuBlk[4] |= 0x1;
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(ssuBlk, sizeof(ssuBlk), false,
+ sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ res = do_scsi_pt(ptvp, -1, START_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+int
+sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+ int power_cond, bool noflush__fl, bool loej, bool start,
+ bool noisy, int verbose)
+{
+ return sg_ll_start_stop_unit_com(NULL, sg_fd, immed, pc_mod__fl_num,
+ power_cond, noflush__fl, loej, start,
+ noisy, verbose);
+}
+
+int
+sg_ll_start_stop_unit_pt(struct sg_pt_base * ptvp, bool immed,
+ int pc_mod__fl_num, int power_cond, bool noflush__fl,
+ bool loej, bool start, bool noisy, int verbose)
+{
+ return sg_ll_start_stop_unit_com(ptvp, -1, immed, pc_mod__fl_num,
+ power_cond, noflush__fl, loej, start,
+ noisy, verbose);
+}
+
+/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command
+ * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3]
+ * prevent==0 allows removal, prevent==1 prevents removal ...
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "prevent allow medium removal";
+ int res, ret, sense_cat;
+ uint8_t p_cdb[PREVENT_ALLOW_CMDLEN] =
+ {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if ((prevent < 0) || (prevent > 3)) {
+ pr2ws("prevent argument should be 0, 1, 2 or 3\n");
+ return -1;
+ }
+ p_cdb[4] |= (prevent & 0x3);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(p_cdb, PREVENT_ALLOW_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
diff --git a/lib/sg_cmds_extra.c b/lib/sg_cmds_extra.c
new file mode 100644
index 00000000..f0bfd16b
--- /dev/null
+++ b/lib/sg_cmds_extra.c
@@ -0,0 +1,2620 @@
+/*
+ * Copyright (c) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */
+
+#define SERVICE_ACTION_IN_16_CMD 0x9e
+#define SERVICE_ACTION_IN_16_CMDLEN 16
+#define SERVICE_ACTION_OUT_16_CMD 0x9f
+#define SERVICE_ACTION_OUT_16_CMDLEN 16
+#define MAINTENANCE_IN_CMD 0xa3
+#define MAINTENANCE_IN_CMDLEN 12
+#define MAINTENANCE_OUT_CMD 0xa4
+#define MAINTENANCE_OUT_CMDLEN 12
+
+#define ATA_PT_12_CMD 0xa1
+#define ATA_PT_12_CMDLEN 12
+#define ATA_PT_16_CMD 0x85
+#define ATA_PT_16_CMDLEN 16
+#define ATA_PT_32_SA 0x1ff0
+#define ATA_PT_32_CMDLEN 32
+#define FORMAT_UNIT_CMD 0x4
+#define FORMAT_UNIT_CMDLEN 6
+#define PERSISTENT_RESERVE_IN_CMD 0x5e
+#define PERSISTENT_RESERVE_IN_CMDLEN 10
+#define PERSISTENT_RESERVE_OUT_CMD 0x5f
+#define PERSISTENT_RESERVE_OUT_CMDLEN 10
+#define READ_BLOCK_LIMITS_CMD 0x5
+#define READ_BLOCK_LIMITS_CMDLEN 6
+#define READ_BUFFER_CMD 0x3c
+#define READ_BUFFER_CMDLEN 10
+#define READ_DEFECT10_CMD 0x37
+#define READ_DEFECT10_CMDLEN 10
+#define REASSIGN_BLKS_CMD 0x7
+#define REASSIGN_BLKS_CMDLEN 6
+#define RECEIVE_DIAGNOSTICS_CMD 0x1c
+#define RECEIVE_DIAGNOSTICS_CMDLEN 6
+#define THIRD_PARTY_COPY_OUT_CMD 0x83 /* was EXTENDED_COPY_CMD */
+#define THIRD_PARTY_COPY_OUT_CMDLEN 16
+#define THIRD_PARTY_COPY_IN_CMD 0x84 /* was RECEIVE_COPY_RESULTS_CMD */
+#define THIRD_PARTY_COPY_IN_CMDLEN 16
+#define SEND_DIAGNOSTIC_CMD 0x1d
+#define SEND_DIAGNOSTIC_CMDLEN 6
+#define SERVICE_ACTION_IN_12_CMD 0xab
+#define SERVICE_ACTION_IN_12_CMDLEN 12
+#define READ_LONG10_CMD 0x3e
+#define READ_LONG10_CMDLEN 10
+#define UNMAP_CMD 0x42
+#define UNMAP_CMDLEN 10
+#define VERIFY10_CMD 0x2f
+#define VERIFY10_CMDLEN 10
+#define VERIFY16_CMD 0x8f
+#define VERIFY16_CMDLEN 16
+#define WRITE_LONG10_CMD 0x3f
+#define WRITE_LONG10_CMDLEN 10
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define PRE_FETCH10_CMD 0x34
+#define PRE_FETCH10_CMDLEN 10
+#define PRE_FETCH16_CMD 0x90
+#define PRE_FETCH16_CMDLEN 16
+#define SEEK10_CMD 0x2b
+#define SEEK10_CMDLEN 10
+
+#define GET_LBA_STATUS16_SA 0x12
+#define GET_LBA_STATUS32_SA 0x12
+#define READ_LONG_16_SA 0x11
+#define READ_MEDIA_SERIAL_NUM_SA 0x1
+#define REPORT_IDENTIFYING_INFORMATION_SA 0x5
+#define REPORT_TGT_PRT_GRP_SA 0xa
+#define SET_IDENTIFYING_INFORMATION_SA 0x6
+#define SET_TGT_PRT_GRP_SA 0xa
+#define WRITE_LONG_16_SA 0x11
+#define REPORT_REFERRALS_SA 0x13
+#define EXTENDED_COPY_LID1_SA 0x0
+
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+ struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp)
+ pr2ws("%s: out of memory\n", cname);
+ return ptvp;
+}
+
+
+/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt,
+ void * resp, int alloc_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Get LBA status(16)";
+ int res, s_cat, ret;
+ uint8_t getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd));
+ getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD;
+ getLbaStatCmd[1] = GET_LBA_STATUS16_SA;
+
+ sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2);
+ sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10);
+ getLbaStatCmd[14] = rt;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(getLbaStatCmd, SERVICE_ACTION_IN_16_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, alloc_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response\n", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp,
+ int alloc_len, bool noisy, int vb)
+{
+ return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp,
+ alloc_len, noisy, vb);
+}
+
+#define GLS32_CMD_LEN 32
+
+int
+sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len,
+ uint32_t element_id, uint8_t rt,
+ void * resp, int alloc_len, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "Get LBA status(32)";
+ int res, s_cat, ret;
+ uint8_t gls32_cmd[GLS32_CMD_LEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(gls32_cmd, 0, sizeof(gls32_cmd));
+ gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD;
+ gls32_cmd[7] = GLS32_CMD_LEN - 8;
+ sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8);
+ gls32_cmd[10] = rt;
+ sg_put_unaligned_be64(start_llba, gls32_cmd + 12);
+ sg_put_unaligned_be32(scan_len, gls32_cmd + 20);
+ sg_put_unaligned_be32(element_id, gls32_cmd + 24);
+ sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(gls32_cmd, GLS32_CMD_LEN, false, sizeof(b),
+ b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, alloc_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response\n", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+ bool noisy, int vb)
+{
+ return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy,
+ vb);
+}
+
+/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+ bool extended, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Report target port groups";
+ int res, ret, s_cat;
+ uint8_t rtpg_cdb[MAINTENANCE_IN_CMDLEN] =
+ {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (extended)
+ rtpg_cdb[1] |= 0x20;
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rtpg_cdb, MAINTENANCE_IN_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "Set target port groups";
+ int res, ret, s_cat;
+ uint8_t stpg_cdb[MAINTENANCE_OUT_CMDLEN] =
+ {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(stpg_cdb, MAINTENANCE_OUT_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg,
+ void * resp, int mx_resp_len, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "Report referrals";
+ int res, ret, s_cat;
+ uint8_t repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
+ {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be64(start_llba, repRef_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10);
+ if (one_seg)
+ repRef_cdb[14] = 0x1;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(repRef_cdb, SERVICE_ACTION_IN_16_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can
+ * take a long time, if so set long_duration flag in which case the timeout
+ * is set to 7200 seconds; if the value of long_duration is > 7200 then that
+ * value is taken as the timeout value in seconds. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_send_diag_com(struct sg_pt_base * ptvp, int sg_fd, int st_code,
+ bool pf_bit, bool st_bit, bool devofl_bit,
+ bool unitofl_bit, int long_duration, void * paramp,
+ int param_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Send diagnostic";
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, s_cat, tmout;
+ uint8_t senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] =
+ {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ senddiag_cdb[1] = (uint8_t)(st_code << 5);
+ if (pf_bit)
+ senddiag_cdb[1] |= 0x10;
+ if (st_bit)
+ senddiag_cdb[1] |= 0x4;
+ if (devofl_bit)
+ senddiag_cdb[1] |= 0x2;
+ if (unitofl_bit)
+ senddiag_cdb[1] |= 0x1;
+ sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3);
+ if (long_duration > LONG_PT_TIMEOUT)
+ tmout = long_duration;
+ else
+ tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT;
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(senddiag_cdb, SEND_DIAGNOSTIC_CMDLEN,
+ false, sizeof(b), b));
+ if (vb > 1) {
+ if (paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ pr2ws(" %s timeout: %d seconds\n", cdb_s, tmout);
+ }
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, -1, tmout, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+int
+sg_ll_send_diag_pt(struct sg_pt_base * ptvp, int st_code, bool pf_bit,
+ bool st_bit, bool devofl_bit, bool unitofl_bit,
+ int long_duration, void * paramp, int param_len,
+ bool noisy, int vb)
+{
+ return sg_ll_send_diag_com(ptvp, -1, st_code, pf_bit, st_bit, devofl_bit,
+ unitofl_bit, long_duration, paramp,
+ param_len, noisy, vb);
+}
+
+int
+sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
+ bool devofl_bit, bool unitofl_bit, int long_duration,
+ void * paramp, int param_len, bool noisy, int vb)
+{
+ return sg_ll_send_diag_com(NULL, sg_fd, st_code, pf_bit, st_bit,
+ devofl_bit, unitofl_bit, long_duration, paramp,
+ param_len, noisy, vb);
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_receive_diag_com(struct sg_pt_base * ptvp, int sg_fd, bool pcv,
+ int pg_code, void * resp, int mx_resp_len,
+ int timeout_secs, int * residp, bool noisy, int vb)
+{
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int resid = 0;
+ int res, ret, s_cat;
+ static const char * const cdb_s = "Receive diagnostic results";
+ uint8_t rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] =
+ {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (pcv)
+ rcvdiag_cdb[1] = 0x1;
+ rcvdiag_cdb[2] = (uint8_t)(pg_code);
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3);
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rcvdiag_cdb, RECEIVE_DIAGNOSTICS_CMDLEN,
+ false, sizeof(b), b));
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, timeout_secs, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+int
+sg_ll_receive_diag_pt(struct sg_pt_base * ptvp, bool pcv, int pg_code,
+ void * resp, int mx_resp_len, int timeout_secs,
+ int * residp, bool noisy, int vb)
+{
+ return sg_ll_receive_diag_com(ptvp, -1, pcv, pg_code, resp, mx_resp_len,
+ timeout_secs, residp, noisy, vb);
+}
+
+/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
+ int mx_resp_len, bool noisy, int vb)
+{
+ return sg_ll_receive_diag_com(NULL, sg_fd, pcv, pg_code, resp,
+ mx_resp_len, 0, NULL, noisy, vb);
+}
+
+int
+sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int vb)
+{
+ return sg_ll_receive_diag_com(NULL, sg_fd, pcv, pg_code, resp,
+ mx_resp_len, timeout_secs, residp, noisy,
+ vb);
+}
+
+/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format,
+ void * resp, int mx_resp_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Read defect(10)";
+ int res, ret, s_cat;
+ uint8_t rdef_cdb[READ_DEFECT10_CMDLEN] =
+ {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ rdef_cdb[2] = (dl_format & 0x7);
+ if (req_plist)
+ rdef_cdb[2] |= 0x10;
+ if (req_glist)
+ rdef_cdb[2] |= 0x8;
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7);
+ if (mx_resp_len > 0xffff) {
+ pr2ws("mx_resp_len too big\n");
+ return -1;
+ }
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rdef_cdb, READ_DEFECT10_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response\n", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len,
+ bool noisy, int vb)
+{
+ static const char * const cdb_s = "Read media serial number";
+ int res, ret, s_cat;
+ uint8_t rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] =
+ {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rmsn_cdb, SERVICE_ACTION_IN_12_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was
+ * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len,
+ bool noisy, int vb)
+{
+ static const char * const cdb_s = "Report identifying information";
+ int res, ret, s_cat;
+ uint8_t rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD,
+ REPORT_IDENTIFYING_INFORMATION_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6);
+ rii_cdb[10] |= (itype << 1) & 0xfe;
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rii_cdb, MAINTENANCE_IN_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, max_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was
+ * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len,
+ bool noisy, int vb)
+{
+ static const char * const cdb_s = "Set identifying information";
+ int res, ret, s_cat;
+ uint8_t sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD,
+ SET_IDENTIFYING_INFORMATION_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6);
+ sii_cdb[10] |= (itype << 1) & 0xfe;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(sii_cdb, MAINTENANCE_OUT_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplst, int dlist_format, int timeout_secs,
+ void * paramp, int param_len, bool noisy, int vb)
+{
+ return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+ dlist_format, 0, timeout_secs, paramp,
+ param_len, noisy, vb);
+}
+
+/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplst, int dlist_format, int ffmt, int timeout_secs,
+ void * paramp, int param_len, bool noisy, int vb)
+{
+ return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst,
+ dlist_format, ffmt, timeout_secs, paramp,
+ param_len, noisy, vb);
+}
+
+/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * FFMT field added in sbc4r10 [20160121] */
+int
+sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata,
+ bool cmplst, int dlist_format, int ffmt,
+ int timeout_secs, void * paramp, int param_len,
+ bool noisy, int vb)
+{
+ static const char * const cdb_s = "Format unit";
+ int res, ret, s_cat, tmout;
+ uint8_t fu_cdb[FORMAT_UNIT_CMDLEN] =
+ {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (fmtpinfo)
+ fu_cdb[1] |= (fmtpinfo << 6);
+ if (longlist)
+ fu_cdb[1] |= 0x20;
+ if (fmtdata)
+ fu_cdb[1] |= 0x10;
+ if (cmplst)
+ fu_cdb[1] |= 0x8;
+ if (dlist_format)
+ fu_cdb[1] |= (dlist_format & 0x7);
+ if (ffmt)
+ fu_cdb[4] |= (ffmt & 0x3);
+ tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(fu_cdb, 6, false, sizeof(b), b));
+ if (vb > 1) {
+ if (param_len > 0) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ pr2ws(" %s timeout: %d seconds\n", cdb_s, tmout);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, tmout, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp,
+ int param_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Reassign blocks";
+ int res, ret, s_cat;
+ uint8_t reass_cdb[REASSIGN_BLKS_CMDLEN] =
+ {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (longlba)
+ reass_cdb[1] = 0x2;
+ if (longlist)
+ reass_cdb[1] |= 0x1;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(reass_cdb, REASSIGN_BLKS_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (vb > 1) {
+ pr2ws(" %s parameter list\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp,
+ int mx_resp_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Persistent reservation in";
+ int res, ret, s_cat;
+ uint8_t prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] =
+ {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (rq_servact > 0)
+ prin_cdb[1] = (uint8_t)(rq_servact & 0x1f);
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7);
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(prin_cdb, PERSISTENT_RESERVE_IN_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0
+ * when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope,
+ unsigned int rq_type, void * paramp,
+ int param_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "Persistent reservation out";
+ int res, ret, s_cat;
+ uint8_t prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] =
+ {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (rq_servact > 0)
+ prout_cdb[1] = (uint8_t)(rq_servact & 0x1f);
+ prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf));
+ sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7);
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(prout_cdb, PERSISTENT_RESERVE_OUT_CMDLEN,
+ false, sizeof(b), b));
+ if (vb > 1) {
+ pr2ws(" %s parameters:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, 0);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static bool
+has_blk_ili(uint8_t * sensep, int sb_len)
+{
+ int resp_code;
+
+ if (sb_len < 8)
+ return false;
+ resp_code = (0x7f & sensep[0]);
+ if (resp_code >= 0x72) { /* descriptor format */
+ const uint8_t * cup;
+
+ /* find block command descriptor */
+ if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5)))
+ return (cup[3] & 0x20);
+ } else /* fixed */
+ return (sensep[2] & 0x20);
+ return false;
+}
+
+/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba,
+ void * resp, int xfer_len, int * offsetp, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "read long(10)";
+ int res, s_cat, ret;
+ uint8_t readLong_cdb[READ_LONG10_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(readLong_cdb, 0, READ_LONG10_CMDLEN);
+ readLong_cdb[0] = READ_LONG10_CMD;
+ if (pblock)
+ readLong_cdb[1] |= 0x4;
+ if (correct)
+ readLong_cdb[1] |= 0x2;
+
+ sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2);
+ sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(readLong_cdb, READ_LONG10_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, xfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ {
+ bool valid, ili;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ ili = has_blk_ili(sense_b, slen);
+ if (valid && ili) {
+ if (offsetp)
+ *offsetp = (int)(int64_t)ull;
+ ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+ } else {
+ if (vb > 1)
+ pr2ws(" info field: 0x%" PRIx64 ", valid: %d, "
+ "ili: %d\n", ull, valid, ili);
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ }
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba,
+ void * resp, int xfer_len, int * offsetp, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "read long(16)";
+ int res, s_cat, ret;
+ uint8_t readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(readLong_cdb, 0, sizeof(readLong_cdb));
+ readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD;
+ readLong_cdb[1] = READ_LONG_16_SA;
+ if (pblock)
+ readLong_cdb[14] |= 0x2;
+ if (correct)
+ readLong_cdb[14] |= 0x1;
+
+ sg_put_unaligned_be64(llba, readLong_cdb + 2);
+ sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(readLong_cdb, SERVICE_ACTION_IN_16_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, xfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ {
+ bool valid, ili;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ ili = has_blk_ili(sense_b, slen);
+ if (valid && ili) {
+ if (offsetp)
+ *offsetp = (int)(int64_t)ull;
+ ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+ } else {
+ if (vb > 1)
+ pr2ws(" info field: 0x%" PRIx64 ", valid: %d, "
+ "ili: %d\n", ull, (int)valid, (int)ili);
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ }
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+ unsigned int lba, void * data_out, int xfer_len,
+ int * offsetp, bool noisy, int vb)
+{
+ static const char * const cdb_s = "write long(10)";
+ int res, s_cat, ret;
+ uint8_t writeLong_cdb[WRITE_LONG10_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN);
+ writeLong_cdb[0] = WRITE_LONG10_CMD;
+ if (cor_dis)
+ writeLong_cdb[1] |= 0x80;
+ if (wr_uncor)
+ writeLong_cdb[1] |= 0x40;
+ if (pblock)
+ writeLong_cdb[1] |= 0x20;
+
+ sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2);
+ sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(writeLong_cdb, (int)sizeof(writeLong_cdb),
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, xfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ {
+ int valid, slen, ili;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ ili = has_blk_ili(sense_b, slen);
+ if (valid && ili) {
+ if (offsetp)
+ *offsetp = (int)(int64_t)ull;
+ ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+ } else {
+ if (vb > 1)
+ pr2ws(" info field: 0x%" PRIx64 ", valid: %d, "
+ "ili: %d\n", ull, (int)valid, (int)ili);
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ }
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len'
+ * is in bytes. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock,
+ uint64_t llba, void * data_out, int xfer_len,
+ int * offsetp, bool noisy, int vb)
+{
+ static const char * const cdb_s = "write long(16)";
+ int res, s_cat, ret;
+ uint8_t writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(writeLong_cdb, 0, sizeof(writeLong_cdb));
+ writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD;
+ writeLong_cdb[1] = WRITE_LONG_16_SA;
+ if (cor_dis)
+ writeLong_cdb[1] |= 0x80;
+ if (wr_uncor)
+ writeLong_cdb[1] |= 0x40;
+ if (pblock)
+ writeLong_cdb[1] |= 0x20;
+
+ sg_put_unaligned_be64(llba, writeLong_cdb + 2);
+ sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(writeLong_cdb, SERVICE_ACTION_OUT_16_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, xfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ {
+ bool valid, ili;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ ili = has_blk_ili(sense_b, slen);
+ if (valid && ili) {
+ if (offsetp)
+ *offsetp = (int)(int64_t)ull;
+ ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO;
+ } else {
+ if (vb > 1)
+ pr2ws(" info field: 0x%" PRIx64 ", valid: %d, "
+ "ili: %d\n", ull, (int)valid, (int)ili);
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ }
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI VERIFY (10) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk,
+ unsigned int lba, int veri_len, void * data_out,
+ int data_out_len, unsigned int * infop, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "verify(10)";
+ int res, ret, s_cat, slen;
+ uint8_t v_cdb[VERIFY10_CMDLEN] =
+ {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+ v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+ if (dpo)
+ v_cdb[1] |= 0x10;
+ sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2);
+ sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7);
+ if (vb > 1) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(v_cdb, VERIFY10_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 3) && bytchk && data_out && (data_out_len > 0)) {
+ int k = data_out_len > 4104 ? 4104 : data_out_len;
+
+ pr2ws(" data_out buffer%s\n",
+ (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+ hex2stderr((const uint8_t *)data_out, k, vb < 5);
+ }
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ if (data_out_len > 0)
+ set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, data_out_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ {
+ bool valid;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid) {
+ if (infop)
+ *infop = (unsigned int)ull;
+ ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+ } else
+ ret = SG_LIB_CAT_MEDIUM_HARD;
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI VERIFY (16) command (SBC and MMC).
+ * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes.
+ * Returns of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba,
+ int veri_len, int group_num, void * data_out,
+ int data_out_len, uint64_t * infop, bool noisy, int vb)
+{
+ static const char * const cdb_s = "verify(16)";
+ int res, ret, s_cat, slen;
+ uint8_t v_cdb[VERIFY16_CMDLEN] =
+ {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */
+ v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ;
+ if (dpo)
+ v_cdb[1] |= 0x10;
+ sg_put_unaligned_be64(llba, v_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10);
+ v_cdb[14] = group_num & GRPNUM_MASK;
+ if (vb > 1) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(v_cdb, VERIFY16_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 3) && bytchk && data_out && (data_out_len > 0)) {
+ int k = data_out_len > 4104 ? 4104 : data_out_len;
+
+ pr2ws(" data_out buffer%s\n",
+ (data_out_len > 4104 ? ", first 4104 bytes" : ""));
+ hex2stderr((const uint8_t *)data_out, k, vb < 5);
+ }
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ if (data_out_len > 0)
+ set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, data_out_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ {
+ bool valid;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid) {
+ if (infop)
+ *infop = ull;
+ ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+ } else
+ ret = SG_LIB_CAT_MEDIUM_HARD;
+ }
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is
+ * selected by the cdb_len argument that can take values of 12, 16 or 32
+ * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9
+ * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from
+ * the control byte, the rest is copied into an internal cdb which is then
+ * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15
+ * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the
+ * timeout is set to 60 seconds. For data in or out transfers set dinp or
+ * doutp, and dlen to the number of bytes to transfer. If dlen is zero then
+ * no data transfer is assumed. If sense buffer obtained then it is written
+ * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is
+ * obtained then written to ata_return_dp, else ata_return_dp[0] is set to
+ * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers.
+ * Returns SCSI status value (>= 0) or -1 if other error. Users are
+ * expected to check the sense buffer themselves. If available the data in
+ * resid is written to residp. Note in SAT-2 and later, fixed format sense
+ * data may be placed in *sensep in which case sensep[0]==0x70, prior to
+ * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72).
+ */
+int
+sg_ll_ata_pt(int sg_fd, const uint8_t * cdbp, int cdb_len,
+ int timeout_secs, void * dinp, void * doutp, int dlen,
+ uint8_t * sensep, int max_sense_len,
+ uint8_t * ata_return_dp, int max_ata_return_len,
+ int * residp, int vb)
+{
+ int k, res, slen, duration;
+ int ret = -1;
+ uint8_t apt_cdb[ATA_PT_32_CMDLEN];
+ uint8_t incoming_apt_cdb[ATA_PT_32_CMDLEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t * sp;
+ const uint8_t * bp;
+ struct sg_pt_base * ptvp;
+ const char * cnamep;
+ char b[256];
+
+ memset(apt_cdb, 0, sizeof(apt_cdb));
+ memset(incoming_apt_cdb, 0, sizeof(incoming_apt_cdb));
+ if (NULL == cdbp) {
+ if (vb)
+ pr2ws("NULL cdb pointer\n");
+ return -1;
+ }
+ memcpy(incoming_apt_cdb, cdbp, cdb_len);
+ b[0] = '\0';
+ switch (cdb_len) {
+ case 12:
+ cnamep = "ATA pass-through(12)";
+ apt_cdb[0] = ATA_PT_12_CMD;
+ memcpy(apt_cdb + 1, incoming_apt_cdb + 1, 10);
+ /* control byte at cdb[11] left at zero */
+ break;
+ case 16:
+ cnamep = "ATA pass-through(16)";
+ apt_cdb[0] = ATA_PT_16_CMD;
+ memcpy(apt_cdb + 1, incoming_apt_cdb + 1, 14);
+ /* control byte at cdb[15] left at zero */
+ break;
+ case 32:
+ cnamep = "ATA pass-through(32)";
+ apt_cdb[0] = SG_VARIABLE_LENGTH_CMD;
+ /* control byte at cdb[1] left at zero */
+ apt_cdb[7] = 0x18; /* length starting at next byte */
+ sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8);
+ memcpy(apt_cdb + 10, incoming_apt_cdb + 10, 32 - 10);
+ break;
+ default:
+ pr2ws("cdb_len must be 12, 16 or 32\n");
+ return -1;
+ }
+ if (sensep && (max_sense_len >= (int)sizeof(sense_b))) {
+ sp = sensep;
+ slen = max_sense_len;
+ } else {
+ sp = sense_b;
+ slen = sizeof(sense_b);
+ }
+ if (vb) {
+ if (cdb_len < 32) {
+ char d[128];
+
+ pr2ws(" %s cdb: %s\n", cnamep,
+ sg_get_command_str(apt_cdb, cdb_len, false, sizeof(d), d));
+ } else {
+ pr2ws(" %s cdb:\n", cnamep);
+ hex2stderr(apt_cdb, cdb_len, -1);
+ }
+ }
+ if (NULL == ((ptvp = create_pt_obj(cnamep))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len);
+ set_scsi_pt_sense(ptvp, sp, slen);
+ if (dlen > 0) {
+ if (dinp)
+ set_scsi_pt_data_in(ptvp, (uint8_t *)dinp, dlen);
+ else if (doutp)
+ set_scsi_pt_data_out(ptvp, (uint8_t *)doutp, dlen);
+ }
+ res = do_scsi_pt(ptvp, sg_fd,
+ ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT),
+ vb);
+ if (SCSI_PT_DO_BAD_PARAMS == res) {
+ if (vb)
+ pr2ws("%s: bad parameters\n", cnamep);
+ goto out;
+ } else if (SCSI_PT_DO_TIMEOUT == res) {
+ if (vb)
+ pr2ws("%s: timeout\n", cnamep);
+ goto out;
+ } else if (res > 2) {
+ if (vb)
+ pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res);
+ }
+
+ if ((vb > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0))
+ pr2ws(" duration=%d ms\n", duration);
+
+ switch (get_scsi_pt_result_category(ptvp)) {
+ case SCSI_PT_RESULT_GOOD:
+ if ((sensep) && (max_sense_len > 0))
+ *sensep = 0;
+ if ((ata_return_dp) && (max_ata_return_len > 0))
+ *ata_return_dp = 0;
+ if (residp && (dlen > 0))
+ *residp = get_scsi_pt_resid(ptvp);
+ ret = 0;
+ break;
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */
+ if ((sensep) && (max_sense_len > 0))
+ *sensep = 0;
+ if ((ata_return_dp) && (max_ata_return_len > 0))
+ *ata_return_dp = 0;
+ ret = get_scsi_pt_status_response(ptvp);
+ break;
+ case SCSI_PT_RESULT_SENSE:
+ if (sensep && (sp != sensep)) {
+ k = get_scsi_pt_sense_len(ptvp);
+ k = (k > max_sense_len) ? max_sense_len : k;
+ memcpy(sensep, sp, k);
+ }
+ if (ata_return_dp && (max_ata_return_len > 0)) {
+ /* search for ATA return descriptor */
+ bp = sg_scsi_sense_desc_find(sp, slen, 0x9);
+ if (bp) {
+ k = bp[1] + 2;
+ k = (k > max_ata_return_len) ? max_ata_return_len : k;
+ memcpy(ata_return_dp, bp, k);
+ } else
+ ata_return_dp[0] = 0x0;
+ }
+ if (residp && (dlen > 0))
+ *residp = get_scsi_pt_resid(ptvp);
+ ret = get_scsi_pt_status_response(ptvp);
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ if (vb)
+ pr2ws("%s: transport error: %s\n", cnamep,
+ get_scsi_pt_transport_err_str(ptvp, sizeof(b), b));
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ if (vb)
+ pr2ws("%s: os error: %s\n", cnamep,
+ get_scsi_pt_os_err_str(ptvp, sizeof(b) , b));
+ break;
+ default:
+ if (vb)
+ pr2ws("%s: unknown pt_result_category=%d\n", cnamep,
+ get_scsi_pt_result_category(ptvp));
+ break;
+ }
+
+out:
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+ void * resp, int mx_resp_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "read buffer(10)";
+ int res, ret, s_cat;
+ uint8_t rbuf_cdb[READ_BUFFER_CMDLEN] =
+ {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ rbuf_cdb[1] = (uint8_t)(mode & 0x1f);
+ rbuf_cdb[2] = (uint8_t)(buffer_id & 0xff);
+ sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3);
+ sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rbuf_cdb, READ_BUFFER_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset,
+ void * paramp, int param_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "write buffer";
+ int res, ret, s_cat;
+ uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+ {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ wbuf_cdb[1] = (uint8_t)(mode & 0x1f);
+ wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff);
+ sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3);
+ sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(wbuf_cdb, WRITE_BUFFER_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list", cdb_s);
+ if (2 == vb) {
+ pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)paramp,
+ (param_len > 256 ? 256 : param_len), -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)paramp, param_len, 0);
+ }
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 ->
+ * success, SG_LIB_CAT_INVALID_OP -> invalid opcode,
+ * 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. Adds mode specific field (spc4r32) and timeout
+ * to command abort to override default of 60 seconds. If timeout_secs is
+ * 0 or less then the default timeout is used instead. */
+int
+sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id,
+ uint32_t buffer_offset, void * paramp,
+ uint32_t param_len, int timeout_secs, bool noisy,
+ int vb)
+{
+ int res, ret, s_cat;
+ uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] =
+ {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (buffer_offset > 0xffffff) {
+ pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__);
+ return -1;
+ }
+ if (param_len > 0xffffff) {
+ pr2ws("%s: param_len value too large for 24 bits\n", __func__);
+ return -1;
+ }
+ wbuf_cdb[1] = (uint8_t)(mode & 0x1f);
+ wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5);
+ wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff);
+ sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3);
+ sg_put_unaligned_be24(param_len, wbuf_cdb + 6);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" Write buffer cdb: %s\n",
+ sg_get_command_str(wbuf_cdb, WRITE_BUFFER_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" Write buffer parameter list%s:\n",
+ ((param_len > 256) ? " (first 256 bytes)" : ""));
+ hex2stderr((const uint8_t *)paramp,
+ ((param_len > 256) ? 256 : param_len), -1);
+ }
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2ws("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout_secs, vb);
+ ret = sg_cmds_process_resp(ptvp, "Write buffer", res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI UNMAP command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp,
+ int param_len, bool noisy, int vb)
+{
+ return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp,
+ param_len, noisy, vb);
+}
+
+/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field
+ * (sbc3r22). Otherwise same as sg_ll_unmap() . */
+int
+sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs,
+ void * paramp, int param_len, bool noisy, int vb)
+{
+ static const char * const cdb_s = "unmap";
+ int res, ret, s_cat, tmout;
+ uint8_t u_cdb[UNMAP_CMDLEN] =
+ {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (anchor)
+ u_cdb[1] |= 0x1;
+ tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+ u_cdb[6] = group_num & GRPNUM_MASK;
+ sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7);
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(u_cdb, UNMAP_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, tmout, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_read_block_limits_v2(int sg_fd, bool mloi, void * resp,
+ int mx_resp_len, int * residp, bool noisy,
+ int vb)
+{
+ static const char * const cdb_s = "read block limits";
+ int ret, res, s_cat;
+ int resid = 0;
+ uint8_t rl_cdb[READ_BLOCK_LIMITS_CMDLEN] =
+ {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (mloi)
+ rl_cdb[1] |= 0x1; /* introduced in ssc4r02 */
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(rl_cdb, READ_BLOCK_LIMITS_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else {
+ if ((vb > 2) && (ret > 0)) {
+ pr2ws(" %s: response", cdb_s);
+ if (3 == vb) {
+ pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, ret, 0);
+ }
+ if (vb)
+ pr2ws("resid=%d\n", resid);
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len,
+ bool noisy, int vb)
+{
+ return sg_ll_read_block_limits_v2(sg_fd, false, resp, mx_resp_len, NULL,
+ noisy, vb);
+}
+
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current
+ * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+ int mx_resp_len, bool noisy, int vb)
+{
+ int res, ret, s_cat;
+ uint8_t rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] =
+ {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ char b[64];
+
+ sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b);
+ rcvcopyres_cdb[1] = (uint8_t)(sa & 0x1f);
+ if (sa <= 4) /* LID1 variants */
+ rcvcopyres_cdb[2] = (uint8_t)(list_id);
+ else if ((sa >= 5) && (sa <= 7)) /* LID4 variants */
+ sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10);
+
+ if (vb) {
+ char d[128];
+
+ pr2ws(" %s cdb: %s\n", b,
+ sg_get_command_str(rcvcopyres_cdb, THIRD_PARTY_COPY_IN_CMDLEN,
+ false, sizeof(d), d));
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(b))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, b, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT"
+ * The original EXTENDED COPY command (now called EXTENDED COPY (LID1))
+ * is the only one supported by sg_ll_extended_copy(). See function
+ * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */
+
+/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy,
+ int vb)
+{
+ int res, ret, s_cat;
+ uint8_t xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+ {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ const char * cdb_s = "Extended copy (LID1)";
+
+ xcopy_cdb[1] = (uint8_t)(EXTENDED_COPY_LID1_SA & 0x1f);
+ sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(xcopy_cdb, THIRD_PARTY_COPY_OUT_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Handles various service actions associated with opcode 0x83 which is
+ * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and
+ * LID4), POPULATE TOKEN and WRITE USING TOKEN commands.
+ * Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+int
+sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num,
+ int timeout_secs, void * paramp, int param_len,
+ bool noisy, int vb)
+{
+ int res, ret, s_cat, tmout;
+ uint8_t xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] =
+ {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ char cname[80];
+
+ sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname),
+ cname);
+ xcopy_cdb[1] = (uint8_t)(sa & 0x1f);
+ switch (sa) {
+ case 0x0: /* XCOPY(LID1) */
+ case 0x1: /* XCOPY(LID4) */
+ sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+ break;
+ case 0x10: /* POPULATE TOKEN (SBC-3) */
+ case 0x11: /* WRITE USING TOKEN (SBC-3) */
+ sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6);
+ sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10);
+ xcopy_cdb[14] = (uint8_t)(group_num & GRPNUM_MASK);
+ break;
+ case 0x1c: /* COPY OPERATION ABORT */
+ sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2);
+ break;
+ default:
+ pr2ws("%s: unknown service action 0x%x\n", __func__, sa);
+ return -1;
+ }
+ tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cname,
+ sg_get_command_str(xcopy_cdb, THIRD_PARTY_COPY_OUT_CMDLEN,
+ false, sizeof(b), b));
+ if ((vb > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cname);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cname))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, tmout, vb);
+ ret = sg_cmds_process_resp(ptvp, cname, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC).
+ * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_*
+ * positive values or -1 -> other errors. Note that CONDITION MET status
+ * is returned when immed=true and num_blocks can fit in device's cache,
+ * somewaht strangely, GOOD status (return 0) is returned if num_blocks
+ * cannot fit in device's cache. If do_seek10==true then does a SEEK(10)
+ * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10)
+ * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then
+ * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */
+int
+sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed,
+ uint64_t lba, uint32_t num_blocks, int group_num,
+ int timeout_secs, bool noisy, int vb)
+{
+ static const char * const cdb10_name_s = "Pre-fetch(10)";
+ static const char * const cdb16_name_s = "Pre-fetch(16)";
+ static const char * const cdb_seek_name_s = "Seek(10)";
+ int res, s_cat, ret, cdb_len, tmout;
+ const char *cdb_s;
+ uint8_t preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(preFetchCdb, 0, sizeof(preFetchCdb));
+ if (do_seek10) {
+ if (lba > UINT32_MAX) {
+ if (vb)
+ pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__,
+ cdb_seek_name_s);
+ return -1;
+ }
+ preFetchCdb[0] = SEEK10_CMD;
+ cdb_len = SEEK10_CMDLEN;
+ cdb_s = cdb_seek_name_s;
+ sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+ } else {
+ if ((! cdb16) &&
+ ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) {
+ cdb16 = true;
+ if (noisy || vb)
+ pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s,
+ (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS");
+ }
+ if (cdb16) {
+ preFetchCdb[0] = PRE_FETCH16_CMD;
+ cdb_len = PRE_FETCH16_CMDLEN;
+ cdb_s = cdb16_name_s;
+ if (immed)
+ preFetchCdb[1] = 0x2;
+ sg_put_unaligned_be64(lba, preFetchCdb + 2);
+ sg_put_unaligned_be32(num_blocks, preFetchCdb + 10);
+ preFetchCdb[14] = GRPNUM_MASK & group_num;
+ } else {
+ preFetchCdb[0] = PRE_FETCH10_CMD;
+ cdb_len = PRE_FETCH10_CMDLEN;
+ cdb_s = cdb10_name_s;
+ if (immed)
+ preFetchCdb[1] = 0x2;
+ sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2);
+ preFetchCdb[6] = GRPNUM_MASK & group_num;
+ sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7);
+ }
+ }
+ tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT;
+ if (vb) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", cdb_s,
+ sg_get_command_str(preFetchCdb, cdb_len, false, sizeof(b), b));
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, tmout, vb);
+ if (0 == res) {
+ int sstat = get_scsi_pt_status_response(ptvp);
+
+ if (SG_LIB_CAT_CONDITION_MET == sstat) {
+ ret = SG_LIB_CAT_CONDITION_MET;
+ if (vb > 2)
+ pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__);
+ goto fini;
+ }
+ }
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (s_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = s_cat;
+ break;
+ }
+ } else
+ ret = 0;
+fini:
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
diff --git a/lib/sg_cmds_mmc.c b/lib/sg_cmds_mmc.c
new file mode 100644
index 00000000..e3629559
--- /dev/null
+++ b/lib/sg_cmds_mmc.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2008-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.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_mmc.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+#define GET_CONFIG_CMD 0x46
+#define GET_CONFIG_CMD_LEN 10
+#define GET_PERFORMANCE_CMD 0xac
+#define GET_PERFORMANCE_CMD_LEN 12
+#define SET_CD_SPEED_CMD 0xbb
+#define SET_CD_SPEED_CMDLEN 12
+#define SET_STREAMING_CMD 0xb6
+#define SET_STREAMING_CMDLEN 12
+
+
+static struct sg_pt_base *
+create_pt_obj(const char * cname)
+{
+ struct sg_pt_base * ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp)
+ pr2ws("%s: out of memory\n", cname);
+ return ptvp;
+}
+
+/* Invokes a SCSI SET CD SPEED command (MMC).
+ * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command 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_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed,
+ int drv_write_speed, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "set cd speed";
+ int res, ret, sense_cat;
+ uint8_t scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0};
+ uint8_t sense_b[SENSE_BUFF_LEN];
+ struct sg_pt_base * ptvp;
+
+ scsCmdBlk[1] |= (rot_control & 0x3);
+ sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2);
+ sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4);
+
+ if (verbose) {
+ int k;
+
+ pr2ws(" %s cdb: ", cdb_s);
+ for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k)
+ pr2ws("%02x ", scsCmdBlk[k]);
+ pr2ws("\n");
+ }
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_NOT_READY:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ case SG_LIB_CAT_INVALID_OP:
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ 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 GET CONFIGURATION command (MMC-3,4,5).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_config(int sg_fd, int rt, int starting, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "get configuration";
+ int res, ret, sense_cat;
+ uint8_t gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN];
+ struct sg_pt_base * ptvp;
+
+ if ((rt < 0) || (rt > 3)) {
+ pr2ws("Bad rt value: %d\n", rt);
+ return -1;
+ }
+ gcCmdBlk[1] = (rt & 0x3);
+ if ((starting < 0) || (starting > 0xffff)) {
+ pr2ws("Bad starting field number: 0x%x\n", starting);
+ return -1;
+ }
+ sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2);
+ if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) {
+ pr2ws("Bad mx_resp_len: 0x%x\n", starting);
+ return -1;
+ }
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7);
+
+ if (verbose) {
+ int k;
+
+ pr2ws(" %s cdb: ", cdb_s);
+ for (k = 0; k < GET_CONFIG_CMD_LEN; ++k)
+ pr2ws("%02x ", gcCmdBlk[k]);
+ pr2ws("\n");
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ 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 {
+ if ((verbose > 2) && (ret > 3)) {
+ uint8_t * bp;
+ int len;
+
+ bp = (uint8_t *)resp;
+ len = sg_get_unaligned_be32(bp + 0);
+ if (len < 0)
+ len = 0;
+ len = (ret < len) ? ret : len;
+ pr2ws(" %s: response:\n", cdb_s);
+ if (3 == verbose) {
+ pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, len, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6).
+ * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not
+ * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */
+int
+sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba,
+ int max_num_desc, int ttype, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const cdb_s = "get performance";
+ int res, ret, sense_cat;
+ uint8_t gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN];
+ struct sg_pt_base * ptvp;
+
+ if ((data_type < 0) || (data_type > 0x1f)) {
+ pr2ws("Bad data_type value: %d\n", data_type);
+ return -1;
+ }
+ gpCmdBlk[1] = (data_type & 0x1f);
+ sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2);
+ if ((max_num_desc < 0) || (max_num_desc > 0xffff)) {
+ pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc);
+ return -1;
+ }
+ sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8);
+ if ((ttype < 0) || (ttype > 0xff)) {
+ pr2ws("Bad type: 0x%x\n", ttype);
+ return -1;
+ }
+ gpCmdBlk[10] = (uint8_t)ttype;
+
+ if (verbose) {
+ int k;
+
+ pr2ws(" %s cdb: ", cdb_s);
+ for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k)
+ pr2ws("%02x ", gpCmdBlk[k]);
+ pr2ws("\n");
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ 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 {
+ if ((verbose > 2) && (ret > 3)) {
+ uint8_t * bp;
+ int len;
+
+ bp = (uint8_t *)resp;
+ len = sg_get_unaligned_be32(bp + 0);
+ if (len < 0)
+ len = 0;
+ len = (ret < len) ? ret : len;
+ pr2ws(" %s: response", cdb_s);
+ if (3 == verbose) {
+ pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len),
+ -1);
+ } else {
+ pr2ws(":\n");
+ hex2stderr((const uint8_t *)resp, len, 0);
+ }
+ }
+ ret = 0;
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
+ * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready,
+ * -1 -> other failure */
+int
+sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len,
+ bool noisy, int verbose)
+{
+ static const char * const cdb_s = "set streaming";
+ int res, ret, sense_cat;
+ uint8_t ssCmdBlk[SET_STREAMING_CMDLEN] =
+ {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN];
+ struct sg_pt_base * ptvp;
+
+ ssCmdBlk[8] = type;
+ sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9);
+ if (verbose) {
+ int k;
+
+ pr2ws(" %s cdb: ", cdb_s);
+ for (k = 0; k < SET_STREAMING_CMDLEN; ++k)
+ pr2ws("%02x ", ssCmdBlk[k]);
+ pr2ws("\n");
+ if ((verbose > 1) && paramp && param_len) {
+ pr2ws(" %s parameter list:\n", cdb_s);
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ if (NULL == ((ptvp = create_pt_obj(cdb_s))))
+ return -1;
+ set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } 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/lib/sg_io_linux.c b/lib/sg_io_linux.c
new file mode 100644
index 00000000..968f5e7a
--- /dev/null
+++ b/lib/sg_io_linux.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 1999-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SG_LIB_LINUX
+
+#include "sg_io_linux.h"
+#include "sg_pr2serr.h"
+
+
+/* Version 1.13 20210831 */
+
+
+void
+sg_print_masked_status(int masked_status)
+{
+ int scsi_status = (masked_status << 1) & 0x7e;
+
+ sg_print_scsi_status(scsi_status);
+}
+
+/* host_bytes: DID_* are Linux SCSI result (a 32 bit variable) bits 16:23 */
+
+static const char * linux_host_bytes[] = {
+ "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+ "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+ "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+ "DID_IMM_RETRY", "DID_REQUEUE", "DID_TRANSPORT_DISRUPTED",
+ "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE",
+ "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR", "DID_TRANSPORT_MARGINAL",
+};
+
+void
+sg_print_host_status(int host_status)
+{
+ pr2ws("Host_status=0x%02x ", host_status);
+ if ((host_status < 0) ||
+ (host_status >= (int)SG_ARRAY_SIZE(linux_host_bytes)))
+ pr2ws("is invalid ");
+ else
+ pr2ws("[%s] ", linux_host_bytes[host_status]);
+}
+
+/* DRIVER_* are Linux SCSI result (a 32 bit variable) bits 24:27 .
+ * These where made obsolete around lk 5.12.0 . Only DRIVER_SENSE [0x8] is
+ * defined in scsi/sg.h for backward comaptibility */
+static const char * linux_driver_bytes[] = {
+ "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+ "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+ "DRIVER_SENSE",
+};
+
+#if 0
+
+/* SUGGEST_* are Linux SCSI result (a 32 bit variable) bits 28:31 */
+
+static const char * linux_driver_suggests[] = {
+ "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+ "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+ "SUGGEST_SENSE",
+};
+#endif
+
+
+void
+sg_print_driver_status(int driver_status)
+{
+ int driv;
+ const char * driv_cp = "invalid";
+
+ driv = driver_status & SG_LIB_DRIVER_MASK;
+ if (driv < (int)SG_ARRAY_SIZE(linux_driver_bytes))
+ driv_cp = linux_driver_bytes[driv];
+ pr2ws("Driver_status=0x%02x", driver_status);
+ pr2ws(" [%s] ", driv_cp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+ * prints error/warning (prefix by 'leadin') to stderr (pr2ws) and
+ * returns 0. */
+int
+sg_linux_sense_print(const char * leadin, int scsi_status, int host_status,
+ int driver_status, const uint8_t * sense_buffer,
+ int sb_len, bool raw_sinfo)
+{
+ bool done_leadin = false;
+ bool done_sense = false;
+
+ scsi_status &= 0x7e; /*sanity */
+ if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status))
+ return 1; /* No problems */
+ if (0 != scsi_status) {
+ if (leadin)
+ pr2ws("%s: ", leadin);
+ done_leadin = true;
+ pr2ws("SCSI status: ");
+ sg_print_scsi_status(scsi_status);
+ pr2ws("\n");
+ if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) ||
+ (scsi_status == SAM_STAT_COMMAND_TERMINATED))) {
+ /* SAM_STAT_COMMAND_TERMINATED is obsolete */
+ sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+ done_sense = true;
+ }
+ }
+ if (0 != host_status) {
+ if (leadin && (! done_leadin))
+ pr2ws("%s: ", leadin);
+ if (done_leadin)
+ pr2ws("plus...: ");
+ else
+ done_leadin = true;
+ sg_print_host_status(host_status);
+ pr2ws("\n");
+ }
+ if (0 != driver_status) {
+ if (done_sense &&
+ (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+ return 0;
+ if (leadin && (! done_leadin))
+ pr2ws("%s: ", leadin);
+ if (done_leadin)
+ pr2ws("plus...: ");
+ sg_print_driver_status(driver_status);
+ pr2ws("\n");
+ if (sense_buffer && (! done_sense) &&
+ (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status)))
+ sg_print_sense(0, sense_buffer, sb_len, raw_sinfo);
+ }
+ return 0;
+}
+
+#ifdef SG_IO
+
+bool
+sg_normalize_sense(const struct sg_io_hdr * hp,
+ struct sg_scsi_sense_hdr * sshp)
+{
+ if ((NULL == hp) || (0 == hp->sb_len_wr)) {
+ if (sshp)
+ memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+ return 0;
+ }
+ return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp);
+}
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+ returns 0. */
+int
+sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp,
+ bool raw_sinfo)
+{
+ return sg_linux_sense_print(leadin, hp->status, hp->host_status,
+ hp->driver_status, hp->sbp, hp->sb_len_wr,
+ raw_sinfo);
+}
+#endif
+
+/* Returns 1 if no errors found and thus nothing printed; otherwise
+ returns 0. */
+int
+sg_chk_n_print(const char * leadin, int masked_status, int host_status,
+ int driver_status, const uint8_t * sense_buffer,
+ int sb_len, bool raw_sinfo)
+{
+ int scsi_status = (masked_status << 1) & 0x7e;
+
+ return sg_linux_sense_print(leadin, scsi_status, host_status,
+ driver_status, sense_buffer, sb_len,
+ raw_sinfo);
+}
+
+#ifdef SG_IO
+int
+sg_err_category3(struct sg_io_hdr * hp)
+{
+ return sg_err_category_new(hp->status, hp->host_status,
+ hp->driver_status, hp->sbp, hp->sb_len_wr);
+}
+#endif
+
+int
+sg_err_category(int masked_status, int host_status, int driver_status,
+ const uint8_t * sense_buffer, int sb_len)
+{
+ int scsi_status = (masked_status << 1) & 0x7e;
+
+ return sg_err_category_new(scsi_status, host_status, driver_status,
+ sense_buffer, sb_len);
+}
+
+int
+sg_err_category_new(int scsi_status, int host_status, int driver_status,
+ const uint8_t * sense_buffer, int sb_len)
+{
+ int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status);
+
+ scsi_status &= 0x7e;
+ if ((0 == scsi_status) && (0 == host_status) &&
+ (0 == masked_driver_status))
+ return SG_LIB_CAT_CLEAN;
+ if ((SAM_STAT_CHECK_CONDITION == scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == scsi_status) ||
+ (SG_LIB_DRIVER_SENSE == masked_driver_status))
+ return sg_err_category_sense(sense_buffer, sb_len);
+ if (0 != host_status) {
+ if ((SG_LIB_DID_NO_CONNECT == host_status) ||
+ (SG_LIB_DID_BUS_BUSY == host_status) ||
+ (SG_LIB_DID_TIME_OUT == host_status))
+ return SG_LIB_CAT_TIMEOUT;
+ if (SG_LIB_DID_NEXUS_FAILURE == host_status)
+ return SG_LIB_CAT_RES_CONFLICT;
+ }
+ if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status)
+ return SG_LIB_CAT_TIMEOUT;
+ return SG_LIB_CAT_OTHER;
+}
+
+#endif /* if SG_LIB_LINUX defined */
diff --git a/lib/sg_json_builder.c b/lib/sg_json_builder.c
new file mode 100644
index 00000000..ed2d1397
--- /dev/null
+++ b/lib/sg_json_builder.c
@@ -0,0 +1,999 @@
+
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2014 James McLaughlin. All rights reserved.
+ * https://github.com/udp/json-builder
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "sg_json_builder.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* This code was fetched from https://github.com/json-parser/json-builder
+ * and comes with the 2 clause BSD license (shown above) which is the same
+ * license that most of the rest of this package uses. */
+
+#ifdef _MSC_VER
+ #define snprintf _snprintf
+#endif
+
+static const json_serialize_opts default_opts =
+{
+ json_serialize_mode_single_line,
+ 0,
+ 3 /* indent_size */
+};
+
+typedef struct json_builder_value
+{
+ json_value value;
+
+ int is_builder_value;
+
+ size_t additional_length_allocated;
+ size_t length_iterated;
+
+} json_builder_value;
+
+static int builderize (json_value * value)
+{
+ if (((json_builder_value *) value)->is_builder_value)
+ return 1;
+
+ if (value->type == json_object)
+ {
+ unsigned int i;
+
+ /* Values straight out of the parser have the names of object entries
+ * allocated in the same allocation as the values array itself. This is
+ * not desirable when manipulating values because the names would be easy
+ * to clobber.
+ */
+ for (i = 0; i < value->u.object.length; ++ i)
+ {
+ json_char * name_copy;
+ json_object_entry * entry = &value->u.object.values [i];
+
+ if (! (name_copy = (json_char *) malloc ((entry->name_length + 1) * sizeof (json_char))))
+ return 0;
+
+ memcpy (name_copy, entry->name, entry->name_length + 1);
+ entry->name = name_copy;
+ }
+ }
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ return 1;
+}
+
+const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value);
+
+/* These flags are set up from the opts before serializing to make the
+ * serializer conditions simpler.
+ */
+const int f_spaces_around_brackets = (1 << 0);
+const int f_spaces_after_commas = (1 << 1);
+const int f_spaces_after_colons = (1 << 2);
+const int f_tabs = (1 << 3);
+
+static int get_serialize_flags (json_serialize_opts opts)
+{
+ int flags = 0;
+
+ if (opts.mode == json_serialize_mode_packed)
+ return 0;
+
+ if (opts.mode == json_serialize_mode_multiline)
+ {
+ if (opts.opts & json_serialize_opt_use_tabs)
+ flags |= f_tabs;
+ }
+ else
+ {
+ if (! (opts.opts & json_serialize_opt_pack_brackets))
+ flags |= f_spaces_around_brackets;
+
+ if (! (opts.opts & json_serialize_opt_no_space_after_comma))
+ flags |= f_spaces_after_commas;
+ }
+
+ if (! (opts.opts & json_serialize_opt_no_space_after_colon))
+ flags |= f_spaces_after_colons;
+
+ return flags;
+}
+
+json_value * json_array_new (size_t length)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_array;
+
+ if (! (value->u.array.values = (json_value **) malloc (length * sizeof (json_value *))))
+ {
+ free (value);
+ return NULL;
+ }
+
+ ((json_builder_value *) value)->additional_length_allocated = length;
+
+ return value;
+}
+
+json_value * json_array_push (json_value * array, json_value * value)
+{
+ assert (array->type == json_array);
+
+ if (!builderize (array) || !builderize (value))
+ return NULL;
+
+ if (((json_builder_value *) array)->additional_length_allocated > 0)
+ {
+ -- ((json_builder_value *) array)->additional_length_allocated;
+ }
+ else
+ {
+ json_value ** values_new = (json_value **) realloc
+ (array->u.array.values, sizeof (json_value *) * (array->u.array.length + 1));
+
+ if (!values_new)
+ return NULL;
+
+ array->u.array.values = values_new;
+ }
+
+ array->u.array.values [array->u.array.length] = value;
+ ++ array->u.array.length;
+
+ value->parent = array;
+
+ return value;
+}
+
+json_value * json_object_new (size_t length)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_object;
+
+ if (! (value->u.object.values = (json_object_entry *) calloc
+ (length, sizeof (*value->u.object.values))))
+ {
+ free (value);
+ return NULL;
+ }
+
+ ((json_builder_value *) value)->additional_length_allocated = length;
+
+ return value;
+}
+
+json_value * json_object_push (json_value * object,
+ const json_char * name,
+ json_value * value)
+{
+ return json_object_push_length (object, strlen (name), name, value);
+}
+
+json_value * json_object_push_length (json_value * object,
+ unsigned int name_length, const json_char * name,
+ json_value * value)
+{
+ json_char * name_copy;
+
+ assert (object->type == json_object);
+
+ if (! (name_copy = (json_char *) malloc ((name_length + 1) * sizeof (json_char))))
+ return NULL;
+
+ memcpy (name_copy, name, name_length * sizeof (json_char));
+ name_copy [name_length] = 0;
+
+ if (!json_object_push_nocopy (object, name_length, name_copy, value))
+ {
+ free (name_copy);
+ return NULL;
+ }
+
+ return value;
+}
+
+json_value * json_object_push_nocopy (json_value * object,
+ unsigned int name_length, json_char * name,
+ json_value * value)
+{
+ json_object_entry * entry;
+
+ assert (object->type == json_object);
+
+ if (!builderize (object) || !builderize (value))
+ return NULL;
+
+ if (((json_builder_value *) object)->additional_length_allocated > 0)
+ {
+ -- ((json_builder_value *) object)->additional_length_allocated;
+ }
+ else
+ {
+ json_object_entry * values_new = (json_object_entry *)
+ realloc (object->u.object.values, sizeof (*object->u.object.values)
+ * (object->u.object.length + 1));
+
+ if (!values_new)
+ return NULL;
+
+ object->u.object.values = values_new;
+ }
+
+ entry = object->u.object.values + object->u.object.length;
+
+ entry->name_length = name_length;
+ entry->name = name;
+ entry->value = value;
+
+ ++ object->u.object.length;
+
+ value->parent = object;
+
+ return value;
+}
+
+json_value * json_string_new (const json_char * buf)
+{
+ return json_string_new_length (strlen (buf), buf);
+}
+
+json_value * json_string_new_length (unsigned int length, const json_char * buf)
+{
+ json_value * value;
+ json_char * copy = (json_char *) malloc ((length + 1) * sizeof (json_char));
+
+ if (!copy)
+ return NULL;
+
+ memcpy (copy, buf, length * sizeof (json_char));
+ copy [length] = 0;
+
+ if (! (value = json_string_new_nocopy (length, copy)))
+ {
+ free (copy);
+ return NULL;
+ }
+
+ return value;
+}
+
+json_value * json_string_new_nocopy (unsigned int length, json_char * buf)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_string;
+ value->u.string.length = length;
+ value->u.string.ptr = buf;
+
+ return value;
+}
+
+json_value * json_integer_new (json_int_t integer)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_integer;
+ value->u.integer = integer;
+
+ return value;
+}
+
+json_value * json_double_new (double dbl)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_double;
+ value->u.dbl = dbl;
+
+ return value;
+}
+
+json_value * json_boolean_new (int b)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_boolean;
+ value->u.boolean = b;
+
+ return value;
+}
+
+json_value * json_null_new (void)
+{
+ json_value * value = (json_value *) calloc (1, sizeof (json_builder_value));
+
+ if (!value)
+ return NULL;
+
+ ((json_builder_value *) value)->is_builder_value = 1;
+
+ value->type = json_null;
+
+ return value;
+}
+
+void json_object_sort (json_value * object, json_value * proto)
+{
+ unsigned int i, out_index = 0;
+
+ if (!builderize (object))
+ return; /* TODO error */
+
+ assert (object->type == json_object);
+ assert (proto->type == json_object);
+
+ for (i = 0; i < proto->u.object.length; ++ i)
+ {
+ unsigned int j;
+ json_object_entry proto_entry = proto->u.object.values [i];
+
+ for (j = 0; j < object->u.object.length; ++ j)
+ {
+ json_object_entry entry = object->u.object.values [j];
+
+ if (entry.name_length != proto_entry.name_length)
+ continue;
+
+ if (memcmp (entry.name, proto_entry.name, entry.name_length) != 0)
+ continue;
+
+ object->u.object.values [j] = object->u.object.values [out_index];
+ object->u.object.values [out_index] = entry;
+
+ ++ out_index;
+ }
+ }
+}
+
+json_value * json_object_merge (json_value * objectA, json_value * objectB)
+{
+ unsigned int i;
+
+ assert (objectA->type == json_object);
+ assert (objectB->type == json_object);
+ assert (objectA != objectB);
+
+ if (!builderize (objectA) || !builderize (objectB))
+ return NULL;
+
+ if (objectB->u.object.length <=
+ ((json_builder_value *) objectA)->additional_length_allocated)
+ {
+ ((json_builder_value *) objectA)->additional_length_allocated
+ -= objectB->u.object.length;
+ }
+ else
+ {
+ json_object_entry * values_new;
+
+ unsigned int alloc =
+ objectA->u.object.length
+ + ((json_builder_value *) objectA)->additional_length_allocated
+ + objectB->u.object.length;
+
+ if (! (values_new = (json_object_entry *)
+ realloc (objectA->u.object.values, sizeof (json_object_entry) * alloc)))
+ {
+ return NULL;
+ }
+
+ objectA->u.object.values = values_new;
+ }
+
+ for (i = 0; i < objectB->u.object.length; ++ i)
+ {
+ json_object_entry * entry = &objectA->u.object.values[objectA->u.object.length + i];
+
+ *entry = objectB->u.object.values[i];
+ entry->value->parent = objectA;
+ }
+
+ objectA->u.object.length += objectB->u.object.length;
+
+ free (objectB->u.object.values);
+ free (objectB);
+
+ return objectA;
+}
+
+static size_t measure_string (unsigned int length,
+ const json_char * str)
+{
+ unsigned int i;
+ size_t measured_length = 0;
+
+ for(i = 0; i < length; ++ i)
+ {
+ json_char c = str [i];
+
+ switch (c)
+ {
+ case '"':
+ case '\\':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+
+ measured_length += 2;
+ break;
+
+ default:
+
+ ++ measured_length;
+ break;
+ };
+ };
+
+ return measured_length;
+}
+
+#define PRINT_ESCAPED(c) do { \
+ *buf ++ = '\\'; \
+ *buf ++ = (c); \
+} while(0); \
+
+static size_t serialize_string (json_char * buf,
+ unsigned int length,
+ const json_char * str)
+{
+ json_char * orig_buf = buf;
+ unsigned int i;
+
+ for(i = 0; i < length; ++ i)
+ {
+ json_char c = str [i];
+
+ switch (c)
+ {
+ case '"': PRINT_ESCAPED ('\"'); continue;
+ case '\\': PRINT_ESCAPED ('\\'); continue;
+ case '\b': PRINT_ESCAPED ('b'); continue;
+ case '\f': PRINT_ESCAPED ('f'); continue;
+ case '\n': PRINT_ESCAPED ('n'); continue;
+ case '\r': PRINT_ESCAPED ('r'); continue;
+ case '\t': PRINT_ESCAPED ('t'); continue;
+
+ default:
+
+ *buf ++ = c;
+ break;
+ };
+ };
+
+ return buf - orig_buf;
+}
+
+size_t json_measure (json_value * value)
+{
+ return json_measure_ex (value, default_opts);
+}
+
+#define MEASURE_NEWLINE() do { \
+ ++ newlines; \
+ indents += depth; \
+} while(0); \
+
+size_t json_measure_ex (json_value * value, json_serialize_opts opts)
+{
+ size_t total = 1; /* null terminator */
+ size_t newlines = 0;
+ size_t depth = 0;
+ size_t indents = 0;
+ int flags;
+ int bracket_size, comma_size, colon_size;
+
+ flags = get_serialize_flags (opts);
+
+ /* to reduce branching
+ */
+ bracket_size = flags & f_spaces_around_brackets ? 2 : 1;
+ comma_size = flags & f_spaces_after_commas ? 2 : 1;
+ colon_size = flags & f_spaces_after_colons ? 2 : 1;
+
+ while (value)
+ {
+ json_int_t integer;
+ json_object_entry * entry;
+
+ switch (value->type)
+ {
+ case json_array:
+
+ if (((json_builder_value *) value)->length_iterated == 0)
+ {
+ if (value->u.array.length == 0)
+ {
+ total += 2; /* `[]` */
+ break;
+ }
+
+ total += bracket_size; /* `[` */
+
+ ++ depth;
+ MEASURE_NEWLINE(); /* \n after [ */
+ }
+
+ if (((json_builder_value *) value)->length_iterated == value->u.array.length)
+ {
+ -- depth;
+ MEASURE_NEWLINE();
+ total += bracket_size; /* `]` */
+
+ ((json_builder_value *) value)->length_iterated = 0;
+ break;
+ }
+
+ if (((json_builder_value *) value)->length_iterated > 0)
+ {
+ total += comma_size; /* `, ` */
+
+ MEASURE_NEWLINE();
+ }
+
+ ((json_builder_value *) value)->length_iterated++;
+ value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
+ continue;
+
+ case json_object:
+
+ if (((json_builder_value *) value)->length_iterated == 0)
+ {
+ if (value->u.object.length == 0)
+ {
+ total += 2; /* `{}` */
+ break;
+ }
+
+ total += bracket_size; /* `{` */
+
+ ++ depth;
+ MEASURE_NEWLINE(); /* \n after { */
+ }
+
+ if (((json_builder_value *) value)->length_iterated == value->u.object.length)
+ {
+ -- depth;
+ MEASURE_NEWLINE();
+ total += bracket_size; /* `}` */
+
+ ((json_builder_value *) value)->length_iterated = 0;
+ break;
+ }
+
+ if (((json_builder_value *) value)->length_iterated > 0)
+ {
+ total += comma_size; /* `, ` */
+ MEASURE_NEWLINE();
+ }
+
+ entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);
+
+ total += 2 + colon_size; /* `"": ` */
+ total += measure_string (entry->name_length, entry->name);
+
+ value = entry->value;
+ continue;
+
+ case json_string:
+
+ total += 2; /* `""` */
+ total += measure_string (value->u.string.length, value->u.string.ptr);
+ break;
+
+ case json_integer:
+
+ integer = value->u.integer;
+
+ if (integer < 0)
+ {
+ total += 1; /* `-` */
+ integer = - integer;
+ }
+
+ ++ total; /* first digit */
+
+ while (integer >= 10)
+ {
+ ++ total; /* another digit */
+ integer /= 10;
+ }
+
+ break;
+
+ case json_double:
+
+ total += snprintf (NULL, 0, "%g", value->u.dbl);
+
+ /* Because sometimes we need to add ".0" if sprintf does not do it
+ * for us. Downside is that we allocate more bytes than strictly
+ * needed for serialization.
+ */
+ total += 2;
+
+ break;
+
+ case json_boolean:
+
+ total += value->u.boolean ?
+ 4: /* `true` */
+ 5; /* `false` */
+
+ break;
+
+ case json_null:
+
+ total += 4; /* `null` */
+ break;
+
+ default:
+ break;
+ };
+
+ value = value->parent;
+ }
+
+ if (opts.mode == json_serialize_mode_multiline)
+ {
+ total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size);
+ total += indents * opts.indent_size;
+ }
+
+ return total;
+}
+
+void json_serialize (json_char * buf, json_value * value)
+{
+ json_serialize_ex (buf, value, default_opts);
+}
+
+#define PRINT_NEWLINE() do { \
+ if (opts.mode == json_serialize_mode_multiline) { \
+ if (opts.opts & json_serialize_opt_CRLF) \
+ *buf ++ = '\r'; \
+ *buf ++ = '\n'; \
+ for(i = 0; i < indent; ++ i) \
+ *buf ++ = indent_char; \
+ } \
+} while(0); \
+
+#define PRINT_OPENING_BRACKET(c) do { \
+ *buf ++ = (c); \
+ if (flags & f_spaces_around_brackets) \
+ *buf ++ = ' '; \
+} while(0); \
+
+#define PRINT_CLOSING_BRACKET(c) do { \
+ if (flags & f_spaces_around_brackets) \
+ *buf ++ = ' '; \
+ *buf ++ = (c); \
+} while(0); \
+
+void json_serialize_ex (json_char * buf, json_value * value, json_serialize_opts opts)
+{
+ json_int_t integer, orig_integer;
+ json_object_entry * entry;
+ json_char * ptr, * dot;
+ int indent = 0;
+ char indent_char;
+ int i;
+ int flags;
+
+ flags = get_serialize_flags (opts);
+
+ indent_char = flags & f_tabs ? '\t' : ' ';
+
+ while (value)
+ {
+ switch (value->type)
+ {
+ case json_array:
+
+ if (((json_builder_value *) value)->length_iterated == 0)
+ {
+ if (value->u.array.length == 0)
+ {
+ *buf ++ = '[';
+ *buf ++ = ']';
+
+ break;
+ }
+
+ PRINT_OPENING_BRACKET ('[');
+
+ indent += opts.indent_size;
+ PRINT_NEWLINE();
+ }
+
+ if (((json_builder_value *) value)->length_iterated == value->u.array.length)
+ {
+ indent -= opts.indent_size;
+ PRINT_NEWLINE();
+ PRINT_CLOSING_BRACKET (']');
+
+ ((json_builder_value *) value)->length_iterated = 0;
+ break;
+ }
+
+ if (((json_builder_value *) value)->length_iterated > 0)
+ {
+ *buf ++ = ',';
+
+ if (flags & f_spaces_after_commas)
+ *buf ++ = ' ';
+
+ PRINT_NEWLINE();
+ }
+
+ ((json_builder_value *) value)->length_iterated++;
+ value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1];
+ continue;
+
+ case json_object:
+
+ if (((json_builder_value *) value)->length_iterated == 0)
+ {
+ if (value->u.object.length == 0)
+ {
+ *buf ++ = '{';
+ *buf ++ = '}';
+
+ break;
+ }
+
+ PRINT_OPENING_BRACKET ('{');
+
+ indent += opts.indent_size;
+ PRINT_NEWLINE();
+ }
+
+ if (((json_builder_value *) value)->length_iterated == value->u.object.length)
+ {
+ indent -= opts.indent_size;
+ PRINT_NEWLINE();
+ PRINT_CLOSING_BRACKET ('}');
+
+ ((json_builder_value *) value)->length_iterated = 0;
+ break;
+ }
+
+ if (((json_builder_value *) value)->length_iterated > 0)
+ {
+ *buf ++ = ',';
+
+ if (flags & f_spaces_after_commas)
+ *buf ++ = ' ';
+
+ PRINT_NEWLINE();
+ }
+
+ entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++);
+
+ *buf ++ = '\"';
+ buf += serialize_string (buf, entry->name_length, entry->name);
+ *buf ++ = '\"';
+ *buf ++ = ':';
+
+ if (flags & f_spaces_after_colons)
+ *buf ++ = ' ';
+
+ value = entry->value;
+ continue;
+
+ case json_string:
+
+ *buf ++ = '\"';
+ buf += serialize_string (buf, value->u.string.length, value->u.string.ptr);
+ *buf ++ = '\"';
+ break;
+
+ case json_integer:
+
+ integer = value->u.integer;
+
+ if (integer < 0)
+ {
+ *buf ++ = '-';
+ integer = - integer;
+ }
+
+ orig_integer = integer;
+
+ ++ buf;
+
+ while (integer >= 10)
+ {
+ ++ buf;
+ integer /= 10;
+ }
+
+ integer = orig_integer;
+ ptr = buf;
+
+ do
+ {
+ *-- ptr = "0123456789"[integer % 10];
+
+ } while ((integer /= 10) > 0);
+
+ break;
+
+ case json_double:
+
+ ptr = buf;
+
+ buf += sprintf (buf, "%g", value->u.dbl);
+
+ if ((dot = strchr (ptr, ',')))
+ {
+ *dot = '.';
+ }
+ else if (!strchr (ptr, '.') && !strchr (ptr, 'e'))
+ {
+ *buf ++ = '.';
+ *buf ++ = '0';
+ }
+
+ break;
+
+ case json_boolean:
+
+ if (value->u.boolean)
+ {
+ memcpy (buf, "true", 4);
+ buf += 4;
+ }
+ else
+ {
+ memcpy (buf, "false", 5);
+ buf += 5;
+ }
+
+ break;
+
+ case json_null:
+
+ memcpy (buf, "null", 4);
+ buf += 4;
+ break;
+
+ default:
+ break;
+ };
+
+ value = value->parent;
+ }
+
+ *buf = 0;
+}
+
+void json_builder_free (json_value * value)
+{
+ json_value * cur_value;
+
+ if (!value)
+ return;
+
+ value->parent = 0;
+
+ while (value)
+ {
+ switch (value->type)
+ {
+ case json_array:
+
+ if (!value->u.array.length)
+ {
+ free (value->u.array.values);
+ break;
+ }
+
+ value = value->u.array.values [-- value->u.array.length];
+ continue;
+
+ case json_object:
+
+ if (!value->u.object.length)
+ {
+ free (value->u.object.values);
+ break;
+ }
+
+ -- value->u.object.length;
+
+ if (((json_builder_value *) value)->is_builder_value)
+ {
+ /* Names are allocated separately for builder values. In parser
+ * values, they are part of the same allocation as the values array
+ * itself.
+ */
+ free (value->u.object.values [value->u.object.length].name);
+ }
+
+ value = value->u.object.values [value->u.object.length].value;
+ continue;
+
+ case json_string:
+
+ free (value->u.string.ptr);
+ break;
+
+ default:
+ break;
+ };
+
+ cur_value = value;
+ value = value->parent;
+ free (cur_value);
+ }
+}
+
+
+
+
+
+
diff --git a/lib/sg_json_builder.h b/lib/sg_json_builder.h
new file mode 100644
index 00000000..9027ed5b
--- /dev/null
+++ b/lib/sg_json_builder.h
@@ -0,0 +1,333 @@
+
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2014 James McLaughlin. All rights reserved.
+ * https://github.com/udp/json-builder
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef SG_JSON_BUILDER_H
+#define SG_JSON_BUILDER_H
+
+/* This code was fetched from https://github.com/json-parser/json-builder
+ * and comes with the 2 clause BSD license (shown above) which is the same
+ * license that most of the rest of this package uses.
+ *
+ * This header file is in this 'lib' directory so its interface is _not_
+ * published with sg3_utils other header files found in the 'include'
+ * directory. Currently only this header's implementation (i.e.
+ * sg_json_builder.c) and sg_pr2serr.c are the only users of this header. */
+
+/*
+ * Used to require json.h from json-parser but what was needed as been
+ * included in this header.
+ * https://github.com/udp/json-parser
+ */
+/* #include "json.h" */
+
+#ifndef json_char
+ #define json_char char
+#endif
+
+#ifndef json_int_t
+ #undef JSON_INT_T_OVERRIDDEN
+ #if defined(_MSC_VER)
+ #define json_int_t __int64
+ #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__cplusplus) && __cplusplus >= 201103L)
+ /* C99 and C++11 */
+ #include <stdint.h>
+ #define json_int_t int_fast64_t
+ #else
+ /* C89 */
+ #define json_int_t long
+ #endif
+#else
+ #define JSON_INT_T_OVERRIDDEN 1
+#endif
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+
+ #include <string.h>
+
+ extern "C"
+ {
+
+#endif
+
+typedef struct
+{
+ unsigned long max_memory; /* should be size_t, but would modify the API */
+ int settings;
+
+ /* Custom allocator support (leave null to use malloc/free)
+ */
+
+ void * (* mem_alloc) (size_t, int zero, void * user_data);
+ void (* mem_free) (void *, void * user_data);
+
+ void * user_data; /* will be passed to mem_alloc and mem_free */
+
+ size_t value_extra; /* how much extra space to allocate for values? */
+
+} json_settings;
+
+#define json_enable_comments 0x01
+
+typedef enum
+{
+ json_none,
+ json_object,
+ json_array,
+ json_integer,
+ json_double,
+ json_string,
+ json_boolean,
+ json_null
+
+} json_type;
+
+extern const struct _json_value json_value_none;
+
+typedef struct _json_object_entry
+{
+ json_char * name;
+ unsigned int name_length;
+
+ struct _json_value * value;
+
+} json_object_entry;
+
+typedef struct _json_value
+{
+ struct _json_value * parent;
+
+ json_type type;
+
+ union
+ {
+ int boolean;
+ json_int_t integer;
+ double dbl;
+
+ struct
+ {
+ unsigned int length;
+ json_char * ptr; /* null terminated */
+
+ } string;
+
+ struct
+ {
+ unsigned int length;
+
+ json_object_entry * values;
+
+ #if defined(__cplusplus)
+ json_object_entry * begin () const
+ { return values;
+ }
+ json_object_entry * end () const
+ { return values + length;
+ }
+ #endif
+
+ } object;
+
+ struct
+ {
+ unsigned int length;
+ struct _json_value ** values;
+
+ #if defined(__cplusplus)
+ _json_value ** begin () const
+ { return values;
+ }
+ _json_value ** end () const
+ { return values + length;
+ }
+ #endif
+
+ } array;
+
+ } u;
+
+ union
+ {
+ struct _json_value * next_alloc;
+ void * object_mem;
+
+ } _reserved;
+
+ #ifdef JSON_TRACK_SOURCE
+
+ /* Location of the value in the source JSON
+ */
+ unsigned int line, col;
+
+ #endif
+
+
+ /* C++ operator sugar removed */
+
+} json_value;
+
+#if 0
+#define json_error_max 128
+json_value * json_parse_ex (json_settings * settings,
+ const json_char * json,
+ size_t length,
+ char * error);
+
+void json_value_free (json_value *);
+
+
+/* Not usually necessary, unless you used a custom mem_alloc and now want to
+ * use a custom mem_free.
+ */
+void json_value_free_ex (json_settings * settings,
+ json_value *);
+#endif
+
+/* <<< end of code from json-parser's json.h >>> */
+
+
+/* IMPORTANT NOTE: If you want to use json-builder functions with values
+ * allocated by json-parser as part of the parsing process, you must pass
+ * json_builder_extra as the value_extra setting in json_settings when
+ * parsing. Otherwise there will not be room for the extra state and
+ * json-builder WILL invoke undefined behaviour.
+ *
+ * Also note that unlike json-parser, json-builder does not currently support
+ * custom allocators (for no particular reason other than that it doesn't have
+ * any settings or global state.)
+ */
+extern const size_t json_builder_extra;
+
+
+/*** Arrays
+ ***
+ * Note that all of these length arguments are just a hint to allow for
+ * pre-allocation - passing 0 is fine.
+ */
+json_value * json_array_new (size_t length);
+json_value * json_array_push (json_value * array, json_value *);
+
+
+/*** Objects
+ ***/
+json_value * json_object_new (size_t length);
+
+json_value * json_object_push (json_value * object,
+ const json_char * name,
+ json_value *);
+
+/* Same as json_object_push, but doesn't call strlen() for you.
+ */
+json_value * json_object_push_length (json_value * object,
+ unsigned int name_length, const json_char * name,
+ json_value *);
+
+/* Same as json_object_push_length, but doesn't copy the name buffer before
+ * storing it in the value. Use this micro-optimisation at your own risk.
+ */
+json_value * json_object_push_nocopy (json_value * object,
+ unsigned int name_length, json_char * name,
+ json_value *);
+
+/* Merges all entries from objectB into objectA and destroys objectB.
+ */
+json_value * json_object_merge (json_value * objectA, json_value * objectB);
+
+/* Sort the entries of an object based on the order in a prototype object.
+ * Helpful when reading JSON and writing it again to preserve user order.
+ */
+void json_object_sort (json_value * object, json_value * proto);
+
+
+
+/*** Strings
+ ***/
+json_value * json_string_new (const json_char *);
+json_value * json_string_new_length (unsigned int length, const json_char *);
+json_value * json_string_new_nocopy (unsigned int length, json_char *);
+
+
+/*** Everything else
+ ***/
+json_value * json_integer_new (json_int_t);
+json_value * json_double_new (double);
+json_value * json_boolean_new (int);
+json_value * json_null_new (void);
+
+
+/*** Serializing
+ ***/
+#define json_serialize_mode_multiline 0
+#define json_serialize_mode_single_line 1
+#define json_serialize_mode_packed 2
+
+#define json_serialize_opt_CRLF (1 << 1)
+#define json_serialize_opt_pack_brackets (1 << 2)
+#define json_serialize_opt_no_space_after_comma (1 << 3)
+#define json_serialize_opt_no_space_after_colon (1 << 4)
+#define json_serialize_opt_use_tabs (1 << 5)
+
+typedef struct json_serialize_opts
+{
+ int mode;
+ int opts;
+ int indent_size;
+
+} json_serialize_opts;
+
+
+/* Returns a length in characters that is at least large enough to hold the
+ * value in its serialized form, including a null terminator.
+ */
+size_t json_measure (json_value *);
+size_t json_measure_ex (json_value *, json_serialize_opts);
+
+
+/* Serializes a JSON value into the buffer given (which must already be
+ * allocated with a length of at least json_measure(value, opts))
+ */
+void json_serialize (json_char * buf, json_value *);
+void json_serialize_ex (json_char * buf, json_value *, json_serialize_opts);
+
+
+/*** Cleaning up
+ ***/
+void json_builder_free (json_value *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SG_JSON_BUILDER_H */
+
+
+
diff --git a/lib/sg_lib.c b/lib/sg_lib.c
new file mode 100644
index 00000000..36fcf5cd
--- /dev/null
+++ b/lib/sg_lib.c
@@ -0,0 +1,4088 @@
+/*
+ * Copyright (c) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* NOTICE:
+ * On 5th October 2004 (v1.00) this file name was changed from sg_err.c
+ * to sg_lib.c and the previous GPL was changed to a FreeBSD license.
+ * The intention is to maintain this file and the related sg_lib.h file
+ * as open source and encourage their unencumbered use.
+ *
+ * CONTRIBUTIONS:
+ * This file started out as a copy of SCSI opcodes, sense keys and
+ * additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem
+ * in the kernel source file: drivers/scsi/constant.c . That file
+ * bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale"
+ * and a GPL notice.
+ *
+ * Much of the data in this file is derived from SCSI draft standards
+ * found at https://www.t10.org with the "SCSI Primary Commands-4" (SPC-4)
+ * being the central point of reference.
+ *
+ * Contributions:
+ * sense key specific field decoding [Trent Piepho 20031116]
+ *
+ */
+
+#define _POSIX_C_SOURCE 200809L /* for posix_memalign() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */
+
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */
+
+typedef unsigned int my_uint; /* convenience to save a few line wraps */
+
+FILE * sg_warnings_strm = NULL; /* would like to default to stderr */
+
+
+int
+pr2ws(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
+ * functions. Returns number of chars placed in cp excluding the
+ * trailing null char. So for cp_max_len > 0 the return value is always
+ * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are
+ * written to cp. Note this means that when cp_max_len = 1, this function
+ * assumes that cp[0] is the null character and does nothing (and returns
+ * 0). Linux kernel has a similar function called scnprintf(). Public
+ * declaration in sg_pr2serr.h header */
+int
+sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ if (cp_max_len < 2)
+ return 0;
+ va_start(args, fmt);
+ n = vsnprintf(cp, cp_max_len, fmt, args);
+ va_end(args);
+ return (n < cp_max_len) ? n : (cp_max_len - 1);
+}
+
+/* Simple ASCII printable (does not use locale), includes space and excludes
+ * DEL (0x7f). */
+static inline int
+my_isprint(int ch)
+{
+ return ((ch >= ' ') && (ch < 0x7f));
+}
+
+/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'.
+ * Only (currently) used in SNTL. */
+bool
+sg_get_initial_dsense(void)
+{
+ int k;
+ const char * cp;
+
+ cp = getenv("SG3_UTILS_DSENSE");
+ if (cp) {
+ if (1 == sscanf(cp, "%d", &k))
+ return k ? true : false;
+ }
+ return false;
+}
+
+/* Searches 'arr' for match on 'value' then 'peri_type'. If matches
+ 'value' but not 'peri_type' then yields first 'value' match entry.
+ Last element of 'arr' has NULL 'name'. If no match returns NULL. */
+static const struct sg_lib_value_name_t *
+get_value_name(const struct sg_lib_value_name_t * arr, int value,
+ int peri_type)
+{
+ const struct sg_lib_value_name_t * vp = arr;
+ const struct sg_lib_value_name_t * holdp;
+
+ if (peri_type < 0)
+ peri_type = 0;
+ for (; vp->name; ++vp) {
+ if (value == vp->value) {
+ if (sg_pdt_s_eq(peri_type, vp->peri_dev_type))
+ return vp;
+ holdp = vp;
+ while ((vp + 1)->name && (value == (vp + 1)->value)) {
+ ++vp;
+ if (sg_pdt_s_eq(peri_type, vp->peri_dev_type))
+ return vp;
+ }
+ return holdp;
+ }
+ }
+ return NULL;
+}
+
+/* If this function is not called, sg_warnings_strm will be NULL and all users
+ * (mainly fprintf() ) need to check and substitute stderr as required */
+void
+sg_set_warnings_strm(FILE * warnings_strm)
+{
+ sg_warnings_strm = warnings_strm;
+}
+
+/* Take care to minimize printf() parsing delays when printing commands */
+static char bin2hexascii[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+
+/* Given a SCSI command pointed to by cdbp of sz bytes this function forms
+ * a SCSI command in ASCII surrounded by square brackets in 'b'. 'b' is at
+ * least blen bytes long. If cmd_name is true then the command is prefixed
+ * by its SCSI command name (e.g. "VERIFY(10) [2f ...]". The command is
+ * shown as spaced separated pairs of hexadecimal digits (i.e. 0-9, a-f).
+ * Each pair represents byte. The leftmost pair of digits is cdbp[0] . If
+ * sz <= 0 then this function tries to guess the length of the command. */
+char *
+sg_get_command_str(const uint8_t * cdbp, int sz, bool cmd_name, int blen,
+ char * b)
+{
+ int k, j, jj;
+
+ if ((cdbp == NULL) || (b == NULL) || (blen < 1))
+ return b;
+ if (cmd_name && (blen > 16)) {
+ sg_get_command_name(cdbp, 0, blen, b);
+ j = (int)strlen(b);
+ if (j < (blen - 1))
+ b[j++] = ' ';
+ } else
+ j = 0;
+ if (j >= blen)
+ goto fini;
+ b[j++] = '[';
+ if (j >= blen)
+ goto fini;
+ if (sz <= 0) {
+ if (SG_VARIABLE_LENGTH_CMD == cdbp[0])
+ sz = cdbp[7] + 8;
+ else
+ sz = sg_get_command_size(cdbp[0]);
+ }
+ jj = j;
+ for (k = 0; (k < sz) && (j < (blen - 3)); ++k, j += 3, ++cdbp) {
+ b[j] = bin2hexascii[(*cdbp >> 4) & 0xf];
+ b[j + 1] = bin2hexascii[*cdbp & 0xf];
+ b[j + 2] = ' ';
+ }
+ if (j > jj)
+ --j; /* don't want trailing space before ']' */
+ if (j >= blen)
+ goto fini;
+ b[j++] = ']';
+fini:
+ if (j >= blen)
+ b[blen - 1] = '\0'; /* truncated string */
+ else
+ b[j] = '\0';
+ return b;
+}
+
+#define CMD_NAME_LEN 128
+
+void
+sg_print_command_len(const uint8_t * cdbp, int sz)
+{
+ char buff[CMD_NAME_LEN];
+
+ sg_get_command_str(cdbp, sz, true, sizeof(buff), buff);
+ pr2ws("%s\n", buff);
+}
+
+void
+sg_print_command(const uint8_t * cdbp)
+{
+ sg_print_command_len(cdbp, 0);
+}
+
+bool
+sg_scsi_status_is_good(int sstatus)
+{
+ sstatus &= 0xfe;
+ switch (sstatus) {
+ case SAM_STAT_GOOD:
+ case SAM_STAT_CONDITION_MET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+sg_scsi_status_is_bad(int sstatus)
+{
+ sstatus &= 0xfe;
+ switch (sstatus) {
+ case SAM_STAT_GOOD:
+ case SAM_STAT_CONDITION_MET:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void
+sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
+{
+ const struct sg_lib_simple_value_name_t * sstatus_p;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return;
+ }
+ scsi_status &= 0x7e; /* sanitize as much as possible */
+ for (sstatus_p = sg_lib_sstatus_str_arr; sstatus_p->name; ++sstatus_p) {
+ if (scsi_status == sstatus_p->value)
+ break;
+ }
+ if (sstatus_p->name)
+ sg_scnpr(buff, buff_len, "%s", sstatus_p->name);
+ else
+ sg_scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status);
+}
+
+void
+sg_print_scsi_status(int scsi_status)
+{
+ char buff[128];
+
+ sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff);
+ buff[sizeof(buff) - 1] = '\0';
+ pr2ws("%s ", buff);
+}
+
+/* Get sense key from sense buffer. If successful returns a sense key value
+ * between 0 and 15. If sense buffer cannot be decode, returns -1 . */
+int
+sg_get_sense_key(const uint8_t * sbp, int sb_len)
+{
+ if ((NULL == sbp) || (sb_len < 2))
+ return -1;
+ switch (sbp[0] & 0x7f) {
+ case 0x70:
+ case 0x71:
+ return (sb_len < 3) ? -1 : (sbp[2] & 0xf);
+ case 0x72:
+ case 0x73:
+ return sbp[1] & 0xf;
+ default:
+ return -1;
+ }
+}
+
+/* Yield string associated with sense_key value. Returns 'buff'. */
+char *
+sg_get_sense_key_str(int sense_key, int buff_len, char * buff)
+{
+ if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ if ((sense_key >= 0) && (sense_key < 16))
+ sg_scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
+ else
+ sg_scnpr(buff, buff_len, "invalid value: 0x%x", sense_key);
+ return buff;
+}
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char *
+sg_get_additional_sense_str(int asc, int ascq, bool add_sense_leadin,
+ int buff_len, char * buff)
+{
+ int k, num, rlen;
+ bool found = false;
+
+ if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
+ struct sg_lib_asc_ascq_range_t * ei2p = &sg_lib_asc_ascq_range[k];
+
+ if ((ei2p->asc == asc) &&
+ (ascq >= ei2p->ascq_min) &&
+ (ascq <= ei2p->ascq_max)) {
+ found = true;
+ if (add_sense_leadin)
+ num = sg_scnpr(buff, buff_len, "Additional sense: ");
+ else
+ num = 0;
+ rlen = buff_len - num;
+ sg_scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq);
+ }
+ }
+ if (found)
+ return buff;
+
+ for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
+ struct sg_lib_asc_ascq_t * eip = &sg_lib_asc_ascq[k];
+
+ if (eip->asc == asc &&
+ eip->ascq == ascq) {
+ found = true;
+ if (add_sense_leadin)
+ sg_scnpr(buff, buff_len, "Additional sense: %s", eip->text);
+ else
+ sg_scnpr(buff, buff_len, "%s", eip->text);
+ }
+ }
+ if (! found) {
+ if (asc >= 0x80)
+ sg_scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x "
+ "(hex)", asc, ascq);
+ else if (ascq >= 0x80)
+ sg_scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification "
+ "ASCQ=%02x (hex)", asc, ascq);
+ else
+ sg_scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq);
+ }
+ return buff;
+}
+
+/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
+char *
+sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff)
+{
+ return sg_get_additional_sense_str(asc, ascq, true, buff_len, buff);
+}
+
+/* Attempt to find the first SCSI sense data descriptor that matches the
+ * given 'desc_type'. If found return pointer to start of sense data
+ * descriptor; otherwise (including fixed format sense data) returns NULL. */
+const uint8_t *
+sg_scsi_sense_desc_find(const uint8_t * sbp, int sb_len,
+ int desc_type)
+{
+ int add_sb_len, desc_len, k;
+ const uint8_t * descp;
+
+ if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+ return NULL;
+ if ((sbp[0] < 0x72) || (sbp[0] > 0x73))
+ return NULL;
+ add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
+ descp = &sbp[8];
+ for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) {
+ int add_d_len;
+
+ descp += desc_len;
+ add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1;
+ desc_len = add_d_len + 2;
+ if (descp[0] == desc_type)
+ return descp;
+ if (add_d_len < 0) /* short descriptor ?? */
+ break;
+ }
+ return NULL;
+}
+
+/* Returns true if valid bit set, false if valid bit clear. Irrespective the
+ * information field is written out via 'info_outp' (except when it is
+ * NULL). Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_info_fld(const uint8_t * sbp, int sb_len,
+ uint64_t * info_outp)
+{
+ const uint8_t * bp;
+
+ if (info_outp)
+ *info_outp = 0;
+ if (sb_len < 7)
+ return false;
+ switch (sbp[0] & 0x7f) {
+ case 0x70:
+ case 0x71:
+ if (info_outp)
+ *info_outp = sg_get_unaligned_be32(sbp + 3);
+ return !!(sbp[0] & 0x80);
+ case 0x72:
+ case 0x73:
+ bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */);
+ if (bp && (0xa == bp[1])) {
+ uint64_t ull = sg_get_unaligned_be64(bp + 4);
+
+ if (info_outp)
+ *info_outp = ull;
+ return !!(bp[2] & 0x80); /* since spc3r23 should be set */
+ } else
+ return false;
+ default:
+ return false;
+ }
+}
+
+/* Returns true if fixed format or command specific information descriptor
+ * is found in the descriptor sense; else false. If available the command
+ * specific information field (4 byte integer in fixed format, 8 byte
+ * integer in descriptor format) is written out via 'cmd_spec_outp'.
+ * Handles both fixed and descriptor sense formats. */
+bool
+sg_get_sense_cmd_spec_fld(const uint8_t * sbp, int sb_len,
+ uint64_t * cmd_spec_outp)
+{
+ const uint8_t * bp;
+
+ if (cmd_spec_outp)
+ *cmd_spec_outp = 0;
+ if (sb_len < 7)
+ return false;
+ switch (sbp[0] & 0x7f) {
+ case 0x70:
+ case 0x71:
+ if (cmd_spec_outp)
+ *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8);
+ return true;
+ case 0x72:
+ case 0x73:
+ bp = sg_scsi_sense_desc_find(sbp, sb_len,
+ 1 /* command specific info desc */);
+ if (bp && (0xa == bp[1])) {
+ if (cmd_spec_outp)
+ *cmd_spec_outp = sg_get_unaligned_be64(bp + 4);
+ return true;
+ } else
+ return false;
+ default:
+ return false;
+ }
+}
+
+/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
+ * In descriptor format if the stream commands descriptor not found
+ * then returns false. Writes true or false corresponding to these bits to
+ * the last three arguments if they are non-NULL. */
+bool
+sg_get_sense_filemark_eom_ili(const uint8_t * sbp, int sb_len,
+ bool * filemark_p, bool * eom_p, bool * ili_p)
+{
+ const uint8_t * bp;
+
+ if (sb_len < 7)
+ return false;
+ switch (sbp[0] & 0x7f) {
+ case 0x70:
+ case 0x71:
+ if (sbp[2] & 0xe0) {
+ if (filemark_p)
+ *filemark_p = !!(sbp[2] & 0x80);
+ if (eom_p)
+ *eom_p = !!(sbp[2] & 0x40);
+ if (ili_p)
+ *ili_p = !!(sbp[2] & 0x20);
+ return true;
+ } else
+ return false;
+ case 0x72:
+ case 0x73:
+ /* Look for stream commands sense data descriptor */
+ bp = sg_scsi_sense_desc_find(sbp, sb_len, 4);
+ if (bp && (bp[1] >= 2)) {
+ if (bp[3] & 0xe0) {
+ if (filemark_p)
+ *filemark_p = !!(bp[3] & 0x80);
+ if (eom_p)
+ *eom_p = !!(bp[3] & 0x40);
+ if (ili_p)
+ *ili_p = !!(bp[3] & 0x20);
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
+/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
+ * returns true if progress indication sense data descriptor found. Places
+ * progress field from sense data where progress_outp points. If progress
+ * field is not available returns false and *progress_outp is unaltered.
+ * Handles both fixed and descriptor sense formats.
+ * Hint: if true is returned *progress_outp may be multiplied by 100 then
+ * divided by 65536 to get the percentage completion. */
+bool
+sg_get_sense_progress_fld(const uint8_t * sbp, int sb_len,
+ int * progress_outp)
+{
+ const uint8_t * bp;
+ int sk, sk_pr;
+
+ if (sb_len < 7)
+ return false;
+ switch (sbp[0] & 0x7f) {
+ case 0x70:
+ case 0x71:
+ sk = (sbp[2] & 0xf);
+ if ((sb_len < 18) ||
+ ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk)))
+ return false;
+ if (sbp[15] & 0x80) { /* SKSV bit set */
+ if (progress_outp)
+ *progress_outp = sg_get_unaligned_be16(sbp + 16);
+ return true;
+ } else
+ return false;
+ case 0x72:
+ case 0x73:
+ /* sense key specific progress (0x2) or progress descriptor (0xa) */
+ sk = (sbp[1] & 0xf);
+ sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
+ if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) &&
+ (0x6 == bp[1]) && (0x80 & bp[4])) {
+ if (progress_outp)
+ *progress_outp = sg_get_unaligned_be16(bp + 5);
+ return true;
+ } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) &&
+ ((0x6 == bp[1]))) {
+ if (progress_outp)
+ *progress_outp = sg_get_unaligned_be16(bp + 6);
+ return true;
+ } else
+ return false;
+ default:
+ return false;
+ }
+}
+
+char *
+sg_get_pdt_str(int pdt, int buff_len, char * buff)
+{
+ if ((pdt < 0) || (pdt > PDT_MAX))
+ sg_scnpr(buff, buff_len, "bad pdt");
+ else
+ sg_scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
+ return buff;
+}
+
+/* Returns true if left argument is "equal" to the right argument. l_pdt_s
+ * is a compound PDT (SCSI Peripheral Device Type) or a negative number
+ * which represents a wildcard (i.e. match anything). r_pdt_s has a similar
+ * form. PDT values are 5 bits long (0 to 31) and a compound pdt_s is
+ * formed by shifting the second (upper) PDT by eight bits to the left and
+ * OR-ing it with the first PDT. The pdt_s values must be defined so
+ * PDT_DISK (0) is _not_ the upper value in a compound pdt_s. */
+bool
+sg_pdt_s_eq(int l_pdt_s, int r_pdt_s)
+{
+ bool upper_l = !!(l_pdt_s & PDT_UPPER_MASK);
+ bool upper_r = !!(r_pdt_s & PDT_UPPER_MASK);
+
+ if ((l_pdt_s < 0) || (r_pdt_s < 0))
+ return true;
+ if (!upper_l && !upper_r)
+ return l_pdt_s == r_pdt_s;
+ else if (upper_l && upper_r)
+ return (((PDT_UPPER_MASK & l_pdt_s) == (PDT_UPPER_MASK & r_pdt_s)) ||
+ ((PDT_LOWER_MASK & l_pdt_s) == (PDT_LOWER_MASK & r_pdt_s)));
+ else if (upper_l)
+ return (((PDT_LOWER_MASK & l_pdt_s) == r_pdt_s) ||
+ ((PDT_UPPER_MASK & l_pdt_s) >> 8) == r_pdt_s);
+ return (((PDT_LOWER_MASK & r_pdt_s) == l_pdt_s) ||
+ ((PDT_UPPER_MASK & r_pdt_s) >> 8) == l_pdt_s);
+}
+
+int
+sg_lib_pdt_decay(int pdt)
+{
+ if ((pdt < 0) || (pdt > PDT_MAX))
+ return 0;
+ return sg_lib_pdt_decay_arr[pdt];
+}
+
+char *
+sg_get_trans_proto_str(int tpi, int buff_len, char * buff)
+{
+ if ((tpi < 0) || (tpi > 15))
+ sg_scnpr(buff, buff_len, "bad tpi");
+ else
+ sg_scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]);
+ return buff;
+}
+
+#define TRANSPORT_ID_MIN_LEN 24
+
+char *
+sg_decode_transportid_str(const char * lip, uint8_t * bp, int bplen,
+ bool only_one, int blen, char * b)
+{
+ int num, k, n;
+ uint64_t ull;
+ int bump;
+
+ if ((NULL == b) || (blen < 1))
+ return b;
+ else if (1 == blen) {
+ b[0] = '\0';
+ return b;
+ }
+ if (NULL == lip)
+ lip = "";
+ /* bump = TRANSPORT_ID_MIN_LEN; // some old compilers insisted on this */
+ for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) {
+ int proto_id, normal_len, tpid_format;
+
+ if ((k > 0) && only_one)
+ break;
+ if ((bplen < 24) || (0 != (bplen % 4)))
+ n += sg_scnpr(b + n, blen - n, "%sTransport Id short or not "
+ "multiple of 4 [length=%d]:\n", lip, blen);
+ else
+ n += sg_scnpr(b + n, blen - n, "%sTransport Id of initiator:\n",
+ lip);
+ tpid_format = ((bp[0] >> 6) & 0x3);
+ proto_id = (bp[0] & 0xf);
+ normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ?
+ TRANSPORT_ID_MIN_LEN : bplen;
+ switch (proto_id) {
+ case TPROTO_FCP: /* Fibre channel */
+ n += sg_scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n",
+ lip);
+ if (0 != tpid_format)
+ n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */
+ n += sg_scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI "
+ "address: 0x%x\n", lip,
+ sg_get_unaligned_be16(bp + 2));
+ if (0 != tpid_format)
+ n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ n += sg_scnpr(b + n, blen - n, "%s relative port number (of "
+ "corresponding target): 0x%x\n", lip,
+ sg_get_unaligned_be16(bp + 6));
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_SSA:
+ n += sg_scnpr(b + n, blen - n, "%s SSA (transport id not "
+ "defined):\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
+ tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_1394: /* IEEE 1394 */
+ n += sg_scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n",
+ lip);
+ if (0 != tpid_format)
+ n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_SRP: /* SCSI over RDMA */
+ n += sg_scnpr(b + n, blen - n, "%s RDMA initiator port "
+ "identifier:\n", lip);
+ if (0 != tpid_format)
+ n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_ISCSI:
+ n += sg_scnpr(b + n, blen - n, "%s iSCSI ", lip);
+ num = sg_get_unaligned_be16(bp + 2);
+ if (0 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]);
+ else if (1 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "world wide unique port id: "
+ "%.*s\n", num, &bp[4]);
+ else {
+ n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: "
+ "%d]\n", tpid_format);
+ n += hex2str(bp, num + 4, lip, 0, blen - n, b + n);
+ }
+ bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ?
+ TRANSPORT_ID_MIN_LEN : num + 4);
+ break;
+ case TPROTO_SAS:
+ ull = sg_get_unaligned_be64(bp + 4);
+ n += sg_scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n",
+ lip, ull);
+ if (0 != tpid_format)
+ n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
+ "%d]\n", lip, tpid_format);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_ADT: /* no TransportID defined by T10 yet */
+ n += sg_scnpr(b + n, blen - n, "%s ADT:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
+ tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_ATA: /* no TransportID defined by T10 yet */
+ n += sg_scnpr(b + n, blen - n, "%s ATAPI:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
+ tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_UAS: /* no TransportID defined by T10 yet */
+ n += sg_scnpr(b + n, blen - n, "%s UAS:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
+ tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_SOP:
+ n += sg_scnpr(b + n, blen - n, "%s SOP ", lip);
+ num = sg_get_unaligned_be16(bp + 2);
+ if (0 == tpid_format)
+ n += sg_scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num);
+ else {
+ n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: "
+ "%d]\n", tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ }
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_PCIE: /* no TransportID defined by T10 yet */
+ n += sg_scnpr(b + n, blen - n, "%s PCIE:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
+ tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ case TPROTO_NONE: /* no TransportID defined by T10 */
+ n += sg_scnpr(b + n, blen - n, "%s No specified protocol\n",
+ lip);
+ /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen),
+ * lip, 0, blen - n, b + n); */
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ default:
+ n += sg_scnpr(b + n, blen - n, "%s unknown protocol id=0x%x "
+ "TPID format=%d\n", lip, proto_id, tpid_format);
+ n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
+ bump = TRANSPORT_ID_MIN_LEN;
+ break;
+ }
+ }
+ return b;
+}
+
+
+static const char * desig_code_set_str_arr[] =
+{
+ "Reserved [0x0]",
+ "Binary",
+ "ASCII",
+ "UTF-8",
+ "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
+ "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
+ "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_code_set_str(int val)
+{
+ if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_code_set_str_arr)))
+ return desig_code_set_str_arr[val];
+ else
+ return NULL;
+}
+
+static const char * desig_assoc_str_arr[] =
+{
+ "Addressed logical unit",
+ "Target port", /* that received request; unless SCSI ports VPD */
+ "Target device that contains addressed lu",
+ "Reserved [0x3]",
+};
+
+const char *
+sg_get_desig_assoc_str(int val)
+{
+ if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_assoc_str_arr)))
+ return desig_assoc_str_arr[val];
+ else
+ return NULL;
+}
+
+static const char * desig_type_str_arr[] =
+{
+ "Vendor specific [0x0]",
+ "T10 vendor identification",
+ "EUI-64 based",
+ "NAA",
+ "Relative target port",
+ "Target port group", /* spc4r09: _primary_ target port group */
+ "Logical unit group",
+ "MD5 logical unit identifier",
+ "SCSI name string",
+ "Protocol specific port identifier", /* spc4r36 */
+ "UUID identifier", /* spc5r08 */
+ "Reserved [0xb]",
+ "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+const char *
+sg_get_desig_type_str(int val)
+{
+ if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_type_str_arr)))
+ return desig_type_str_arr[val];
+ else
+ return NULL;
+}
+
+char *
+sg_get_zone_type_str(uint8_t zt, int buff_len, char * buff)
+{
+ if ((NULL == buff) || (buff_len < 1))
+ return NULL;
+ switch (zt) {
+ case 1:
+ sg_scnpr(buff, buff_len, "conventional");
+ break;
+ case 2:
+ sg_scnpr(buff, buff_len, "sequential write required");
+ break;
+ case 3:
+ sg_scnpr(buff, buff_len, "sequential write preferred");
+ break;
+ case 4:
+ sg_scnpr(buff, buff_len, "sequential or before required");
+ break;
+ case 5:
+ sg_scnpr(buff, buff_len, "gap");
+ break;
+ default:
+ sg_scnpr(buff, buff_len, "unknown [0x%x]", zt);
+ break;
+ }
+ return buff;
+}
+
+
+/* Expects a T10 UUID designator (as found in the Device Identification VPD
+ * page) pointed to by 'dp'. To not produce an error string in 'b', c_set
+ * should be 1 (binary) and dlen should be 18. Currently T10 only supports
+ * locally assigned UUIDs. Writes output to string 'b' of no more than blen
+ * bytes and returns the number of bytes actually written to 'b' but doesn't
+ * count the trailing null character it always appends (if blen > 0). 'lip'
+ * is lead-in string (on each line) than may be NULL. skip_prefix avoids
+ * outputting: ' Locally assigned UUID: ' before the UUID. */
+int
+sg_t10_uuid_desig2str(const uint8_t *dp, int dlen, int c_set, bool do_long,
+ bool skip_prefix, const char * lip /* lead-in */,
+ int blen, char * b)
+{
+ int m;
+ int n = 0;
+
+ if (NULL == lip)
+ lip = "";
+ if (1 != c_set) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set >>\n", lip);
+ n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
+ return n;
+ }
+ if ((1 != ((dp[0] >> 4) & 0xf)) || (18 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected locally "
+ "assigned UUID, 16 bytes long >>\n", lip);
+ n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
+ return n;
+ }
+ if (skip_prefix) {
+ if (strlen(lip) > 0)
+ n += sg_scnpr(b + n, blen - n, "%s", lip);
+ } else
+ n += sg_scnpr(b + n, blen - n, "%s Locally assigned UUID: ",
+ lip);
+ for (m = 0; m < 16; ++m) {
+ if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
+ n += sg_scnpr(b + n, blen - n, "-");
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
+ }
+ n += sg_scnpr(b + n, blen - n, "\n");
+ if (do_long) {
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ }
+ return n;
+}
+
+int
+sg_get_designation_descriptor_str(const char * lip, const uint8_t * ddp,
+ int dd_len, bool print_assoc, bool do_long,
+ int blen, char * b)
+{
+ int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa;
+ int vsi, k, n, dlen;
+ uint64_t ccc_id, vsei;
+ const uint8_t * ip;
+ char e[64];
+ const char * cp;
+
+ n = 0;
+ if (NULL == lip)
+ lip = "";
+ if (dd_len < 4) {
+ n += sg_scnpr(b + n, blen - n, "%sdesignator desc too short: got "
+ "length of %d want 4 or more\n", lip, dd_len);
+ return n;
+ }
+ dlen = ddp[3];
+ if (dlen > (dd_len - 4)) {
+ n += sg_scnpr(b + n, blen - n, "%sdesignator too long: says it is %d "
+ "bytes, but given %d bytes\n", lip, dlen, dd_len - 4);
+ return n;
+ }
+ ip = ddp + 4;
+ p_id = ((ddp[0] >> 4) & 0xf);
+ c_set = (ddp[0] & 0xf);
+ piv = ((ddp[1] & 0x80) ? 1 : 0);
+ assoc = ((ddp[1] >> 4) & 0x3);
+ desig_type = (ddp[1] & 0xf);
+ if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc))))
+ n += sg_scnpr(b + n, blen - n, "%s %s:\n", lip, cp);
+ n += sg_scnpr(b + n, blen - n, "%s designator type: ", lip);
+ cp = sg_get_desig_type_str(desig_type);
+ if (cp)
+ n += sg_scnpr(b + n, blen - n, "%s", cp);
+ n += sg_scnpr(b + n, blen - n, ", code set: ");
+ cp = sg_get_desig_code_set_str(c_set);
+ if (cp)
+ n += sg_scnpr(b + n, blen - n, "%s", cp);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ if (piv && ((1 == assoc) || (2 == assoc)))
+ n += sg_scnpr(b + n, blen - n, "%s transport: %s\n", lip,
+ sg_get_trans_proto_str(p_id, sizeof(e), e));
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ k = 0;
+ if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */
+ for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k)
+ ;
+ if (k >= dlen)
+ k = 1;
+ }
+ if (k)
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific: %.*s\n",
+ lip, dlen, ip);
+ else {
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific:\n", lip);
+ n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+ }
+ break;
+ case 1: /* T10 vendor identification */
+ n += sg_scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip);
+ if (dlen > 8) {
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific: "
+ "%.*s\n", lip, dlen - 8, ip + 8);
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific: 0x",
+ lip);
+ for (m = 8; m < dlen; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ }
+ break;
+ case 2: /* EUI-64 based */
+ if (! do_long) {
+ if ((8 != dlen) && (12 != dlen) && (16 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expect 8, 12 and "
+ "16 byte EUI, got %d >>\n", lip, dlen);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < dlen; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s EUI-64 based %d byte "
+ "identifier\n", lip, dlen);
+ if (1 != c_set) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set (1) >>\n", lip);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ ci_off = 0;
+ if (16 == dlen) { /* first 8 bytes are 'Identifier Extension' */
+ uint64_t id_ext = sg_get_unaligned_be64(ip);
+
+ ci_off = 8;
+ n += sg_scnpr(b + n, blen - n, "%s Identifier extension: 0x%"
+ PRIx64 "\n", lip, id_ext);
+ } else if ((8 != dlen) && (12 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << can only decode 8, 12 "
+ "and 16 byte ids >>\n", lip);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ ccc_id = sg_get_unaligned_be64(ip + ci_off);
+ n += sg_scnpr(b + n, blen - n, "%s IEEE identifier: 0x%"
+ PRIx64 "\n", lip, ccc_id);
+ if (12 == dlen) {
+ d_id = sg_get_unaligned_be32(ip + 8);
+ n += sg_scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n",
+ lip, d_id);
+ }
+ break;
+ case 3: /* NAA <n> */
+ if (1 != c_set) {
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected code set "
+ "%d for NAA >>\n", lip, c_set);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ naa = (ip[0] >> 4) & 0xff;
+ switch (naa) {
+ case 2: /* NAA 2: IEEE Extended */
+ if (8 != dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 2 "
+ "identifier length: 0x%x >>\n", lip, dlen);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+ c_id = sg_get_unaligned_be24(ip + 2);
+ vsi = sg_get_unaligned_be24(ip + 5);
+ if (do_long) {
+ n += sg_scnpr(b + n, blen - n, "%s NAA 2, vendor "
+ "specific identifier A: 0x%x\n", lip, d_id);
+ n += sg_scnpr(b + n, blen - n, "%s AOI: 0x%x\n", lip,
+ c_id);
+ n += sg_scnpr(b + n, blen - n, "%s vendor specific "
+ "identifier B: 0x%x\n", lip, vsi);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ }
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 3: /* NAA 3: Locally assigned */
+ if (8 != dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 3 "
+ "identifier length: 0x%x >>\n", lip, dlen);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ if (do_long)
+ n += sg_scnpr(b + n, blen - n, "%s NAA 3, Locally "
+ "assigned:\n", lip);
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 5: /* NAA 5: IEEE Registered */
+ if (8 != dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 5 "
+ "identifier length: 0x%x >>\n", lip, dlen);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ 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];
+ }
+ if (do_long) {
+ n += sg_scnpr(b + n, blen - n, "%s NAA 5, AOI: 0x%x\n",
+ lip, c_id);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ break;
+ case 6: /* NAA 6: IEEE Registered extended */
+ if (16 != dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 6 "
+ "identifier length: 0x%x >>\n", lip, dlen);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ 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];
+ }
+ if (do_long) {
+ n += sg_scnpr(b + n, blen - n, "%s NAA 6, AOI: 0x%x\n",
+ lip, c_id);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", lip, vsei);
+ vsei = sg_get_unaligned_be64(ip + 8);
+ n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
+ "Identifier Extension: 0x%" PRIx64 "\n", lip,
+ vsei);
+ n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "]\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ break;
+ default:
+ n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] "
+ ">>\n", lip, naa);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ break;
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set, target port association, length 4 >>\n",
+ lip);
+ n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ n += sg_scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n",
+ lip, d_id);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set, target port association, length 4 >>\n",
+ lip);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ n += sg_scnpr(b + n, blen - n, "%s Target port group: 0x%x\n",
+ lip, d_id);
+ break;
+ case 6: /* Logical unit group */
+ if ((1 != c_set) || (0 != assoc) || (4 != dlen)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set, logical unit association, length 4 >>\n",
+ lip);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ n += sg_scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n",
+ lip, d_id);
+ break;
+ case 7: /* MD5 logical unit identifier */
+ if ((1 != c_set) || (0 != assoc)) {
+ n += sg_scnpr(b + n, blen - n, "%s << expected binary "
+ "code_set, logical unit association >>\n", lip);
+ n += hex2str(ip, dlen, "", 1, blen - n, b + n);
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s MD5 logical unit "
+ "identifier:\n", lip);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) { /* accept ASCII as subset of UTF-8 */
+ if (2 == c_set) {
+ if (do_long)
+ n += sg_scnpr(b + n, blen - n, "%s << expected "
+ "UTF-8, use ASCII >>\n", lip);
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s << expected UTF-8 "
+ "code_set >>\n", lip);
+ n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
+ break;
+ }
+ }
+ n += sg_scnpr(b + n, blen - n, "%s SCSI name string:\n", lip);
+ /* does %s print out UTF-8 ok??
+ * Seems to depend on the locale. Looks ok here with my
+ * locale setting: en_AU.UTF-8
+ */
+ n += sg_scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen,
+ (const char *)ip);
+ break;
+ case 9: /* Protocol specific port identifier */
+ /* added in spc4r36, PIV must be set, proto_id indicates */
+ /* whether UAS (USB) or SOP (PCIe) or ... */
+ if (! piv)
+ n += sg_scnpr(b + n, blen - n, " %s >>>> Protocol specific "
+ "port identifier expects protocol\n%s "
+ "identifier to be valid and it is not\n", lip, lip);
+ if (TPROTO_UAS == p_id) {
+ n += sg_scnpr(b + n, blen - n, "%s USB device address: "
+ "0x%x\n", lip, 0x7f & ip[0]);
+ n += sg_scnpr(b + n, blen - n, "%s USB interface number: "
+ "0x%x\n", lip, ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ n += sg_scnpr(b + n, blen - n, "%s PCIe routing ID, bus "
+ "number: 0x%x\n", lip, ip[0]);
+ n += sg_scnpr(b + n, blen - n, "%s function number: "
+ "0x%x\n", lip, ip[1]);
+ n += sg_scnpr(b + n, blen - n, "%s [or device number: "
+ "0x%x, function number: 0x%x]\n", lip,
+ (0x1f & (ip[1] >> 3)), 0x7 & ip[1]);
+ } else
+ n += sg_scnpr(b + n, blen - n, "%s >>>> unexpected protocol "
+ "identifier: %s\n%s with Protocol "
+ "specific port identifier\n", lip,
+ sg_get_trans_proto_str(p_id, sizeof(e), e), lip);
+ break;
+ case 0xa: /* UUID identifier */
+ n += sg_t10_uuid_desig2str(ip, dlen, c_set, do_long, false, lip,
+ blen - n, b + n);
+ break;
+ default: /* reserved */
+ n += sg_scnpr(b + n, blen - n, "%s reserved designator=0x%x\n",
+ lip, desig_type);
+ n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
+ break;
+ }
+ return n;
+}
+
+static int
+decode_sks(const char * lip, const uint8_t * descp, int add_d_len,
+ int sense_key, bool * processedp, int blen, char * b)
+{
+ int progress, pr, rem, n;
+
+ n = 0;
+ if (NULL == lip)
+ lip = "";
+ switch (sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "Field pointer: ");
+ goto too_short;
+ }
+ /* abbreviate to fit on one line */
+ n += sg_scnpr(b + n, blen - n, "Field pointer:\n");
+ n += sg_scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip,
+ (descp[4] & 0x40) ? "Command" : "Data parameters",
+ sg_get_unaligned_be16(descp + 5));
+ if (descp[4] & 0x08) {
+ n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+ } else
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case SPC_SK_HARDWARE_ERROR:
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_RECOVERED_ERROR:
+ n += sg_scnpr(b + n, blen - n, "Actual retry count: ");
+ if (add_d_len < 6)
+ goto too_short;
+ n += sg_scnpr(b + n, blen - n,"%u\n",
+ sg_get_unaligned_be16(descp + 5));
+ break;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_NOT_READY:
+ n += sg_scnpr(b + n, blen - n, "Progress indication: ");
+ if (add_d_len < 6)
+ goto too_short;
+ progress = sg_get_unaligned_be16(descp + 5);
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ n += sg_scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem);
+ break;
+ case SPC_SK_COPY_ABORTED:
+ n += sg_scnpr(b + n, blen - n, "Segment pointer:\n");
+ if (add_d_len < 6)
+ goto too_short;
+ n += sg_scnpr(b + n, blen - n, "%s Relative to start of %s, "
+ "byte %d", lip, (descp[4] & 0x20) ?
+ "segment descriptor" : "parameter list",
+ sg_get_unaligned_be16(descp + 5));
+ if (descp[4] & 0x08)
+ n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
+ else
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case SPC_SK_UNIT_ATTENTION:
+ n += sg_scnpr(b + n, blen - n, "Unit attention condition queue:\n");
+ n += sg_scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip,
+ !!(descp[4] & 0x1));
+ break;
+ default:
+ n += sg_scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n",
+ sense_key);
+ *processedp = false;
+ break;
+ }
+ return n;
+
+too_short:
+ n += sg_scnpr(b + n, blen - n, "%s\n", " >> descriptor too short");
+ *processedp = false;
+ return n;
+}
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_OFFLINE 0xe
+#define TPGS_STATE_TRANSITIONING 0xf
+
+static int
+decode_tpgs_state(int st, char * b, int blen)
+{
+ switch (st) {
+ case TPGS_STATE_OPTIMIZED:
+ return sg_scnpr(b, blen, "active/optimized");
+ case TPGS_STATE_NONOPTIMIZED:
+ return sg_scnpr(b, blen, "active/non optimized");
+ case TPGS_STATE_STANDBY:
+ return sg_scnpr(b, blen, "standby");
+ case TPGS_STATE_UNAVAILABLE:
+ return sg_scnpr(b, blen, "unavailable");
+ case TPGS_STATE_OFFLINE:
+ return sg_scnpr(b, blen, "offline");
+ case TPGS_STATE_TRANSITIONING:
+ return sg_scnpr(b, blen, "transitioning between states");
+ default:
+ return sg_scnpr(b, blen, "unknown: 0x%x", st);
+ }
+}
+
+static int
+uds_referral_descriptor_str(char * b, int blen, const uint8_t * dp,
+ int alen, const char * lip)
+{
+ int n = 0;
+ int dlen = alen - 2;
+ int k, j, g, f;
+ const uint8_t * tp;
+ char c[40];
+
+ if (NULL == lip)
+ lip = "";
+ n += sg_scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip,
+ !!(dp[2] & 0x1));
+ dp += 4;
+ for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
+ int ntpgd = dp[3];
+ uint64_t ull;
+
+ g = (ntpgd * 4) + 20;
+ n += sg_scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f);
+ if ((k + g) > dlen) {
+ n += sg_scnpr(b + n, blen - n, "%s truncated descriptor, "
+ "stop\n", lip);
+ return n;
+ }
+ ull = sg_get_unaligned_be64(dp + 4);
+ n += sg_scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64
+ "\n", lip, ull);
+ ull = sg_get_unaligned_be64(dp + 12);
+ n += sg_scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64
+ "\n", lip, ull);
+ for (j = 0; j < ntpgd; ++j) {
+ tp = dp + 20 + (j * 4);
+ decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
+ n += sg_scnpr(b + n, blen - n, "%s tpg: %d state: %s\n",
+ lip, sg_get_unaligned_be16(tp + 2), c);
+ }
+ }
+ return n;
+}
+
+static const char * dd_usage_reason_str_arr[] = {
+ "Unknown",
+ "resend this and further commands to:",
+ "resend this command to:",
+ "new subsidiary lu added to this administrative lu:",
+ "administrative lu associated with a preferred binding:",
+ };
+
+
+/* Decode descriptor format sense descriptors (assumes sense buffer is
+ * in descriptor format). 'leadin' is string prepended to each line written
+ * to 'b', NULL treated as "". Returns the number of bytes written to 'b'
+ * excluding the trailing '\0'. If problem, returns 0. */
+int
+sg_get_sense_descriptors_str(const char * lip, const uint8_t * sbp,
+ int sb_len, int blen, char * b)
+{
+ int add_sb_len, desc_len, k, j, sense_key;
+ int n, progress, pr, rem;
+ uint16_t sct_sc;
+ bool processed;
+ const uint8_t * descp;
+ char z[64];
+ static const char * dtsp = " >> descriptor too short";
+ static const char * eccp = "Extended copy command";
+ static const char * ddp = "destination device";
+
+ if ((NULL == b) || (blen <= 0))
+ return 0;
+ b[0] = '\0';
+ if (lip)
+ sg_scnpr(z, sizeof(z), "%.60s ", lip);
+ else
+ sg_scnpr(z, sizeof(z), " ");
+ if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
+ return 0;
+ add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
+ sense_key = (sbp[1] & 0xf);
+
+ for (descp = (sbp + 8), k = 0, n = 0;
+ (k < add_sb_len) && (n < blen);
+ k += desc_len, descp += desc_len) {
+ int add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
+
+ if ((k + add_d_len + 2) > add_sb_len)
+ add_d_len = add_sb_len - k - 2;
+ desc_len = add_d_len + 2;
+ n += sg_scnpr(b + n, blen - n, "%s Descriptor type: ", lip);
+ processed = true;
+ switch (descp[0]) {
+ case 0:
+ n += sg_scnpr(b + n, blen - n, "Information: ");
+ if (add_d_len >= 10) {
+ if (! (0x80 & descp[2]))
+ n += sg_scnpr(b + n, blen - n, "Valid=0 (-> vendor "
+ "specific) ");
+ n += sg_scnpr(b + n, blen - n, "0x");
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 1:
+ n += sg_scnpr(b + n, blen - n, "Command specific: ");
+ if (add_d_len >= 10) {
+ n += sg_scnpr(b + n, blen - n, "0x");
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 2: /* Sense Key Specific */
+ n += sg_scnpr(b + n, blen - n, "Sense key specific: ");
+ n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+ blen - n, b + n);
+ break;
+ case 3:
+ n += sg_scnpr(b + n, blen - n, "Field replaceable unit code: ");
+ if (add_d_len >= 2)
+ n += sg_scnpr(b + n, blen - n, "0x%x\n", descp[3]);
+ else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 4:
+ n += sg_scnpr(b + n, blen - n, "Stream commands: ");
+ if (add_d_len >= 2) {
+ if (descp[3] & 0x80)
+ n += sg_scnpr(b + n, blen - n, "FILEMARK");
+ if (descp[3] & 0x40)
+ n += sg_scnpr(b + n, blen - n, "End Of Medium (EOM)");
+ if (descp[3] & 0x20)
+ n += sg_scnpr(b + n, blen - n, "Incorrect Length "
+ "Indicator (ILI)");
+ n += sg_scnpr(b + n, blen - n, "\n");
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 5:
+ n += sg_scnpr(b + n, blen - n, "Block commands: ");
+ if (add_d_len >= 2)
+ n += sg_scnpr(b + n, blen - n, "Incorrect Length Indicator "
+ "(ILI) %s\n",
+ (descp[3] & 0x20) ? "set" : "clear");
+ else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 6:
+ n += sg_scnpr(b + n, blen - n, "OSD object identification\n");
+ processed = false;
+ break;
+ case 7:
+ n += sg_scnpr(b + n, blen - n, "OSD response integrity check "
+ "value\n");
+ processed = false;
+ break;
+ case 8:
+ n += sg_scnpr(b + n, blen - n, "OSD attribute identification\n");
+ processed = false;
+ break;
+ case 9: /* this is defined in SAT (SAT-2) */
+ n += sg_scnpr(b + n, blen - n, "ATA Status Return: ");
+ if (add_d_len >= 12) {
+ int extend, count;
+
+ extend = descp[2] & 1;
+ count = descp[5] + (extend ? (descp[4] << 8) : 0);
+ n += sg_scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s"
+ " count=0x%x ", extend, descp[3], lip,
+ count);
+ if (extend)
+ n += sg_scnpr(b + n, blen - n,
+ "lba=0x%02x%02x%02x%02x%02x%02x ",
+ descp[10], descp[8], descp[6], descp[11],
+ descp[9], descp[7]);
+ else
+ n += sg_scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ",
+ descp[11], descp[9], descp[7]);
+ n += sg_scnpr(b + n, blen - n, "device=0x%x status=0x%x\n",
+ descp[12], descp[13]);
+ } else {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ }
+ break;
+ case 0xa:
+ /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
+ n += sg_scnpr(b + n, blen - n, "Another progress indication: ");
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ progress = sg_get_unaligned_be16(descp + 6);
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ n += sg_scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem);
+ n += sg_scnpr(b + n, blen - n, "%s [sense_key=0x%x "
+ "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3],
+ descp[4]);
+ break;
+ case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
+ n += sg_scnpr(b + n, blen - n, "User data segment referral: ");
+ if (add_d_len < 2) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "\n");
+ n += uds_referral_descriptor_str(b + n, blen - n, descp,
+ add_d_len, lip);
+ break;
+ case 0xc: /* Added in SPC-4 rev 28 */
+ n += sg_scnpr(b + n, blen - n, "Forwarded sense data\n");
+ if (add_d_len < 2) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "%s FSDT: %s\n", lip,
+ (descp[2] & 0x80) ? "set" : "clear");
+ j = descp[2] & 0xf;
+ n += sg_scnpr(b + n, blen - n, "%s Sense data source: ", lip);
+ switch (j) {
+ case 0:
+ n += sg_scnpr(b + n, blen - n, "%s source device\n", eccp);
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ n += sg_scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1);
+ break;
+ default:
+ n += sg_scnpr(b + n, blen - n, "unknown [%d]\n", j);
+ }
+ {
+ char c[480];
+
+ sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
+ c[sizeof(c) - 1] = '\0';
+ n += sg_scnpr(b + n, blen - n, "%s Forwarded status: %s\n",
+ lip, c);
+ if (add_d_len > 2) {
+ /* recursing; hope not to get carried away */
+ n += sg_scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n",
+ lip);
+ sg_get_sense_str(lip, descp + 4, add_d_len - 2, false,
+ sizeof(c), c);
+ n += sg_scnpr(b + n, blen - n, "%s", c);
+ n += sg_scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n",
+ lip);
+ }
+ }
+ break;
+ case 0xd: /* Added in SBC-3 rev 36d */
+ /* this descriptor combines descriptors 0, 1, 2 and 3 */
+ n += sg_scnpr(b + n, blen - n, "Direct-access block device\n");
+ if (add_d_len < 28) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ if (0x20 & descp[2])
+ n += sg_scnpr(b + n, blen - n, "%s ILI (incorrect length "
+ "indication) set\n", lip);
+ if (0x80 & descp[4]) {
+ n += sg_scnpr(b + n, blen - n, "%s Sense key specific: ",
+ lip);
+ n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
+ blen - n, b + n);
+ }
+ n += sg_scnpr(b + n, blen - n, "%s Field replaceable unit "
+ "code: 0x%x\n", lip, descp[7]);
+ if (0x80 & descp[2]) {
+ n += sg_scnpr(b + n, blen - n, "%s Information: 0x", lip);
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[8 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ n += sg_scnpr(b + n, blen - n, "%s Command specific: 0x", lip);
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", descp[16 + j]);
+ n += sg_scnpr(b + n, blen - n, "\n");
+ break;
+ case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */
+ n += sg_scnpr(b + n, blen - n, "Device designation\n");
+ j = (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr);
+ if (descp[3] < j)
+ n += sg_scnpr(b + n, blen - n, "%s Usage reason: %s\n",
+ lip, dd_usage_reason_str_arr[descp[3]]);
+ else
+ n += sg_scnpr(b + n, blen - n, "%s Usage reason: "
+ "reserved[%d]\n", lip, descp[3]);
+ n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2,
+ true, false, blen - n,
+ b + n);
+ break;
+ case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */
+ n += sg_scnpr(b + n, blen - n, "Microcode activation ");
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ progress = sg_get_unaligned_be16(descp + 6);
+ n += sg_scnpr(b + n, blen - n, "time: ");
+ if (0 == progress)
+ n += sg_scnpr(b + n, blen - n, "unknown\n");
+ else
+ n += sg_scnpr(b + n, blen - n, "%d seconds\n", progress);
+ break;
+ case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */
+ n += sg_scnpr(b + n, blen - n, "NVMe Status: ");
+ if (add_d_len < 6) {
+ n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
+ processed = false;
+ break;
+ }
+ n += sg_scnpr(b + n, blen - n, "DNR=%d, M=%d, ",
+ (int)!!(0x80 & descp[5]), (int)!!(0x40 & descp[5]));
+ sct_sc = sg_get_unaligned_be16(descp + 6);
+ n += sg_scnpr(b + n, blen - n, "SCT_SC=0x%x\n", sct_sc);
+ if (sct_sc > 0) {
+ char d[80];
+
+ n += sg_scnpr(b + n, blen - n, " %s\n",
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(d), d));
+ }
+ break;
+ default:
+ if (descp[0] >= 0x80)
+ n += sg_scnpr(b + n, blen - n, "Vendor specific [0x%x]\n",
+ descp[0]);
+ else
+ n += sg_scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]);
+ processed = false;
+ break;
+ }
+ if (! processed) {
+ if (add_d_len > 0) {
+ n += sg_scnpr(b + n, blen - n, "%s ", lip);
+ for (j = 0; j < add_d_len; ++j) {
+ if ((j > 0) && (0 == (j % 24)))
+ n += sg_scnpr(b + n, blen - n, "\n%s ", lip);
+ n += sg_scnpr(b + n, blen - n, "%02x ", descp[j + 2]);
+ }
+ n += sg_scnpr(b + n, blen - n, "\n");
+ }
+ }
+ if (add_d_len < 0)
+ n += sg_scnpr(b + n, blen - n, "%s short descriptor\n", lip);
+ }
+ return n;
+}
+
+/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count'
+ * and/or 'lba' values to indicate that not all data in those fields is shown.
+ * That extra field information may be available in the ATA pass-through
+ * results log page parameter with the corresponding 'log_index'. */
+static int
+sg_get_sense_sat_pt_fixed_str(const char * lip, const uint8_t * sp,
+ int slen, int blen, char * b)
+{
+ int n = 0;
+ bool extend, count_upper_nz, lba_upper_nz;
+
+ if ((blen < 1) || (slen < 12))
+ return n;
+ if (NULL == lip)
+ lip = "";
+ if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2]))
+ n += sg_scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered "
+ "Error ??\n", lip);
+ /* Fixed sense command-specific information field starts at sp + 8 */
+ extend = !!(0x80 & sp[8]);
+ count_upper_nz = !!(0x40 & sp[8]);
+ lba_upper_nz = !!(0x20 & sp[8]);
+ /* Fixed sense information field starts at sp + 3 */
+ n += sg_scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, "
+ "device=0x%x, count(7:0)=0x%x%c\n", lip, sp[3], sp[4],
+ sp[5], sp[6], (count_upper_nz ? '+' : ' '));
+ n += sg_scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, "
+ "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip,
+ (int)extend, (0xf & sp[8]), sp[11], sp[10], sp[9],
+ (lba_upper_nz ? '+' : ' '));
+ return n;
+}
+
+/* Fetch sense information */
+int
+sg_get_sense_str(const char * lip, const uint8_t * sbp, int sb_len,
+ bool raw_sinfo, int cblen, char * cbp)
+{
+ bool descriptor_format = false;
+ bool sdat_ovfl = false;
+ bool valid_info_fld;
+ int len, progress, n, r, pr, rem, blen;
+ unsigned int info;
+ uint8_t resp_code;
+ const char * ebp = NULL;
+ char ebuff[64];
+ char b[256];
+ struct sg_scsi_sense_hdr ssh;
+
+ if ((NULL == cbp) || (cblen <= 0))
+ return 0;
+ else if (1 == cblen) {
+ cbp[0] = '\0';
+ return 0;
+ }
+ blen = sizeof(b);
+ n = 0;
+ if (NULL == lip)
+ lip = "";
+ if ((NULL == sbp) || (sb_len < 1)) {
+ n += sg_scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip);
+ return n;
+ }
+ resp_code = 0x7f & sbp[0];
+ valid_info_fld = !!(sbp[0] & 0x80);
+ len = sb_len;
+ if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
+ switch (ssh.response_code) {
+ case 0x70: /* fixed, current */
+ ebp = "Fixed format, current";
+ len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+ len = (len > sb_len) ? sb_len : len;
+ sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+ break;
+ case 0x71: /* fixed, deferred */
+ /* error related to a previous command */
+ ebp = "Fixed format, <<<deferred>>>";
+ len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+ len = (len > sb_len) ? sb_len : len;
+ sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+ break;
+ case 0x72: /* descriptor, current */
+ descriptor_format = true;
+ ebp = "Descriptor format, current";
+ sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+ break;
+ case 0x73: /* descriptor, deferred */
+ descriptor_format = true;
+ ebp = "Descriptor format, <<<deferred>>>";
+ sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+ break;
+ case 0x0:
+ ebp = "Response code: 0x0 (?)";
+ break;
+ default:
+ sg_scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x",
+ ssh.response_code);
+ ebp = ebuff;
+ break;
+ }
+ n += sg_scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp,
+ sg_lib_sense_key_desc[ssh.sense_key]);
+ if (sdat_ovfl)
+ n += sg_scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow "
+ "(SDAT_OVFL)>>>\n", lip);
+ if (descriptor_format) {
+ n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+ sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+ n += sg_get_sense_descriptors_str(lip, sbp, len,
+ cblen - n, cbp + n);
+ } else if ((len > 12) && (0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ /* SAT ATA PASS-THROUGH fixed format */
+ n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+ sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+ n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len,
+ cblen - n, cbp + n);
+ } else if (len > 2) { /* fixed format */
+ if (len > 12)
+ n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
+ sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
+ r = 0;
+ if (strlen(lip) > 0)
+ r += sg_scnpr(b + r, blen - r, "%s", lip);
+ if (len > 6) {
+ info = sg_get_unaligned_be32(sbp + 3);
+ if (valid_info_fld)
+ r += sg_scnpr(b + r, blen - r, " Info fld=0x%x [%u] ",
+ info, info);
+ else if (info > 0)
+ r += sg_scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x "
+ "[%u] ", info, info);
+ } else
+ info = 0;
+ if (sbp[2] & 0xe0) {
+ if (sbp[2] & 0x80)
+ r += sg_scnpr(b + r, blen - r, " FMK");
+ /* current command has read a filemark */
+ if (sbp[2] & 0x40)
+ r += sg_scnpr(b + r, blen - r, " EOM");
+ /* end-of-medium condition exists */
+ if (sbp[2] & 0x20)
+ r += sg_scnpr(b + r, blen - r, " ILI");
+ /* incorrect block length requested */
+ r += sg_scnpr(b + r, blen - r, "\n");
+ } else if (valid_info_fld || (info > 0))
+ r += sg_scnpr(b + r, blen - r, "\n");
+ if ((len >= 14) && sbp[14])
+ r += sg_scnpr(b + r, blen - r, "%s Field replaceable unit "
+ "code: %d\n", lip, sbp[14]);
+ if ((len >= 18) && (sbp[15] & 0x80)) {
+ /* sense key specific decoding */
+ switch (ssh.sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ r += sg_scnpr(b + r, blen - r, "%s Sense Key Specific: "
+ "Error in %s: byte %d", lip,
+ ((sbp[15] & 0x40) ?
+ "Command" : "Data parameters"),
+ sg_get_unaligned_be16(sbp + 16));
+ if (sbp[15] & 0x08)
+ r += sg_scnpr(b + r, blen - r, " bit %d\n",
+ sbp[15] & 0x07);
+ else
+ r += sg_scnpr(b + r, blen - r, "\n");
+ break;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_NOT_READY:
+ progress = sg_get_unaligned_be16(sbp + 16);
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ r += sg_scnpr(b + r, blen - r, "%s Progress indication: "
+ "%d.%02d%%\n", lip, pr, rem);
+ break;
+ case SPC_SK_HARDWARE_ERROR:
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_RECOVERED_ERROR:
+ r += sg_scnpr(b + r, blen - r, "%s Actual retry count: "
+ "0x%02x%02x\n", lip, sbp[16], sbp[17]);
+ break;
+ case SPC_SK_COPY_ABORTED:
+ r += sg_scnpr(b + r, blen - r, "%s Segment pointer: ",
+ lip);
+ r += sg_scnpr(b + r, blen - r, "Relative to start of %s, "
+ "byte %d", ((sbp[15] & 0x20) ?
+ "segment descriptor" : "parameter list"),
+ sg_get_unaligned_be16(sbp + 16));
+ if (sbp[15] & 0x08)
+ r += sg_scnpr(b + r, blen - r, " bit %d\n",
+ sbp[15] & 0x07);
+ else
+ r += sg_scnpr(b + r, blen - r, "\n");
+ break;
+ case SPC_SK_UNIT_ATTENTION:
+ r += sg_scnpr(b + r, blen - r, "%s Unit attention "
+ "condition queue: ", lip);
+ r += sg_scnpr(b + r, blen - r, "overflow flag is %d\n",
+ !!(sbp[15] & 0x1));
+ break;
+ default:
+ r += sg_scnpr(b + r, blen - r, "%s Sense_key: 0x%x "
+ "unexpected\n", lip, ssh.sense_key);
+ break;
+ }
+ }
+ if (r > 0)
+ n += sg_scnpr(cbp + n, cblen - n, "%s", b);
+ } else
+ n += sg_scnpr(cbp + n, cblen - n, "%s fixed descriptor length "
+ "too short, len=%d\n", lip, len);
+ } else { /* unable to normalise sense buffer, something irregular */
+ if (sb_len < 4) { /* Too short */
+ n += sg_scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 "
+ "byte minimum)\n", lip);
+ goto check_raw;
+ }
+ if (0x7f == resp_code) { /* Vendor specific */
+ n += sg_scnpr(cbp + n, cblen - n, "%sVendor specific sense "
+ "buffer, in hex:\n", lip);
+ n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n);
+ return n; /* no need to check raw, just output in hex */
+ }
+ /* non-extended SCSI-1 sense data ?? */
+ r = 0;
+ if (strlen(lip) > 0)
+ r += sg_scnpr(b + r, blen - r, "%s", lip);
+ r += sg_scnpr(b + r, blen - r, "Probably uninitialized data.\n%s "
+ "Try to view as SCSI-1 non-extended sense:\n", lip);
+ r += sg_scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error "
+ "code=%d\n", valid_info_fld, ((sbp[0] >> 4) & 0x7),
+ (sbp[0] & 0xf));
+ if (valid_info_fld)
+ sg_scnpr(b + r, blen - r, "%s lba=0x%x\n", lip,
+ sg_get_unaligned_be24(sbp + 1) & 0x1fffff);
+ n += sg_scnpr(cbp + n, cblen - n, "%s\n", b);
+ }
+check_raw:
+ if (raw_sinfo) {
+ int calculated_len;
+ char z[64];
+
+ n += sg_scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex), "
+ "sb_len=%d", lip, sb_len);
+ if (n >= (cblen - 1))
+ return n;
+ if ((sb_len > 7) && (sbp[0] >= 0x70) && (sbp[0] < 0x74)) {
+ calculated_len = sbp[7] + 8;
+ n += sg_scnpr(cbp + n, cblen - n, ", calculated_len=%d\n",
+ calculated_len);
+ } else {
+ calculated_len = sb_len;
+ n += sg_scnpr(cbp + n, cblen - n, "\n");
+ }
+ if (n >= (cblen - 1))
+ return n;
+
+ sg_scnpr(z, sizeof(z), "%.50s ", lip);
+ n += hex2str(sbp, calculated_len, z, -1, cblen - n, cbp + n);
+ }
+ return n;
+}
+
+/* Print sense information */
+void
+sg_print_sense(const char * leadin, const uint8_t * sbp, int sb_len,
+ bool raw_sinfo)
+{
+ uint32_t pg_sz = sg_get_page_size();
+ char *cp;
+ uint8_t *free_cp;
+
+ cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, false);
+ if (NULL == cp)
+ return;
+ sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp);
+ pr2ws("%s", cp);
+ free(free_cp);
+}
+
+/* This examines exit_status and if an error message is known it is output
+ * as a string to 'b' and true is returned. If 'longer' is true and extra
+ * information is available then it is added to the output. If no error
+ * message is available a null character is output and false is returned.
+ * If exit_status is zero (no error) and 'longer' is true then the string
+ * 'No errors' is output; if 'longer' is false then a null character is
+ * output; in both cases true is returned. If exit_status is negative then
+ * a null character is output and false is returned. All messages are a
+ * single line (less than 80 characters) with no trailing LF. The output
+ * string including the trailing null character is no longer than b_len.
+ * exit_status represents the Unix exit status available after a utility
+ * finishes executing (for whatever reason). */
+bool sg_exit2str(int exit_status, bool longer, int b_len, char *b)
+{
+ const struct sg_value_2names_t * ess = sg_exit_str_arr;
+
+ if ((b_len < 1) || (NULL == b))
+ return false;
+ /* if there is a valid buffer, initialize it to a valid empty string */
+ b[0] = '\0';
+ if (exit_status < 0)
+ return false;
+ else if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status)) {
+ if (longer)
+ goto fini;
+ return true;
+ }
+
+ if ((exit_status > SG_LIB_OS_BASE_ERR) && /* 51 to 96 inclusive */
+ (exit_status < SG_LIB_CAT_MALFORMED)) {
+ snprintf(b, b_len, "%s%s", (longer ? "OS error: " : ""),
+ safe_strerror(exit_status - SG_LIB_OS_BASE_ERR));
+ return true;
+ } else if ((exit_status > 128) && (exit_status < 255)) {
+ snprintf(b, b_len, "Utility stopped/aborted by signal number: %d",
+ exit_status - 128);
+ return true;
+ }
+fini:
+ for ( ; ess->name; ++ess) {
+ if (exit_status == ess->value)
+ break;
+ }
+ if (ess->name) {
+ if (longer && ess->name2)
+ snprintf(b, b_len, "%s, %s", ess->name, ess->name2);
+ else
+ snprintf(b, b_len, "%s", ess->name);
+ return true;
+ }
+ return false;
+}
+
+static bool
+sg_if_can2fp(const char * leadin, int exit_status, FILE * fp)
+{
+ char b[256];
+ const char * s = leadin ? leadin : "";
+
+ if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status))
+ return true; /* don't print anything */
+ else if (sg_exit2str(exit_status, false, sizeof(b), b)) {
+ fprintf(fp, "%s%s\n", s, b);
+ return true;
+ } else
+ return false;
+}
+
+/* This examines exit_status and if an error message is known it is output
+ * to stdout/stderr and true is returned. If no error message is
+ * available nothing is output and false is returned. If exit_status is
+ * zero (no error) nothing is output and true is returned. If exit_status
+ * is negative then nothing is output and false is returned. If leadin is
+ * non-NULL then it is printed before the error message. All messages are
+ * a single line with a trailing LF. */
+bool
+sg_if_can2stdout(const char * leadin, int exit_status)
+{
+ return sg_if_can2fp(leadin, exit_status, stdout);
+}
+
+/* See sg_if_can2stdout() comments */
+bool
+sg_if_can2stderr(const char * leadin, int exit_status)
+{
+ return sg_if_can2fp(leadin, exit_status,
+ sg_warnings_strm ? sg_warnings_strm : stderr);
+}
+
+/* If os_err_num is within bounds then the returned value is 'os_err_num +
+ * SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR is returned. If
+ * os_err_num is 0 then 0 is returned. */
+int
+sg_convert_errno(int os_err_num)
+{
+ if (os_err_num <= 0) {
+ if (os_err_num < 0)
+ return SG_LIB_OS_BASE_ERR;
+ return os_err_num; /* os_err_num of 0 maps to 0 */
+ }
+ if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR))
+ return SG_LIB_OS_BASE_ERR + os_err_num;
+ return SG_LIB_OS_BASE_ERR;
+}
+
+static const char * const bad_sense_cat = "Bad sense category";
+
+/* Yield string associated with sense category. Returns 'b' (or pointer
+ * to "Bad sense category" if 'b' is NULL). If sense_cat unknown then
+ * yield "Sense category: <sense_cat_val>" string. The original 'sense
+ * category' concept has been expanded to most detected errors and is
+ * returned by these utilities as their exit status value (an (unsigned)
+ * 8 bit value where 0 means good (i.e. no errors)). Uses sg_exit2str()
+ * function. */
+const char *
+sg_get_category_sense_str(int sense_cat, int b_len, char * b, int verbose)
+{
+ if (NULL == b)
+ return bad_sense_cat;
+ if (b_len <= 0)
+ return b;
+ if (! sg_exit2str(sense_cat, (verbose > 0), b_len, b)) {
+ int n = sg_scnpr(b, b_len, "Sense category: %d", sense_cat);
+
+ if ((0 == verbose) && (n < (b_len - 1)))
+ sg_scnpr(b + n, b_len - n, ", try '-v' option for more "
+ "information");
+ }
+ return b; /* Note that a valid C string is returned in all cases */
+}
+
+/* See description in sg_lib.h header file */
+bool
+sg_scsi_normalize_sense(const uint8_t * sbp, int sb_len,
+ struct sg_scsi_sense_hdr * sshp)
+{
+ uint8_t resp_code;
+ if (sshp)
+ memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
+ if ((NULL == sbp) || (sb_len < 1))
+ return false;
+ resp_code = 0x7f & sbp[0];
+ if ((resp_code < 0x70) || (resp_code > 0x73))
+ return false;
+ if (sshp) {
+ sshp->response_code = resp_code;
+ if (sshp->response_code >= 0x72) { /* descriptor format */
+ if (sb_len > 1)
+ sshp->sense_key = (0xf & sbp[1]);
+ if (sb_len > 2)
+ sshp->asc = sbp[2];
+ if (sb_len > 3)
+ sshp->ascq = sbp[3];
+ if (sb_len > 7)
+ sshp->additional_length = sbp[7];
+ sshp->byte4 = sbp[4]; /* bit 7: SDAT_OVFL bit */
+ /* sbp[5] and sbp[6] reserved for descriptor format */
+ } else { /* fixed format */
+ if (sb_len > 2)
+ sshp->sense_key = (0xf & sbp[2]);
+ if (sb_len > 7) {
+ sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8);
+ if (sb_len > 12)
+ sshp->asc = sbp[12];
+ if (sb_len > 13)
+ sshp->ascq = sbp[13];
+ }
+ if (sb_len > 6) { /* lower 3 bytes of INFO field */
+ sshp->byte4 = sbp[4];
+ sshp->byte5 = sbp[5];
+ sshp->byte6 = sbp[6];
+ }
+ }
+ }
+ return true;
+}
+
+/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a
+ * less common sense key then return SG_LIB_CAT_SENSE .*/
+int
+sg_err_category_sense(const uint8_t * sbp, int sb_len)
+{
+ struct sg_scsi_sense_hdr ssh;
+
+ if ((sbp && (sb_len > 2)) &&
+ (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) {
+ switch (ssh.sense_key) { /* 0 to 0x1f */
+ case SPC_SK_NO_SENSE:
+ return SG_LIB_CAT_NO_SENSE;
+ case SPC_SK_RECOVERED_ERROR:
+ return SG_LIB_CAT_RECOVERED;
+ case SPC_SK_NOT_READY:
+ if ((0x04 == ssh.asc) && (0x0b == ssh.ascq))
+ return SG_LIB_CAT_STANDBY;
+ if ((0x04 == ssh.asc) && (0x0c == ssh.ascq))
+ return SG_LIB_CAT_UNAVAILABLE;
+ return SG_LIB_CAT_NOT_READY;
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_HARDWARE_ERROR:
+ case SPC_SK_BLANK_CHECK:
+ return SG_LIB_CAT_MEDIUM_HARD;
+ case SPC_SK_UNIT_ATTENTION:
+ return SG_LIB_CAT_UNIT_ATTENTION;
+ /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */
+ case SPC_SK_ILLEGAL_REQUEST:
+ if ((0x20 == ssh.asc) && (0x0 == ssh.ascq))
+ return SG_LIB_CAT_INVALID_OP;
+ else if ((0x21 == ssh.asc) && (0x0 == ssh.ascq))
+ return SG_LIB_LBA_OUT_OF_RANGE;
+ else
+ return SG_LIB_CAT_ILLEGAL_REQ;
+ break;
+ case SPC_SK_ABORTED_COMMAND:
+ if (0x10 == ssh.asc)
+ return SG_LIB_CAT_PROTECTION;
+ else
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ case SPC_SK_MISCOMPARE:
+ return SG_LIB_CAT_MISCOMPARE;
+ case SPC_SK_DATA_PROTECT:
+ return SG_LIB_CAT_DATA_PROTECT;
+ case SPC_SK_COPY_ABORTED:
+ return SG_LIB_CAT_COPY_ABORTED;
+ case SPC_SK_COMPLETED:
+ case SPC_SK_VOLUME_OVERFLOW:
+ return SG_LIB_CAT_SENSE;
+ default:
+ ; /* reserved and vendor specific sense keys fall through */
+ }
+ }
+ return SG_LIB_CAT_SENSE;
+}
+
+/* Beware: gives wrong answer for variable length command (opcode=0x7f) */
+int
+sg_get_command_size(uint8_t opcode)
+{
+ switch ((opcode >> 5) & 0x7) {
+ case 0:
+ return 6;
+ case 3: case 5:
+ return 12;
+ case 4:
+ return 16;
+ default: /* 1, 2, 6, 7 */
+ return 10;
+ }
+}
+
+void
+sg_get_command_name(const uint8_t * cdbp, int peri_type, int buff_len,
+ char * buff)
+{
+ int service_action;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return;
+ }
+ if (NULL == cdbp) {
+ sg_scnpr(buff, buff_len, "%s", "<null> command pointer");
+ return;
+ }
+ service_action = (SG_VARIABLE_LENGTH_CMD == cdbp[0]) ?
+ sg_get_unaligned_be16(cdbp + 8) : (cdbp[1] & 0x1f);
+ sg_get_opcode_sa_name(cdbp[0], service_action, peri_type, buff_len, buff);
+}
+
+struct op_code2sa_t {
+ int op_code;
+ int pdt_s;
+ struct sg_lib_value_name_t * arr;
+ const char * prefix;
+};
+
+static struct op_code2sa_t op_code2sa_arr[] = {
+ {SG_VARIABLE_LENGTH_CMD, PDT_ALL, sg_lib_variable_length_arr, NULL},
+ {SG_MAINTENANCE_IN, PDT_ALL, sg_lib_maint_in_arr, NULL},
+ {SG_MAINTENANCE_OUT, PDT_ALL, sg_lib_maint_out_arr, NULL},
+ {SG_SERVICE_ACTION_IN_12, PDT_ALL, sg_lib_serv_in12_arr, NULL},
+ {SG_SERVICE_ACTION_OUT_12, PDT_ALL, sg_lib_serv_out12_arr, NULL},
+ {SG_SERVICE_ACTION_IN_16, PDT_ALL, sg_lib_serv_in16_arr, NULL},
+ {SG_SERVICE_ACTION_OUT_16, PDT_ALL, sg_lib_serv_out16_arr, NULL},
+ {SG_SERVICE_ACTION_BIDI, PDT_ALL, sg_lib_serv_bidi_arr, NULL},
+ {SG_PERSISTENT_RESERVE_IN, PDT_ALL, sg_lib_pr_in_arr,
+ "Persistent reserve in"},
+ {SG_PERSISTENT_RESERVE_OUT, PDT_ALL, sg_lib_pr_out_arr,
+ "Persistent reserve out"},
+ {SG_3PARTY_COPY_OUT, PDT_ALL, sg_lib_xcopy_sa_arr, NULL},
+ {SG_3PARTY_COPY_IN, PDT_ALL, sg_lib_rec_copy_sa_arr, NULL},
+ {SG_READ_BUFFER, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(10)"},
+ {SG_READ_BUFFER_16, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(16)"},
+ {SG_READ_ATTRIBUTE, PDT_ALL, sg_lib_read_attr_arr, "Read attribute"},
+ {SG_READ_POSITION, PDT_TAPE, sg_lib_read_pos_arr, "Read position"},
+ {SG_SANITIZE, PDT_DISK_ZBC, sg_lib_sanitize_sa_arr, "Sanitize"},
+ {SG_WRITE_BUFFER, PDT_ALL, sg_lib_write_buff_arr, "Write buffer"},
+ {SG_ZONING_IN, PDT_DISK_ZBC, sg_lib_zoning_in_arr, NULL},
+ {SG_ZONING_OUT, PDT_DISK_ZBC, sg_lib_zoning_out_arr, NULL},
+ {0xffff, -1, NULL, NULL},
+};
+
+void
+sg_get_opcode_sa_name(uint8_t cmd_byte0, int service_action,
+ int peri_type, int buff_len, char * buff)
+{
+ int d_pdt;
+ const struct sg_lib_value_name_t * vnp;
+ const struct op_code2sa_t * osp;
+ char b[80];
+
+ if ((NULL == buff) || (buff_len < 1))
+ return;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return;
+ }
+
+ if (peri_type < 0)
+ peri_type = 0;
+ d_pdt = sg_lib_pdt_decay(peri_type);
+ for (osp = op_code2sa_arr; osp->arr; ++osp) {
+ if ((int)cmd_byte0 == osp->op_code) {
+ if (sg_pdt_s_eq(osp->pdt_s, d_pdt)) {
+ vnp = get_value_name(osp->arr, service_action, peri_type);
+ if (vnp) {
+ if (osp->prefix)
+ sg_scnpr(buff, buff_len, "%s, %s", osp->prefix,
+ vnp->name);
+ else
+ sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else {
+ sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b);
+ sg_scnpr(buff, buff_len, "%s service action=0x%x", b,
+ service_action);
+ }
+ } else
+ sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+ return;
+ }
+ }
+ sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
+}
+
+void
+sg_get_opcode_name(uint8_t cmd_byte0, int peri_type, int buff_len,
+ char * buff)
+{
+ const struct sg_lib_value_name_t * vnp;
+ int grp;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return;
+ }
+ if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) {
+ sg_scnpr(buff, buff_len, "%s", "Variable length");
+ return;
+ }
+ grp = (cmd_byte0 >> 5) & 0x7;
+ switch (grp) {
+ case 0:
+ case 1:
+ case 2:
+ case 4:
+ case 5:
+ vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type);
+ if (vnp)
+ sg_scnpr(buff, buff_len, "%s", vnp->name);
+ else
+ sg_scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
+ break;
+ case 3:
+ sg_scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
+ break;
+ case 6:
+ case 7:
+ sg_scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
+ break;
+ }
+}
+
+/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte
+ * command) of command. Gets Admin NVMe command name if 'admin' is true
+ * (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name
+ * (e.g. opcode=0 -> Flush). Returns 'buff'. */
+char *
+sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len,
+ char * buff)
+{
+ const struct sg_lib_simple_value_name_t * vnp = admin ?
+ sg_lib_nvme_admin_cmd_arr : sg_lib_nvme_nvm_cmd_arr;
+
+ if ((NULL == buff) || (buff_len < 1))
+ return buff;
+ else if (1 == buff_len) {
+ buff[0] = '\0';
+ return buff;
+ }
+ for ( ; vnp->name; ++vnp) {
+ if (cmd_byte0 == (uint8_t)vnp->value) {
+ snprintf(buff, buff_len, "%s", vnp->name);
+ return buff;
+ }
+ }
+ if (admin) {
+ if (cmd_byte0 >= 0xc0)
+ snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
+ cmd_byte0);
+ else if (cmd_byte0 >= 0x80)
+ snprintf(buff, buff_len, "Command set specific opcode: 0x%x",
+ cmd_byte0);
+ else
+ snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
+ } else { /* NVM (non-Admin) command set */
+ if (cmd_byte0 >= 0x80)
+ snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
+ cmd_byte0);
+ else
+ snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
+ }
+ return buff;
+}
+
+/* Iterates to next designation descriptor in the device identification
+ * VPD page. The 'initial_desig_desc' should point to start of first
+ * descriptor with 'page_len' being the number of valid bytes in that
+ * and following descriptors. To start, 'off' should point to a negative
+ * value, thereafter it should point to the value yielded by the previous
+ * call. If 0 returned then 'initial_desig_desc + *off' should be a valid
+ * descriptor; returns -1 if normal end condition and -2 for an abnormal
+ * termination. Matches association, designator_type and/or code_set when
+ * any of those values are greater than or equal to zero. */
+int
+sg_vpd_dev_id_iter(const uint8_t * initial_desig_desc, int page_len,
+ int * off, int m_assoc, int m_desig_type, int m_code_set)
+{
+ bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0));
+ int k = *off;
+ const uint8_t * bp = initial_desig_desc;
+
+ while ((k + 3) < page_len) {
+ k = (k < 0) ? 0 : (k + bp[k + 3] + 4);
+ if ((k + 4) > page_len)
+ break;
+ if (fltr) {
+ if (m_code_set >= 0) {
+ if ((bp[k] & 0xf) != m_code_set)
+ continue;
+ }
+ if (m_assoc >= 0) {
+ if (((bp[k + 1] >> 4) & 0x3) != m_assoc)
+ continue;
+ }
+ if (m_desig_type >= 0) {
+ if ((bp[k + 1] & 0xf) != m_desig_type)
+ continue;
+ }
+ }
+ *off = k;
+ return 0;
+ }
+ return (k == page_len) ? -1 : -2;
+}
+
+static const char * sg_sfs_spc_reserved = "SPC Reserved";
+static const char * sg_sfs_sbc_reserved = "SBC Reserved";
+static const char * sg_sfs_ssc_reserved = "SSC Reserved";
+static const char * sg_sfs_zbc_reserved = "ZBC Reserved";
+static const char * sg_sfs_reserved = "Reserved";
+
+/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
+ * returns pointer to string (same as 'buff') associated with 'sfs_code'.
+ * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
+ * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
+ * then where it points is set to true if a match is found else it is set to
+ * false. If 'buff' is not NULL then in the case of a match a descriptive
+ * string is written to 'buff' while if there is not a not then a string
+ * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
+ * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
+ * Example:
+ * char b[64];
+ * ...
+ * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
+ */
+const char *
+sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff,
+ bool * foundp, int verbose)
+{
+ const struct sg_lib_value_name_t * vnp = NULL;
+ int n = 0;
+ int my_pdt;
+
+ if ((NULL == buff) || (buff_len < 1)) {
+ if (foundp)
+ *foundp = false;
+ return NULL;
+ } else if (1 == buff_len) {
+ buff[0] = '\0';
+ if (foundp)
+ *foundp = false;
+ return NULL;
+ }
+ my_pdt = ((peri_type < -1) || (peri_type > PDT_MAX)) ? -2 : peri_type;
+ vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt);
+ if (vnp && (-2 != my_pdt)) {
+ if (! sg_pdt_s_eq(my_pdt, vnp->peri_dev_type))
+ vnp = NULL; /* shouldn't really happen */
+ }
+ if (foundp)
+ *foundp = vnp ? true : false;
+ if (sfs_code < 0x100) { /* SPC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "SPC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved);
+ } else if (sfs_code < 0x200) { /* SBC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "SBC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved);
+ } else if (sfs_code < 0x300) { /* SSC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "SSC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved);
+ } else if (sfs_code < 0x400) { /* ZBC Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "ZBC %s", vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved);
+ } else { /* Other SCSI Feature Sets */
+ if (vnp) {
+ if (verbose)
+ n += sg_scnpr(buff, buff_len, "[unrecognized PDT] %s",
+ vnp->name);
+ else
+ n += sg_scnpr(buff, buff_len, "%s", vnp->name);
+ } else
+ n += sg_scnpr(buff, buff_len, "%s", sg_sfs_reserved);
+
+ }
+ if (verbose > 4)
+ pr2ws("%s: length of returned string (n) %d\n", __func__, n);
+ return buff;
+}
+
+/* This is a heuristic that takes into account the command bytes and length
+ * to decide whether the presented unstructured sequence of bytes could be
+ * a SCSI command. If so it returns true otherwise false. Vendor specific
+ * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
+ * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
+ * only SCSI commands considered above 16 bytes of length are the Variable
+ * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e).
+ * Both have an inbuilt length field which can be cross checked with clen.
+ * No NVMe commands (64 bytes long plus some extra added by some OSes) have
+ * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
+ * structures that are sent across the wire. The FIS register structure is
+ * used to move a command from a SATA host to device, but the ATA 'command'
+ * is not the first byte. So it is harder to say what will happen if a
+ * FIS structure is presented as a SCSI command, hopefully there is a low
+ * probability this function will yield true in that case. */
+bool
+sg_is_scsi_cdb(const uint8_t * cdbp, int clen)
+{
+ uint8_t opcode;
+ uint8_t top3bits;
+
+ if (clen < 6)
+ return false;
+ opcode = cdbp[0];
+ top3bits = opcode >> 5;
+ if (0x3 == top3bits) {
+ int ilen, sa;
+
+ if ((clen < 12) || (clen % 4))
+ return false; /* must be modulo 4 and 12 or more bytes */
+ switch (opcode) {
+ case 0x7e: /* Extended cdb (XCDB) */
+ ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+ return (ilen == clen);
+ case 0x7f: /* Variable Length cdb */
+ ilen = 8 + cdbp[7];
+ sa = sg_get_unaligned_be16(cdbp + 8);
+ /* service action (sa) 0x0 is reserved */
+ return ((ilen == clen) && sa);
+ default:
+ return false;
+ }
+ } else if (clen <= 16) {
+ switch (clen) {
+ case 6:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x0 == top3bits); /* 6 byte cdb */
+ case 10:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
+ case 16:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x4 == top3bits); /* 16 byte cdb */
+ case 12:
+ if (top3bits > 0x5) /* vendor */
+ return true;
+ return (0x5 == top3bits); /* 12 byte cdb */
+ default:
+ return false;
+ }
+ }
+ /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
+ * opcode > 0x7f). */
+ return false;
+}
+
+/* Yield string associated with NVMe command status value in sct_sc. It
+ * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
+ * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
+ * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
+ * a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
+ * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
+char *
+sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b)
+{
+ int k;
+ uint16_t s = 0x3ff & sct_sc;
+ const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+
+ if ((b_len <= 0) || (NULL == b))
+ return b;
+ else if (1 == b_len) {
+ b[0] = '\0';
+ return b;
+ }
+ for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+ if (s == (uint16_t)vp->value) {
+ strncpy(b, vp->name, b_len);
+ b[b_len - 1] = '\0';
+ return b;
+ }
+ }
+ if (k >= 1000)
+ pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+ __func__);
+ snprintf(b, b_len, "Reserved [0x%x]", sct_sc);
+ return b;
+}
+
+/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status,
+ * sense_key, asc and ascq tuple. If successful returns true and writes to
+ * non-NULL pointer arguments; otherwise returns false. */
+bool
+sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
+ uint8_t * asc_p, uint8_t * ascq_p)
+{
+ int k, ind;
+ uint16_t s = 0x3ff & sct_sc;
+ struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
+ struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr;
+
+ for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
+ if (s == (uint16_t)vp->value)
+ break;
+ }
+ if (k >= 1000) {
+ pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
+ __func__);
+ return false;
+ }
+ if (NULL == vp->name)
+ return false;
+ ind = vp->peri_dev_type;
+
+
+ for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp)
+ ; /* count entries for valid index range */
+ if (k >= 1000) {
+ pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n",
+ __func__);
+ return false;
+ } else if (ind >= k)
+ return false;
+
+ mp = sg_lib_scsi_status_sense_arr + ind;
+ if (status_p)
+ *status_p = mp->t1;
+ if (sk_p)
+ *sk_p = mp->t2;
+ if (asc_p)
+ *asc_p = mp->t3;
+ if (ascq_p)
+ *ascq_p = mp->t4;
+ return true;
+}
+
+/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status
+ * field. Assumes descriptor (i.e. not fixed) sense. Assumes sbp has room. */
+void
+sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc)
+{
+ int len = sbp[7] + 8;
+
+ sbp[len] = 0xde; /* vendor specific descriptor type */
+ sbp[len + 1] = 6; /* descriptor is 8 bytes long */
+ memset(sbp + len + 2, 0, 6);
+ if (dnr)
+ sbp[len + 5] = 0x80;
+ if (more)
+ sbp[len + 5] |= 0x40;
+ sg_put_unaligned_be16(sct_sc, sbp + len + 6);
+ sbp[7] += 8;
+}
+
+/* Build minimum sense buffer, either descriptor type (desc=true) or fixed
+ * type (desc=false). Assume sbp has enough room (8 or 14 bytes
+ * respectively). sbp should have room for 32 or 18 bytes respectively */
+void
+sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey, uint8_t asc,
+ uint8_t ascq)
+{
+ if (desc) {
+ sbp[0] = 0x72; /* descriptor, current */
+ sbp[1] = skey;
+ sbp[2] = asc;
+ sbp[3] = ascq;
+ sbp[7] = 0;
+ } else {
+ sbp[0] = 0x70; /* fixed, current */
+ sbp[2] = skey;
+ sbp[7] = 0xa; /* Assumes length is 18 bytes */
+ sbp[12] = asc;
+ sbp[13] = ascq;
+ }
+}
+
+/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
+ * Allows for situation in which strerror() is given a wild value (or the
+ * C library is incomplete) and returns NULL. Still not thread safe.
+ */
+
+static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
+ 'e', 'r', 'r', 'n', 'o', ':', ' ', 0};
+
+char *
+safe_strerror(int errnum)
+{
+ char * errstr;
+
+ if (errnum < 0)
+ errnum = -errnum;
+ errstr = strerror(errnum);
+ if (NULL == errstr) {
+ size_t len = strlen(safe_errbuf);
+
+ sg_scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum);
+ return safe_errbuf;
+ }
+ return errstr;
+}
+
+static int
+trimTrailingSpaces(char * b)
+{
+ int n = strlen(b);
+
+ while ((n > 0) && (' ' == b[n - 1]))
+ b[--n] = '\0';
+ return n;
+}
+
+/* Read binary starting at 'str' for 'len' bytes and output as ASCII
+ * hexadecinal into file pointer (fp). 16 bytes per line are output with an
+ * additional space between 8th and 9th byte on each line (for readability).
+ * 'no_ascii' selects one of 3 output format types:
+ * > 0 each line has address then up to 16 ASCII-hex bytes
+ * = 0 in addition, the bytes are listed in ASCII to the right
+ * < 0 only the ASCII-hex bytes are listed (i.e. without address) */
+void
+dStrHexFp(const char* str, int len, int no_ascii, FILE * fp)
+{
+ const char * p = str;
+ const char * formatstr;
+ uint8_t c;
+ char buff[82];
+ int a = 0;
+ int bpstart = 5;
+ const int cpstart = 60;
+ int cpos = cpstart;
+ int bpos = bpstart;
+ int i, k, blen;
+
+ if (len <= 0)
+ return;
+ blen = (int)sizeof(buff);
+ if (0 == no_ascii) /* address at left and ASCII at right */
+ formatstr = "%.76s\n";
+ else /* previously when > 0 str was "%.58s\n" */
+ formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */
+ memset(buff, ' ', 80);
+ buff[80] = '\0';
+ if (no_ascii < 0) {
+ bpstart = 0;
+ bpos = bpstart;
+ for (k = 0; k < len; k++) {
+ c = *p++;
+ if (bpos == (bpstart + (8 * 3)))
+ bpos++;
+ sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if ((k > 0) && (0 == ((k + 1) % 16))) {
+ trimTrailingSpaces(buff);
+ fprintf(fp, formatstr, buff);
+ bpos = bpstart;
+ memset(buff, ' ', 80);
+ } else
+ bpos += 3;
+ }
+ if (bpos > bpstart) {
+ buff[bpos + 2] = '\0';
+ trimTrailingSpaces(buff);
+ fprintf(fp, "%s\n", buff);
+ }
+ return;
+ }
+ /* no_ascii>=0, start each line with address (offset) */
+ k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+
+ for (i = 0; i < len; i++) {
+ c = *p++;
+ bpos += 3;
+ if (bpos == (bpstart + (9 * 3)))
+ bpos++;
+ sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if (no_ascii)
+ buff[cpos++] = ' ';
+ else {
+ if (! my_isprint(c))
+ c = '.';
+ buff[cpos++] = c;
+ }
+ if (cpos > (cpstart + 15)) {
+ if (no_ascii)
+ trimTrailingSpaces(buff);
+ fprintf(fp, formatstr, buff);
+ bpos = bpstart;
+ cpos = cpstart;
+ a += 16;
+ memset(buff, ' ', 80);
+ k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ if (cpos > cpstart) {
+ buff[cpos] = '\0';
+ if (no_ascii)
+ trimTrailingSpaces(buff);
+ fprintf(fp, "%s\n", buff);
+ }
+}
+
+void
+dStrHex(const char* str, int len, int no_ascii)
+{
+ dStrHexFp(str, len, no_ascii, stdout);
+}
+
+void
+dStrHexErr(const char* str, int len, int no_ascii)
+{
+ dStrHexFp(str, len, no_ascii,
+ (sg_warnings_strm ? sg_warnings_strm : stderr));
+}
+
+#define DSHS_LINE_BLEN 160 /* maximum characters per line */
+#define DSHS_BPL 16 /* bytes per line */
+
+/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space separated)
+ * to 'b' not to exceed 'b_len' characters. Each line starts with 'leadin'
+ * (NULL for no leadin) and there are 16 bytes per line with an extra space
+ * between the 8th and 9th bytes. 'oformat' is 0 for repeat in printable ASCII
+ * ('.' for non printable chars) to right of each line; 1 don't (so just
+ * output ASCII hex). If 'oformat' is 2 output same as 1 but any LFs are
+ * replaced by space (and trailing spaces are trimmed). Note that an address
+ * is not printed on each line preceding the hex data. Returns number of bytes
+ * written to 'b' excluding the trailing '\0'. The only difference between
+ * dStrHexStr() and hex2str() is the type of the first argument. */
+int
+dStrHexStr(const char * str, int len, const char * leadin, int oformat,
+ int b_len, char * b)
+{
+ bool want_ascii = (0 == oformat);
+ int bpstart, bpos, k, n, prior_ascii_len;
+ char buff[DSHS_LINE_BLEN + 2]; /* allow for trailing null */
+ char a[DSHS_BPL + 1]; /* printable ASCII bytes or '.' */
+ const char * p = str;
+ const char * lf_or = (oformat > 1) ? " " : "\n";
+
+ if (len <= 0) {
+ if (b_len > 0)
+ b[0] = '\0';
+ return 0;
+ }
+ if (b_len <= 0)
+ return 0;
+ if (want_ascii) {
+ memset(a, ' ', DSHS_BPL);
+ a[DSHS_BPL] = '\0';
+ }
+ n = 0;
+ bpstart = 0;
+ if (leadin) {
+ if (oformat > 1)
+ n += sg_scnpr(b + n, b_len - n, "%s", leadin);
+ else {
+ bpstart = strlen(leadin);
+ /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */
+ if (bpstart > (DSHS_LINE_BLEN - 70))
+ bpstart = DSHS_LINE_BLEN - 70;
+ }
+ }
+ bpos = bpstart;
+ prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1;
+ memset(buff, ' ', DSHS_LINE_BLEN);
+ buff[DSHS_LINE_BLEN] = '\0';
+ if (bpstart > 0)
+ memcpy(buff, leadin, bpstart);
+ for (k = 0; k < len; k++) {
+ uint8_t c = *p++;
+
+ if (bpos == (bpstart + ((DSHS_BPL / 2) * 3)))
+ bpos++; /* for extra space in middle of each line's hex */
+ sg_scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x",
+ (int)(uint8_t)c);
+ buff[bpos + 2] = ' ';
+ if (want_ascii)
+ a[k % DSHS_BPL] = my_isprint(c) ? c : '.';
+ if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) {
+ trimTrailingSpaces(buff);
+ if (want_ascii) {
+ n += sg_scnpr(b + n, b_len - n, "%-*s %s\n",
+ prior_ascii_len, buff, a);
+ memset(a, ' ', DSHS_BPL);
+ } else
+ n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
+ if (n >= (b_len - 1))
+ goto fini;
+ memset(buff, ' ', DSHS_LINE_BLEN);
+ bpos = bpstart;
+ if (bpstart > 0)
+ memcpy(buff, leadin, bpstart);
+ } else
+ bpos += 3;
+ }
+ if (bpos > bpstart) {
+ trimTrailingSpaces(buff);
+ if (want_ascii)
+ n += sg_scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len,
+ buff, a);
+ else
+ n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
+ }
+fini:
+ if (oformat > 1)
+ n = trimTrailingSpaces(b);
+ return n;
+}
+
+void
+hex2stdout(const uint8_t * b_str, int len, int no_ascii)
+{
+ dStrHex((const char *)b_str, len, no_ascii);
+}
+
+void
+hex2stderr(const uint8_t * b_str, int len, int no_ascii)
+{
+ dStrHexErr((const char *)b_str, len, no_ascii);
+}
+
+int
+hex2str(const uint8_t * b_str, int len, const char * leadin, int oformat,
+ int b_len, char * b)
+{
+ return dStrHexStr((const char *)b_str, len, leadin, oformat, b_len, b);
+}
+
+void
+hex2fp(const uint8_t * b_str, int len, const char * leadin, int oformat,
+ FILE * fp)
+{
+ int k, num;
+ char b[800]; /* allow for 4 lines of 16 bytes (in hex) each */
+
+ if (leadin && (strlen(leadin) > 118)) {
+ fprintf(fp, ">>> leadin parameter is too large\n");
+ return;
+ }
+ for (k = 0; k < len; k += num) {
+ num = ((k + 64) < len) ? 64 : (len - k);
+ hex2str(b_str + k, num, leadin, oformat, sizeof(b), b);
+ fprintf(fp, "%s", b);
+ }
+}
+
+/* Returns true when executed on big endian machine; else returns false.
+ * Useful for displaying ATA identify words (which need swapping on a
+ * big endian machine). */
+bool
+sg_is_big_endian()
+{
+ union u_t {
+ uint16_t s;
+ uint8_t c[sizeof(uint16_t)];
+ } u;
+
+ u.s = 0x0102;
+ return (u.c[0] == 0x01); /* The lowest address contains
+ the most significant byte */
+}
+
+bool
+sg_all_zeros(const uint8_t * bp, int b_len)
+{
+ if ((NULL == bp) || (b_len <= 0))
+ return false;
+ for (--b_len; b_len >= 0; --b_len) {
+ if (0x0 != bp[b_len])
+ return false;
+ }
+ return true;
+}
+
+bool
+sg_all_ffs(const uint8_t * bp, int b_len)
+{
+ if ((NULL == bp) || (b_len <= 0))
+ return false;
+ for (--b_len; b_len >= 0; --b_len) {
+ if (0xff != bp[b_len])
+ return false;
+ }
+ return true;
+}
+
+static uint16_t
+swapb_uint16(uint16_t u)
+{
+ uint16_t r;
+
+ r = (u >> 8) & 0xff;
+ r |= ((u & 0xff) << 8);
+ return r;
+}
+
+/* Note the ASCII-hex output goes to stdout. [Most other output from functions
+ * in this file go to sg_warnings_strm (default stderr).]
+ * 'no_ascii' allows for 3 output types:
+ * > 0 each line has address then up to 8 ASCII-hex 16 bit words
+ * = 0 in addition, the ASCI bytes pairs are listed to the right
+ * = -1 only the ASCII-hex words are listed (i.e. without address)
+ * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin"
+ * < -2 same as -1
+ * If 'swapb' is true then bytes in each word swapped. Needs to be set
+ * for ATA IDENTIFY DEVICE response on big-endian machines. */
+void
+dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb)
+{
+ const uint16_t * p = words;
+ uint16_t c;
+ char buff[82];
+ uint8_t upp, low;
+ int a = 0;
+ const int bpstart = 3;
+ const int cpstart = 52;
+ int cpos = cpstart;
+ int bpos = bpstart;
+ int i, k, blen;
+
+ if (num <= 0)
+ return;
+ blen = (int)sizeof(buff);
+ memset(buff, ' ', 80);
+ buff[80] = '\0';
+ if (no_ascii < 0) {
+ for (k = 0; k < num; k++) {
+ c = *p++;
+ if (swapb)
+ c = swapb_uint16(c);
+ bpos += 5;
+ sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)c);
+ buff[bpos + 4] = ' ';
+ if ((k > 0) && (0 == ((k + 1) % 8))) {
+ if (-2 == no_ascii)
+ printf("%.39s\n", buff +8);
+ else
+ printf("%.47s\n", buff);
+ bpos = bpstart;
+ memset(buff, ' ', 80);
+ }
+ }
+ if (bpos > bpstart) {
+ if (-2 == no_ascii)
+ printf("%.39s\n", buff +8);
+ else
+ printf("%.47s\n", buff);
+ }
+ return;
+ }
+ /* no_ascii>=0, start each line with address (offset) */
+ k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+
+ for (i = 0; i < num; i++) {
+ c = *p++;
+ if (swapb)
+ c = swapb_uint16(c);
+ bpos += 5;
+ sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)c);
+ buff[bpos + 4] = ' ';
+ if (no_ascii) {
+ buff[cpos++] = ' ';
+ buff[cpos++] = ' ';
+ buff[cpos++] = ' ';
+ } else {
+ upp = (c >> 8) & 0xff;
+ low = c & 0xff;
+ if (! my_isprint(upp))
+ upp = '.';
+ buff[cpos++] = upp;
+ if (! my_isprint(low))
+ low = '.';
+ buff[cpos++] = low;
+ buff[cpos++] = ' ';
+ }
+ if (cpos > (cpstart + 23)) {
+ printf("%.76s\n", buff);
+ bpos = bpstart;
+ cpos = cpstart;
+ a += 8;
+ memset(buff, ' ', 80);
+ k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ if (cpos > cpstart)
+ printf("%.76s\n", buff);
+}
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and
+ * tabs; accept comma, hyphen, space, tab and hash as terminator.
+ * Handles zero and positive values up to 2**31-1 .
+ * Experimental: left argument (must in with hexadecimal digit) added
+ * to, or multiplied, by right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
+int
+sg_get_num(const char * buf)
+{
+ bool is_hex = false;
+ int res, num, n, len;
+ unsigned int unum;
+ char * cp;
+ const char * b;
+ const char * b2p;
+ char c = 'c';
+ char c2 = '\0'; /* keep static checker happy */
+ char c3 = '\0'; /* keep static checker happy */
+ char lb[16];
+
+ if ((NULL == buf) || ('\0' == buf[0]))
+ return -1;
+ len = strlen(buf);
+ n = strspn(buf, " \t");
+ if (n > 0) {
+ if (n == len)
+ return -1;
+ buf += n;
+ len -= n;
+ }
+ /* following hack to keep C++ happy */
+ cp = strpbrk((char *)buf, " \t,#-");
+ if (cp) {
+ len = cp - buf;
+ n = (int)sizeof(lb) - 1;
+ len = (len < n) ? len : n;
+ memcpy(lb, buf, len);
+ lb[len] = '\0';
+ b = lb;
+ } else
+ b = buf;
+
+ b2p = b;
+ if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+ res = sscanf(b + 2, "%x%c", &unum, &c);
+ num = unum;
+ is_hex = true;
+ b2p = b + 2;
+ } else if ('H' == toupper((int)b[len - 1])) {
+ res = sscanf(b, "%x", &unum);
+ num = unum;
+ } else
+ res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3);
+
+ if (res < 1)
+ return -1;
+ else if (1 == res)
+ return num;
+ else {
+ c = toupper((int)c);
+ if (is_hex) {
+ if (! ((c == '+') || (c == 'X')))
+ return -1;
+ }
+ if (res > 2)
+ c2 = toupper((int)c2);
+ if (res > 3)
+ c3 = toupper((int)c3);
+
+ switch (c) {
+ case 'C':
+ return num;
+ case 'W':
+ return num * 2;
+ case 'B':
+ return num * 512;
+ case 'K':
+ if (2 == res)
+ return num * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1024;
+ return -1;
+ case 'M':
+ if (2 == res)
+ return num * 1048576;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1048576;
+ return -1;
+ case 'G':
+ if (2 == res)
+ return num * 1073741824;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1073741824;
+ return -1;
+ case 'X': /* experimental: multiplication */
+ /* left argument must end with hexadecimal digit */
+ cp = (char *)strchr(b2p, 'x');
+ if (NULL == cp)
+ cp = (char *)strchr(b2p, 'X');
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (-1 != n)
+ return num * n;
+ }
+ return -1;
+ case '+': /* experimental: addition */
+ /* left argument must end with hexadecimal digit */
+ cp = (char *)strchr(b2p, '+');
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (-1 != n)
+ return num + n;
+ }
+ return -1;
+ default:
+ pr2ws("unrecognized multiplier\n");
+ return -1;
+ }
+ }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. */
+int
+sg_get_num_nomult(const char * buf)
+{
+ int res, len, num;
+ unsigned int unum;
+ char * commap;
+
+ if ((NULL == buf) || ('\0' == buf[0]))
+ return -1;
+ len = strlen(buf);
+ commap = (char *)strchr(buf + 1, ',');
+ if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+ res = sscanf(buf + 2, "%x", &unum);
+ num = unum;
+ } else if (commap && ('H' == toupper((int)*(commap - 1)))) {
+ res = sscanf(buf, "%x", &unum);
+ num = unum;
+ } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) {
+ res = sscanf(buf, "%x", &unum);
+ num = unum;
+ } else
+ res = sscanf(buf, "%d", &num);
+ if (1 == res)
+ return num;
+ else
+ return -1;
+}
+
+/* If the number in 'buf' can not be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X), hex suffix
+ * (h or H), or a decimal multiplier suffix (as per GNU's dd (since 2002:
+ * SI and IEC 60027-2)). Main (SI) multipliers supported: K, M, G, T, P
+ * and E. Ignore leading spaces and tabs; accept comma, hyphen, space, tab
+ * and hash as terminator. Handles zero and positive values up to 2**63-1 .
+ * Experimental: left argument (must in with hexadecimal digit) added
+ * to, or multiplied by right argument. No embedded spaces.
+ * Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
+int64_t
+sg_get_llnum(const char * buf)
+{
+ bool is_hex = false;
+ int res, len, n;
+ int64_t num, ll;
+ uint64_t unum;
+ char * cp;
+ const char * b;
+ const char * b2p;
+ char c = 'c';
+ char c2 = '\0'; /* keep static checker happy */
+ char c3 = '\0'; /* keep static checker happy */
+ char lb[32];
+
+ if ((NULL == buf) || ('\0' == buf[0]))
+ return -1LL;
+ len = strlen(buf);
+ n = strspn(buf, " \t");
+ if (n > 0) {
+ if (n == len)
+ return -1LL;
+ buf += n;
+ len -= n;
+ }
+ /* following cast hack to keep C++ happy */
+ cp = strpbrk((char *)buf, " \t,#-");
+ if (cp) {
+ len = cp - buf;
+ n = (int)sizeof(lb) - 1;
+ len = (len < n) ? len : n;
+ memcpy(lb, buf, len);
+ lb[len] = '\0';
+ b = lb;
+ } else
+ b = buf;
+
+ b2p = b;
+ if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+ res = sscanf(b + 2, "%" SCNx64 "%c", &unum, &c);
+ num = unum;
+ is_hex = true;
+ b2p = b + 2;
+ } else if ('H' == toupper((int)b[len - 1])) {
+ res = sscanf(b, "%" SCNx64 , &unum);
+ num = unum;
+ } else
+ res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
+
+ if (res < 1)
+ return -1LL;
+ else if (1 == res)
+ return num;
+ else {
+ c = toupper((int)c);
+ if (is_hex) {
+ if (! ((c == '+') || (c == 'X')))
+ return -1;
+ }
+ if (res > 2)
+ c2 = toupper((int)c2);
+ if (res > 3)
+ c3 = toupper((int)c3);
+
+ switch (c) {
+ case 'C':
+ return num;
+ case 'W':
+ return num * 2;
+ case 'B':
+ return num * 512;
+ case 'K': /* kilo or kibi */
+ if (2 == res)
+ return num * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1024; /* KiB */
+ return -1LL;
+ case 'M': /* mega or mebi */
+ if (2 == res)
+ return num * 1048576; /* M */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000; /* MB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1048576; /* MiB */
+ return -1LL;
+ case 'G': /* giga or gibi */
+ if (2 == res)
+ return num * 1073741824; /* G */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000; /* GB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1073741824; /* GiB */
+ return -1LL;
+ case 'T': /* tera or tebi */
+ if (2 == res)
+ return num * 1099511627776LL; /* T */
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL; /* TB */
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL; /* TiB */
+ return -1LL;
+ case 'P': /* peta or pebi */
+ if (2 == res)
+ return num * 1099511627776LL * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL * 1024;
+ return -1LL;
+ case 'E': /* exa or exbi */
+ if (2 == res)
+ return num * 1099511627776LL * 1024 * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL * 1000 * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL * 1024 * 1024;
+ return -1LL;
+ case 'X': /* experimental: decimal (left arg) multiplication */
+ cp = (char *)strchr(b2p, 'x');
+ if (NULL == cp)
+ cp = (char *)strchr(b2p, 'X');
+ if (cp) {
+ ll = sg_get_llnum(cp + 1);
+ if (-1LL != ll)
+ return num * ll;
+ }
+ return -1LL;
+ case '+': /* experimental: decimal (left arg) addition */
+ cp = (char *)strchr(b2p, '+');
+ if (cp) {
+ ll = sg_get_llnum(cp + 1);
+ if (-1LL != ll)
+ return num + ll;
+ }
+ return -1LL;
+ default:
+ pr2ws("unrecognized multiplier\n");
+ return -1LL;
+ }
+ }
+}
+
+/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
+ * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
+ * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"),
+ * a whitespace or newline as terminator. Only decimal numbers can represent
+ * negative numbers and '-1' must be treated separately. */
+int64_t
+sg_get_llnum_nomult(const char * buf)
+{
+ int res, len;
+ int64_t num;
+ uint64_t unum;
+
+ if ((NULL == buf) || ('\0' == buf[0]))
+ return -1;
+ len = strlen(buf);
+ if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
+ res = sscanf(buf + 2, "%" SCNx64 "", &unum);
+ num = unum;
+ } else if ('H' == toupper(buf[len - 1])) {
+ res = sscanf(buf, "%" SCNx64 "", &unum);
+ num = unum;
+ } else
+ res = sscanf(buf, "%" SCNd64 "", &num);
+ return (1 == res) ? num : -1;
+}
+
+#define MAX_NUM_ASCII_LINES 1048576
+
+/* Read ASCII hex bytes or binary from fname (a file named '-' taken as
+ * stdin). If reading ASCII hex then there should be either one entry per
+ * line or a comma, space, hyphen or tab separated list of bytes. If no_space
+ * is set then a string of ACSII hex digits is expected, 2 perbyte.
+ * Everything from and including a '#' on a line is ignored. Returns 0 if ok,
+ * or an error code. If the error code is SG_LIB_LBA_OUT_OF_RANGE then mp_arr
+ * would be exceeded and both mp_arr and mp_arr_len are written to.
+ * The max_arr_len_and argument may carry extra information: when it
+ * is negative its absolute value is used for the maximum number of bytes to
+ * write to mp_arr _and_ the first hexadecimal value on each line is skipped.
+ * Many hexadecimal output programs place a running address (index) as the
+ * first field on each line. When as_binary and/or no_space are true, the
+ * absolute value of max_arr_len_and is used. */
+int
+sg_f2hex_arr(const char * fname, bool as_binary, bool no_space,
+ uint8_t * mp_arr, int * mp_arr_len, int max_arr_len_and)
+{
+ bool has_stdin, split_line, skip_first, redo_first;
+ int fn_len, in_len, k, j, m, fd, err, max_arr_len;
+ int off = 0;
+ int ret = 0;
+ unsigned int h;
+ const char * lcp;
+ FILE * fp = NULL;
+ struct stat a_stat;
+ char line[512];
+ char carry_over[4];
+
+ if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) {
+ pr2ws("%s: bad arguments\n", __func__);
+ return SG_LIB_LOGIC_ERROR;
+ }
+ if (max_arr_len_and < 0) {
+ skip_first = true;
+ max_arr_len = -max_arr_len_and;
+ } else {
+ skip_first = false;
+ max_arr_len = max_arr_len_and;
+ }
+ fn_len = strlen(fname);
+ if (0 == fn_len)
+ return SG_LIB_SYNTAX_ERROR;
+ has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */
+ if (as_binary) {
+ if (has_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ pr2ws("unable to open binary file %s: %s\n", fname,
+ safe_strerror(err));
+ return sg_convert_errno(err);
+ }
+ }
+ k = read(fd, mp_arr, max_arr_len);
+ if (k <= 0) {
+ if (0 == k) {
+ ret = SG_LIB_FILE_ERROR;
+ pr2ws("read 0 bytes from binary file %s\n", fname);
+ } else {
+ ret = sg_convert_errno(errno);
+ pr2ws("read from binary file %s: %s\n", fname,
+ safe_strerror(errno));
+ }
+ } else if ((k < max_arr_len) && (0 == fstat(fd, &a_stat)) &&
+ S_ISFIFO(a_stat.st_mode)) {
+ /* pipe; keep reading till error or 0 read */
+ while (k < max_arr_len) {
+ m = read(fd, mp_arr + k, max_arr_len - k);
+ if (0 == m)
+ break;
+ if (m < 0) {
+ err = errno;
+ pr2ws("read from binary pipe %s: %s\n", fname,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ break;
+ }
+ k += m;
+ }
+ }
+ if (k >= 0)
+ *mp_arr_len = k;
+ if ((fd >= 0) && (! has_stdin))
+ close(fd);
+ return ret;
+ }
+
+ /* So read the file as ASCII hex */
+ if (has_stdin)
+ fp = stdin;
+ else {
+ fp = fopen(fname, "r");
+ if (NULL == fp) {
+ err = errno;
+ pr2ws("Unable to open %s for reading: %s\n", fname,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ }
+
+ carry_over[0] = 0;
+ for (j = 0; j < MAX_NUM_ASCII_LINES; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit(line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%4x", &h)) {
+ if (off > 0) {
+ if (off > max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ *mp_arr_len = max_arr_len;
+ goto fini;
+ } else
+ mp_arr[off - 1] = h; /* back up and overwrite */
+ }
+ } else {
+ pr2ws("%s: carry_over error ['%s'] around line %d\n",
+ __func__, carry_over, j + 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,-\t");
+ if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) {
+ pr2ws("%s: syntax error at line %d, pos %d\n", __func__,
+ j + 1, m + k + 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (no_space) {
+ for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1));
+ ++k, lcp += 2) {
+ if (1 != sscanf(lcp, "%2x", &h)) {
+ pr2ws("%s: bad hex number in line %d, pos %d\n",
+ __func__, j + 1, (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ *mp_arr_len = max_arr_len;
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ goto fini;
+ } else
+ mp_arr[off + k] = h;
+ }
+ if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1))))
+ carry_over[0] = *lcp;
+ off += k;
+ } else { /* (white)space separated ASCII hexadecimal bytes */
+ for (redo_first = false, k = 0; k < 1024;
+ k = (redo_first ? k : k + 1)) {
+ if (1 == sscanf(lcp, "%10x", &h)) {
+ if (h > 0xff) {
+ pr2ws("%s: hex number larger than 0xff in line "
+ "%d, pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2ws("%s: array length exceeded\n", __func__);
+ ret = SG_LIB_LBA_OUT_OF_RANGE;
+ *mp_arr_len = max_arr_len;
+ goto fini;
+ } else if ((0 == k) && skip_first && (! redo_first))
+ redo_first = true;
+ else {
+ redo_first = false;
+ mp_arr[off + k] = h;
+ }
+ lcp = strpbrk(lcp, " ,-\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,-\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if (('#' == *lcp) || ('\r' == *lcp)) {
+ --k;
+ break;
+ }
+ pr2ws("%s: error in line %d, at pos %d\n", __func__,
+ j + 1, (int)(lcp - line + 1));
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ }
+ off += (k + 1);
+ }
+ } /* end of per line loop */
+ if (j >= MAX_NUM_ASCII_LINES) {
+ pr2ws("%s: wow, more than %d lines of ASCII, give up\n", __func__,
+ SG_LIB_LBA_OUT_OF_RANGE);
+ return SG_LIB_LBA_OUT_OF_RANGE;
+ }
+ *mp_arr_len = off;
+ if (fp && (! has_stdin))
+ fclose(fp);
+ return 0;
+fini:
+ if (fp && (! has_stdin))
+ fclose(fp);
+ return ret;
+}
+
+/* Extract character sequence from ATA words as in the model string
+ * in a IDENTIFY DEVICE response. Returns number of characters
+ * written to 'ochars' before 0 character is found or 'num' words
+ * are processed. */
+int
+sg_ata_get_chars(const uint16_t * word_arr, int start_word,
+ int num_words, bool is_big_endian, char * ochars)
+{
+ int k;
+ char * op = ochars;
+
+ for (k = start_word; k < (start_word + num_words); ++k) {
+ char a, b;
+ uint16_t s = word_arr[k];
+
+ if (is_big_endian) {
+ a = s & 0xff;
+ b = (s >> 8) & 0xff;
+ } else {
+ a = (s >> 8) & 0xff;
+ b = s & 0xff;
+ }
+ if (a == 0)
+ break;
+ *op++ = a;
+ if (b == 0)
+ break;
+ *op++ = b;
+ }
+ return op - ochars;
+}
+
+#ifdef SG_LIB_FREEBSD
+#include <sys/param.h>
+#elif defined(SG_LIB_WIN32)
+#include <windows.h>
+#endif
+
+uint32_t
+sg_get_page_size(void)
+{
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ {
+ long res = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+
+ return (res <= 0) ? 4096 : res;
+ }
+#elif defined(SG_LIB_WIN32)
+ static bool got_page_size = false;
+ static uint32_t win_page_size;
+
+ if (! got_page_size) {
+ SYSTEM_INFO si;
+
+ GetSystemInfo(&si);
+ win_page_size = si.dwPageSize;
+ got_page_size = true;
+ }
+ return win_page_size;
+#elif defined(SG_LIB_FREEBSD)
+ return PAGE_SIZE;
+#else
+ return 4096; /* give up, pick likely figure */
+#endif
+}
+
+#if defined(SG_LIB_WIN32)
+#if defined(MSC_VER) || defined(__MINGW32__)
+/* windows.h already included above */
+#define sg_sleep_for(seconds) Sleep( (seconds) * 1000)
+#else
+#define sg_sleep_for(seconds) sleep(seconds)
+#endif
+#else
+#define sg_sleep_for(seconds) sleep(seconds)
+#endif
+
+void
+sg_sleep_secs(int num_secs)
+{
+ sg_sleep_for(num_secs);
+}
+
+void
+sg_warn_and_wait(const char * cmd_name, const char * dev_name,
+ bool stress_all)
+{
+ int k, j;
+ const char * stressp = stress_all ? "ALL d" : "D";
+ const char * will_mayp = stress_all ? "will" : "may";
+
+ for (k = 0, j = 15; k < 3; ++k, j -= 5) {
+ printf("\nA %s command will commence in %d seconds\n", cmd_name, j);
+ printf(" %sata on %s %s be DESTROYED%s\n", stressp, dev_name,
+ will_mayp, (stress_all ? "" : " or modified"));
+ printf(" Press control-C to abort\n");
+ sg_sleep_secs(5);
+ }
+ sg_sleep_secs(1);
+}
+
+/* Returns pointer to heap (or NULL) that is aligned to a align_to byte
+ * boundary. Sends back *buff_to_free pointer in third argument that may be
+ * different from the return value. If it is different then the *buff_to_free
+ * pointer should be freed (rather than the returned value) when the heap is
+ * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all
+ * returned heap to zeros. If num_bytes is 0 then set to page size. */
+uint8_t *
+sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free,
+ bool vb)
+{
+ size_t psz;
+
+ if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */
+ *buff_to_free = NULL;
+ psz = (align_to > 0) ? align_to : sg_get_page_size();
+ if (0 == num_bytes)
+ num_bytes = psz; /* ugly to handle otherwise */
+
+#ifdef HAVE_POSIX_MEMALIGN
+ {
+ int err;
+ uint8_t * res;
+ void * wp = NULL;
+
+ err = posix_memalign(&wp, psz, num_bytes);
+ if (err || (NULL == wp)) {
+ pr2ws("%s: posix_memalign: error [%d], out of memory?\n",
+ __func__, err);
+ return NULL;
+ }
+ memset(wp, 0, num_bytes);
+ if (buff_to_free)
+ *buff_to_free = (uint8_t *)wp;
+ res = (uint8_t *)wp;
+ if (vb) {
+ pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes);
+ if (buff_to_free)
+ pr2ws("wrkBuffp=%p, ", (void *)res);
+ pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res);
+ }
+ return res;
+ }
+#else
+ {
+ void * wrkBuff;
+ uint8_t * res;
+ sg_uintptr_t align_1 = psz - 1;
+
+ wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1);
+ if (NULL == wrkBuff) {
+ if (buff_to_free)
+ *buff_to_free = NULL;
+ return NULL;
+ } else if (buff_to_free)
+ *buff_to_free = (uint8_t *)wrkBuff;
+ res = (uint8_t *)(void *)
+ (((sg_uintptr_t)wrkBuff + align_1) & (~align_1));
+ if (vb) {
+ pr2ws("%s: hack, len=%d, ", __func__, num_bytes);
+ if (buff_to_free)
+ pr2ws("buff_to_free=%p, ", wrkBuff);
+ pr2ws("align_1=%" PRIuPTR "u, rp=%p\n", align_1, (void *)res);
+ }
+ return res;
+ }
+#endif
+}
+
+/* If byte_count is 0 or less then the OS page size is used as denominator.
+ * Returns true if the remainder of ((unsigned)pointer % byte_count) is 0,
+ * else returns false. */
+bool
+sg_is_aligned(const void * pointer, int byte_count)
+{
+ return 0 == ((sg_uintptr_t)pointer %
+ ((byte_count > 0) ? (uint32_t)byte_count :
+ sg_get_page_size()));
+}
+
+/* Does similar job to sg_get_unaligned_be*() but this function starts at
+ * a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit)
+ * offset. Maximum number of num_bits is 64. For example, these two
+ * invocations are equivalent (and should yield the same result);
+ * sg_get_big_endian(from_bp, 7, 16)
+ * sg_get_unaligned_be16(from_bp) */
+uint64_t
+sg_get_big_endian(const uint8_t * from_bp, int start_bit /* 0 to 7 */,
+ int num_bits /* 1 to 64 */)
+{
+ uint64_t res;
+ int sbit_o1 = start_bit + 1;
+
+ res = (*from_bp++ & ((1 << sbit_o1) - 1));
+ num_bits -= sbit_o1;
+ while (num_bits > 0) {
+ res <<= 8;
+ res |= *from_bp++;
+ num_bits -= 8;
+ }
+ if (num_bits < 0)
+ res >>= (-num_bits);
+ return res;
+}
+
+/* Does similar job to sg_put_unaligned_be*() but this function starts at
+ * a given start_bit offset. Maximum number of num_bits is 64. Preserves
+ * residual bits in partially written bytes. start_bit 7 is MSb. */
+void
+sg_set_big_endian(uint64_t val, uint8_t * to,
+ int start_bit /* 0 to 7 */, int num_bits /* 1 to 64 */)
+{
+ int sbit_o1 = start_bit + 1;
+ int mask, num, k, x;
+
+ if ((NULL == to) || (start_bit > 7) || (num_bits > 64)) {
+ pr2ws("%s: bad args: start_bit=%d, num_bits=%d\n", __func__,
+ start_bit, num_bits);
+ return;
+ }
+ mask = (8 != sbit_o1) ? ((1 << sbit_o1) - 1) : 0xff;
+ k = start_bit - ((num_bits - 1) % 8);
+ if (0 != k)
+ val <<= ((k > 0) ? k : (8 + k));
+ num = (num_bits + 15 - sbit_o1) / 8;
+ for (k = 0; k < num; ++k) {
+ if ((sbit_o1 - num_bits) > 0)
+ mask &= ~((1 << (sbit_o1 - num_bits)) - 1);
+ if (k < (num - 1))
+ x = (val >> ((num - k - 1) * 8)) & 0xff;
+ else
+ x = val & 0xff;
+ to[k] = (to[k] & ~mask) | (x & mask);
+ mask = 0xff;
+ num_bits -= sbit_o1;
+ sbit_o1 = 8;
+ }
+}
+
+const char *
+sg_lib_version()
+{
+ return sg_lib_version_str;
+}
+
+
+#ifdef SG_LIB_MINGW
+/* Non Unix OSes distinguish between text and binary files.
+ Set text mode on fd. Does nothing in Unix. Returns negative number on
+ failure. */
+
+#include <fcntl.h>
+
+int
+sg_set_text_mode(int fd)
+{
+ return setmode(fd, O_TEXT);
+}
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+ failure. */
+int
+sg_set_binary_mode(int fd)
+{
+ return setmode(fd, O_BINARY);
+}
+
+#else
+/* For Unix the following functions are dummies. */
+int
+sg_set_text_mode(int fd)
+{
+ return fd; /* fd should be >= 0 */
+}
+
+int
+sg_set_binary_mode(int fd)
+{
+ return fd;
+}
+
+#endif
diff --git a/lib/sg_lib_data.c b/lib/sg_lib_data.c
new file mode 100644
index 00000000..f875fabc
--- /dev/null
+++ b/lib/sg_lib_data.c
@@ -0,0 +1,2014 @@
+/*
+ * Copyright (c) 2007-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+#define SG_SCSI_STRINGS 1
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+
+
+const char * sg_lib_version_str = "2.95 20221104";
+/* spc6r06, sbc5r03, zbc2r13 */
+
+
+/* indexed by pdt; those that map to own index do not decay */
+int sg_lib_pdt_decay_arr[32] = {
+ PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR,
+ PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */,
+ PDT_MCHANGER, PDT_COMMS, 0xa, 0xb,
+ PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW,
+ PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD,
+ PDT_DISK /* zbc */, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN
+};
+
+/* SCSI Status values */
+struct sg_lib_simple_value_name_t sg_lib_sstatus_str_arr[] = {
+ {0x0, "Good"},
+ {0x2, "Check Condition"},
+ {0x4, "Condition Met"},
+ {0x8, "Busy"},
+ {0x10, "Intermediate (obsolete)"},
+ {0x14, "Intermediate-Condition Met (obsolete)"},
+ {0x18, "Reservation Conflict"},
+ {0x22, "Command terminated (obsolete)"},
+ {0x28, "Task Set Full"},
+ {0x30, "ACA Active"},
+ {0x40, "Task Aborted"},
+ {0xffff, NULL},
+};
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+ {0, PDT_ALL, "Test Unit Ready"},
+ {0x1, PDT_ALL, "Rezero Unit"},
+ {0x1, PDT_TAPE, "Rewind"},
+ {0x3, PDT_ALL, "Request Sense"},
+ {0x4, PDT_DISK_ZBC, "Format Unit"},
+ {0x4, PDT_TAPE, "Format medium"},
+ {0x4, PDT_PRINTER, "Format"},
+ {0x5, PDT_TAPE, "Read Block Limits"},
+ {0x7, PDT_DISK_ZBC, "Reassign Blocks"},
+ {0x7, PDT_MCHANGER, "Initialize element status"},
+ {0x8, PDT_DISK_ZBC, "Read(6)"}, /* obsolete in sbc3r30 */
+ {0x8, PDT_PROCESSOR, "Receive"},
+ {0xa, PDT_DISK_ZBC, "Write(6)"}, /* obsolete in sbc3r30 */
+ {0xa, PDT_PRINTER, "Print"},
+ {0xa, PDT_PROCESSOR, "Send"},
+ {0xb, PDT_ALL, "Seek(6)"},
+ {0xb, PDT_TAPE, "Set capacity"},
+ {0xb, PDT_PRINTER, "Slew and print"},
+ {0xf, PDT_TAPE, "Read reverse(6)"},
+ {0x10, PDT_TAPE, "Write filemarks(6)"},
+ {0x10, PDT_PRINTER, "Synchronize buffer"},
+ {0x11, PDT_TAPE, "Space(6)"},
+ {0x12, PDT_ALL, "Inquiry"},
+ {0x13, PDT_TAPE, "Verify(6)"}, /* SSC */
+ {0x14, PDT_ALL, "Recover buffered data"},
+ {0x15, PDT_ALL, "Mode select(6)"},/* sbc3r31 recommends Mode select(10) */
+ {0x16, PDT_ALL, "Reserve(6)"}, /* obsolete in SPC-4 r11 */
+ {0x16, PDT_MCHANGER, "Reserve element(6)"},
+ {0x17, PDT_ALL, "Release(6)"}, /* obsolete in SPC-4 r11 */
+ {0x17, PDT_MCHANGER, "Release element(6)"},
+ {0x18, PDT_ALL, "Copy"}, /* obsolete in SPC-4 r11 */
+ {0x19, PDT_ALL, "Erase(6)"},
+ {0x1a, PDT_ALL, "Mode sense(6)"},/* sbc3r31 recommends Mode sense(10) */
+ {0x1b, PDT_ALL, "Start stop unit"},
+ {0x1b, PDT_TAPE, "Load unload"},
+ {0x1b, PDT_ADC, "Load unload"},
+ {0x1b, PDT_PRINTER, "Stop print"},
+ {0x1c, PDT_ALL, "Receive diagnostic results"},
+ {0x1d, PDT_ALL, "Send diagnostic"},
+ {0x1e, PDT_ALL, "Prevent allow medium removal"},
+ {0x23, PDT_MMC, "Read Format capacities"},
+ {0x24, PDT_ALL, "Set window"},
+ {0x25, PDT_ALL, "Read capacity(10)"},
+ /* sbc3r31 recommends Read capacity(16) */
+ {0x25, PDT_OCRW, "Read card capacity"},
+ {0x28, PDT_ALL, "Read(10)"}, /* sbc3r31 recommends Read(16) */
+ {0x29, PDT_ALL, "Read generation"},
+ {0x2a, PDT_ALL, "Write(10)"}, /* sbc3r31 recommends Write(16) */
+ {0x2b, PDT_ALL, "Seek(10)"},
+ {0x2b, PDT_TAPE, "Locate(10)"},
+ {0x2b, PDT_MCHANGER, "Position to element"},
+ {0x2c, PDT_ALL, "Erase(10)"},
+ {0x2d, PDT_OPTICAL, "Read updated block"},
+ {0x2e, PDT_ALL, "Write and verify(10)"},
+ /* sbc3r31 recommends Write and verify(16) */
+ {0x2f, PDT_ALL, "Verify(10)"}, /* sbc3r31 recommends Verify(16) */
+ {0x30, PDT_ALL, "Search data high(10)"},
+ {0x31, PDT_ALL, "Search data equal(10)"},
+ {0x32, PDT_ALL, "Search data low(10)"},
+ {0x33, PDT_ALL, "Set limits(10)"},
+ {0x34, PDT_ALL, "Pre-fetch(10)"}, /* sbc3r31 recommends Pre-fetch(16) */
+ {0x34, PDT_TAPE, "Read position"},
+ {0x35, PDT_ALL, "Synchronize cache(10)"},
+ /* SBC-3 r31 recommends Synchronize cache(16) */
+ {0x36, PDT_ALL, "Lock unlock cache(10)"},
+ {0x37, PDT_MCHANGER, "Initialize element status with range"},
+ {0x37, PDT_ALL, "Read defect data(10)"},
+ /* SBC-3 r31 recommends Read defect data(12) */
+ {0x38, PDT_DISK_ZBC, "Format with preset scan"},
+ {0x38, PDT_OCRW, "Medium scan"},
+ {0x39, PDT_ALL, "Compare"}, /* obsolete in SPC-4 r11 */
+ {0x3a, PDT_ALL, "Copy and verify"}, /* obsolete in SPC-4 r11 */
+ {0x3b, PDT_ALL, "Write buffer"},
+ {0x3c, PDT_ALL, "Read buffer(10)"},
+ {0x3d, PDT_OPTICAL, "Update block"},
+ {0x3e, PDT_ALL, "Read long(10)"}, /* obsolete in SBC-4 r7 */
+ {0x3f, PDT_ALL, "Write long(10)"}, /* sbc3r31 recommends Write long(16) */
+ {0x40, PDT_ALL, "Change definition"}, /* obsolete in SPC-4 r11 */
+ {0x41, PDT_ALL, "Write same(10)"}, /* sbc3r31 recommends Write same(16) */
+ {0x42, PDT_DISK_ZBC, "Unmap"}, /* added SPC-4 rev 18 */
+ {0x42, PDT_MMC, "Read sub-channel"},
+ {0x43, PDT_MMC, "Read TOC/PMA/ATIP"},
+ {0x44, PDT_ALL, "Report density support"},
+ {0x45, PDT_MMC, "Play audio(10)"},
+ {0x46, PDT_MMC, "Get configuration"},
+ {0x47, PDT_MMC, "Play audio msf"},
+ {0x48, PDT_ALL, "Sanitize"},
+ {0x4a, PDT_MMC, "Get event status notification"},
+ {0x4b, PDT_MMC, "Pause/resume"},
+ {0x4c, PDT_ALL, "Log select"},
+ {0x4d, PDT_ALL, "Log sense"},
+ {0x4e, PDT_MMC, "Stop play/scan"},
+ {0x50, PDT_DISK, "Xdwrite(10)"}, /* obsolete in SBC-3 r31 */
+ {0x51, PDT_DISK, "Xpwrite(10)"}, /* obsolete in SBC-4 r15 */
+ {0x51, PDT_MMC, "Read disk information"},
+ {0x52, PDT_DISK, "Xdread(10)"}, /* obsolete in SBC-3 r31 */
+ {0x52, PDT_MMC, "Read track information"},
+ {0x53, PDT_DISK, "Xdwriteread(10)"}, /* obsolete in SBC-4 r15 */
+ {0x54, PDT_MMC, "Send OPC information"},
+ {0x55, PDT_ALL, "Mode select(10)"},
+ {0x56, PDT_ALL, "Reserve(10)"}, /* obsolete in SPC-4 r11 */
+ {0x56, PDT_MCHANGER, "Reserve element(10)"},
+ {0x57, PDT_ALL, "Release(10)"}, /* obsolete in SPC-4 r11 */
+ {0x57, PDT_MCHANGER, "Release element(10)"},
+ {0x58, PDT_MMC, "Repair track"},
+ {0x5a, PDT_ALL, "Mode sense(10)"},
+ {0x5b, PDT_MMC, "Close track/session"},
+ {0x5c, PDT_MMC, "Read buffer capacity"},
+ {0x5d, PDT_MMC, "Send cue sheet"},
+ {0x5e, PDT_ALL, "Persistent reserve in"},
+ {0x5f, PDT_ALL, "Persistent reserve out"},
+ {0x7e, PDT_ALL, "Extended cdb (XCBD)"}, /* added in SPC-4 r12 */
+ {0x80, PDT_DISK, "Xdwrite extended(16)"}, /* obsolete in SBC-4 r15 */
+ {0x80, PDT_TAPE, "Write filemarks(16)"},
+ {0x81, PDT_DISK, "Rebuild(16)"},
+ {0x81, PDT_TAPE, "Read reverse(16)"},
+ {0x82, PDT_DISK, "Regenerate(16)"},
+ {0x83, PDT_ALL, "Third party copy out"},/* Extended copy before spc4r34 */
+ /* Following was "Receive copy results", before spc4r34 */
+ {0x84, PDT_ALL, "Third party copy in"},
+ {0x85, PDT_ALL, "ATA pass-through(16)"}, /* was 0x98 in spc3 rev21c */
+ {0x86, PDT_ALL, "Access control in"},
+ {0x87, PDT_ALL, "Access control out"},
+ {0x88, PDT_ALL, "Read(16)"},
+ {0x89, PDT_ALL, "Compare and write"},
+ {0x8a, PDT_ALL, "Write(16)"},
+ {0x8b, PDT_DISK, "Orwrite(16)"},
+ {0x8c, PDT_ALL, "Read attribute"},
+ {0x8d, PDT_ALL, "Write attribute"},
+ {0x8e, PDT_ALL, "Write and verify(16)"},
+ {0x8f, PDT_ALL, "Verify(16)"},
+ {0x90, PDT_ALL, "Pre-fetch(16)"},
+ {0x91, PDT_ALL, "Synchronize cache(16)"},
+ {0x91, PDT_TAPE, "Space(16)"},
+ {0x92, PDT_DISK, "Lock unlock cache(16)"},
+ {0x92, PDT_TAPE, "Locate(16)"},
+ {0x93, PDT_ALL, "Write same(16)"},
+ {0x93, PDT_TAPE, "Erase(16)"},
+ {0x94, PDT_DISK_ZBC, "ZBC out"}, /* new sbc4r04, has service actions */
+ {0x95, PDT_DISK_ZBC, "ZBC in"}, /* new sbc4r04, has service actions */
+ {0x9a, PDT_ALL, "Write stream(16)"}, /* added sbc4r07 */
+ {0x9b, PDT_ALL, "Read buffer(16)"}, /* added spc5r02 */
+ {0x9c, PDT_ALL, "Write atomic(16)"},
+ {0x9d, PDT_ALL, "Service action bidirectional"}, /* added spc4r35 */
+ {0x9e, PDT_ALL, "Service action in(16)"},
+ {0x9f, PDT_ALL, "Service action out(16)"},
+ {0xa0, PDT_ALL, "Report luns"},
+ {0xa1, PDT_ALL, "ATA pass-through(12)"},
+ {0xa1, PDT_MMC, "Blank"},
+ {0xa2, PDT_ALL, "Security protocol in"},
+ {0xa3, PDT_ALL, "Maintenance in"},
+ {0xa3, PDT_MMC, "Send key"},
+ {0xa4, PDT_ALL, "Maintenance out"},
+ {0xa4, PDT_MMC, "Report key"},
+ {0xa5, PDT_ALL, "Move medium"},
+ {0xa5, PDT_MMC, "Play audio(12)"},
+ {0xa6, PDT_MCHANGER, "Exchange medium"},
+ {0xa6, PDT_MMC, "Load/unload medium"},
+ {0xa7, PDT_OPTICAL, "Move medium attached"},
+ {0xa7, PDT_MMC, "Set read ahead"},
+ {0xa8, PDT_ALL, "Read(12)"}, /* SBC-3 r31 recommends Read(16) */
+ {0xa9, PDT_ALL, "Service action out(12)"},
+ {0xaa, PDT_ALL, "Write(12)"}, /* SBC-3 r31 recommends Write(16) */
+ {0xab, PDT_ALL, "Service action in(12)"},
+ {0xac, PDT_OPTICAL, "erase(12)"},
+ {0xac, PDT_MMC, "Get performance"},
+ {0xad, PDT_MMC, "Read DVD/BD structure"},
+ {0xae, PDT_ALL, "Write and verify(12)"},
+ /* SBC-3 r31 recommends Write and verify(16) */
+ {0xaf, PDT_ALL, "Verify(12)"}, /* SBC-3 r31 recommends Verify(16) */
+ {0xb0, PDT_DISK, "Search data high(12)"},
+ {0xb1, PDT_DISK, "Search data equal(12)"},
+ {0xb1, PDT_MCHANGER, "Open/close import/export element"},
+ {0xb2, PDT_DISK, "Search data low(12)"},
+ {0xb3, PDT_DISK, "Set limits(12)"},
+ {0xb4, PDT_ALL, "Read element status attached"},
+ {0xb5, PDT_MCHANGER, "Request volume element address"},
+ {0xb5, PDT_ALL, "Security protocol out"},
+ {0xb6, PDT_MCHANGER, "Send volume tag"},
+ {0xb6, PDT_MMC, "Set streaming"},
+ {0xb7, PDT_ALL, "Read defect data(12)"},
+ {0xb8, PDT_ALL, "Read element status"},
+ {0xb9, PDT_MMC, "Read CD msf"},
+ {0xba, PDT_MMC, "Scan"},
+ {0xba, PDT_ALL, "Redundancy group in"},
+ {0xbb, PDT_ALL, "Redundancy group out"},
+ {0xbb, PDT_MMC, "Set CD speed"},
+ {0xbc, PDT_ALL, "Spare in"},
+ {0xbd, PDT_ALL, "Spare out"},
+ {0xbd, PDT_MMC, "Mechanism status"},
+ {0xbe, PDT_MMC, "Read CD"},
+ {0xbe, PDT_ALL, "Volume set in"},
+ {0xbf, PDT_MMC, "Send DVD/BD structure"},
+ {0xbf, PDT_ALL, "Volume set out"},
+ {0xffff, 0, NULL},
+};
+
+/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa),
+ * need prefix */
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = {
+ {0x0, PDT_ALL, "combined header and data [or multiple modes]"},
+ {0x2, PDT_ALL, "data"},
+ {0x3, PDT_ALL, "descriptor"},
+ {0xa, PDT_ALL, "read data from echo buffer"},
+ {0xb, PDT_ALL, "echo buffer descriptor"},
+ {0xf, PDT_ALL, "read microcode status"}, /* added in spc5r20 */
+ {0x1a, PDT_ALL, "enable expander comms protocol and echo buffer"},
+ {0x1c, PDT_ALL, "error history"},
+ {0xffff, 0, NULL},
+};
+
+/* Write buffer [0x3b] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = {
+ {0x0, PDT_ALL, "combined header and data [or multiple modes]"},
+ {0x2, PDT_ALL, "data"},
+ {0x4, PDT_ALL, "download microcode and activate"},
+ {0x5, PDT_ALL, "download microcode, save, and activate"},
+ {0x6, PDT_ALL, "download microcode with offsets and activate"},
+ {0x7, PDT_ALL, "download microcode with offsets, save, and activate"},
+ {0xa, PDT_ALL, "write data to echo buffer"},
+ {0xd, PDT_ALL, "download microcode with offsets, select activation "
+ "events, save and defer activate"},
+ {0xe, PDT_ALL, "download microcode with offsets, save and defer activate"},
+ {0xf, PDT_ALL, "activate deferred microcode"},
+ {0x1a, PDT_ALL, "enable expander comms protocol and echo buffer"},
+ {0x1b, PDT_ALL, "disable expander comms protocol"},
+ {0x1c, PDT_ALL, "download application client error history"},
+ {0xffff, 0, NULL},
+};
+
+/* Read position (SSC) [0x34] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_read_pos_arr[] = {
+ {0x0, PDT_TAPE, "short form - block id"},
+ {0x1, PDT_TAPE, "short form - vendor specific"},
+ {0x6, PDT_TAPE, "long form"},
+ {0x8, PDT_TAPE, "extended form"},
+ {0xffff, 0, NULL},
+};
+
+/* Maintenance in [0xa3] service actions */
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = {
+ {0x0, PDT_SAC, "Report assigned/unassigned p_extent"},
+ {0x0, PDT_ADC, "Report automation device attributes"},
+ {0x1, PDT_SAC, "Report component device"},
+ {0x2, PDT_SAC, "Report component device attachments"},
+ {0x3, PDT_SAC, "Report peripheral device"},
+ {0x4, PDT_SAC, "Report peripheral device associations"},
+ {0x5, PDT_ALL, "Report identifying information"},
+ /* was "Report device identifier" prior to spc4r07 */
+ {0x6, PDT_SAC, "Report states"},
+ {0x7, PDT_SAC, "Report device identification"},
+ {0x8, PDT_SAC, "Report unconfigured capacity"},
+ {0x9, PDT_SAC, "Report supported configuration method"},
+ {0xa, PDT_ALL, "Report target port groups"},
+ {0xb, PDT_ALL, "Report aliases"},
+ {0xc, PDT_ALL, "Report supported operation codes"},
+ {0xd, PDT_ALL, "Report supported task management functions"},
+ {0xe, PDT_ALL, "Report priority"},
+ {0xf, PDT_ALL, "Report timestamp"},
+ {0x10, PDT_ALL, "Management protocol in"},
+ {0x1d, PDT_DISK, "Report provisioning initialization pattern"},
+ /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */
+ {0x1d, PDT_TAPE, "Receive recommended access order"},
+ {0x1e, PDT_TAPE, "Read dynamic runtime attribute"},
+ {0x1e, PDT_ADC, "Report automation device attributes"},
+ {0x1f, PDT_ALL, "Maintenance in vendor specific"},
+ {0xffff, 0, NULL},
+};
+
+/* Maintenance out [0xa4] service actions */
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = {
+ {0x0, PDT_SAC, "Add peripheral device / component device"},
+ {0x0, PDT_ADC, "Set automation device attribute"},
+ {0x1, PDT_SAC, "Attach to component device"},
+ {0x2, PDT_SAC, "Exchange p_extent"},
+ {0x3, PDT_SAC, "Exchange peripheral device / component device"},
+ {0x4, PDT_SAC, "Instruct component device"},
+ {0x5, PDT_SAC, "Remove peripheral device / component device"},
+ {0x6, PDT_ALL, "Set identifying information"},
+ /* was "Set device identifier" prior to spc4r07 */
+ {0x7, PDT_SAC, "Break peripheral device / component device"},
+ {0xa, PDT_ALL, "Set target port groups"},
+ {0xb, PDT_ALL, "Change aliases"},
+ {0xc, PDT_ALL, "Remove I_T nexus"},
+ {0xe, PDT_ALL, "Set priority"},
+ {0xf, PDT_ALL, "Set timestamp"},
+ {0x10, PDT_ALL, "Management protocol out"},
+ {0x1d, PDT_TAPE, "Generate recommended access order"},
+ {0x1e, PDT_TAPE, "write dynamic runtime attribute"},
+ {0x1e, PDT_ADC, "Set automation device attributes"},
+ {0x1f, PDT_ALL, "Maintenance out vendor specific"},
+ {0xffff, 0, NULL},
+};
+
+/* Sanitize [0x48] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = {
+ {0x1, PDT_ALL, "overwrite"},
+ {0x2, PDT_ALL, "block erase"},
+ {0x3, PDT_ALL, "cryptographic erase"},
+ {0x1f, PDT_ALL, "exit failure mode"},
+ {0xffff, 0, NULL},
+};
+
+/* Service action in(12) [0xab] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = {
+ {0x1, PDT_ALL, "Read media serial number"},
+ {0xffff, 0, NULL},
+};
+
+/* Service action out(12) [0xa9] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = {
+ {0x1f, PDT_ADC, "Set medium attribute"},
+ {0xff, PDT_ALL, "Impossible command name"},
+ {0xffff, 0, NULL},
+};
+
+/* Service action in(16) [0x9e] service actions */
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = {
+ {0xf, PDT_ALL, "Receive binding report"}, /* added spc5r11 */
+ {0x10, PDT_ALL, "Read capacity(16)"},
+ {0x11, PDT_ALL, "Read long(16)"}, /* obsolete in SBC-4 r7 */
+ {0x12, PDT_ALL, "Get LBA status(16)"},/* also 32 byte variant sbc4r14 */
+ {0x13, PDT_ALL, "Report referrals"},
+ {0x14, PDT_ALL, "Stream control"},
+ {0x15, PDT_ALL, "Background control"},
+ {0x16, PDT_ALL, "Get stream status"},
+ {0x17, PDT_ALL, "Get physical element status"}, /* added sbc4r13 */
+ {0x18, PDT_ALL, "Remove element and truncate"}, /* added sbc4r13 */
+ {0x19, PDT_ALL, "Restore elements and rebuild"}, /* added sbc4r19 */
+ {0x1a, PDT_ALL, "Remove element and modify zones"}, /* added zbc2r07 */
+ {0xffff, 0, NULL},
+};
+
+/* Service action out(16) [0x9f] service actions */
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = {
+ {0x0b, PDT_ALL, "Test bind"}, /* added spc5r13 */
+ {0x0c, PDT_ALL, "Prepare bind report"}, /* added spc5r11 */
+ {0x0d, PDT_ALL, "Set affiliation"},
+ {0x0e, PDT_ALL, "Bind"},
+ {0x0f, PDT_ALL, "Unbind"},
+ {0x11, PDT_ALL, "Write long(16)"},
+ {0x12, PDT_ALL, "Write scattered(16)"}, /* added sbc4r11 */
+ {0x14, PDT_DISK_ZBC, "Reset write pointer"},
+ {0x1f, PDT_ADC, "Notify data transfer device(16)"},
+ {0xffff, 0, NULL},
+};
+
+/* Service action bidirectional [0x9d] service actions */
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = {
+ {0xffff, 0, NULL},
+};
+
+/* Persistent reserve in [0x5e] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = {
+ {0x0, PDT_ALL, "read keys"},
+ {0x1, PDT_ALL, "read reservation"},
+ {0x2, PDT_ALL, "report capabilities"},
+ {0x3, PDT_ALL, "read full status"},
+ {0xffff, 0, NULL},
+};
+
+/* Persistent reserve out [0x5f] service actions, need prefix */
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = {
+ {0x0, PDT_ALL, "register"},
+ {0x1, PDT_ALL, "reserve"},
+ {0x2, PDT_ALL, "release"},
+ {0x3, PDT_ALL, "clear"},
+ {0x4, PDT_ALL, "preempt"},
+ {0x5, PDT_ALL, "preempt and abort"},
+ {0x6, PDT_ALL, "register and ignore existing key"},
+ {0x7, PDT_ALL, "register and move"},
+ {0x8, PDT_ALL, "replace lost reservation"},
+ {0xffff, 0, NULL},
+};
+
+/* Third party copy in [0x83] service actions
+ * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34
+ * LID1 is an abbreviation of List Identifier length of 1 byte. In SPC-5
+ * LID1 discontinued (references back to SPC-4) and "(LID4)" suffix removed
+ * as there is no need to differentiate. */
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* originating */
+ {0x0, PDT_ALL, "Extended copy(LID1)"},
+ {0x1, PDT_ALL, "Extended copy"}, /* 'Extended copy(LID4)' until spc5r01 */
+ {0x10, PDT_ALL, "Populate token"},
+ {0x11, PDT_ALL, "Write using token"},
+ {0x16, PDT_TAPE, "Set tape stream mirroring"}, /* ADC-4 and SSC-5 */
+ {0x1c, PDT_ALL, "Copy operation abort"},
+ {0xffff, 0, NULL},
+};
+
+/* Third party copy out [0x84] service actions
+ * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34
+ * LID4 is an abbreviation of List Identifier length of 4 bytes */
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* retrieve */
+ {0x0, PDT_ALL, "Receive copy status(LID1)"},
+ {0x1, PDT_ALL, "Receive copy data(LID1)"},
+ {0x3, PDT_ALL, "Receive copy operating parameters"},
+ {0x4, PDT_ALL, "Receive copy failure details(LID1)"},
+ {0x5, PDT_ALL, "Receive copy status"},/* was: Receive copy status(LID4) */
+ {0x6, PDT_ALL, "Receive copy data"}, /* was: Receive copy data(LID4) */
+ {0x7, PDT_ALL, "Receive ROD token information"},
+ {0x8, PDT_ALL, "Report all ROD tokens"},
+ {0x16, PDT_TAPE, "Report tape stream mirroring"}, /* SSC-5 */
+ {0xffff, 0, NULL},
+};
+
+/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */
+struct sg_lib_value_name_t sg_lib_variable_length_arr[] = {
+ {0x1, PDT_ALL, "Rebuild(32)"},
+ {0x2, PDT_ALL, "Regenerate(32)"},
+ {0x3, PDT_ALL, "Xdread(32)"}, /* obsolete in SBC-3 r31 */
+ {0x4, PDT_ALL, "Xdwrite(32)"}, /* obsolete in SBC-3 r31 */
+ {0x5, PDT_ALL, "Xdwrite extended(32)"}, /* obsolete in SBC-4 r15 */
+ {0x6, PDT_ALL, "Xpwrite(32)"}, /* obsolete in SBC-4 r15 */
+ {0x7, PDT_ALL, "Xdwriteread(32)"}, /* obsolete in SBC-4 r15 */
+ {0x8, PDT_ALL, "Xdwrite extended(64)"}, /* obsolete in SBC-4 r15 */
+ {0x9, PDT_ALL, "Read(32)"},
+ {0xa, PDT_ALL, "Verify(32)"},
+ {0xb, PDT_ALL, "Write(32)"},
+ {0xc, PDT_ALL, "Write and verify(32)"},
+ {0xd, PDT_ALL, "Write same(32)"},
+ {0xe, PDT_ALL, "Orwrite(32)"}, /* added sbc3r25 */
+ {0xf, PDT_ALL, "Atomic write(32)"}, /* added sbc4r02 */
+ {0x10, PDT_ALL, "Write stream(32)"}, /* added sbc4r07 */
+ {0x11, PDT_ALL, "Write scattered(32)"}, /* added sbc4r11 */
+ {0x12, PDT_ALL, "Get LBA status(32)"}, /* added sbc4r14 */
+ {0x1800, PDT_ALL, "Receive credential"},
+ {0x1ff0, PDT_ALL, "ATA pass-through(32)"},/* added sat4r05 */
+ {0x8801, PDT_ALL, "Format OSD (osd)"},
+ {0x8802, PDT_ALL, "Create (osd)"},
+ {0x8803, PDT_ALL, "List (osd)"},
+ {0x8805, PDT_ALL, "Read (osd)"},
+ {0x8806, PDT_ALL, "Write (osd)"},
+ {0x8807, PDT_ALL, "Append (osd)"},
+ {0x8808, PDT_ALL, "Flush (osd)"},
+ {0x880a, PDT_ALL, "Remove (osd)"},
+ {0x880b, PDT_ALL, "Create partition (osd)"},
+ {0x880c, PDT_ALL, "Remove partition (osd)"},
+ {0x880e, PDT_ALL, "Get attributes (osd)"},
+ {0x880f, PDT_ALL, "Set attributes (osd)"},
+ {0x8812, PDT_ALL, "Create and write (osd)"},
+ {0x8815, PDT_ALL, "Create collection (osd)"},
+ {0x8816, PDT_ALL, "Remove collection (osd)"},
+ {0x8817, PDT_ALL, "List collection (osd)"},
+ {0x8818, PDT_ALL, "Set key (osd)"},
+ {0x8819, PDT_ALL, "Set master key (osd)"},
+ {0x881a, PDT_ALL, "Flush collection (osd)"},
+ {0x881b, PDT_ALL, "Flush partition (osd)"},
+ {0x881c, PDT_ALL, "Flush OSD (osd)"},
+ {0x8880, PDT_ALL, "Object structure check (osd-2)"},
+ {0x8881, PDT_ALL, "Format OSD (osd-2)"},
+ {0x8882, PDT_ALL, "Create (osd-2)"},
+ {0x8883, PDT_ALL, "List (osd-2)"},
+ {0x8884, PDT_ALL, "Punch (osd-2)"},
+ {0x8885, PDT_ALL, "Read (osd-2)"},
+ {0x8886, PDT_ALL, "Write (osd-2)"},
+ {0x8887, PDT_ALL, "Append (osd-2)"},
+ {0x8888, PDT_ALL, "Flush (osd-2)"},
+ {0x8889, PDT_ALL, "Clear (osd-2)"},
+ {0x888a, PDT_ALL, "Remove (osd-2)"},
+ {0x888b, PDT_ALL, "Create partition (osd-2)"},
+ {0x888c, PDT_ALL, "Remove partition (osd-2)"},
+ {0x888e, PDT_ALL, "Get attributes (osd-2)"},
+ {0x888f, PDT_ALL, "Set attributes (osd-2)"},
+ {0x8892, PDT_ALL, "Create and write (osd-2)"},
+ {0x8895, PDT_ALL, "Create collection (osd-2)"},
+ {0x8896, PDT_ALL, "Remove collection (osd-2)"},
+ {0x8897, PDT_ALL, "List collection (osd-2)"},
+ {0x8898, PDT_ALL, "Set key (osd-2)"},
+ {0x8899, PDT_ALL, "Set master key (osd-2)"},
+ {0x889a, PDT_ALL, "Flush collection (osd-2)"},
+ {0x889b, PDT_ALL, "Flush partition (osd-2)"},
+ {0x889c, PDT_ALL, "Flush OSD (osd-2)"},
+ {0x88a0, PDT_ALL, "Query (osd-2)"},
+ {0x88a1, PDT_ALL, "Remove member objects (osd-2)"},
+ {0x88a2, PDT_ALL, "Get member attributes (osd-2)"},
+ {0x88a3, PDT_ALL, "Set member attributes (osd-2)"},
+ {0x88b1, PDT_ALL, "Read map (osd-2)"},
+ {0x8f7c, PDT_ALL, "Perform SCSI command (osd-2)"},
+ {0x8f7d, PDT_ALL, "Perform task management function (osd-2)"},
+ {0x8f7e, PDT_ALL, "Perform SCSI command (osd)"},
+ {0x8f7f, PDT_ALL, "Perform task management function (osd)"},
+ {0xffff, 0, NULL},
+};
+
+/* Zoning out [0x94] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+ {0x1, PDT_DISK_ZBC, "Close zone"},
+ {0x2, PDT_DISK_ZBC, "Finish zone"},
+ {0x3, PDT_DISK_ZBC, "Open zone"},
+ {0x4, PDT_DISK_ZBC, "Reset write pointer"},
+ {0x10, PDT_DISK_ZBC, "Sequentialize zone"}, /* zbc2r01b */
+ {0xffff, 0, NULL},
+};
+
+/* Zoning in [0x95] service actions */
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+ {0x0, PDT_DISK_ZBC, "Report zones"},
+ {0x6, PDT_DISK_ZBC, "Report realms"}, /* zbc2r04 */
+ {0x7, PDT_DISK_ZBC, "Report zone domains"}, /* zbc2r04 */
+ {0x8, PDT_DISK_ZBC, "Zone activate"}, /* zbc2r04 */
+ {0x9, PDT_DISK_ZBC, "Zone query"}, /* zbc2r04 */
+ {0xffff, 0, NULL},
+};
+
+const char * sg_lib_tapealert_strs[] = {
+ "<parameter code 0, unknown>", /* 0x0 */
+ "Read warning",
+ "Write warning",
+ "Hard error",
+ "Media",
+ "Read failure",
+ "Write failure",
+ "Media life",
+ "Not data grade", /* 0x8 */
+ "Write protect",
+ "No removal",
+ "Cleaning media",
+ "Unsupported format",
+ "Recoverable mechanical cartridge failure",
+ "Unrecoverable mechanical cartridge failure",
+ "Memory chip in cartridge failure",
+ "Forced eject", /* 0x10 */
+ "Read only format",
+ "Tape directory corrupted on load",
+ "Nearing media life",
+ "Cleaning required",
+ "Cleaning requested",
+ "Expired cleaning media",
+ "Invalid cleaning tape",
+ "Retension requested", /* 0x18 */
+ "Dual port interface error",
+ "Cooling fan failing",
+ "Power supply failure",
+ "Power consumption",
+ "Drive maintenance",
+ "Hardware A",
+ "Hardware B",
+ "Interface", /* 0x20 */
+ "Eject media",
+ "Microcode update fail",
+ "Drive humidity",
+ "Drive temperature",
+ "Drive voltage",
+ "Predictive failure",
+ "Diagnostics required",
+ "Reserved (28h)", /* 0x28 */
+ "Reserved (29h)",
+ "Reserved (2Ah)",
+ "Reserved (2Bh)",
+ "Reserved (2Ch)",
+ "Reserved (2Dh)",
+ "Reserved (2Eh)",
+ "External data encryption control - communications failure",
+ "External data encryption control - key manager returned error",/* 0x30 */
+ "Diminished native capacity",
+ "Lost statistics",
+ "Tape directory invalid at unload",
+ "Tape system area write failure",
+ "Tape system area read failure",
+ "No start of data",
+ "Loading failure",
+ "Unrecoverable unload failure", /* 0x38 */
+ "Automation interface failure",
+ "Firmware failure",
+ "WORM medium - integrity check failed",
+ "WORM medium - overwrite attempted",
+ "Encryption policy violation",
+ "Reserved (3Eh)",
+ "Reserved (3Fh)",
+ "Reserved (40h)", /* 0x40 */
+ NULL,
+};
+
+/* Read attribute [0x8c] service actions */
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+ {0x0, PDT_ALL, "attribute values"},
+ {0x1, PDT_ALL, "attribute list"},
+ {0x2, PDT_ALL, "logical volume list"},
+ {0x3, PDT_ALL, "partition list"},
+ {0x5, PDT_ALL, "supported attributes"},
+ {0xffff, 0, NULL},
+};
+
+#else /* SG_SCSI_STRINGS */
+
+struct sg_lib_value_name_t sg_lib_normal_opcodes[] = {
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { /* opcode 0x3c */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { /* opcode 0x3b */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { /* opcode 0x34 (SSC) */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { /* opcode 0xa3 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { /* opcode 0xa4 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { /* opcode 0x94 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_variable_length_arr[] = {
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = {
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = {
+ {0xffff, 0, NULL},
+};
+
+struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
+ {0xffff, 0, NULL},
+};
+
+const char * sg_lib_tapealert_strs[] = {
+ NULL,
+};
+
+#endif /* SG_SCSI_STRINGS */
+
+/* A conveniently formatted list of SCSI ASC/ASCQ codes and their
+ * corresponding text can be found at: www.t10.org/lists/asc-num.txt
+ * The following should match asc-num.txt dated 20200817 */
+
+#ifdef SG_SCSI_STRINGS
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+ {0x40,0x01,0x7f,"Ram failure [0x%x]"},
+ {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"},
+ {0x41,0x01,0xff,"Data path failure [0x%x]"},
+ {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"},
+ {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"},
+ {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"},
+ {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+ {0x00,0x00,"No additional sense information"},
+ {0x00,0x01,"Filemark detected"},
+ {0x00,0x02,"End-of-partition/medium detected"},
+ {0x00,0x03,"Setmark detected"},
+ {0x00,0x04,"Beginning-of-partition/medium detected"},
+ {0x00,0x05,"End-of-data detected"},
+ {0x00,0x06,"I/O process terminated"},
+ {0x00,0x07,"Programmable early warning detected"},
+ {0x00,0x11,"Audio play operation in progress"},
+ {0x00,0x12,"Audio play operation paused"},
+ {0x00,0x13,"Audio play operation successfully completed"},
+ {0x00,0x14,"Audio play operation stopped due to error"},
+ {0x00,0x15,"No current audio status to return"},
+ {0x00,0x16,"operation in progress"},
+ {0x00,0x17,"Cleaning requested"},
+ {0x00,0x18,"Erase operation in progress"},
+ {0x00,0x19,"Locate operation in progress"},
+ {0x00,0x1a,"Rewind operation in progress"},
+ {0x00,0x1b,"Set capacity operation in progress"},
+ {0x00,0x1c,"Verify operation in progress"},
+ {0x00,0x1d,"ATA pass through information available"},
+ {0x00,0x1e,"Conflicting SA creation request"},
+ {0x00,0x1f,"Logical unit transitioning to another power condition"},
+ {0x00,0x20,"Extended copy information available"},
+ {0x00,0x21,"Atomic command aborted due to ACA"},
+ {0x00,0x22,"Deferred microcode is pending"},
+ {0x01,0x00,"No index/sector signal"},
+ {0x02,0x00,"No seek complete"},
+ {0x03,0x00,"Peripheral device write fault"},
+ {0x03,0x01,"No write current"},
+ {0x03,0x02,"Excessive write errors"},
+ {0x04,0x00,"Logical unit not ready, cause not reportable"},
+ {0x04,0x01,"Logical unit is in process of becoming ready"},
+ {0x04,0x02,"Logical unit not ready, "
+ "initializing command required"},
+ {0x04,0x03,"Logical unit not ready, "
+ "manual intervention required"},
+ {0x04,0x04,"Logical unit not ready, format in progress"},
+ {0x04,0x05,"Logical unit not ready, rebuild in progress"},
+ {0x04,0x06,"Logical unit not ready, recalculation in progress"},
+ {0x04,0x07,"Logical unit not ready, operation in progress"},
+ {0x04,0x08,"Logical unit not ready, long write in progress"},
+ {0x04,0x09,"Logical unit not ready, self-test in progress"},
+ {0x04,0x0a,"Logical unit "
+ "not accessible, asymmetric access state transition"},
+ {0x04,0x0b,"Logical unit "
+ "not accessible, target port in standby state"},
+ {0x04,0x0c,"Logical unit "
+ "not accessible, target port in unavailable state"},
+ {0x04,0x0d,"Logical unit not ready, structure check required"},
+ {0x04,0x0e,"Logical unit not ready, security session in progress"},
+ {0x04,0x10,"Logical unit not ready, "
+ "auxiliary memory not accessible"},
+ {0x04,0x11,"Logical unit not ready, "
+ "notify (enable spinup) required"},
+ {0x04,0x12,"Logical unit not ready, offline"},
+ {0x04,0x13,"Logical unit not ready, SA creation in progress"},
+ {0x04,0x14,"Logical unit not ready, space allocation in progress"},
+ {0x04,0x15,"Logical unit not ready, robotics disabled"},
+ {0x04,0x16,"Logical unit not ready, configuration required"},
+ {0x04,0x17,"Logical unit not ready, calibration required"},
+ {0x04,0x18,"Logical unit not ready, a door is open"},
+ {0x04,0x19,"Logical unit not ready, operating in sequential mode"},
+ {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"},
+ {0x04,0x1b,"Logical unit not ready, sanitize in progress"},
+ {0x04,0x1c,"Logical unit not ready, additional power use not yet "
+ "granted"},
+ {0x04,0x1d,"Logical unit not ready, configuration in progress"},
+ {0x04,0x1e,"Logical unit not ready, microcode activation required"},
+ {0x04,0x1f,"Logical unit not ready, microcode download required"},
+ {0x04,0x20,"Logical unit not ready, logical unit reset required"},
+ {0x04,0x21,"Logical unit not ready, hard reset required"},
+ {0x04,0x22,"Logical unit not ready, power cycle required"},
+ {0x04,0x23,"Logical unit not ready, affiliation required"},
+ {0x04,0x24,"Depopulation in progress"}, /* spc5r15 */
+ {0x04,0x25,"Depopulation restoration in progress"}, /* spc6r02 */
+ {0x05,0x00,"Logical unit does not respond to selection"},
+ {0x06,0x00,"No reference position found"},
+ {0x07,0x00,"Multiple peripheral devices selected"},
+ {0x08,0x00,"Logical unit communication failure"},
+ {0x08,0x01,"Logical unit communication time-out"},
+ {0x08,0x02,"Logical unit communication parity error"},
+ {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"},
+ {0x08,0x04,"Unreachable copy target"},
+ {0x09,0x00,"Track following error"},
+ {0x09,0x01,"Tracking servo failure"},
+ {0x09,0x02,"Focus servo failure"},
+ {0x09,0x03,"Spindle servo failure"},
+ {0x09,0x04,"Head select fault"},
+ {0x09,0x05,"Vibration induced tracking error"},
+ {0x0A,0x00,"Error log overflow"},
+ {0x0B,0x00,"Warning"},
+ {0x0B,0x01,"Warning - specified temperature exceeded"},
+ {0x0B,0x02,"Warning - enclosure degraded"},
+ {0x0B,0x03,"Warning - background self-test failed"},
+ {0x0B,0x04,"Warning - background pre-scan detected medium error"},
+ {0x0B,0x05,"Warning - background medium scan detected medium error"},
+ {0x0B,0x06,"Warning - non-volatile cache now volatile"},
+ {0x0B,0x07,"Warning - degraded power to non-volatile cache"},
+ {0x0B,0x08,"Warning - power loss expected"},
+ {0x0B,0x09,"Warning - device statistics notification active"},
+ {0x0B,0x0A,"Warning - high critical temperature limit exceeded"},
+ {0x0B,0x0B,"Warning - low critical temperature limit exceeded"},
+ {0x0B,0x0C,"Warning - high operating temperature limit exceeded"},
+ {0x0B,0x0D,"Warning - low operating temperature limit exceeded"},
+ {0x0B,0x0E,"Warning - high critical humidity limit exceeded"},
+ {0x0B,0x0F,"Warning - low critical humidity limit exceeded"},
+ {0x0B,0x10,"Warning - high operating humidity limit exceeded"},
+ {0x0B,0x11,"Warning - low operating humidity limit exceeded"},
+ {0x0B,0x12,"Warning - microcode security at risk"},
+ {0x0B,0x13,"Warning - microcode digital signature validation failure"},
+ {0x0B,0x14,"Warning - physical element status change"}, /* spc5r15 */
+ {0x0C,0x00,"Write error"},
+ {0x0C,0x01,"Write error - recovered with auto reallocation"},
+ {0x0C,0x02,"Write error - auto reallocation failed"},
+ {0x0C,0x03,"Write error - recommend reassignment"},
+ {0x0C,0x04,"Compression check miscompare error"},
+ {0x0C,0x05,"Data expansion occurred during compression"},
+ {0x0C,0x06,"Block not compressible"},
+ {0x0C,0x07,"Write error - recovery needed"},
+ {0x0C,0x08,"Write error - recovery failed"},
+ {0x0C,0x09,"Write error - loss of streaming"},
+ {0x0C,0x0A,"Write error - padding blocks added"},
+ {0x0C,0x0B,"Auxiliary memory write error"},
+ {0x0C,0x0C,"Write error - unexpected unsolicited data"},
+ {0x0C,0x0D,"Write error - not enough unsolicited data"},
+ {0x0C,0x0E,"Multiple write errors"},
+ {0x0C,0x0F,"Defects in error window"},
+ {0x0C,0x10,"Incomplete multiple atomic write operations"},
+ {0x0C,0x11,"Write error - recovery scan needed"},
+ {0x0C,0x12,"Write error - insufficient zone resources"},
+ {0x0D,0x00,"Error detected by third party temporary initiator"},
+ {0x0D,0x01,"Third party device failure"},
+ {0x0D,0x02,"Copy target device not reachable"},
+ {0x0D,0x03,"Incorrect copy target device type"},
+ {0x0D,0x04,"Copy target device data underrun"},
+ {0x0D,0x05,"Copy target device data overrun"},
+ {0x0E,0x00,"Invalid information unit"},
+ {0x0E,0x01,"Information unit too short"},
+ {0x0E,0x02,"Information unit too long"},
+ {0x0E,0x03,"Invalid field in command information unit"},
+ {0x10,0x00,"Id CRC or ECC error"},
+ {0x10,0x01,"Logical block guard check failed"},
+ {0x10,0x02,"Logical block application tag check failed"},
+ {0x10,0x03,"Logical block reference tag check failed"},
+ {0x10,0x04,"Logical block protection error on recover buffered data"},
+ {0x10,0x05,"Logical block protection method error"},
+ {0x11,0x00,"Unrecovered read error"},
+ {0x11,0x01,"Read retries exhausted"},
+ {0x11,0x02,"Error too long to correct"},
+ {0x11,0x03,"Multiple read errors"},
+ {0x11,0x04,"Unrecovered read error - auto reallocate failed"},
+ {0x11,0x05,"L-EC uncorrectable error"},
+ {0x11,0x06,"CIRC unrecovered error"},
+ {0x11,0x07,"Data re-synchronization error"},
+ {0x11,0x08,"Incomplete block read"},
+ {0x11,0x09,"No gap found"},
+ {0x11,0x0A,"Miscorrected error"},
+ {0x11,0x0B,"Unrecovered read error - recommend reassignment"},
+ {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"},
+ {0x11,0x0D,"De-compression CRC error"},
+ {0x11,0x0E,"Cannot decompress using declared algorithm"},
+ {0x11,0x0F,"Error reading UPC/EAN number"},
+ {0x11,0x10,"Error reading ISRC number"},
+ {0x11,0x11,"Read error - loss of streaming"},
+ {0x11,0x12,"Auxiliary memory read error"},
+ {0x11,0x13,"Read error - failed retransmission request"},
+ {0x11,0x14,"Read error - LBA marked bad by application client"},
+ {0x11,0x15,"Write after sanitize required"},
+ {0x12,0x00,"Address mark not found for id field"},
+ {0x13,0x00,"Address mark not found for data field"},
+ {0x14,0x00,"Recorded entity not found"},
+ {0x14,0x01,"Record not found"},
+ {0x14,0x02,"Filemark or setmark not found"},
+ {0x14,0x03,"End-of-data not found"},
+ {0x14,0x04,"Block sequence error"},
+ {0x14,0x05,"Record not found - recommend reassignment"},
+ {0x14,0x06,"Record not found - data auto-reallocated"},
+ {0x14,0x07,"Locate operation failure"},
+ {0x15,0x00,"Random positioning error"},
+ {0x15,0x01,"Mechanical positioning error"},
+ {0x15,0x02,"Positioning error detected by read of medium"},
+ {0x16,0x00,"Data synchronization mark error"},
+ {0x16,0x01,"Data sync error - data rewritten"},
+ {0x16,0x02,"Data sync error - recommend rewrite"},
+ {0x16,0x03,"Data sync error - data auto-reallocated"},
+ {0x16,0x04,"Data sync error - recommend reassignment"},
+ {0x17,0x00,"Recovered data with no error correction applied"},
+ {0x17,0x01,"Recovered data with retries"},
+ {0x17,0x02,"Recovered data with positive head offset"},
+ {0x17,0x03,"Recovered data with negative head offset"},
+ {0x17,0x04,"Recovered data with retries and/or circ applied"},
+ {0x17,0x05,"Recovered data using previous sector id"},
+ {0x17,0x06,"Recovered data without ECC - data auto-reallocated"},
+ {0x17,0x07,"Recovered data without ECC - recommend reassignment"},
+ {0x17,0x08,"Recovered data without ECC - recommend rewrite"},
+ {0x17,0x09,"Recovered data without ECC - data rewritten"},
+ {0x18,0x00,"Recovered data with error correction applied"},
+ {0x18,0x01,"Recovered data with error corr. & retries applied"},
+ {0x18,0x02,"Recovered data - data auto-reallocated"},
+ {0x18,0x03,"Recovered data with CIRC"},
+ {0x18,0x04,"Recovered data with L-EC"},
+ {0x18,0x05,"Recovered data - recommend reassignment"},
+ {0x18,0x06,"Recovered data - recommend rewrite"},
+ {0x18,0x07,"Recovered data with ECC - data rewritten"},
+ {0x18,0x08,"Recovered data with linking"},
+ {0x19,0x00,"Defect list error"},
+ {0x19,0x01,"Defect list not available"},
+ {0x19,0x02,"Defect list error in primary list"},
+ {0x19,0x03,"Defect list error in grown list"},
+ {0x1A,0x00,"Parameter list length error"},
+ {0x1B,0x00,"Synchronous data transfer error"},
+ {0x1C,0x00,"Defect list not found"},
+ {0x1C,0x01,"Primary defect list not found"},
+ {0x1C,0x02,"Grown defect list not found"},
+ {0x1D,0x00,"Miscompare during verify operation"},
+ {0x1D,0x01,"Miscompare verify of unmapped lba"},
+ {0x1E,0x00,"Recovered id with ECC correction"},
+ {0x1F,0x00,"Partial defect list transfer"},
+ {0x20,0x00,"Invalid command operation code"},
+ {0x20,0x01,"Access denied - initiator pending-enrolled"},
+ {0x20,0x02,"Access denied - no access rights"},
+ {0x20,0x03,"Access denied - invalid mgmt id key"},
+ {0x20,0x04,"Illegal command while in write capable state"},
+ {0x20,0x05,"Write type operation while in read capable state (obs)"},
+ {0x20,0x06,"Illegal command while in explicit address mode"},
+ {0x20,0x07,"Illegal command while in implicit address mode"},
+ {0x20,0x08,"Access denied - enrollment conflict"},
+ {0x20,0x09,"Access denied - invalid LU identifier"},
+ {0x20,0x0A,"Access denied - invalid proxy token"},
+ {0x20,0x0B,"Access denied - ACL LUN conflict"},
+ {0x20,0x0C,"Illegal command when not in append-only mode"},
+ {0x20,0x0D,"Not an administrative logical unit"},
+ {0x20,0x0E,"Not a subsidiary logical unit"},
+ {0x20,0x0F,"Not a conglomerate logical unit"},
+ {0x21,0x00,"Logical block address out of range"},
+ {0x21,0x01,"Invalid element address"},
+ {0x21,0x02,"Invalid address for write"},
+ {0x21,0x03,"Invalid write crossing layer jump"},
+ {0x21,0x04,"Unaligned write command"},
+ {0x21,0x05,"Write boundary violation"},
+ {0x21,0x06,"Attempt to read invalid data"},
+ {0x21,0x07,"Read boundary violation"},
+ {0x21,0x08,"Misaligned write command"},
+ {0x21,0x09,"Attempt to access gap zone"},
+ {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"},
+ {0x23,0x00,"Invalid token operation, cause not reportable"},
+ {0x23,0x01,"Invalid token operation, unsupported token type"},
+ {0x23,0x02,"Invalid token operation, remote token usage not supported"},
+ {0x23,0x03,"invalid token operation, remote rod token creation not "
+ "supported"},
+ {0x23,0x04,"Invalid token operation, token unknown"},
+ {0x23,0x05,"Invalid token operation, token corrupt"},
+ {0x23,0x06,"Invalid token operation, token revoked"},
+ {0x23,0x07,"Invalid token operation, token expired"},
+ {0x23,0x08,"Invalid token operation, token cancelled"},
+ {0x23,0x09,"Invalid token operation, token deleted"},
+ {0x23,0x0a,"Invalid token operation, invalid token length"},
+ {0x24,0x00,"Invalid field in cdb"},
+ {0x24,0x01,"CDB decryption error"},
+ {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"},
+ {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"},
+ {0x24,0x04,"Security audit value frozen"},
+ {0x24,0x05,"Security working key frozen"},
+ {0x24,0x06,"Nonce not unique"},
+ {0x24,0x07,"Nonce timestamp out of range"},
+ {0x24,0x08,"Invalid xcdb"},
+ {0x24,0x09,"Invalid fast format"},
+ {0x25,0x00,"Logical unit not supported"},
+ {0x26,0x00,"Invalid field in parameter list"},
+ {0x26,0x01,"Parameter not supported"},
+ {0x26,0x02,"Parameter value invalid"},
+ {0x26,0x03,"Threshold parameters not supported"},
+ {0x26,0x04,"Invalid release of persistent reservation"},
+ {0x26,0x05,"Data decryption error"},
+ {0x26,0x06,"Too many target descriptors"},
+ {0x26,0x07,"Unsupported target descriptor type code"},
+ {0x26,0x08,"Too many segment descriptors"},
+ {0x26,0x09,"Unsupported segment descriptor type code"},
+ {0x26,0x0A,"Unexpected inexact segment"},
+ {0x26,0x0B,"Inline data length exceeded"},
+ {0x26,0x0C,"Invalid operation for copy source or destination"},
+ {0x26,0x0D,"Copy segment granularity violation"},
+ {0x26,0x0E,"Invalid parameter while port is enabled"},
+ {0x26,0x0F,"Invalid data-out buffer integrity check value"},
+ {0x26,0x10,"Data decryption key fail limit reached"},
+ {0x26,0x11,"Incomplete key-associated data set"},
+ {0x26,0x12,"Vendor specific key reference not found"},
+ {0x26,0x13,"Application tag mode page is invalid"},
+ {0x26,0x14,"Tape stream mirroring prevented"},
+ {0x26,0x15,"Copy source or copy destination not authorized"},
+ {0x26,0x16,"Fast copy not possible"},
+ {0x27,0x00,"Write protected"},
+ {0x27,0x01,"Hardware write protected"},
+ {0x27,0x02,"Logical unit software write protected"},
+ {0x27,0x03,"Associated write protect"},
+ {0x27,0x04,"Persistent write protect"},
+ {0x27,0x05,"Permanent write protect"},
+ {0x27,0x06,"Conditional write protect"},
+ {0x27,0x07,"Space allocation failed write protect"},
+ {0x27,0x08,"Zone is read only"},
+ {0x28,0x00,"Not ready to ready change, medium may have changed"},
+ {0x28,0x01,"Import or export element accessed"},
+ {0x28,0x02,"Format-layer may have changed"},
+ {0x28,0x03,"Import/export element accessed, medium changed"},
+ {0x29,0x00,"Power on, reset, or bus device reset occurred"},
+ {0x29,0x01,"Power on occurred"},
+ {0x29,0x02,"SCSI bus reset occurred"},
+ {0x29,0x03,"Bus device reset function occurred"},
+ {0x29,0x04,"Device internal reset"},
+ {0x29,0x05,"Transceiver mode changed to single-ended"},
+ {0x29,0x06,"Transceiver mode changed to lvd"},
+ {0x29,0x07,"I_T nexus loss occurred"},
+ {0x2A,0x00,"Parameters changed"},
+ {0x2A,0x01,"Mode parameters changed"},
+ {0x2A,0x02,"Log parameters changed"},
+ {0x2A,0x03,"Reservations preempted"},
+ {0x2A,0x04,"Reservations released"},
+ {0x2A,0x05,"Registrations preempted"},
+ {0x2A,0x06,"Asymmetric access state changed"},
+ {0x2A,0x07,"Implicit asymmetric access state transition failed"},
+ {0x2A,0x08,"Priority changed"},
+ {0x2A,0x09,"Capacity data has changed"},
+ {0x2A,0x0c, "Error recovery attributes have changed"},
+ {0x2A,0x0d, "Data encryption capabilities changed"},
+ {0x2A,0x10,"Timestamp changed"},
+ {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"},
+ {0x2A,0x12,"Data encryption parameters changed by vendor specific event"},
+ {0x2A,0x13,"Data encryption key instance counter has changed"},
+ {0x2A,0x0a,"Error history i_t nexus cleared"},
+ {0x2A,0x0b,"Error history snapshot released"},
+ {0x2A,0x14,"SA creation capabilities data has changed"},
+ {0x2A,0x15,"Medium removal prevention preempted"},
+ {0x2A,0x16,"Zone reset write pointer recommended"},
+ {0x2B,0x00,"Copy cannot execute since host cannot disconnect"},
+ {0x2C,0x00,"Command sequence error"},
+ {0x2C,0x01,"Too many windows specified"},
+ {0x2C,0x02,"Invalid combination of windows specified"},
+ {0x2C,0x03,"Current program area is not empty"},
+ {0x2C,0x04,"Current program area is empty"},
+ {0x2C,0x05,"Illegal power condition request"},
+ {0x2C,0x06,"Persistent prevent conflict"},
+ {0x2C,0x07,"Previous busy status"},
+ {0x2C,0x08,"Previous task set full status"},
+ {0x2C,0x09,"Previous reservation conflict status"},
+ {0x2C,0x0A,"Partition or collection contains user objects"},
+ {0x2C,0x0B,"Not reserved"},
+ {0x2C,0x0C,"ORWRITE generation does not match"},
+ {0x2C,0x0D,"Reset write pointer not allowed"},
+ {0x2C,0x0E,"Zone is offline"},
+ {0x2C,0x0F,"Stream not open"},
+ {0x2C,0x10,"Unwritten data in zone"},
+ {0x2C,0x11,"Descriptor format sense data required"},
+ {0x2C,0x12,"Zone is inactive"},
+ {0x2C,0x13,"Well known logical unit access required"}, /* spc6r02 */
+ {0x2D,0x00,"Overwrite error on update in place"},
+ {0x2E,0x00,"Insufficient time for operation"},
+ {0x2E,0x01,"Command timeout before processing"},
+ {0x2E,0x02,"Command timeout during processing"},
+ {0x2E,0x03,"Command timeout during processing due to error recovery"},
+ {0x2F,0x00,"Commands cleared by another initiator"},
+ {0x2F,0x01,"Commands cleared by power loss notification"},
+ {0x2F,0x02,"Commands cleared by device server"},
+ {0x2F,0x03,"Some commands cleared by queuing layer event"},
+ {0x30,0x00,"Incompatible medium installed"},
+ {0x30,0x01,"Cannot read medium - unknown format"},
+ {0x30,0x02,"Cannot read medium - incompatible format"},
+ {0x30,0x03,"Cleaning cartridge installed"},
+ {0x30,0x04,"Cannot write medium - unknown format"},
+ {0x30,0x05,"Cannot write medium - incompatible format"},
+ {0x30,0x06,"Cannot format medium - incompatible medium"},
+ {0x30,0x07,"Cleaning failure"},
+ {0x30,0x08,"Cannot write - application code mismatch"},
+ {0x30,0x09,"Current session not fixated for append"},
+ {0x30,0x0A,"Cleaning request rejected"},
+ {0x30,0x0B,"Cleaning tape expired"},
+ {0x30,0x0C,"WORM medium - overwrite attempted"},
+ {0x30,0x0D,"WORM medium - integrity check"},
+ {0x30,0x10,"Medium not formatted"},
+ {0x30,0x11,"Incompatible volume type"},
+ {0x30,0x12,"Incompatible volume qualifier"},
+ {0x30,0x13,"Cleaning volume expired"},
+ {0x31,0x00,"Medium format corrupted"},
+ {0x31,0x01,"Format command failed"},
+ {0x31,0x02,"Zoned formatting failed due to spare linking"},
+ {0x31,0x03,"Sanitize command failed"},
+ {0x31,0x04,"Depopulation failed"}, /* spc5r15 */
+ {0x31,0x05,"Depopulation restoration failed"}, /* spc6r02 */
+ {0x32,0x00,"No defect spare location available"},
+ {0x32,0x01,"Defect list update failure"},
+ {0x33,0x00,"Tape length error"},
+ {0x34,0x00,"Enclosure failure"},
+ {0x35,0x00,"Enclosure services failure"},
+ {0x35,0x01,"Unsupported enclosure function"},
+ {0x35,0x02,"Enclosure services unavailable"},
+ {0x35,0x03,"Enclosure services transfer failure"},
+ {0x35,0x04,"Enclosure services transfer refused"},
+ {0x35,0x05,"Enclosure services checksum error"},
+ {0x36,0x00,"Ribbon, ink, or toner failure"},
+ {0x37,0x00,"Rounded parameter"},
+ {0x38,0x00,"Event status notification"},
+ {0x38,0x02,"Esn - power management class event"},
+ {0x38,0x04,"Esn - media class event"},
+ {0x38,0x06,"Esn - device busy class event"},
+ {0x38,0x07,"Thin provisioning soft threshold reached"},
+ {0x38,0x08,"Depopulation interrupted"}, /* spc6r03 */
+ {0x39,0x00,"Saving parameters not supported"},
+ {0x3A,0x00,"Medium not present"},
+ {0x3A,0x01,"Medium not present - tray closed"},
+ {0x3A,0x02,"Medium not present - tray open"},
+ {0x3A,0x03,"Medium not present - loadable"},
+ {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"},
+ {0x3B,0x00,"Sequential positioning error"},
+ {0x3B,0x01,"Tape position error at beginning-of-medium"},
+ {0x3B,0x02,"Tape position error at end-of-medium"},
+ {0x3B,0x03,"Tape or electronic vertical forms unit not ready"},
+ {0x3B,0x04,"Slew failure"},
+ {0x3B,0x05,"Paper jam"},
+ {0x3B,0x06,"Failed to sense top-of-form"},
+ {0x3B,0x07,"Failed to sense bottom-of-form"},
+ {0x3B,0x08,"Reposition error"},
+ {0x3B,0x09,"Read past end of medium"},
+ {0x3B,0x0A,"Read past beginning of medium"},
+ {0x3B,0x0B,"Position past end of medium"},
+ {0x3B,0x0C,"Position past beginning of medium"},
+ {0x3B,0x0D,"Medium destination element full"},
+ {0x3B,0x0E,"Medium source element empty"},
+ {0x3B,0x0F,"End of medium reached"},
+ {0x3B,0x11,"Medium magazine not accessible"},
+ {0x3B,0x12,"Medium magazine removed"},
+ {0x3B,0x13,"Medium magazine inserted"},
+ {0x3B,0x14,"Medium magazine locked"},
+ {0x3B,0x15,"Medium magazine unlocked"},
+ {0x3B,0x16,"Mechanical positioning or changer error"},
+ {0x3B,0x17,"Read past end of user object"},
+ {0x3B,0x18,"Element disabled"},
+ {0x3B,0x19,"Element enabled"},
+ {0x3B,0x1a,"Data transfer device removed"},
+ {0x3B,0x1b,"Data transfer device inserted"},
+ {0x3B,0x1c,"Too many logical objects on partition to support operation"},
+ {0x3B,0x20,"Element static information changed"},
+ {0x3D,0x00,"Invalid bits in identify message"},
+ {0x3E,0x00,"Logical unit has not self-configured yet"},
+ {0x3E,0x01,"Logical unit failure"},
+ {0x3E,0x02,"Timeout on logical unit"},
+ {0x3E,0x03,"Logical unit failed self-test"},
+ {0x3E,0x04,"Logical unit unable to update self-test log"},
+ {0x3F,0x00,"Target operating conditions have changed"},
+ {0x3F,0x01,"Microcode has been changed"},
+ {0x3F,0x02,"Changed operating definition"},
+ {0x3F,0x03,"Inquiry data has changed"},
+ {0x3F,0x04,"Component device attached"},
+ {0x3F,0x05,"Device identifier changed"},
+ {0x3F,0x06,"Redundancy group created or modified"},
+ {0x3F,0x07,"Redundancy group deleted"},
+ {0x3F,0x08,"Spare created or modified"},
+ {0x3F,0x09,"Spare deleted"},
+ {0x3F,0x0A,"Volume set created or modified"},
+ {0x3F,0x0B,"Volume set deleted"},
+ {0x3F,0x0C,"Volume set deassigned"},
+ {0x3F,0x0D,"Volume set reassigned"},
+ {0x3F,0x0E,"Reported luns data has changed"},
+ {0x3F,0x0F,"Echo buffer overwritten"},
+ {0x3F,0x10,"Medium loadable"},
+ {0x3F,0x11,"Medium auxiliary memory accessible"},
+ {0x3F,0x12,"iSCSI IP address added"},
+ {0x3F,0x13,"iSCSI IP address removed"},
+ {0x3F,0x14,"iSCSI IP address changed"},
+ {0x3F,0x15,"Inspect referrals sense descriptors"},
+ {0x3F,0x16,"Microcode has been changed without reset"},
+ {0x3F,0x17,"Zone transition to full"},
+ {0x3F,0x18,"Bind completed"},
+ {0x3F,0x19,"Bind redirected"},
+ {0x3F,0x1A,"Subsidiary binding changed"},
+
+ /*
+ * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries
+ * for ascq > 1. Preferred error message for this group is
+ * "Diagnostic failure on component nn (80h-ffh)".
+ */
+ {0x40,0x00,"Ram failure (should use 40 nn)"},
+ {0x41,0x00,"Data path failure (should use 40 nn)"},
+ {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"},
+
+ {0x43,0x00,"Message error"},
+ {0x44,0x00,"Internal target failure"},
+ {0x44,0x01,"Persistent reservation information lost"},
+ {0x44,0x71,"ATA device failed Set Features"},
+ {0x45,0x00,"Select or reselect failure"},
+ {0x46,0x00,"Unsuccessful soft reset"},
+ {0x47,0x00,"SCSI parity error"},
+ {0x47,0x01,"Data phase CRC error detected"},
+ {0x47,0x02,"SCSI parity error detected during st data phase"},
+ {0x47,0x03,"Information unit iuCRC error detected"},
+ {0x47,0x04,"Asynchronous information protection error detected"},
+ {0x47,0x05,"Protocol service CRC error"},
+ {0x47,0x06,"Phy test function in progress"},
+ {0x47,0x7F,"Some commands cleared by iSCSI protocol event"},
+ {0x48,0x00,"Initiator detected error message received"},
+ {0x49,0x00,"Invalid message error"},
+ {0x4A,0x00,"Command phase error"},
+ {0x4B,0x00,"Data phase error"},
+ {0x4B,0x01,"Invalid target port transfer tag received"},
+ {0x4B,0x02,"Too much write data"},
+ {0x4B,0x03,"Ack/nak timeout"},
+ {0x4B,0x04,"Nak received"},
+ {0x4B,0x05,"Data offset error"},
+ {0x4B,0x06,"Initiator response timeout"},
+ {0x4B,0x07,"Connection lost"},
+ {0x4B,0x08,"Data-in buffer overflow - data buffer size"},
+ {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"},
+ {0x4B,0x0A,"Data-in buffer error"},
+ {0x4B,0x0B,"Data-out buffer overflow - data buffer size"},
+ {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"},
+ {0x4B,0x0D,"Data-out buffer error"},
+ {0x4B,0x0E,"PCIe fabric error"},
+ {0x4B,0x0f,"PCIe completion timeout"},
+ {0x4B,0x10,"PCIe completer abort"},
+ {0x4B,0x11,"PCIe poisoned tlp received"},
+ {0x4B,0x12,"PCIe ecrc check failed"},
+ {0x4B,0x13,"PCIe unsupported request"},
+ {0x4B,0x14,"PCIe acs violation"},
+ {0x4B,0x15,"PCIe tlp prefix blocked"},
+ {0x4C,0x00,"Logical unit failed self-configuration"},
+ /*
+ * ASC 0x4D overridden by an "additional2" array entry
+ * so there is no need to have them here.
+ */
+ /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */
+
+ {0x4E,0x00,"Overlapped commands attempted"},
+ {0x50,0x00,"Write append error"},
+ {0x50,0x01,"Write append position error"},
+ {0x50,0x02,"Position error related to timing"},
+ {0x51,0x00,"Erase failure"},
+ {0x51,0x01,"Erase failure - incomplete erase operation detected"},
+ {0x52,0x00,"Cartridge fault"},
+ {0x53,0x00,"Media load or eject failed"},
+ {0x53,0x01,"Unload tape failure"},
+ {0x53,0x02,"Medium removal prevented"},
+ {0x53,0x03,"Medium removal prevented by data transfer element"},
+ {0x53,0x04,"Medium thread or unthread failure"},
+ {0x53,0x05,"Volume identifier invalid"},
+ {0x53,0x06,"Volume identifier missing"},
+ {0x53,0x07,"Duplicate volume identifier"},
+ {0x53,0x08,"Element status unknown"},
+ {0x53,0x09,"Data transfer device error - load failed"},
+ {0x53,0x0A,"Data transfer device error - unload failed"},
+ {0x53,0x0B,"Data transfer device error - unload missing"},
+ {0x53,0x0C,"Data transfer device error - eject failed"},
+ {0x53,0x0D,"Data transfer device error - library communication failed"},
+ {0x54,0x00,"SCSI to host system interface failure"},
+ {0x55,0x00,"System resource failure"},
+ {0x55,0x01,"System buffer full"},
+ {0x55,0x02,"Insufficient reservation resources"},
+ {0x55,0x03,"Insufficient resources"},
+ {0x55,0x04,"Insufficient registration resources"},
+ {0x55,0x05,"Insufficient access control resources"},
+ {0x55,0x06,"Auxiliary memory out of space"},
+ {0x55,0x07,"Quota error"},
+ {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"},
+ {0x55,0x09,"Medium auxiliary memory not accessible"},
+ {0x55,0x0a,"Data currently unavailable"},
+ {0x55,0x0b,"Insufficient power for operation"},
+ {0x55,0x0c,"Insufficient resources to create rod"},
+ {0x55,0x0d,"Insufficient resources to create rod token"},
+ {0x55,0x0e,"Insufficient zone resources"},
+ {0x55,0x0f,"Insufficient zone resources to complete write"},
+ {0x55,0x10,"Maximum number of streams open"},
+ {0x55,0x11,"Insufficient resources to bind"},
+ {0x57,0x00,"Unable to recover table-of-contents"},
+ {0x58,0x00,"Generation does not exist"},
+ {0x59,0x00,"Updated block read"},
+ {0x5A,0x00,"Operator request or state change input"},
+ {0x5A,0x01,"Operator medium removal request"},
+ {0x5A,0x02,"Operator selected write protect"},
+ {0x5A,0x03,"Operator selected write permit"},
+ {0x5B,0x00,"Log exception"},
+ {0x5B,0x01,"Threshold condition met"},
+ {0x5B,0x02,"Log counter at maximum"},
+ {0x5B,0x03,"Log list codes exhausted"},
+ {0x5C,0x00,"Rpl status change"},
+ {0x5C,0x01,"Spindles synchronized"},
+ {0x5C,0x02,"Spindles not synchronized"},
+ {0x5D,0x00,"Failure prediction threshold exceeded"},
+ {0x5D,0x01,"Media failure prediction threshold exceeded"},
+ {0x5D,0x02,"Logical unit failure prediction threshold exceeded"},
+ {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"},
+ {0x5D,0x10,"Hardware impending failure general hard drive failure"},
+ {0x5D,0x11,"Hardware impending failure drive error rate too high" },
+ {0x5D,0x12,"Hardware impending failure data error rate too high" },
+ {0x5D,0x13,"Hardware impending failure seek error rate too high" },
+ {0x5D,0x14,"Hardware impending failure too many block reassigns"},
+ {0x5D,0x15,"Hardware impending failure access times too high" },
+ {0x5D,0x16,"Hardware impending failure start unit times too high" },
+ {0x5D,0x17,"Hardware impending failure channel parametrics"},
+ {0x5D,0x18,"Hardware impending failure controller detected"},
+ {0x5D,0x19,"Hardware impending failure throughput performance"},
+ {0x5D,0x1A,"Hardware impending failure seek time performance"},
+ {0x5D,0x1B,"Hardware impending failure spin-up retry count"},
+ {0x5D,0x1C,"Hardware impending failure drive calibration retry count"},
+ {0x5D,0x1D,"Hardware impending failure power loss protection circuit"},
+ {0x5D,0x20,"Controller impending failure general hard drive failure"},
+ {0x5D,0x21,"Controller impending failure drive error rate too high" },
+ {0x5D,0x22,"Controller impending failure data error rate too high" },
+ {0x5D,0x23,"Controller impending failure seek error rate too high" },
+ {0x5D,0x24,"Controller impending failure too many block reassigns"},
+ {0x5D,0x25,"Controller impending failure access times too high" },
+ {0x5D,0x26,"Controller impending failure start unit times too high" },
+ {0x5D,0x27,"Controller impending failure channel parametrics"},
+ {0x5D,0x28,"Controller impending failure controller detected"},
+ {0x5D,0x29,"Controller impending failure throughput performance"},
+ {0x5D,0x2A,"Controller impending failure seek time performance"},
+ {0x5D,0x2B,"Controller impending failure spin-up retry count"},
+ {0x5D,0x2C,"Controller impending failure drive calibration retry count"},
+ {0x5D,0x30,"Data channel impending failure general hard drive failure"},
+ {0x5D,0x31,"Data channel impending failure drive error rate too high" },
+ {0x5D,0x32,"Data channel impending failure data error rate too high" },
+ {0x5D,0x33,"Data channel impending failure seek error rate too high" },
+ {0x5D,0x34,"Data channel impending failure too many block reassigns"},
+ {0x5D,0x35,"Data channel impending failure access times too high" },
+ {0x5D,0x36,"Data channel impending failure start unit times too high" },
+ {0x5D,0x37,"Data channel impending failure channel parametrics"},
+ {0x5D,0x38,"Data channel impending failure controller detected"},
+ {0x5D,0x39,"Data channel impending failure throughput performance"},
+ {0x5D,0x3A,"Data channel impending failure seek time performance"},
+ {0x5D,0x3B,"Data channel impending failure spin-up retry count"},
+ {0x5D,0x3C,"Data channel impending failure drive calibration retry count"},
+ {0x5D,0x40,"Servo impending failure general hard drive failure"},
+ {0x5D,0x41,"Servo impending failure drive error rate too high" },
+ {0x5D,0x42,"Servo impending failure data error rate too high" },
+ {0x5D,0x43,"Servo impending failure seek error rate too high" },
+ {0x5D,0x44,"Servo impending failure too many block reassigns"},
+ {0x5D,0x45,"Servo impending failure access times too high" },
+ {0x5D,0x46,"Servo impending failure start unit times too high" },
+ {0x5D,0x47,"Servo impending failure channel parametrics"},
+ {0x5D,0x48,"Servo impending failure controller detected"},
+ {0x5D,0x49,"Servo impending failure throughput performance"},
+ {0x5D,0x4A,"Servo impending failure seek time performance"},
+ {0x5D,0x4B,"Servo impending failure spin-up retry count"},
+ {0x5D,0x4C,"Servo impending failure drive calibration retry count"},
+ {0x5D,0x50,"Spindle impending failure general hard drive failure"},
+ {0x5D,0x51,"Spindle impending failure drive error rate too high" },
+ {0x5D,0x52,"Spindle impending failure data error rate too high" },
+ {0x5D,0x53,"Spindle impending failure seek error rate too high" },
+ {0x5D,0x54,"Spindle impending failure too many block reassigns"},
+ {0x5D,0x55,"Spindle impending failure access times too high" },
+ {0x5D,0x56,"Spindle impending failure start unit times too high" },
+ {0x5D,0x57,"Spindle impending failure channel parametrics"},
+ {0x5D,0x58,"Spindle impending failure controller detected"},
+ {0x5D,0x59,"Spindle impending failure throughput performance"},
+ {0x5D,0x5A,"Spindle impending failure seek time performance"},
+ {0x5D,0x5B,"Spindle impending failure spin-up retry count"},
+ {0x5D,0x5C,"Spindle impending failure drive calibration retry count"},
+ {0x5D,0x60,"Firmware impending failure general hard drive failure"},
+ {0x5D,0x61,"Firmware impending failure drive error rate too high" },
+ {0x5D,0x62,"Firmware impending failure data error rate too high" },
+ {0x5D,0x63,"Firmware impending failure seek error rate too high" },
+ {0x5D,0x64,"Firmware impending failure too many block reassigns"},
+ {0x5D,0x65,"Firmware impending failure access times too high" },
+ {0x5D,0x66,"Firmware impending failure start unit times too high" },
+ {0x5D,0x67,"Firmware impending failure channel parametrics"},
+ {0x5D,0x68,"Firmware impending failure controller detected"},
+ {0x5D,0x69,"Firmware impending failure throughput performance"},
+ {0x5D,0x6A,"Firmware impending failure seek time performance"},
+ {0x5D,0x6B,"Firmware impending failure spin-up retry count"},
+ {0x5D,0x6C,"Firmware impending failure drive calibration retry count"},
+ {0x5D,0x73,"Media impending failure endurance limit met"},
+ {0x5D,0xFF,"Failure prediction threshold exceeded (false)"},
+ {0x5E,0x00,"Low power condition on"},
+ {0x5E,0x01,"Idle condition activated by timer"},
+ {0x5E,0x02,"Standby condition activated by timer"},
+ {0x5E,0x03,"Idle condition activated by command"},
+ {0x5E,0x04,"Standby condition activated by command"},
+ {0x5E,0x05,"Idle_b condition activated by timer"},
+ {0x5E,0x06,"Idle_b condition activated by command"},
+ {0x5E,0x07,"Idle_c condition activated by timer"},
+ {0x5E,0x08,"Idle_c condition activated by command"},
+ {0x5E,0x09,"Standby_y condition activated by timer"},
+ {0x5E,0x0a,"Standby_y condition activated by command"},
+ {0x5E,0x41,"Power state change to active"},
+ {0x5E,0x42,"Power state change to idle"},
+ {0x5E,0x43,"Power state change to standby"},
+ {0x5E,0x45,"Power state change to sleep"},
+ {0x5E,0x47,"Power state change to device control"},
+ {0x60,0x00,"Lamp failure"},
+ {0x61,0x00,"Video acquisition error"},
+ {0x61,0x01,"Unable to acquire video"},
+ {0x61,0x02,"Out of focus"},
+ {0x62,0x00,"Scan head positioning error"},
+ {0x63,0x00,"End of user area encountered on this track"},
+ {0x63,0x01,"Packet does not fit in available space"},
+ {0x64,0x00,"Illegal mode for this track"},
+ {0x64,0x01,"Invalid packet size"},
+ {0x65,0x00,"Voltage fault"},
+ {0x66,0x00,"Automatic document feeder cover up"},
+ {0x66,0x01,"Automatic document feeder lift up"},
+ {0x66,0x02,"Document jam in automatic document feeder"},
+ {0x66,0x03,"Document miss feed automatic in document feeder"},
+ {0x67,0x00,"Configuration failure"},
+ {0x67,0x01,"Configuration of incapable logical units failed"},
+ {0x67,0x02,"Add logical unit failed"},
+ {0x67,0x03,"Modification of logical unit failed"},
+ {0x67,0x04,"Exchange of logical unit failed"},
+ {0x67,0x05,"Remove of logical unit failed"},
+ {0x67,0x06,"Attachment of logical unit failed"},
+ {0x67,0x07,"Creation of logical unit failed"},
+ {0x67,0x08,"Assign failure occurred"},
+ {0x67,0x09,"Multiply assigned logical unit"},
+ {0x67,0x0A,"Set target port groups command failed"},
+ {0x67,0x0B,"ATA device feature not enabled"},
+ {0x67,0x0C,"Command rejected"},
+ {0x67,0x0D,"Explicit bind not allowed"},
+ {0x68,0x00,"Logical unit not configured"},
+ {0x68,0x01,"Subsidiary logical unit not configured"},
+ {0x69,0x00,"Data loss on logical unit"},
+ {0x69,0x01,"Multiple logical unit failures"},
+ {0x69,0x02,"Parity/data mismatch"},
+ {0x6A,0x00,"Informational, refer to log"},
+ {0x6B,0x00,"State change has occurred"},
+ {0x6B,0x01,"Redundancy level got better"},
+ {0x6B,0x02,"Redundancy level got worse"},
+ {0x6C,0x00,"Rebuild failure occurred"},
+ {0x6D,0x00,"Recalculate failure occurred"},
+ {0x6E,0x00,"Command to logical unit failed"},
+ {0x6F,0x00,"Copy protection key exchange failure - authentication "
+ "failure"},
+ {0x6F,0x01,"Copy protection key exchange failure - key not present"},
+ {0x6F,0x02,"Copy protection key exchange failure - key not established"},
+ {0x6F,0x03,"Read of scrambled sector without authentication"},
+ {0x6F,0x04,"Media region code is mismatched to logical unit region"},
+ {0x6F,0x05,"Drive region must be permanent/region reset count error"},
+ {0x6F,0x06,"Insufficient block count for binding nonce recording"},
+ {0x6F,0x07,"Conflict in binding nonce recording"},
+ {0x6F,0x08,"Insufficient permission"},
+ {0x6F,0x09,"Invalid drive-host pairing server"},
+ {0x6F,0x0A,"Drive-host pairing suspended"},
+ /*
+ * ASC 0x70 overridden by an "additional2" array entry
+ * so there is no need to have them here.
+ */
+ /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */
+
+ {0x71,0x00,"Decompression exception long algorithm id"},
+ {0x72,0x00,"Session fixation error"},
+ {0x72,0x01,"Session fixation error writing lead-in"},
+ {0x72,0x02,"Session fixation error writing lead-out"},
+ {0x72,0x03,"Session fixation error - incomplete track in session"},
+ {0x72,0x04,"Empty or partially written reserved track"},
+ {0x72,0x05,"No more track reservations allowed"},
+ {0x72,0x06,"RMZ extension is not allowed"},
+ {0x72,0x07,"No more test zone extensions are allowed"},
+ {0x73,0x00,"CD control error"},
+ {0x73,0x01,"Power calibration area almost full"},
+ {0x73,0x02,"Power calibration area is full"},
+ {0x73,0x03,"Power calibration area error"},
+ {0x73,0x04,"Program memory area update failure"},
+ {0x73,0x05,"Program memory area is full"},
+ {0x73,0x06,"RMA/PMA is almost full"},
+ {0x73,0x10,"Current power calibration area almost full"},
+ {0x73,0x11,"Current power calibration area is full"},
+ {0x73,0x17,"RDZ is full"},
+ {0x74,0x00,"Security error"},
+ {0x74,0x01,"Unable to decrypt data"},
+ {0x74,0x02,"Unencrypted data encountered while decrypting"},
+ {0x74,0x03,"Incorrect data encryption key"},
+ {0x74,0x04,"Cryptographic integrity validation failed"},
+ {0x74,0x05,"Error decrypting data"},
+ {0x74,0x06,"Unknown signature verification key"},
+ {0x74,0x07,"Encryption parameters not usable"},
+ {0x74,0x08,"Digital signature validation failure"},
+ {0x74,0x09,"Encryption mode mismatch on read"},
+ {0x74,0x0a,"Encrypted block not raw read enabled"},
+ {0x74,0x0b,"Incorrect Encryption parameters"},
+ {0x74,0x0c,"Unable to decrypt parameter list"},
+ {0x74,0x0d,"Encryption algorithm disabled"},
+ {0x74,0x10,"SA creation parameter value invalid"},
+ {0x74,0x11,"SA creation parameter value rejected"},
+ {0x74,0x12,"Invalid SA usage"},
+ {0x74,0x21,"Data encryption configuration prevented"},
+ {0x74,0x30,"SA creation parameter not supported"},
+ {0x74,0x40,"Authentication failed"},
+ {0x74,0x61,"External data encryption key manager access error"},
+ {0x74,0x62,"External data encryption key manager error"},
+ {0x74,0x63,"External data encryption key not found"},
+ {0x74,0x64,"External data encryption request not authorized"},
+ {0x74,0x6e,"External data encryption control timeout"},
+ {0x74,0x6f,"External data encryption control error"},
+ {0x74,0x71,"Logical unit access not authorized"},
+ {0x74,0x79,"Security conflict in translated device"},
+ {0, 0, NULL}
+};
+
+#else /* SG_SCSI_STRINGS */
+
+struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
+{
+ {0, 0, 0, NULL}
+};
+
+struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
+{
+ {0, 0, NULL}
+};
+#endif /* SG_SCSI_STRINGS */
+
+const char * sg_lib_sense_key_desc[] = {
+ "No Sense", /* Filemark, ILI and/or EOM; progress
+ indication (during FORMAT); power
+ condition sensing (REQUEST SENSE) */
+ "Recovered Error", /* The last command completed successfully
+ but used error correction */
+ "Not Ready", /* The addressed target is not ready */
+ "Medium Error", /* Data error detected on the medium */
+ "Hardware Error", /* Controller or device failure */
+ "Illegal Request",
+ "Unit Attention", /* Removable medium was changed, or
+ the target has been reset */
+ "Data Protect", /* Access to the data is blocked */
+ "Blank Check", /* Reached unexpected written or unwritten
+ region of the medium */
+ "Vendor specific(9)", /* Vendor specific */
+ "Copy Aborted", /* COPY or COMPARE was aborted */
+ "Aborted Command", /* The target aborted the command */
+ "Equal", /* SEARCH DATA found data equal (obsolete) */
+ "Volume Overflow", /* Medium full with data to be written */
+ "Miscompare", /* Source data and data on the medium
+ do not agree */
+ "Completed" /* may occur for successful cmd (spc4r23) */
+};
+
+const char * sg_lib_pdt_strs[32] = { /* should have 2**5 elements */
+ /* 0 */ "disk",
+ "tape",
+ "printer", /* obsolete, spc5r01 */
+ "processor", /* often SAF-TE device, copy manager */
+ "write once optical disk", /* obsolete, spc5r01 */
+ /* 5 */ "cd/dvd",
+ "scanner", /* obsolete */
+ "optical memory device",
+ "medium changer",
+ "communications", /* obsolete */
+ /* 0xa */ "graphics [0xa]", /* obsolete */
+ "graphics [0xb]", /* obsolete */
+ "storage array controller",
+ "enclosure services device",
+ "simplified direct access device",
+ "optical card reader/writer device",
+ /* 0x10 */ "bridge controller commands",
+ "object based storage",
+ "automation/driver interface",
+ "security manager device", /* obsolete, spc5r01 */
+ "host managed zoned block",
+ "0x15", "0x16", "0x17", "0x18",
+ "0x19", "0x1a", "0x1b", "0x1c", "0x1d",
+ "well known logical unit",
+ "unknown or no device type", /* coupled with PQ=3 for not accessible
+ via this lu's port (try the other) */
+};
+
+const char * sg_lib_transport_proto_strs[] =
+{
+ "Fibre Channel Protocol for SCSI (FCP-5)", /* now at fcp5r01 */
+ "SCSI Parallel Interface (SPI-5)", /* obsolete in spc5r01 */
+ "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)",
+ "Serial Bus Protocol for IEEE 1394 (SBP-3)",
+ "SCSI RDMA Protocol (SRP)",
+ "Internet SCSI (iSCSI)",
+ "Serial Attached SCSI Protocol (SPL-4)",
+ "Automation/Drive Interface Transport (ADT-2)",
+ "AT Attachment Interface (ACS-2)", /* 0x8 */
+ "USB Attached SCSI (UAS-2)",
+ "SCSI over PCI Express (SOP)",
+ "PCIe", /* added in spc5r02 */
+ "Oxc", "Oxd", "Oxe",
+ "No specific protocol"
+};
+
+/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */
+struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] =
+{
+ {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"},
+ {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"},
+ {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"},
+ {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"},
+ {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"},
+ {SCSI_FS_ZBC_HOST_AWARE_2020, PDT_DISK_ZBC, "Host Aware 2020"},
+ {SCSI_FS_ZBC_HOST_MANAGED_2020, PDT_DISK_ZBC, "Host Managed 2020"},
+ {SCSI_FS_ZBC_DOMAINS_REALMS_2020, PDT_DISK_ZBC, "Domains and Realms 2020"},
+ {0x0, 0, NULL}, /* 0x0 is reserved sfs; trailing sentinel */
+};
+
+#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME))
+
+/* Commands sent to the NVMe Admin Queue (queue id 0) have the following
+ * names in the NVM Express 1.3a document dated 20171024 */
+struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[] =
+{
+ {0x0, "Delete I/O Submission Queue"}, /* first mandatory command */
+ {0x1, "Create I/O Submission Queue"},
+ {0x2, "Get Log Page"},
+ {0x4, "Delete I/O Completion Queue"},
+ {0x5, "Create I/O Completion Queue"},
+ {0x6, "Identify"},
+ {0x8, "Abort"},
+ {0x9, "Set Features"},
+ {0xa, "Get Features"},
+ {0xc, "Asynchronous Event Request"}, /* last mandatory command */
+ {0xd, "Namespace Management"}, /* first optional command */
+ {0x10, "Firmware commit"},
+ {0x11, "Firmware image download"},
+ {0x14, "Device Self-test"},
+ {0x15, "Namespace Attachment"},
+ {0x18, "Keep Alive"},
+ {0x19, "Directive Send"},
+ {0x1a, "Directive Receive"},
+ {0x1c, "Virtualization Management"},
+ {0x1d, "NVMe-MI Send"}, /* SES SEND DIAGNOSTIC cmd passes thru here */
+ {0x1e, "NVMe-MI Receive"}, /* RECEIVE DIAGNOSTIC RESULTS thru here */
+ {0x7c, "Doorbell Buffer Config"},
+ {0x7f, "NVMe over Fabrics"},
+
+ /* I/O command set specific 0x80 to 0xbf */
+ {0x80, "Format NVM"}, /* first NVM specific */
+ {0x81, "Security Send"},
+ {0x82, "Security Receive"},
+ {0x84, "Sanitize"}, /* last NVM specific in 1.3a */
+ {0x86, "Get LBA status"}, /* NVM specific, new in 1.4 */
+ /* Vendor specific 0xc0 to 0xff */
+ {0xffff, NULL}, /* Sentinel */
+};
+
+/* Commands sent any NVMe non-Admin Queue (queue id >0) for the NVM command
+ * set have the following names in the NVM Express 1.3a document dated
+ * 20171024 */
+struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[] =
+{
+ {0x0, "Flush"}, /* first mandatory command */
+ {0x1, "Write"},
+ {0x2, "Read"}, /* last mandatory command */
+ {0x4, "Write Uncorrectable"}, /* first optional command */
+ {0x5, "Compare"},
+ {0x8, "Write Zeroes"},
+ {0x9, "Dataset Management"},
+ {0xd, "Reservation Register"},
+ {0xe, "Reservation Report"},
+ {0x11, "Reservation Acquire"},
+ {0x15, "Reservation Release"}, /* last optional command in 1.3a */
+
+ /* Vendor specific 0x80 to 0xff */
+ {0xffff, NULL}, /* Sentinel */
+};
+
+
+/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff)
+ * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[]
+ * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less
+ * capitalization.
+ * NVMe term bits 31:17 of DW3 in the completion field as the "Status
+ * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M).
+ * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT)
+ * and bits 24:17 are the Status Code (SC). This table is in ascending
+ * order of its .value field so a binary search could be done on it. */
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+ /* Generic command status values, Status Code Type (SCT): 0h
+ * Lowest 8 bits are the Status Code (SC), in this case:
+ * 00h - 7Fh: Applicable to Admin Command Set, or across multiple
+ * command sets
+ * 80h - BFh: I/O Command Set Specific status codes
+ * c0h - FFh: I/O Vendor Specific status codes */
+ {0x0, 0, "Successful completion"},
+ {0x1, 1, "Invalid command opcode"},
+ {0x2, 2, "Invalid field in command"},
+ {0x3, 2, "Command id conflict"},
+ {0x4, 3, "Data transfer error"},
+ {0x5, 4, "Command aborted due to power loss notification"},
+ {0x6, 5, "Internal error"},
+ {0x7, 6, "Command abort requested"},
+ {0x8, 6, "Command aborted due to SQ deletion"},
+ {0x9, 6, "Command aborted due to failed fused command"},
+ {0xa, 6, "Command aborted due to missing fused command"},
+ {0xb, 7, "Invalid namespace or format"},
+ {0xc, 5, "Command sequence error"},
+ {0xd, 5, "Invalid SGL segment descriptor"},
+ {0xe, 5, "Invalid number of SGL descriptors"},
+ {0xf, 5, "Data SGL length invalid"},
+ {0x10, 5, "Metadata SGL length invalid"},
+ {0x11, 5, "SGL descriptor type invalid"},
+ {0x12, 5, "Invalid use of controller memory buffer"},
+ {0x13, 5, "PRP offset invalid"},
+ {0x14, 2, "Atomic write unit exceeded"},
+ {0x15, 8, "Operation denied"},
+ {0x16, 5, "SGL offset invalid"},
+ {0x17, 5, "Reserved [0x17]"},
+ {0x18, 5, "Host identifier inconsistent format"},
+ {0x19, 5, "Keep alive timeout expired"},
+ {0x1a, 5, "Keep alive timeout invalid"},
+ {0x1b, 6, "Command aborted due to Preempt and Abort"},
+ {0x1c, 10, "Sanitize failed"},
+ {0x1d, 11, "Sanitize in progress"},
+ {0x1e, 5, "SGL data block granularity invalid"},
+ {0x1f, 5, "Command not supported for queue in CMB"},
+ {0x20, 18, "Namespace is write protected"}, /* NVMe 1.4 */
+ {0x21, 6, "Command interrupted"}, /* NVMe 1.4 */
+ {0x22, 5, "Transient transport error"}, /* NVMe 1.4 */
+ {0x23, 5, "Prohibited by lockdown"}, /* NVMe 2.0 */
+ {0x24, 5, "Admin command: media not ready"}, /* NVMe 2.0 */
+
+ /* 0x80 - 0xbf: I/O command set specific */
+ /* Command specific status values, NVM (I/O) Command Set */
+ {0x80, 12, "LBA out of range"},
+ {0x81, 3, "Capacity exceeded"},
+ {0x82, 13, "Namespace not ready"},
+ {0x83, 14, "Reservation conflict"},
+ {0x84, 15, "Format in progress"},
+ {0x85, 2, "Invalid value size"},
+ {0x86, 2, "Invalid key size"},
+ {0x87, 2, "KV key does not exist"},
+ {0x88, 15, "Unrecovered error"},
+ {0x89, 2, "Key exists"},
+
+ /* Command specific status values, ZNS (NVM) Command Set */
+ {0xb8, 0x1f, "Zone boundary error"},
+ {0xb9, 0x2, "Zone is full"},
+ {0xba, 0x1b, "Zone is read only"},
+ {0xbb, 0x1c, "Zone is offline"},
+ {0xbc, 2, "Zone invalid write"},
+ {0xbd, 0x20, "Too many active zones"},
+ {0xbe, 0x20, "Too many open zones"},
+ {0xbf, 2, "Invalid zone state transition"},
+ /* 0xc0 - 0xff: vendor specific */
+
+ /* Command specific status values, Status Code Type (SCT): 1h */
+ {0x100, 5, "Completion queue invalid"},
+ {0x101, 5, "Invalid queue identifier"},
+ {0x102, 5, "Invalid queue size"},
+ {0x103, 5, "Abort command limit exceeded"},
+ {0x104, 5, "Reserved [0x104]"},
+ {0x105, 5, "Asynchronous event request limit exceeded"},
+ {0x106, 5, "Invalid firmware slot"},
+ {0x107, 5, "Invalid firmware image"},
+ {0x108, 5, "Invalid interrupt vector"},
+ {0x109, 5, "Invalid log page"},
+ {0x10a,16, "Invalid format"},
+ {0x10b, 5, "Firmware activation requires conventional reset"},
+ {0x10c, 5, "Invalid queue deletion"},
+ {0x10d, 5, "Feature identifier not saveable"},
+ {0x10e, 5, "Feature not changeable"},
+ {0x10f, 5, "Feature not namespace specific"},
+ {0x110, 5, "Firmware activation requires NVM subsystem reset"},
+ {0x111, 5, "Firmware activation requires reset"},
+ {0x112, 5, "Firmware activation requires maximum time violation"},
+ {0x113, 5, "Firmware activation prohibited"},
+ {0x114, 5, "Overlapping range"},
+ {0x115, 5, "Namespace insufficient capacity"},
+ {0x116, 5, "Namespace identifier unavailable"},
+ {0x117, 5, "Reserved [0x107]"},
+ {0x118, 5, "Namespace already attached"},
+ {0x119, 5, "Namespace is private"},
+ {0x11a, 5, "Namespace not attached"},
+ {0x11b, 3, "Thin provisioning not supported"},
+ {0x11c, 3, "Controller list invalid"},
+ {0x11d,17, "Device self-test in progress"},
+ {0x11e,18, "Boot partition write prohibited"},
+ {0x11f, 5, "Invalid controller identifier"},
+ {0x120, 5, "Invalid secondary controller state"},
+ {0x121, 5, "Invalid number of controller resources"},
+ {0x122, 5, "Invalid resource identifier"},
+ {0x123, 5, "Sanitize prohibited while PM enabled"}, /* NVMe 1.4 */
+ {0x124, 5, "ANA group identifier invalid"}, /* NVMe 1.4 */
+ {0x125, 5, "ANA attach failed"}, /* NVMe 1.4 */
+
+ /* Command specific status values, Status Code Type (SCT): 1h
+ * for NVM (I/O) Command Set */
+ {0x180, 2, "Conflicting attributes"},
+ {0x181,19, "Invalid protection information"},
+ {0x182,18, "Attempted write to read only range"},
+ /* 0x1c0 - 0x1ff: vendor specific */
+
+ /* Media and Data Integrity error values, Status Code Type (SCT): 2h */
+ {0x280,20, "Write fault"},
+ {0x281,21, "Unrecovered read error"},
+ {0x282,22, "End-to-end guard check error"},
+ {0x283,23, "End-to-end application tag check error"},
+ {0x284,24, "End-to-end reference tag check error"},
+ {0x285,25, "Compare failure"},
+ {0x286, 8, "Access denied"},
+ {0x287,26, "Deallocated or unwritten logical block"},
+ /* 0x2c0 - 0x2ff: vendor specific */
+
+ /* Leave this Sentinel value at end of this array */
+ {0x3ff, 0, NULL},
+};
+
+/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index
+ * to this array. It allows an NVMe status (error) value to be mapped
+ * to this SCSI tuple: status, sense_key, additional sense code (asc) and
+ * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written
+ * as 0x2. */
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+ /* SCSI Status, SCSI sense key, ASC, ASCQ */
+/* index: 0 */
+ {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0}, /* it's all good */
+ {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0}, /* field in cdb */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0},
+ {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8},
+ {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0}, /* internal error */
+ {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0},
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9}, /* invalid LU */
+
+/* index: 8 */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2}, /* access denied */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0}, /* cmd sequence error */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3}, /* sanitize failed */ /* 10 */
+ {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0}, /* LBA out of range */
+ {0x2, SPC_SK_NOT_READY, 0x4, 0x0}, /* not reportable; 0x1: becoming */
+ {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0},
+ {0x2, SPC_SK_NOT_READY, 0x4, 0x4}, /* format in progress */
+
+/* index: 0x10 */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1}, /* format failed */
+ {0x2, SPC_SK_NOT_READY, 0x4, 0x9}, /* self-test in progress */
+ {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0}, /* write prohibited */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5}, /* protection info */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0}, /* unrecoc rd */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1}, /* PI guard */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */
+
+/* index: 0x18 */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x3}, /* PI reference tag */
+ {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0}, /* during verify */
+ {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6}, /* read invalid data */
+ {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x8}, /* zone is read only */
+ {0x2, SPC_SK_DATA_PROTECT, 0x2c, 0xe}, /* zone is offline */
+ {0x2, SPC_SK_DATA_PROTECT, 0x2c, 0x12}, /* zone is inactive */
+ {0x2, SPC_SK_DATA_PROTECT, 0x3f, 0x17}, /* zone is full */
+ {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x5}, /* Write boundary violation */
+
+/* index: 0x20 */
+ {0x2, SPC_SK_DATA_PROTECT, 0x55, 0xe}, /* Insufficient zone resources */
+
+ /* Leave this Sentinel value at end of this array */
+ {0xff, 0xff, 0xff, 0xff},
+};
+
+/* These are the error (or warning) exit status values and their associated
+ * strings. They combine utility input syntax errors, SCSI status and sense
+ * key categories, OS errors (e.g. ENODEV for device not found), one that
+ * indicates NVMe non-zero status plus listing those that a Unix OS generates
+ * for any executable (that fails). The convention is 0 means no error and
+ * that in Unix the exit status is an (unsigned) 8 bit value. */
+struct sg_value_2names_t sg_exit_str_arr[] = {
+ {0, "No errors", "may also convey true"},
+ {1, "Syntax error", "command line options (usually)"},
+ {2, "Device not ready", "type: sense key"},
+ {3, "Medium or hardware error", "type: sense key (plus blank check for "
+ "tape)"},
+ {5, "Illegal request", "type: sense key, apart from Invalid opcode"},
+ {6, "Unit attention", "type: sense key"},
+ {7, "Data protect", "type: sense key; write protected media?"},
+ {9, "Illegal request, Invalid opcode", "type: sense key + asc,ascq"},
+ {10, "Copy aborted", "type: sense key"},
+ {11, "Aborted command",
+ "type: sense key, other than protection related (asc=0x10)"},
+ {12, "Device not ready, standby", "type: sense key"},
+ {13, "Device not ready, unavailable", "type: sense key"},
+ {14, "Miscompare", "type: sense key"},
+ {15, "File error", NULL},
+ {17, "Illegal request with Info field", NULL},
+ {18, "Medium or hardware error with Info", NULL},
+ {20, "No sense key", "type: probably additional sense code"},
+ {21, "Recovered error (warning)", "type: sense key"},
+ /* N.B. this is a warning not error */
+ {22, "LBA out of range", NULL},
+ {24, "Reservation conflict", "type: SCSI status"},
+ {25, "Condition met", "type: SCSI status"}, /* from PRE-FETCH command */
+ {26, "Busy", "type: SCSI status"}, /* could be transport issue */
+ {27, "Task set full", "type: SCSI status"},
+ {28, "ACA aactive", "type: SCSI status"},
+ {29, "Task aborted", "type: SCSI status"},
+ {31, "Contradict", "command line options contradict or select bad mode"},
+ {32, "Logic error", "unexpected situation, contact author"},
+ {33, "SCSI command timeout", NULL}, /* OS timed out command */
+ {34, "Windows error number", "doesn't fit in 7 bits"},
+ {35, "Transport error", "driver or interconnect error"},
+ {36, "No errors (false)", NULL},
+ {40, "Aborted command, protection error", NULL},
+ {41, "Aborted command, protection error with Info field", NULL},
+ {47, "flock (Unix system call) error", NULL}, /* ddpt */
+ {48, "NVMe command with non-zero status", NULL},
+ {50, "An OS error occurred", "(errno > 46 or negative)"},
+ /* OS errors (errno in Unix) from 1 to 46 mapped into this range */
+ {97, "Malformed SCSI command", "trouble building command"},
+ {98, "Some other sense error", "try '-v' option for more information"},
+ {99, "Some other error", "possible transport of driver issue"},
+ {100, "Parameter list length error", NULL}, /* these for ddpt, xcopy */
+ {101, "Invalid field in parameter", NULL},
+ {102, "Too many segments in parameters", NULL},
+ {103, "Target underrun", NULL},
+ {104, "Target overrun", NULL},
+ {105, "Operation in progress", NULL},
+ {106, "Insufficient resources to create ROD", NULL},
+ {107, "Insufficient resources to create ROD token", NULL},
+ {108, "Commands cleared by device server", NULL},
+ {109, "See leave_reason for error", NULL}, /* internal error */
+ /* DDPT_CAT_TOKOP_BASE: asc=0x23, ascq=110 follow */
+ {110, "Invalid token operation, cause not reportable", NULL},
+ {111, "Invalid token operation, unsupported token type", NULL},
+ {112, "Invalid token operation, remote token usage not supported", NULL},
+ {113, "Invalid token operation, remote token creation not supported",
+ NULL},
+ {114, "Invalid token operation, token unknown", NULL},
+ {115, "Invalid token operation, token corrupt", NULL},
+ {116, "Invalid token operation, token revoked", NULL},
+ {117, "Invalid token operation, token expired", NULL},
+ {118, "Invalid token operation, token cancelled", NULL},
+ {119, "Invalid token operation, token deleted", NULL},
+ {120, "Invalid token operation, invalid token length", NULL},
+
+ /* The following error codes are generated by a Unix OS */
+ {126, "Utility found but did not have execute permissions", NULL},
+ {127, "Utility to be executed was not found", NULL},
+ {128, "Utility stopped/aborted by signal number: 0", "signal # 0 ??"},
+ /* 128 + <signal_number>: signal number that aborted the utility.
+ real time signals start at offset SIGRTMIN */
+ /* OS signals from 1 to 126 mapped into this range (129 to 254) */
+ {255, "Utility returned 255 or higher", "Windows error number?"},
+ {0xffff, NULL, NULL}, /* end marking sentinel */
+};
+
+#else /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
+
+struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[] =
+{
+
+ /* Vendor specific 0x80 to 0xff */
+ {0xffff, NULL}, /* Sentinel */
+};
+
+struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[] =
+{
+
+ /* Vendor specific 0x80 to 0xff */
+ {0xffff, NULL}, /* Sentinel */
+};
+
+struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] =
+{
+
+ /* Leave this Sentinel value at end of this array */
+ {0x3ff, 0, NULL},
+};
+
+struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] =
+{
+
+ /* Leave this Sentinel value at end of this array */
+ {0xff, 0xff, 0xff, 0xff},
+};
+
+struct sg_value_2names_t sg_exit_str_arr[] = {
+ {0xffff, NULL, NULL}, /* end marking sentinel */
+};
+
+#endif /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/lib/sg_lib_names.c b/lib/sg_lib_names.c
new file mode 100644
index 00000000..19ffa441
--- /dev/null
+++ b/lib/sg_lib_names.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#else
+#define SG_SCSI_STRINGS 1
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_lib_names.h"
+
+/* List of SPC, then SBC, the ZBC mode page names. Tape and other mode pages
+ * are squeezed into this list as long as they don't conflict.
+ * The value is: (mode_page << 8) | mode_subpage
+ * Maintain the list in numerical order to allow binary search. */
+struct sg_lib_simple_value_name_t sg_lib_names_mode_arr[] = {
+ {0x0000, "Unit Attention condition"}, /* common vendor specific page */
+ {0x0100, "Read-Write error recovery"}, /* SBC */
+ {0x0200, "Disconnect-Reconnect"}, /* SPC */
+ {0x0300, "Format (obsolete)"}, /* SBC */
+ {0x0400, "Rigid disk geometry (obsolete)"}, /* SBC */
+ {0x0500, "Flexible disk (obsolete)"}, /* SBC */
+ {0x0700, "Verify error recovery"}, /* SBC */
+ {0x0800, "Caching"}, /* SBC */
+ {0x0900, "Peripheral device (obsolete)"}, /* SPC */
+ {0x0a00, "Control"}, /* SPC */
+ {0x0a01, "Control extension"}, /* SPC */
+ {0x0a02, "Application tag"}, /* SBC */
+ {0x0a03, "Command duration limit A"}, /* SPC */
+ {0x0a04, "Command duration limit B"}, /* SPC */
+ {0x0a05, "IO Advice Hints Grouping"}, /* SBC */
+ {0x0a06, "Background operation control"}, /* SBC */
+ {0x0af0, "Control data protection"}, /* SSC */
+ {0x0af1, "PATA control"}, /* SAT */
+ {0x0b00, "Medium Types Supported (obsolete)"}, /* SSC */
+ {0x0c00, "Notch and partition (obsolete)"}, /* SBC */
+ {0x0d00, "Power condition (obsolete), CD device parameters"},
+ {0x0e00, "CD audio control"}, /* MMC */
+ {0x0e01, "Target device"}, /* ADC */
+ {0x0e02, "DT device primary port"}, /* ADC */
+ {0x0e03, "Logical unit"}, /* ADC */
+ {0x0e04, "Target device serial number"}, /* ADC */
+ {0x0f00, "Data compression"}, /* SSC */
+ {0x1000, "XOR control (obsolete, Device configuration"}, /* SBC,SSC */
+ {0x1001, "Device configuration extension"}, /* SSC */
+ {0x1100, "Medium partition (1)"}, /* SSC */
+ {0x1400, "Enclosure services management"}, /* SES */
+ {0x1800, "Protocol specific logical unit"}, /* transport */
+ {0x1900, "Protocol specific port"}, /* transport */
+ {0x1901, "Phy control and discovery"}, /* SPL */
+ {0x1902, "Shared port control"}, /* SPL */
+ {0x1903, "Enhanced phy control"}, /* SPL */
+ {0x1904, "Out of band management control"}, /* SPL */
+ {0x1A00, "Power condition"}, /* SPC */
+ {0x1A01, "Power consumption"}, /* SPC */
+ {0x1Af1, "ATA Power condition"}, /* SPC */
+ {0x1b00, "LUN mapping"}, /* ADC */
+ {0x1c00, "Information exceptions control"}, /* SPC */
+ {0x1c01, "Background control"}, /* SBC */
+ {0x1c02, "Logical block provisioning"}, /* SBC */
+ {0x1c02, "Logical block provisioning"}, /* SBC */
+ {0x1d00, "Medium configuration, CD/DVD timeout, "
+ "element address assignments"}, /* SSC,MMC,SMC */
+ {0x1e00, "Transport geometry assignments"}, /* SMC */
+ {0x1f00, "Device capabilities"}, /* SMC */
+
+ {-1, NULL}, /* sentinel */
+};
+
+/* Don't count sentinel when doing binary searches, etc */
+const size_t sg_lib_names_mode_len =
+ SG_ARRAY_SIZE(sg_lib_names_mode_arr) - 1;
+
+/* List of SPC, then SBC, the ZBC VPD page names. Tape and other VPD pages
+ * are squeezed into this list as long as they don't conflict.
+ * For VPDs > 0 the value is: (vpd << 8) | vpd_number
+ * Maintain the list in numerical order to allow binary search. */
+struct sg_lib_simple_value_name_t sg_lib_names_vpd_arr[] = {
+ {0x00, "Supported VPD pages"}, /* SPC */
+ {0x80, "Unit serial number"}, /* SPC */
+ {0x81, "Implemented operating definition (obsolete)"}, /* SPC */
+ {0x82, "ASCII implemented operating definition (obsolete)"}, /* SPC */
+ {0x83, "Device identification"}, /* SPC */
+ {0x84, "Software interface identification"}, /* SPC */
+ {0x85, "Management network addresses"}, /* SPC */
+ {0x86, "Extended INQUIRY data"}, /* SPC */
+ {0x87, "Mode page policy"}, /* SPC */
+ {0x88, "SCSI ports"}, /* SPC */
+ {0x89, "ATA information"}, /* SAT */
+ {0x8a, "Power condition"}, /* SPC */
+ {0x8b, "Device constituents"}, /* SSC */
+ {0x8c, "CFA profile information"}, /* SPC */
+ {0x8d, "Power consumption"}, /* SPC */
+ {0x8f, "Third party copy"}, /* SPC */
+ {0x90, "Protocol specific logical unit information"}, /* transport */
+ {0x91, "Protocol specific port information"}, /* transport */
+ {0x92, "SCSI feature sets"}, /* SPC,SBC */
+ {0xb0, "Block limits"}, /* SBC */
+ {0xb1, "Block device characteristics"}, /* SBC */
+ {0xb2, "Logical block provisioning"}, /* SBC */
+ {0xb3, "Referrals"}, /* SBC */
+ {0xb4, "Supported Block Lengths and Protection Types"}, /* SBC */
+ {0xb5, "Block device characteristics extension"}, /* SBC */
+ {0xb6, "Zoned block device characteristics"}, /* ZBC */
+ {0xb7, "Block limits extension"}, /* SBC */
+ {0xb8, "Format presets"}, /* SBC */
+ {0xb9, "Concurrent positioning ranges"}, /* SBC */
+ {0x01b0, "Sequential access Device Capabilities"}, /* SSC */
+ {0x01b1, "Manufacturer-assigned serial number"}, /* SSC */
+ {0x01b2, "TapeAlert supported flags"}, /* SSC */
+ {0x01b3, "Automation device serial number"}, /* SSC */
+ {0x01b4, "Data transfer device element address"}, /* SSC */
+ {0x01b5, "Data transfer device element address"}, /* SSC */
+ {0x11b0, "OSD information"}, /* OSD */
+ {0x11b1, "Security token"}, /* OSD */
+
+ {-1, NULL}, /* sentinel */
+};
+
+/* Don't count sentinel when doing binary searches, etc */
+const size_t sg_lib_names_vpd_len =
+ SG_ARRAY_SIZE(sg_lib_names_vpd_arr) - 1;
diff --git a/lib/sg_pr2serr.c b/lib/sg_pr2serr.c
new file mode 100644
index 00000000..ef533967
--- /dev/null
+++ b/lib/sg_pr2serr.c
@@ -0,0 +1,2026 @@
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "sg_pr2serr.h"
+#include "sg_json_builder.h"
+
+/*
+ * Some users of sg_pr2serr may not need fixed and descriptor sense decoded
+ * for JSON output. If the following define is commented out the effective
+ * compile size of this file is reduced by 800 lines plus dependencies on
+ * other large components of the sg3_utils library.
+ * Comment out the next line to remove dependency on sg_lib.h and its code.
+ */
+#define SG_PRSE_SENSE_DECODE 1
+
+#ifdef SG_PRSE_SENSE_DECODE
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_unaligned.h"
+#endif
+
+
+#define sgj_opts_ev "SG3_UTILS_JSON_OPTS"
+
+/*
+ * #define json_serialize_mode_multiline 0
+ * #define json_serialize_mode_single_line 1
+ * #define json_serialize_mode_packed 2
+ *
+ * #define json_serialize_opt_CRLF (1 << 1)
+ * #define json_serialize_opt_pack_brackets (1 << 2)
+ * #define json_serialize_opt_no_space_after_comma (1 << 3)
+ * #define json_serialize_opt_no_space_after_colon (1 << 4)
+ * #define json_serialize_opt_use_tabs (1 << 5)
+ */
+
+
+static const json_serialize_opts def_out_settings = {
+ json_serialize_mode_multiline, /* one of serialize_mode_* */
+ 0, /* serialize_opt_* OR-ed together */
+ 4 /* indent size */
+};
+
+static int sgj_name_to_snake(const char * in, char * out, int maxlen_out);
+
+
+/* Users of the sg_pr2serr.h header need this function definition */
+int
+pr2serr(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+#ifndef SG_PRSE_SENSE_DECODE
+
+/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
+ * functions. Returns number of chars placed in cp excluding the
+ * trailing null char. So for cp_max_len > 0 the return value is always
+ * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are
+ * written to cp. Note this means that when cp_max_len = 1, this function
+ * assumes that cp[0] is the null character and does nothing (and returns
+ * 0). Linux kernel has a similar function called scnprintf(). Public
+ * declaration in sg_pr2serr.h header */
+int
+sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ if (cp_max_len < 2)
+ return 0;
+ va_start(args, fmt);
+ n = vsnprintf(cp, cp_max_len, fmt, args);
+ va_end(args);
+ return (n < cp_max_len) ? n : (cp_max_len - 1);
+}
+
+int
+pr2ws(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+#endif
+
+static bool
+sgj_parse_opts(sgj_state * jsp, const char * j_optarg)
+{
+ bool bad_arg = false;
+ bool prev_negate = false;
+ bool negate;
+ int k, c;
+
+ for (k = 0; j_optarg[k]; ++k) { /* step over leading whitespace */
+ if (! isspace(j_optarg[k]))
+ break;
+ }
+ for ( ; j_optarg[k]; ++k) {
+ c = j_optarg[k];
+ negate = false;
+ switch (c) {
+ case '=':
+ if (0 == k)
+ break; /* allow and ignore leading '=' */
+ bad_arg = true;
+ if (0 == jsp->first_bad_char)
+ jsp->first_bad_char = c;
+ break;
+ case '!':
+ case '~':
+ case '-': /* '-' is probably most practical negation symbol */
+ negate = true;
+ break;
+ case '0':
+ case '2':
+ jsp->pr_indent_size = 2;
+ break;
+ case '3':
+ jsp->pr_indent_size = 3;
+ break;
+ case '4':
+ jsp->pr_indent_size = 4;
+ break;
+ case '8':
+ jsp->pr_indent_size = 8;
+ break;
+ case 'e':
+ jsp->pr_exit_status = ! prev_negate;
+ break;
+ case 'g':
+ jsp->pr_format = 'g';
+ break;
+ case 'h':
+ jsp->pr_hex = ! prev_negate;
+ break;
+ case 'k':
+ jsp->pr_packed = ! prev_negate;
+ break;
+ case 'l':
+ jsp->pr_leadin = ! prev_negate;
+ break;
+ case 'n':
+ jsp->pr_name_ex = ! prev_negate;
+ break;
+ case 'o':
+ jsp->pr_out_hr = ! prev_negate;
+ break;
+ case 'p':
+ jsp->pr_pretty = ! prev_negate;
+ break;
+ case 's':
+ jsp->pr_string = ! prev_negate;
+ break;
+ case 'v':
+ ++jsp->verbose;
+ break;
+ case 'y':
+ jsp->pr_format = 'g';
+ break;
+ case '?':
+ bad_arg = true;
+ jsp->first_bad_char = '\0';
+ break;
+ default:
+ bad_arg = true;
+ if (0 == jsp->first_bad_char)
+ jsp->first_bad_char = c;
+ break;
+ }
+ prev_negate = negate ? ! prev_negate : false;
+ }
+ return ! bad_arg;
+}
+
+char *
+sg_json_usage(int char_if_not_j, char * b, int blen)
+{
+ int n = 0;
+ char short_opt = char_if_not_j ? char_if_not_j : 'j';
+
+ if ((NULL == b) || (blen < 1))
+ goto fini;
+ n += sg_scnpr(b + n, blen - n, "JSON option usage:\n");
+ n += sg_scnpr(b + n, blen - n,
+ " --json[-JO] | -%c[JO]\n\n", short_opt);
+ n += sg_scnpr(b + n, blen - n, " where JO is a string of one or more "
+ "of:\n");
+ n += sg_scnpr(b + n, blen - n,
+ " 0 | 2 tab pretty output to 2 spaces\n");
+ n += sg_scnpr(b + n, blen - n,
+ " 4 tab pretty output to 4 spaces\n");
+ n += sg_scnpr(b + n, blen - n,
+ " 8 tab pretty output to 8 spaces\n");
+ if (n >= (blen - 1))
+ goto fini;
+ n += sg_scnpr(b + n, blen - n,
+ " e show 'exit_status' field\n");
+ n += sg_scnpr(b + n, blen - n,
+ " h show 'hex' fields\n");
+ n += sg_scnpr(b + n, blen - n,
+ " k packed, only non-pretty printed output\n");
+ n += sg_scnpr(b + n, blen - n,
+ " l show lead-in fields (invocation "
+ "information)\n");
+ n += sg_scnpr(b + n, blen - n,
+ " n show 'name_extra' information fields\n");
+ n += sg_scnpr(b + n, blen - n,
+ " o non-JSON output placed in 'output' array in "
+ "lead-in\n");
+ if (n >= (blen - 1))
+ goto fini;
+ n += sg_scnpr(b + n, blen - n,
+ " p pretty print the JSON output\n");
+ n += sg_scnpr(b + n, blen - n,
+ " s show string output (usually fields named "
+ "'meaning')\n");
+ n += sg_scnpr(b + n, blen - n,
+ " v make JSON output more verbose\n");
+ n += sg_scnpr(b + n, blen - n,
+ " = ignored if first character, else it's an "
+ "error\n");
+ n += sg_scnpr(b + n, blen - n,
+ " - | ~ | ! toggle next letter setting\n");
+
+ n += sg_scnpr(b + n, blen - n, "\nIn the absence of the optional JO "
+ "argument, the following are set\non: 'elps' while the "
+ "others are set off, and tabs are set to 4.\nBefore "
+ "command line JO options are applied, the environment\n"
+ "variable: %s is applied (if present). Note that\nno "
+ "space is permitted between the short option ('-%c') "
+ "and its\nargument ('JO').\n", sgj_opts_ev, short_opt);
+fini:
+ return b;
+}
+
+char *
+sg_json_settings(sgj_state * jsp, char * b, int blen)
+{
+ snprintf(b, blen, "%d%se%sh%sk%sl%sn%so%sp%ss%sv", jsp->pr_indent_size,
+ jsp->pr_exit_status ? "" : "-", jsp->pr_hex ? "" : "-",
+ jsp->pr_packed ? "" : "-", jsp->pr_leadin ? "" : "-",
+ jsp->pr_name_ex ? "" : "-", jsp->pr_out_hr ? "" : "-",
+ jsp->pr_pretty ? "" : "-", jsp->pr_string ? "" : "-",
+ jsp->verbose ? "" : "-");
+ return b;
+}
+
+static void
+sgj_def_opts(sgj_state * jsp)
+{
+ jsp->pr_as_json = true;
+ jsp->pr_exit_status = true;
+ jsp->pr_hex = false;
+ jsp->pr_leadin = true;
+ jsp->pr_out_hr = false;
+ jsp->pr_name_ex = false;
+ jsp->pr_packed = false; /* 'k' control character, needs '-p' */
+ jsp->pr_pretty = true;
+ jsp->pr_string = true;
+ jsp->pr_format = 0;
+ jsp->first_bad_char = 0;
+ jsp->verbose = 0;
+ jsp->pr_indent_size = 4;
+}
+
+bool
+sgj_init_state(sgj_state * jsp, const char * j_optarg)
+{
+ const char * cp;
+
+ sgj_def_opts(jsp);
+ jsp->basep = NULL;
+ jsp->out_hrp = NULL;
+ jsp->userp = NULL;
+
+ cp = getenv(sgj_opts_ev);
+ if (cp) {
+ if (! sgj_parse_opts(jsp, cp)) {
+ pr2ws("error parsing %s environment variable, ignore\n",
+ sgj_opts_ev);
+ sgj_def_opts(jsp);
+ }
+ }
+ return j_optarg ? sgj_parse_opts(jsp, j_optarg) : true;
+}
+
+sgj_opaque_p
+sgj_start_r(const char * util_name, const char * ver_str, int argc,
+ char *argv[], sgj_state * jsp)
+{
+ int k;
+ json_value * jvp = json_object_new(0);
+ json_value * jv2p = NULL;
+ json_value * jap = NULL;
+
+ if (NULL == jvp)
+ return NULL;
+ if (NULL == jsp)
+ return jvp;
+
+ jsp->basep = jvp;
+ if (jsp->pr_leadin) {
+ jap = json_array_new(0);
+ if (NULL == jap) {
+ json_builder_free((json_value *)jvp);
+ return NULL;
+ }
+ /* assume rest of json_*_new() calls succeed */
+ json_array_push((json_value *)jap, json_integer_new(1));
+ json_array_push((json_value *)jap, json_integer_new(0));
+ json_object_push((json_value *)jvp, "json_format_version",
+ (json_value *)jap);
+ if (util_name) {
+ jap = json_array_new(0);
+ if (argv) {
+ for (k = 0; k < argc; ++k)
+ json_array_push((json_value *)jap,
+ json_string_new(argv[k]));
+ }
+ jv2p = json_object_push((json_value *)jvp, "utility_invoked",
+ json_object_new(0));
+ json_object_push((json_value *)jv2p, "name",
+ json_string_new(util_name));
+ if (ver_str)
+ json_object_push((json_value *)jv2p, "version_date",
+ json_string_new(ver_str));
+ else
+ json_object_push((json_value *)jv2p, "version_date",
+ json_string_new("0.0"));
+ json_object_push((json_value *)jv2p, "argv", jap);
+ }
+ if (jsp->verbose) {
+ const char * cp = getenv(sgj_opts_ev);
+ char b[32];
+
+ json_object_push((json_value *)jv2p, "environment_variable_name",
+ json_string_new(sgj_opts_ev));
+ json_object_push((json_value *)jv2p, "environment_variable_value",
+ json_string_new(cp ? cp : "no available"));
+ sg_json_settings(jsp, b, sizeof(b));
+ json_object_push((json_value *)jv2p, "json_options",
+ json_string_new(b));
+ }
+ } else {
+ if (jsp->pr_out_hr && util_name)
+ jv2p = json_object_push((json_value *)jvp, "utility_invoked",
+ json_object_new(0));
+ }
+ if (jsp->pr_out_hr && jv2p) {
+ jsp->out_hrp = json_object_push((json_value *)jv2p, "output",
+ json_array_new(0));
+ if (jsp->pr_leadin && (jsp->verbose > 3)) {
+ char * bp = (char *)calloc(4096, 1);
+
+ if (bp) {
+ sg_json_usage(0, bp, 4096);
+ sgj_js_str_out(jsp, bp, strlen(bp));
+ free(bp);
+ }
+ }
+ }
+ return jvp;
+}
+
+void
+sgj_js2file(sgj_state * jsp, sgj_opaque_p jop, int exit_status, FILE * fp)
+{
+ size_t len;
+ char * b;
+ json_value * jvp = (json_value *)(jop ? jop : jsp->basep);
+ json_serialize_opts out_settings;
+
+ if (NULL == jvp) {
+ fprintf(fp, "%s: all NULL pointers ??\n", __func__);
+ return;
+ }
+ if ((NULL == jop) && jsp->pr_exit_status) {
+ char d[80];
+
+#ifdef SG_PRSE_SENSE_DECODE
+ if (sg_exit2str(exit_status, jsp->verbose, sizeof(d), d)) {
+ if (0 == strlen(d))
+ strncpy(d, "no errors", sizeof(d) - 1);
+ } else
+ strncpy(d, "not available", sizeof(d) - 1);
+#else
+ if (0 == exit_status)
+ strncpy(d, "no errors", sizeof(d) - 1);
+ else
+ snprintf(d, sizeof(d), "exit_status=%d", exit_status);
+#endif
+ sgj_js_nv_istr(jsp, jop, "exit_status", exit_status, NULL, d);
+ }
+ memcpy(&out_settings, &def_out_settings, sizeof(out_settings));
+ if (jsp->pr_indent_size != def_out_settings.indent_size)
+ out_settings.indent_size = jsp->pr_indent_size;
+ if (! jsp->pr_pretty)
+ out_settings.mode = jsp->pr_packed ? json_serialize_mode_packed :
+ json_serialize_mode_single_line;
+
+ len = json_measure_ex(jvp, out_settings);
+ if (len < 1)
+ return;
+ if (jsp->verbose > 3)
+ fprintf(fp, "%s: serialization length: %zu bytes\n", __func__, len);
+ b = (char *)calloc(len, 1);
+ if (NULL == b) {
+ if (jsp->verbose > 3)
+ pr2serr("%s: unable to get %zu bytes on heap\n", __func__, len);
+ return;
+ }
+
+ json_serialize_ex(b, jvp, out_settings);
+ if (jsp->verbose > 3)
+ fprintf(fp, "json serialized:\n");
+ fprintf(fp, "%s\n", b);
+}
+
+void
+sgj_finish(sgj_state * jsp)
+{
+ if (jsp && jsp->basep) {
+ json_builder_free((json_value *)jsp->basep);
+ jsp->basep = NULL;
+ jsp->out_hrp = NULL;
+ jsp->userp = NULL;
+ }
+}
+
+void
+sgj_free_unattached(sgj_opaque_p jop)
+{
+ if (jop)
+ json_builder_free((json_value *)jop);
+}
+
+void
+sgj_pr_hr(sgj_state * jsp, const char * fmt, ...)
+{
+ va_list args;
+
+ if (jsp->pr_as_json && jsp->pr_out_hr) {
+ size_t len;
+ char b[256];
+
+ va_start(args, fmt);
+ len = vsnprintf(b, sizeof(b), fmt, args);
+ if ((len > 0) && (len < sizeof(b))) {
+ const char * cp = b;
+
+ /* remove up to two trailing linefeeds */
+ if (b[len - 1] == '\n') {
+ --len;
+ if (b[len - 1] == '\n')
+ --len;
+ b[len] = '\0';
+ }
+ /* remove leading linefeed, if present */
+ if ((len > 0) && ('\n' == b[0]))
+ ++cp;
+ json_array_push((json_value *)jsp->out_hrp, json_string_new(cp));
+ }
+ va_end(args);
+ } else if (jsp->pr_as_json) {
+ va_start(args, fmt);
+ va_end(args);
+ } else {
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+ }
+}
+
+/* jop will 'own' returned value (if non-NULL) */
+sgj_opaque_p
+sgj_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop, const char * name)
+{
+ sgj_opaque_p resp = NULL;
+
+ if (jsp && jsp->pr_as_json && name)
+ resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name,
+ json_object_new(0));
+ return resp;
+}
+
+sgj_opaque_p
+sgj_snake_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * conv2sname)
+{
+ if (jsp && jsp->pr_as_json && conv2sname) {
+ int olen = strlen(conv2sname);
+ char * sname = (char *)malloc(olen + 8);
+ int nlen = sgj_name_to_snake(conv2sname, sname, olen + 8);
+
+ if (nlen > 0)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ sname, json_object_new(0));
+ }
+ return NULL;
+}
+
+/* jop will 'own' returned value (if non-NULL) */
+sgj_opaque_p
+sgj_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, const char * name)
+{
+ sgj_opaque_p resp = NULL;
+
+ if (jsp && jsp->pr_as_json && name)
+ resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name,
+ json_array_new(0));
+ return resp;
+}
+
+sgj_opaque_p
+sgj_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop,
+ const char * conv2sname)
+{
+ if (jsp && jsp->pr_as_json && conv2sname) {
+ int olen = strlen(conv2sname);
+ char * sname = (char *)malloc(olen + 8);
+ int nlen = sgj_name_to_snake(conv2sname, sname, olen + 8);
+
+ if (nlen > 0)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ sname, json_array_new(0));
+ }
+ return NULL;
+}
+
+/* Newly created object is un-attached to jsp->basep tree */
+sgj_opaque_p
+sgj_new_unattached_object_r(sgj_state * jsp)
+{
+ return (jsp && jsp->pr_as_json) ? json_object_new(0) : NULL;
+}
+
+/* Newly created array is un-attached to jsp->basep tree */
+sgj_opaque_p
+sgj_new_unattached_array_r(sgj_state * jsp)
+{
+ return (jsp && jsp->pr_as_json) ? json_array_new(0) : NULL;
+}
+
+sgj_opaque_p
+sgj_js_nv_s(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const char * value)
+{
+ if (jsp && jsp->pr_as_json && value) {
+ if (name)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ name, json_string_new(value));
+ else
+ return json_array_push((json_value *)(jop ? jop : jsp->basep),
+ json_string_new(value));
+ } else
+ return NULL;
+}
+
+sgj_opaque_p
+sgj_js_nv_s_len(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const char * value, int slen)
+{
+ int k;
+
+ if (jsp && jsp->pr_as_json && value && (slen >= 0)) {
+ for (k = 0; k < slen; ++k) { /* don't want '\0' in value string */
+ if (0 == value[k])
+ break;
+ }
+ if (name)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ name, json_string_new_length(k, value));
+ else
+ return json_array_push((json_value *)(jop ? jop : jsp->basep),
+ json_string_new_length(k, value));
+ } else
+ return NULL;
+}
+
+sgj_opaque_p
+sgj_js_nv_i(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t value)
+{
+ if (jsp && jsp->pr_as_json) {
+ if (name)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ name, json_integer_new(value));
+ else
+ return json_array_push((json_value *)(jop ? jop : jsp->basep),
+ json_integer_new(value));
+ }
+ else
+ return NULL;
+}
+
+sgj_opaque_p
+sgj_js_nv_b(sgj_state * jsp, sgj_opaque_p jop, const char * name, bool value)
+{
+ if (jsp && jsp->pr_as_json) {
+ if (name)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ name, json_boolean_new(value));
+ else
+ return json_array_push((json_value *)(jop ? jop : jsp->basep),
+ json_boolean_new(value));
+ } else
+ return NULL;
+}
+
+/* jop will 'own' ua_jop (if returned value is non-NULL) */
+sgj_opaque_p
+sgj_js_nv_o(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ sgj_opaque_p ua_jop)
+{
+ if (jsp && jsp->pr_as_json && ua_jop) {
+ if (name)
+ return json_object_push((json_value *)(jop ? jop : jsp->basep),
+ name, (json_value *)ua_jop);
+ else
+ return json_array_push((json_value *)(jop ? jop : jsp->basep),
+ (json_value *)ua_jop);
+ } else
+ return NULL;
+}
+
+void
+sgj_js_nv_ihex(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ uint64_t value)
+{
+ if ((NULL == jsp) || (NULL == name) || (! jsp->pr_as_json))
+ return;
+ else if (jsp->pr_hex) {
+ sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name);
+ char b[64];
+
+ if (NULL == jo2p)
+ return;
+ sgj_js_nv_i(jsp, jo2p, "i", (int64_t)value);
+ snprintf(b, sizeof(b), "%" PRIx64, value);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ } else
+ sgj_js_nv_i(jsp, jop, name, (int64_t)value);
+}
+
+static const char * sc_mn_s = "meaning";
+
+void
+sgj_js_nv_istr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t val_i, const char * str_name, const char * val_s)
+{
+ if ((NULL == jsp) || (! jsp->pr_as_json))
+ return;
+ else if (val_s && jsp->pr_string) {
+ sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name);
+
+ if (NULL == jo2p)
+ return;
+ sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i);
+ sgj_js_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s);
+ } else
+ sgj_js_nv_i(jsp, jop, name, val_i);
+}
+
+void
+sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t val_i, const char * str_name, const char * val_s)
+{
+ bool as_str;
+
+ if ((NULL == jsp) || (! jsp->pr_as_json))
+ return;
+ as_str = jsp->pr_string && val_s;
+ if ((! jsp->pr_hex) && (! as_str))
+ sgj_js_nv_i(jsp, jop, name, val_i);
+ else {
+ char b[64];
+ sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name);
+
+ if (NULL == jo2p)
+ return;
+ sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i);
+ if (jsp->pr_hex) {
+ snprintf(b, sizeof(b), "%" PRIx64, val_i);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ }
+ if (as_str)
+ sgj_js_nv_s(jsp, jo2p, str_name ? str_name : sc_mn_s, val_s);
+ }
+}
+
+static const char * sc_nex_s = "name_extra";
+
+void
+sgj_js_nv_ihex_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t val_i, bool hex_as_well, const char * nex_s)
+{
+ bool as_hex, as_nex;
+
+ if ((NULL == jsp) || (! jsp->pr_as_json))
+ return;
+ as_hex = jsp->pr_hex && hex_as_well;
+ as_nex = jsp->pr_name_ex && nex_s;
+ if (! (as_hex || as_nex))
+ sgj_js_nv_i(jsp, jop, name, val_i);
+ else {
+ char b[64];
+ sgj_opaque_p jo2p =
+ sgj_named_subobject_r(jsp, jop, name);
+
+ if (NULL == jo2p)
+ return;
+ sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i);
+ if (as_hex) {
+ snprintf(b, sizeof(b), "%" PRIx64, val_i);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ }
+ if (as_nex)
+ sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s);
+ }
+}
+
+#ifndef SG_PRSE_SENSE_DECODE
+static void
+h2str(const uint8_t * byte_arr, int num_bytes, char * bp, int blen)
+{
+ int j, k, n;
+
+ for (k = 0, n = 0; (k < num_bytes) && (n < blen); ) {
+ j = sg_scnpr(bp + n, blen - n, "%02x ", byte_arr[k]);
+ if (j < 2)
+ break;
+ n += j;
+ ++k;
+ if ((0 == (k % 8)) && (k < num_bytes) && (n < blen)) {
+ bp[n++] = ' ';
+ }
+ }
+ j = strlen(bp);
+ if ((j > 0) && (' ' == bp[j - 1]))
+ bp[j - 1] = '\0'; /* chop off trailing space */
+}
+#endif
+
+/* Add hex byte strings irrespective of jsp->pr_hex setting. */
+void
+sgj_js_nv_hex_bytes(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const uint8_t * byte_arr, int num_bytes)
+{
+ int blen = num_bytes * 4;
+ char * bp;
+
+ if ((NULL == jsp) || (! jsp->pr_as_json))
+ return;
+ bp = (char *)calloc(blen + 4, 1);
+ if (bp) {
+#ifdef SG_PRSE_SENSE_DECODE
+ hex2str(byte_arr, num_bytes, NULL, 2, blen, bp);
+#else
+ h2str(byte_arr, num_bytes, bp, blen);
+#endif
+ sgj_js_nv_s(jsp, jop, name, bp);
+ free(bp);
+ }
+}
+
+void
+sgj_js_nv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ int64_t val_i, bool hex_as_well, const char * str_name,
+ const char * val_s, const char * nex_s)
+{
+ bool as_hex = jsp->pr_hex && hex_as_well;
+ bool as_str = jsp->pr_string && val_s;
+ bool as_nex = jsp->pr_name_ex && nex_s;
+ const char * sname = str_name ? str_name : sc_mn_s;
+
+ if ((NULL == jsp) || (! jsp->pr_as_json))
+ return;
+ if (! (as_hex || as_nex || as_str))
+ sgj_js_nv_i(jsp, jop, name, val_i);
+ else {
+ char b[64];
+ sgj_opaque_p jo2p =
+ sgj_named_subobject_r(jsp, jop, name);
+
+ if (NULL == jo2p)
+ return;
+ sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i);
+ if (as_nex) {
+ if (as_hex) {
+ snprintf(b, sizeof(b), "%" PRIx64, val_i);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ }
+ if (as_str) {
+ sgj_js_nv_s(jsp, jo2p, sname, val_s);
+ }
+ sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s);
+ } else if (as_hex) {
+ snprintf(b, sizeof(b), "%" PRIx64, val_i);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ if (as_str)
+ sgj_js_nv_s(jsp, jo2p, sname, val_s);
+ } else if (as_str)
+ sgj_js_nv_s(jsp, jo2p, sname, val_s);
+ }
+}
+
+/* Treat '\n' in sp as line breaks. Consumes characters from sp until either
+ * a '\0' is found or slen is exhausted. Add each line to jsp->out_hrp JSON
+ * array (if conditions met). */
+void
+sgj_js_str_out(sgj_state * jsp, const char * sp, int slen)
+{
+ char c;
+ int k, n;
+ const char * prev_sp = sp;
+ const char * cur_sp = sp;
+
+ if ((NULL == jsp) || (NULL == jsp->out_hrp) || (! jsp->pr_as_json) ||
+ (! jsp->pr_out_hr))
+ return;
+ for (k = 0; k < slen; ++k, ++cur_sp) {
+ c = *cur_sp;
+ if ('\0' == c)
+ break;
+ else if ('\n' == c) {
+ n = cur_sp - prev_sp;
+ /* when name is NULL, add to array (jsp->out_hrp) */
+ sgj_js_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n);
+ prev_sp = cur_sp + 1;
+ }
+ }
+ if (prev_sp < cur_sp) {
+ n = cur_sp - prev_sp;
+ sgj_js_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n);
+ }
+}
+
+char *
+sgj_convert_to_snake_name(const char * in_name, char * sname,
+ int max_sname_len)
+{
+ sgj_name_to_snake(in_name, sname, max_sname_len);
+ return sname;
+}
+
+bool
+sgj_is_snake_name(const char * in_name)
+{
+ size_t k;
+ size_t ln = strlen(in_name);
+ char c;
+
+ for (k = 0; k < ln; ++k) {
+ c = in_name[k];
+ if (((c >= '0') && (c <= '9')) ||
+ ((c >= 'a') && (c <= 'z')) ||
+ (c == '_'))
+ continue;
+ else
+ return false;
+ }
+ return true;
+}
+
+/* This function tries to convert the 'in' C string to "snake_case"
+ * convention so the output 'out' only contains lower case ASCII letters,
+ * numerals and "_" as a separator. Any leading or trailing underscores
+ * are removed as are repeated underscores (e.g. "_Snake __ case" becomes
+ * "snake_case"). Parentheses and the characters between them are removed.
+ * Returns number of characters placed in 'out' excluding the trailing
+ * NULL */
+static int
+sgj_name_to_snake(const char * in, char * out, int maxlen_out)
+{
+ bool prev_underscore = false;
+ bool within_paren = false;
+ int c, k, j, inlen;
+
+ if (maxlen_out < 2) {
+ if (maxlen_out == 1)
+ out[0] = '\0';
+ return 0;
+ }
+ inlen = strlen(in);
+ for (k = 0, j = 0; (k < inlen) && (j < maxlen_out); ++k) {
+ c = in[k];
+ if (within_paren) {
+ if (')' == c)
+ within_paren = false;
+ continue;
+ }
+ if (isalnum(c)) {
+ out[j++] = isupper(c) ? tolower(c) : c;
+ prev_underscore = false;
+ } else if ('(' == c)
+ within_paren = true;
+ else if ((j > 0) && (! prev_underscore)) {
+ out[j++] = '_';
+ prev_underscore = true;
+ }
+ /* else we are skipping character 'c' */
+ }
+ if (j == maxlen_out)
+ out[--j] = '\0';
+ /* trim of trailing underscores (might have been spaces) */
+ for (k = j - 1; k >= 0; --k) {
+ if (out[k] != '_')
+ break;
+ }
+ if (k < 0)
+ k = 0;
+ else
+ ++k;
+ out[k] = '\0';
+ return k;
+}
+
+static int
+sgj_jtype_to_s(char * b, int blen_max, json_value * jvp)
+{
+ json_type jtype = jvp ? jvp->type : json_none;
+
+ switch (jtype) {
+ case json_string:
+ return sg_scnpr(b, blen_max, "%s", jvp->u.string.ptr);
+ case json_integer:
+ return sg_scnpr(b, blen_max, "%" PRIi64, jvp->u.integer);
+ case json_boolean:
+ return sg_scnpr(b, blen_max, "%s", jvp->u.boolean ? "true" : "false");
+ case json_none:
+ default:
+ if ((blen_max > 0) && ('\0' != b[0]))
+ b[0] = '\0';
+ break;
+ }
+ return 0;
+}
+
+static int
+sgj_haj_helper(char * b, int blen_max, const char * name,
+ enum sgj_separator_t sep, bool use_jvp,
+ json_value * jvp, int64_t val_instead)
+{
+ int n = 0;
+
+ if (name) {
+ n += sg_scnpr(b + n, blen_max - n, "%s", name);
+ switch (sep) {
+ case SGJ_SEP_NONE:
+ break;
+ case SGJ_SEP_SPACE_1:
+ n += sg_scnpr(b + n, blen_max - n, " ");
+ break;
+ case SGJ_SEP_SPACE_2:
+ n += sg_scnpr(b + n, blen_max - n, " ");
+ break;
+ case SGJ_SEP_SPACE_3:
+ n += sg_scnpr(b + n, blen_max - n, " ");
+ break;
+ case SGJ_SEP_SPACE_4:
+ n += sg_scnpr(b + n, blen_max - n, " ");
+ break;
+ case SGJ_SEP_EQUAL_NO_SPACE:
+ n += sg_scnpr(b + n, blen_max - n, "=");
+ break;
+ case SGJ_SEP_EQUAL_1_SPACE:
+ n += sg_scnpr(b + n, blen_max - n, "= ");
+ break;
+ case SGJ_SEP_COLON_NO_SPACE:
+ n += sg_scnpr(b + n, blen_max - n, ":");
+ break;
+ case SGJ_SEP_COLON_1_SPACE:
+ n += sg_scnpr(b + n, blen_max - n, ": ");
+ break;
+ default:
+ break;
+ }
+ }
+ if (use_jvp)
+ n += sgj_jtype_to_s(b + n, blen_max - n, jvp);
+ else
+ n += sg_scnpr(b + n, blen_max - n, "%" PRIi64, val_instead);
+ return n;
+}
+
+static void
+sgj_haj_xx(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, json_value * jvp,
+ bool hex_as_well, const char * val_s, const char * nex_s)
+{
+ bool eaten = false;
+ bool as_json = (jsp && jsp->pr_as_json);
+ bool done;
+ int n;
+ json_type jtype = jvp ? jvp->type : json_none;
+ char b[256];
+ char jname[96];
+ static const int blen = sizeof(b);
+
+ if (leadin_sp > 128)
+ leadin_sp = 128;
+ for (n = 0; n < leadin_sp; ++n)
+ b[n] = ' ';
+ b[n] = '\0';
+ if (NULL == name) {
+ if ((! as_json) || (jsp && jsp->pr_out_hr)) {
+ n += sgj_jtype_to_s(b + n, blen - n, jvp);
+ printf("%s\n", b);
+ }
+ if (NULL == jop) {
+ if (as_json && jsp->pr_out_hr) {
+ eaten = true;
+ json_array_push((json_value *)jsp->out_hrp,
+ jvp ? jvp : json_null_new());
+ }
+ } else { /* assume jop points to named array */
+ if (as_json) {
+ eaten = true;
+ json_array_push((json_value *)jop,
+ jvp ? jvp : json_null_new());
+ }
+ }
+ goto fini;
+ }
+ if (as_json) {
+ int k;
+
+ if (NULL == jop)
+ jop = jsp->basep;
+ k = sgj_name_to_snake(name, jname, sizeof(jname));
+ if (k > 0) {
+ done = false;
+ if (nex_s && (strlen(nex_s) > 0)) {
+ switch (jtype) {
+ case json_string:
+ break;
+ case json_integer:
+ sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.integer,
+ hex_as_well, sc_mn_s, val_s, nex_s);
+ done = true;
+ break;
+ case json_boolean:
+ sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.boolean,
+ false, sc_mn_s, val_s, nex_s);
+ done = true;
+ break;
+ case json_none:
+ default:
+ break;
+ }
+ } else {
+ switch (jtype) {
+ case json_string:
+ break;
+ case json_integer:
+ if (hex_as_well) {
+ sgj_js_nv_ihexstr(jsp, jop, jname, jvp->u.integer,
+ sc_mn_s, val_s);
+ done = true;
+ }
+ break;
+ case json_none:
+ default:
+ break;
+ }
+ }
+ if (! done) {
+ eaten = true;
+ json_object_push((json_value *)jop, jname,
+ jvp ? jvp : json_null_new());
+ }
+ }
+ }
+ if (jvp && ((as_json && jsp->pr_out_hr) || (! as_json)))
+ n += sgj_haj_helper(b + n, blen - n, name, sep, true, jvp, 0);
+
+ if (as_json && jsp->pr_out_hr)
+ json_array_push((json_value *)jsp->out_hrp, json_string_new(b));
+ if (! as_json)
+ printf("%s\n", b);
+fini:
+ if (jvp && (! eaten))
+ json_builder_free((json_value *)jvp);
+}
+
+void
+sgj_haj_vs(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, const char * value)
+{
+ json_value * jvp;
+
+ /* make json_value even if jsp->pr_as_json is false */
+ jvp = value ? json_string_new(value) : NULL;
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL);
+}
+
+void
+sgj_haj_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, int64_t value,
+ bool hex_as_well)
+{
+ json_value * jvp;
+
+ jvp = json_integer_new(value);
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, NULL, NULL);
+}
+
+void
+sgj_haj_vistr(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, int64_t value,
+ bool hex_as_well, const char * val_s)
+{
+ json_value * jvp;
+
+ jvp = json_integer_new(value);
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, val_s,
+ NULL);
+}
+
+void
+sgj_haj_vi_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well, const char * nex_s)
+{
+ json_value * jvp;
+
+ jvp = json_integer_new(value);
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, NULL, nex_s);
+}
+
+void
+sgj_haj_vistr_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep,
+ int64_t value, bool hex_as_well,
+ const char * val_s, const char * nex_s)
+{
+ json_value * jvp;
+
+ jvp = json_integer_new(value);
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_as_well, val_s,
+ nex_s);
+}
+
+void
+sgj_haj_vb(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, bool value)
+{
+ json_value * jvp;
+
+ jvp = json_boolean_new(value);
+ sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL);
+}
+
+sgj_opaque_p
+sgj_haj_subo_r(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
+ const char * name, enum sgj_separator_t sep, int64_t value,
+ bool hex_as_well)
+{
+ bool as_json = (jsp && jsp->pr_as_json);
+ int n = 0;
+ sgj_opaque_p jo2p;
+ char b[256];
+ static const int blen = sizeof(b);
+
+ if (NULL == name)
+ return NULL;
+ for (n = 0; n < leadin_sp; ++n)
+ b[n] = ' ';
+ b[n] = '\0';
+ if ((! as_json) || (jsp && jsp->pr_out_hr))
+ n += sgj_haj_helper(b + n, blen - n, name, sep, false, NULL, value);
+
+ if (as_json && jsp->pr_out_hr)
+ json_array_push((json_value *)jsp->out_hrp, json_string_new(b));
+ if (! as_json)
+ printf("%s\n", b);
+
+ if (as_json) {
+ sgj_name_to_snake(name, b, blen);
+ jo2p = sgj_named_subobject_r(jsp, jop, b);
+ if (jo2p) {
+ sgj_js_nv_i(jsp, jo2p, "i", value);
+ if (hex_as_well && jsp->pr_hex) {
+ snprintf(b, blen, "%" PRIx64, value);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ }
+ }
+ return jo2p;
+ }
+ return NULL;
+}
+
+#ifdef SG_PRSE_SENSE_DECODE
+
+static const char * dtsp = "descriptor too short";
+static const char * sksvp = "sense-key specific valid";
+static const char * ddep = "designation_descriptor_error";
+static const char * naa_exp = "Network Address Authority";
+static const char * aoi_exp = "IEEE-Administered Organizational Identifier";
+
+bool
+sgj_js_designation_descriptor(sgj_state * jsp, sgj_opaque_p jop,
+ const uint8_t * ddp, int dd_len)
+{
+ int p_id, piv, c_set, assoc, desig_type, d_id, naa;
+ int n, aoi, vsi, dlen;
+ uint64_t ull;
+ const uint8_t * ip;
+ char e[80];
+ char b[256];
+ const char * cp;
+ const char * naa_sp;
+ sgj_opaque_p jo2p;
+ static const int blen = sizeof(b);
+ static const int elen = sizeof(e);
+
+ if (dd_len < 4) {
+ sgj_js_nv_s(jsp, jop, ddep, "too short");
+ return false;
+ }
+ dlen = ddp[3];
+ if (dlen > (dd_len - 4)) {
+ snprintf(e, elen, "too long: says it is %d bytes, but given %d "
+ "bytes\n", dlen, dd_len - 4);
+ sgj_js_nv_s(jsp, jop, ddep, e);
+ return false;
+ }
+ ip = ddp + 4;
+ p_id = ((ddp[0] >> 4) & 0xf);
+ c_set = (ddp[0] & 0xf);
+ piv = ((ddp[1] & 0x80) ? 1 : 0);
+ assoc = ((ddp[1] >> 4) & 0x3);
+ desig_type = (ddp[1] & 0xf);
+ cp = sg_get_desig_assoc_str(assoc);
+ if (assoc == 3)
+ cp = "Reserved [0x3]"; /* should not happen */
+ sgj_js_nv_ihexstr(jsp, jop, "association", assoc, NULL, cp);
+ cp = sg_get_desig_type_str(desig_type);
+ if (NULL == cp)
+ cp = "unknown";
+ sgj_js_nv_ihexstr(jsp, jop, "designator_type", desig_type,
+ NULL, cp);
+ cp = sg_get_desig_code_set_str(c_set);
+ if (NULL == cp)
+ cp = "unknown";
+ sgj_js_nv_ihexstr(jsp, jop, "code_set", desig_type,
+ NULL, cp);
+ sgj_js_nv_ihex_nex(jsp, jop, "piv", piv, false,
+ "Protocol Identifier Valid");
+ sg_get_trans_proto_str(p_id, elen, e);
+ sgj_js_nv_ihexstr(jsp, jop, "protocol_identifier", p_id, NULL, e);
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ sgj_js_nv_hex_bytes(jsp, jop, "vendor_specific_hexbytes", ip, dlen);
+ break;
+ case 1: /* T10 vendor identification */
+ n = (dlen < 8) ? dlen : 8;
+ snprintf(b, blen, "%.*s", n, ip);
+ sgj_js_nv_s(jsp, jop, "t10_vendor_identification", b);
+ b[0] = '\0';
+ if (dlen > 8)
+ snprintf(b, blen, "%.*s", dlen - 8, ip + 8);
+ sgj_js_nv_s(jsp, jop, "vendor_specific_identifier", b);
+ break;
+ case 2: /* EUI-64 based */
+ sgj_js_nv_i(jsp, jop, "eui_64_based_designator_length", dlen);
+ ull = sg_get_unaligned_be64(ip);
+ switch (dlen) {
+ case 8:
+ sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ull);
+ break;
+ case 12:
+ sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ull);
+ sgj_js_nv_ihex(jsp, jop, "directory_id",
+ sg_get_unaligned_be32(ip + 8));
+ break;
+ case 16:
+ sgj_js_nv_ihex(jsp, jop, "identifier_extension", ull);
+ sgj_js_nv_ihex(jsp, jop, "ieee_identifier",
+ sg_get_unaligned_be64(ip + 8));
+ break;
+ default:
+ sgj_js_nv_s(jsp, jop, "eui_64", "decoding failed");
+ break;
+ }
+ break;
+ case 3: /* NAA <n> */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen);
+ naa = (ip[0] >> 4) & 0xff;
+ switch (naa) {
+ case 2:
+ naa_sp = "IEEE Extended";
+ sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp,
+ naa_exp);
+ d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+ sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_a", d_id);
+ aoi = sg_get_unaligned_be24(ip + 2);
+ sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp);
+ vsi = sg_get_unaligned_be24(ip + 5);
+ sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_b", vsi);
+ break;
+ case 3:
+ naa_sp = "Locally Assigned";
+ sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp,
+ naa_exp);
+ ull = sg_get_unaligned_be64(ip + 0) & 0xfffffffffffffffULL;
+ sgj_js_nv_ihex(jsp, jop, "locally_administered_value", ull);
+ break;
+ case 5:
+ naa_sp = "IEEE Registered";
+ sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp,
+ naa_exp);
+ aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff;
+ sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp);
+ ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL;
+ sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier", ull);
+ break;
+ case 6:
+ naa_sp = "IEEE Registered Extended";
+ sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, false, NULL, naa_sp,
+ naa_exp);
+ aoi = (sg_get_unaligned_be32(ip + 0) >> 4) & 0xffffff;
+ sgj_js_nv_ihex_nex(jsp, jop, "aoi", aoi, true, aoi_exp);
+ ull = sg_get_unaligned_be48(ip + 2) & 0xfffffffffULL;
+ sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier", ull);
+ ull = sg_get_unaligned_be64(ip + 8);
+ sgj_js_nv_ihex(jsp, jop, "vendor_specific_identifier_extension",
+ ull);
+ break;
+ default:
+ snprintf(b, blen, "unknown NAA value=0x%x", naa);
+ sgj_js_nv_ihexstr_nex(jsp, jop, "naa", naa, true, NULL, b,
+ naa_exp);
+ sgj_js_nv_hex_bytes(jsp, jop, "full_naa_hexbytes", ip, dlen);
+ break;
+ }
+ break;
+ case 4: /* Relative target port */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "relative_target_port_hexbytes",
+ ip, dlen);
+ sgj_js_nv_ihex(jsp, jop, "relative_target_port_identifier",
+ sg_get_unaligned_be16(ip + 2));
+ break;
+ case 5: /* (primary) Target port group */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "target_port_group_hexbytes",
+ ip, dlen);
+ sgj_js_nv_ihex(jsp, jop, "target_port_group",
+ sg_get_unaligned_be16(ip + 2));
+ break;
+ case 6: /* Logical unit group */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "logical_unit_group_hexbytes",
+ ip, dlen);
+ sgj_js_nv_ihex(jsp, jop, "logical_unit_group",
+ sg_get_unaligned_be16(ip + 2));
+ break;
+ case 7: /* MD5 logical unit identifier */
+ sgj_js_nv_hex_bytes(jsp, jop, "md5_logical_unit_hexbytes",
+ ip, dlen);
+ break;
+ case 8: /* SCSI name string */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "scsi_name_string_hexbytes",
+ ip, dlen);
+ snprintf(b, blen, "%.*s", dlen, ip);
+ sgj_js_nv_s(jsp, jop, "scsi_name_string", b);
+ break;
+ case 9: /* Protocol specific port identifier */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop,
+ "protocol_specific_port_identifier_hexbytes",
+ ip, dlen);
+ if (TPROTO_UAS == p_id) {
+ jo2p = sgj_named_subobject_r(jsp, jop,
+ "usb_target_port_identifier");
+ sgj_js_nv_ihex(jsp, jo2p, "device_address", 0x7f & ip[0]);
+ sgj_js_nv_ihex(jsp, jo2p, "interface_number", ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ jo2p = sgj_named_subobject_r(jsp, jop, "pci_express_routing_id");
+ sgj_js_nv_ihex(jsp, jo2p, "routing_id",
+ sg_get_unaligned_be16(ip + 0));
+ } else
+ sgj_js_nv_s(jsp, jop, "protocol_specific_port_identifier",
+ "decoding failure");
+
+ break;
+ case 0xa: /* UUID identifier */
+ if (jsp->pr_hex)
+ sgj_js_nv_hex_bytes(jsp, jop, "uuid_hexbytes", ip, dlen);
+ sg_t10_uuid_desig2str(ip, dlen, c_set, false, true, NULL, blen, b);
+ n = strlen(b);
+ if ((n > 0) && ('\n' == b[n - 1]))
+ b[n - 1] = '\0';
+ sgj_js_nv_s(jsp, jop, "uuid", b);
+ break;
+ default: /* reserved */
+ sgj_js_nv_hex_bytes(jsp, jop, "reserved_designator_hexbytes",
+ ip, dlen);
+ break;
+ }
+ return true;
+}
+
+static void
+sgj_progress_indication(sgj_state * jsp, sgj_opaque_p jop,
+ uint16_t prog_indic, bool is_another)
+{
+ uint32_t progress, pr, rem;
+ sgj_opaque_p jo2p;
+ char b[64];
+
+ if (is_another)
+ jo2p = sgj_named_subobject_r(jsp, jop, "another_progress_indication");
+ else
+ jo2p = sgj_named_subobject_r(jsp, jop, "progress_indication");
+ if (NULL == jo2p)
+ return;
+ progress = prog_indic;
+ sgj_js_nv_i(jsp, jo2p, "i", progress);
+ snprintf(b, sizeof(b), "%x", progress);
+ sgj_js_nv_s(jsp, jo2p, "hex", b);
+ progress *= 100;
+ pr = progress / 65536;
+ rem = (progress % 65536) / 656;
+ snprintf(b, sizeof(b), "%d.02%d%%\n", pr, rem);
+ sgj_js_nv_s(jsp, jo2p, "percentage", b);
+}
+
+static bool
+sgj_decode_sks(sgj_state * jsp, sgj_opaque_p jop, const uint8_t * dp, int dlen,
+ int sense_key)
+{
+ switch (sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if (dlen < 3) {
+ sgj_js_nv_s(jsp, jop, "illegal_request_sks", dtsp);
+ return false;
+ }
+ sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
+ sksvp);
+ sgj_js_nv_ihex_nex(jsp, jop, "c_d", !! (dp[0] & 0x40), false,
+ "c: cdb; d: data-out");
+ sgj_js_nv_ihex_nex(jsp, jop, "bpv", !! (dp[0] & 0x8), false,
+ "bit pointer (index) valid");
+ sgj_js_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7);
+ sgj_js_nv_ihex(jsp, jop, "field_pointer",
+ sg_get_unaligned_be16(dp + 1));
+ break;
+ case SPC_SK_HARDWARE_ERROR:
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_RECOVERED_ERROR:
+ if (dlen < 3) {
+ sgj_js_nv_s(jsp, jop, "actual_retry_count_sks", dtsp);
+ return false;
+ }
+ sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
+ sksvp);
+ sgj_js_nv_ihex(jsp, jop, "actual_retry_count",
+ sg_get_unaligned_be16(dp + 1));
+ break;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_NOT_READY:
+ if (dlen < 7) {
+ sgj_js_nv_s(jsp, jop, "progress_indication_sks", dtsp);
+ return false;
+ }
+ sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
+ sksvp);
+ sgj_progress_indication(jsp, jop, sg_get_unaligned_be16(dp + 1),
+ false);
+ break;
+ case SPC_SK_COPY_ABORTED:
+ if (dlen < 7) {
+ sgj_js_nv_s(jsp, jop, "segment_indication_sks", dtsp);
+ return false;
+ }
+ sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
+ sksvp);
+ sgj_js_nv_ihex_nex(jsp, jop, "sd", !! (dp[0] & 0x20), false,
+ "field pointer relative to: 1->segment "
+ "descriptor, 0->parameter list");
+ sgj_js_nv_ihex_nex(jsp, jop, "bpv", !! (dp[0] & 0x8), false,
+ "bit pointer (index) valid");
+ sgj_js_nv_i(jsp, jop, "bit_pointer", dp[0] & 0x7);
+ sgj_js_nv_ihex(jsp, jop, "field_pointer",
+ sg_get_unaligned_be16(dp + 1));
+ break;
+ case SPC_SK_UNIT_ATTENTION:
+ if (dlen < 7) {
+ sgj_js_nv_s(jsp, jop, "segment_indication_sks", dtsp);
+ return false;
+ }
+ sgj_js_nv_ihex_nex(jsp, jop, "sksv", !! (dp[0] & 0x80), false,
+ sksvp);
+ sgj_js_nv_i(jsp, jop, "overflow", !! (dp[0] & 0x80));
+ break;
+ default:
+ sgj_js_nv_ihex(jsp, jop, "unexpected_sense_key", sense_key);
+ return false;
+ }
+ return true;
+}
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_OFFLINE 0xe
+#define TPGS_STATE_TRANSITIONING 0xf
+
+static int
+decode_tpgs_state(int st, char * b, int blen)
+{
+ switch (st) {
+ case TPGS_STATE_OPTIMIZED:
+ return sg_scnpr(b, blen, "active/optimized");
+ case TPGS_STATE_NONOPTIMIZED:
+ return sg_scnpr(b, blen, "active/non optimized");
+ case TPGS_STATE_STANDBY:
+ return sg_scnpr(b, blen, "standby");
+ case TPGS_STATE_UNAVAILABLE:
+ return sg_scnpr(b, blen, "unavailable");
+ case TPGS_STATE_OFFLINE:
+ return sg_scnpr(b, blen, "offline");
+ case TPGS_STATE_TRANSITIONING:
+ return sg_scnpr(b, blen, "transitioning between states");
+ default:
+ return sg_scnpr(b, blen, "unknown: 0x%x", st);
+ }
+}
+
+static bool
+sgj_uds_referral_descriptor(sgj_state * jsp, sgj_opaque_p jop,
+ const uint8_t * dp, int alen)
+{
+ int dlen = alen - 2;
+ int k, j, g, f, aas;
+ uint64_t ull;
+ const uint8_t * tp;
+ sgj_opaque_p jap, jo2p, ja2p, jo3p;
+ char c[40];
+
+ sgj_js_nv_ihex_nex(jsp, jop, "not_all_r", (dp[2] & 0x1), false,
+ "Not all referrals");
+ dp += 4;
+ jap = sgj_named_subarray_r(jsp, jop,
+ "user_data_segment_referral_descriptor_list");
+ for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
+ int ntpgd = dp[3];
+
+ jo2p = sgj_new_unattached_object_r(jsp);
+ g = (ntpgd * 4) + 20;
+ sgj_js_nv_ihex(jsp, jo2p, "number_of_target_port_group_descriptors",
+ ntpgd);
+ if ((k + g) > dlen) {
+ sgj_js_nv_i(jsp, jo2p, "truncated_descriptor_dlen", dlen);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return false;
+ }
+ ull = sg_get_unaligned_be64(dp + 4);
+ sgj_js_nv_ihex(jsp, jo2p, "first_user_date_sgment_lba", ull);
+ ull = sg_get_unaligned_be64(dp + 12);
+ sgj_js_nv_ihex(jsp, jo2p, "last_user_date_sgment_lba", ull);
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "target_port_group_descriptor_list");
+ for (j = 0; j < ntpgd; ++j) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ tp = dp + 20 + (j * 4);
+ aas = tp[0] & 0xf;
+ decode_tpgs_state(aas, c, sizeof(c));
+ sgj_js_nv_ihexstr(jsp, jo3p, "asymmetric_access_state", aas,
+ NULL, c);
+ sgj_js_nv_ihex(jsp, jo3p, "target_port_group",
+ sg_get_unaligned_be16(tp + 2));
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ return true;
+}
+
+/* Copy of static array in sg_lib.c */
+static const char * dd_usage_reason_str_arr[] = {
+ "Unknown",
+ "resend this and further commands to:",
+ "resend this command to:",
+ "new subsidiary lu added to this administrative lu:",
+ "administrative lu associated with a preferred binding:",
+ };
+
+static bool
+sgj_js_sense_descriptors(sgj_state * jsp, sgj_opaque_p jop,
+ const struct sg_scsi_sense_hdr * sshp,
+ const uint8_t * sbp, int sb_len)
+{
+ bool processed = true;
+ int add_sb_len, desc_len, k, dt, sense_key, n, sds;
+ uint16_t sct_sc;
+ uint64_t ull;
+ const uint8_t * descp;
+ const char * cp;
+ sgj_opaque_p jap, jo2p, jo3p;
+ char b[80];
+ static const int blen = sizeof(b);
+ static const char * parsing = "parsing_error";
+#if 0
+ static const char * eccp = "Extended copy command";
+ static const char * ddp = "destination device";
+#endif
+
+ add_sb_len = sshp->additional_length;
+ add_sb_len = (add_sb_len < sb_len) ? add_sb_len : sb_len;
+ sense_key = sshp->sense_key;
+ jap = sgj_named_subarray_r(jsp, jop, "sense_data_descriptor_list");
+
+ for (descp = sbp, k = 0; (k < add_sb_len);
+ k += desc_len, descp += desc_len) {
+ int add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
+
+ jo2p = sgj_new_unattached_object_r(jsp);
+ if ((k + add_d_len + 2) > add_sb_len)
+ add_d_len = add_sb_len - k - 2;
+ desc_len = add_d_len + 2;
+ processed = true;
+ dt = descp[0];
+ switch (dt) {
+ case 0:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt,
+ NULL, "Information");
+ if (add_d_len >= 10) {
+ int valid = !! (0x80 & descp[2]);
+ sgj_js_nv_ihexstr(jsp, jo2p, "valid", valid, NULL,
+ valid ? "as per T10" : "Vendor specific");
+ sgj_js_nv_ihex(jsp, jo2p, "information",
+ sg_get_unaligned_be64(descp + 4));
+ } else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 1:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt,
+ NULL, "Command specific");
+ if (add_d_len >= 10) {
+ sgj_js_nv_ihex(jsp, jo2p, "command_specific_information",
+ sg_get_unaligned_be64(descp + 4));
+ } else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 2: /* Sense Key Specific */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Sense key specific");
+ processed = sgj_decode_sks(jsp, jo2p, descp + 4, desc_len - 4,
+ sense_key);
+ break;
+ case 3:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Field replaceable unit code");
+ if (add_d_len >= 2)
+ sgj_js_nv_ihex(jsp, jo2p, "field_replaceable_unit_code",
+ descp[3]);
+ else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 4:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Stream commands");
+ if (add_d_len >= 2) {
+ sgj_js_nv_i(jsp, jo2p, "filemark", !! (descp[3] & 0x80));
+ sgj_js_nv_ihex_nex(jsp, jo2p, "eom", !! (descp[3] & 0x40),
+ false, "End Of Medium");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (descp[3] & 0x20),
+ false, "Incorrect Length Indicator");
+ } else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 5:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Block commands");
+ if (add_d_len >= 2)
+ sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (descp[3] & 0x20),
+ false, "Incorrect Length Indicator");
+ else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 6:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "OSD object identification");
+ sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported");
+ processed = false;
+ break;
+ case 7:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "OSD response integrity check value");
+ sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported");
+ break;
+ case 8:
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "OSD attribute identification");
+ sgj_js_nv_s(jsp, jo2p, parsing, "Unsupported");
+ processed = false;
+ break;
+ case 9: /* this is defined in SAT (SAT-2) */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "ATA status return");
+ if (add_d_len >= 12) {
+ sgj_js_nv_i(jsp, jo2p, "extend", !! (descp[2] & 1));
+ sgj_js_nv_ihex(jsp, jo2p, "error", descp[3]);
+ sgj_js_nv_ihex(jsp, jo2p, "count",
+ sg_get_unaligned_be16(descp + 4));
+ ull = ((uint64_t)descp[10] << 40) |
+ ((uint64_t)descp[8] << 32) |
+ (descp[6] << 24) |
+ (descp[11] << 16) |
+ (descp[9] << 8) |
+ descp[7];
+ sgj_js_nv_ihex(jsp, jo2p, "lba", ull);
+ sgj_js_nv_ihex(jsp, jo2p, "device", descp[12]);
+ sgj_js_nv_ihex(jsp, jo2p, "status", descp[13]);
+ } else {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 0xa:
+ /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Another progress indication");
+ if (add_d_len < 6) {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_ihex(jsp, jo2p, "another_sense_key", descp[2]);
+ sgj_js_nv_ihex(jsp, jo2p, "another_additional_sense_code",
+ descp[3]);
+ sgj_js_nv_ihex(jsp, jo2p,
+ "another_additional_sense_code_qualifier",
+ descp[4]);
+ sgj_progress_indication(jsp, jo2p,
+ sg_get_unaligned_be16(descp + 6), true);
+ break;
+ case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "User data segment referral");
+ if (add_d_len < 2) {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ if (! sgj_uds_referral_descriptor(jsp, jo2p, descp, add_d_len)) {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ }
+ break;
+ case 0xc: /* Added in SPC-4 rev 28 */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Forwarded sense data");
+ if (add_d_len < 2) {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_ihex_nex(jsp, jo2p, "fsdt", !! (0x80 & descp[2]),
+ false, "Forwarded Sense Data Truncated");
+ sds = (0x7 & descp[2]);
+ if (sds < 1)
+ snprintf(b, blen, "%s [%d]", "Unknown", sds);
+ else if (sds > 9)
+ snprintf(b, blen, "%s [%d]", "Reserved", sds);
+ else {
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, "EXTENDED COPY command copy %s",
+ (sds == 1) ? "source" : "destination");
+ if (sds > 1)
+ n += sg_scnpr(b + n, blen - n, " %d", sds - 1);
+ }
+ sgj_js_nv_ihexstr(jsp, jo2p, "sense_data_source",
+ (0x7 & descp[2]), NULL, b);
+ jo3p = sgj_named_subobject_r(jsp, jo2p, "forwarded_sense_data");
+ sgj_js_sense(jsp, jo3p, descp + 4, desc_len - 4);
+ break;
+ case 0xd: /* Added in SBC-3 rev 36d */
+ /* this descriptor combines descriptors 0, 1, 2 and 3 */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Direct-access block device");
+ if (add_d_len < 28) {
+ sgj_js_nv_s(jsp, jo2p, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_i(jsp, jo2p, "valid", (0x80 & descp[2]));
+ sgj_js_nv_ihex_nex(jsp, jo2p, "ili", !! (0x20 & descp[2]),
+ false, "Incorrect Length Indicator");
+ processed = sgj_decode_sks(jsp, jo2p, descp + 4, desc_len - 4,
+ sense_key);
+ sgj_js_nv_ihex(jsp, jo2p, "field_replaceable_unit_code",
+ descp[7]);
+ sgj_js_nv_ihex(jsp, jo2p, "information",
+ sg_get_unaligned_be64(descp + 8));
+ sgj_js_nv_ihex(jsp, jo2p, "command_specific_information",
+ sg_get_unaligned_be64(descp + 16));
+ break;
+ case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Device designation");
+ n = descp[3];
+ cp = (n < (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr)) ?
+ dd_usage_reason_str_arr[n] : "Unknown (reserved)";
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_usage_reason",
+ n, NULL, cp);
+ jo3p = sgj_named_subobject_r(jsp, jo2p,
+ "device_designation_descriptor");
+ sgj_js_designation_descriptor(jsp, jo3p, descp + 4, desc_len - 4);
+ break;
+ case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "Microcode activation");
+ if (add_d_len < 6) {
+ sgj_js_nv_s(jsp, jop, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_ihex(jsp, jo2p, "microcode_activation_time",
+ sg_get_unaligned_be16(descp + 6));
+ break;
+ case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */
+ sgj_js_nv_ihexstr(jsp, jo2p, "descriptor_type", dt, NULL,
+ "NVME status (sg3_utils)");
+ if (add_d_len < 6) {
+ sgj_js_nv_s(jsp, jop, parsing, dtsp);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_ihex_nex(jsp, jo2p, "dnr", !! (0x80 & descp[5]),
+ false, "Do not retry");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "m", !! (0x40 & descp[5]),
+ false, "More");
+ sct_sc = sg_get_unaligned_be16(descp + 6);
+ sgj_js_nv_ihexstr_nex
+ (jsp, jo2p, "sct_sc", sct_sc, true, NULL,
+ sg_get_nvme_cmd_status_str(sct_sc, blen, b),
+ "Status Code Type (upper 8 bits) and Status Code");
+ break;
+ default:
+ if (dt >= 0x80)
+ sgj_js_nv_ihex(jsp, jo2p, "vendor_specific_descriptor_type",
+ dt);
+ else
+ sgj_js_nv_ihex(jsp, jo2p, "unknown_descriptor_type", dt);
+ sgj_js_nv_hex_bytes(jsp, jo2p, "descriptor_hexbytes",
+ descp, desc_len);
+ processed = false;
+ break;
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ return processed;
+}
+
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */
+
+/* Fetch sense information */
+bool
+sgj_js_sense(sgj_state * jsp, sgj_opaque_p jop, const uint8_t * sbp,
+ int sb_len)
+{
+ bool descriptor_format = false;
+ bool sdat_ovfl = false;
+ bool ret = true;
+ bool valid_info_fld;
+ int len, n;
+ uint32_t info;
+ uint8_t resp_code;
+ const char * ebp = NULL;
+ char ebuff[64];
+ char b[256];
+ struct sg_scsi_sense_hdr ssh;
+ static int blen = sizeof(b);
+ static int elen = sizeof(ebuff);
+
+ if ((NULL == sbp) || (sb_len < 1)) {
+ snprintf(ebuff, elen, "sense buffer empty\n");
+ ebp = ebuff;
+ ret = false;
+ goto fini;
+ }
+ resp_code = 0x7f & sbp[0];
+ valid_info_fld = !!(sbp[0] & 0x80);
+ len = sb_len;
+ if (! sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
+ ebp = "unable to normalize sense buffer";
+ ret = false;
+ goto fini;
+ }
+ /* We have been able to normalize the sense buffer */
+ switch (resp_code) {
+ case 0x70: /* fixed, current */
+ ebp = "Fixed format, current";
+ len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+ len = (len > sb_len) ? sb_len : len;
+ sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+ break;
+ case 0x71: /* fixed, deferred */
+ /* error related to a previous command */
+ ebp = "Fixed format, <<<deferred>>>";
+ len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
+ len = (len > sb_len) ? sb_len : len;
+ sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
+ break;
+ case 0x72: /* descriptor, current */
+ descriptor_format = true;
+ ebp = "Descriptor format, current";
+ sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+ break;
+ case 0x73: /* descriptor, deferred */
+ descriptor_format = true;
+ ebp = "Descriptor format, <<<deferred>>>";
+ sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
+ break;
+ default:
+ sg_scnpr(ebuff, elen, "Unknown code: 0x%x", resp_code);
+ ebp = ebuff;
+ break;
+ }
+ sgj_js_nv_ihexstr(jsp, jop, "response_code", resp_code, NULL, ebp);
+ sgj_js_nv_b(jsp, jop, "descriptor_format", descriptor_format);
+ sgj_js_nv_ihex_nex(jsp, jop, "sdat_ovfl", sdat_ovfl, false,
+ "Sense data overflow");
+ sgj_js_nv_ihexstr(jsp, jop, "sense_key", ssh.sense_key, NULL,
+ sg_lib_sense_key_desc[ssh.sense_key]);
+ sgj_js_nv_ihex(jsp, jop, "additional_sense_code", ssh.asc);
+ sgj_js_nv_ihex(jsp, jop, "additional_sense_code_qualifier", ssh.ascq);
+ sgj_js_nv_s(jsp, jop, "additional_sense_str",
+ sg_get_additional_sense_str(ssh.asc, ssh.ascq, false,
+ blen, b));
+ if (descriptor_format) {
+ if (len > 8) {
+ ret = sgj_js_sense_descriptors(jsp, jop, &ssh, sbp + 8, len - 8);
+ if (ret == false) {
+ ebp = "unable to decode sense descriptor";
+ goto fini;
+ }
+ }
+ } else if ((len > 12) && (0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ /* SAT ATA PASS-THROUGH fixed format */
+ sgj_js_nv_ihex(jsp, jop, "error", sbp[3]);
+ sgj_js_nv_ihex(jsp, jop, "status", sbp[4]);
+ sgj_js_nv_ihex(jsp, jop, "device", sbp[5]);
+ sgj_js_nv_i(jsp, jop, "extend", !! (0x80 & sbp[8]));
+ sgj_js_nv_i(jsp, jop, "count_upper_nonzero", !! (0x40 & sbp[8]));
+ sgj_js_nv_i(jsp, jop, "lba_upper_nonzero", !! (0x20 & sbp[8]));
+ sgj_js_nv_i(jsp, jop, "log_index", (0xf & sbp[8]));
+ sgj_js_nv_i(jsp, jop, "lba", sg_get_unaligned_le24(sbp + 9));
+ } else if (len > 2) { /* fixed format */
+ sgj_js_nv_i(jsp, jop, "valid", valid_info_fld);
+ sgj_js_nv_i(jsp, jop, "filemark", !! (sbp[2] & 0x80));
+ sgj_js_nv_ihex_nex(jsp, jop, "eom", !! (sbp[2] & 0x40),
+ false, "End Of Medium");
+ sgj_js_nv_ihex_nex(jsp, jop, "ili", !! (sbp[2] & 0x20),
+ false, "Incorrect Length Indicator");
+ info = sg_get_unaligned_be32(sbp + 3);
+ sgj_js_nv_ihex(jsp, jop, "information", info);
+ sgj_js_nv_ihex(jsp, jop, "additional_sense_length", sbp[7]);
+ if (sb_len > 11) {
+ info = sg_get_unaligned_be32(sbp + 8);
+ sgj_js_nv_ihex(jsp, jop, "command_specific_information", info);
+ }
+ if (sb_len > 14)
+ sgj_js_nv_ihex(jsp, jop, "field_replaceable_unit_code", sbp[14]);
+ if (sb_len > 17)
+ sgj_decode_sks(jsp, jop, sbp + 15, sb_len - 15, ssh.sense_key);
+ n = sbp[7];
+ n = (sb_len > n) ? n : sb_len;
+ sgj_js_nv_ihex(jsp, jop, "number_of_bytes_beyond_18",
+ (n > 18) ? n - 18 : 0);
+ } else {
+ snprintf(ebuff, sizeof(ebuff), "sb_len=%d too short", sb_len);
+ ebp = ebuff;
+ ret = false;
+ }
+fini:
+ if ((! ret) && ebp)
+ sgj_js_nv_s(jsp, jop, "sense_decode_error", ebp);
+ return ret;
+}
+
+#endif
diff --git a/lib/sg_pt_common.c b/lib/sg_pt_common.c
new file mode 100644
index 00000000..9af5020a
--- /dev/null
+++ b/lib/sg_pt_common.c
@@ -0,0 +1,651 @@
+/*
+ * Copyright (c) 2009-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+#include "sg_pr2serr.h"
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+#include "sg_pt_nvme.h"
+#endif
+
+static const char * scsi_pt_version_str = "3.19 20220127";
+
+/* List of external functions that need to be defined for each OS are
+ * listed at the top of sg_pt_dummy.c */
+
+const char *
+scsi_pt_version()
+{
+ return scsi_pt_version_str;
+}
+
+const char *
+sg_pt_version()
+{
+ return scsi_pt_version_str;
+}
+
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
+
+#define SAVING_PARAMS_UNSUP 0x39
+#define INVALID_FIELD_IN_CDB 0x24
+#define INVALID_FIELD_IN_PARAM_LIST 0x26
+#define PARAMETER_LIST_LENGTH_ERR 0x1a
+
+static const char * nvme_scsi_vendor_str = "NVMe ";
+
+
+#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH 0x100 /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP 0x200
+
+/* Table of SCSI operation code (opcodes) supported by SNTL */
+static struct sg_opcode_info_t sg_opcode_info_arr[] =
+{
+ {0x0, 0, 0, {6, /* TEST UNIT READY */
+ 0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x3, 0, 0, {6, /* REQUEST SENSE */
+ 0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x12, 0, 0, {6, /* INQUIRY */
+ 0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x1b, 0, 0, {6, /* START STOP UNIT */
+ 0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x1c, 0, 0, {6, /* RECEIVE DIAGNOSTIC RESULTS */
+ 0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x1d, 0, 0, {6, /* SEND DIAGNOSTIC */
+ 0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+ {0x25, 0, 0, {10, /* READ CAPACITY(10) */
+ 0x1, 0xff, 0xff, 0xff, 0xff, 0, 0, 0x1, 0xc7, 0, 0, 0, 0, 0, 0} },
+ {0x28, 0, 0, {10, /* READ(10) */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0,
+ 0, 0} },
+ {0x2a, 0, 0, {10, /* WRITE(10) */
+ 0xfb, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0,
+ 0, 0} },
+ {0x2f, 0, 0, {10, /* VERIFY(10) */
+ 0xf6, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0,
+ 0, 0} },
+ {0x35, 0, 0, {10, /* SYNCHRONIZE CACHE(10) */
+ 0x7, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0,
+ 0, 0} },
+ {0x41, 0, 0, {10, /* WRITE SAME(10) */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0,
+ 0, 0} },
+ {0x55, 0, 0, {10, /* MODE SELECT(10) */
+ 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} },
+ {0x5a, 0, 0, {10, /* MODE SENSE(10) */
+ 0x18, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} },
+ {0x88, 0, 0, {16, /* READ(16) */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xc7} },
+ {0x8a, 0, 0, {16, /* WRITE(16) */
+ 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xc7} },
+ {0x8f, 0, 0, {16, /* VERIFY(16) */
+ 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x3f, 0xc7} },
+ {0x91, 0, 0, {16, /* SYNCHRONIZE CACHE(16) */
+ 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x3f, 0xc7} },
+ {0x93, 0, 0, {16, /* WRITE SAME(16) */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x3f, 0xc7} },
+ {0x9e, 0x10, F_SA_LOW, {16, /* READ CAPACITY(16) [service action in] */
+ 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x1, 0xc7} },
+ {0xa0, 0, 0, {12, /* REPORT LUNS */
+ 0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+ {0xa3, 0xc, F_SA_LOW, {12, /* REPORT SUPPORTED OPERATION CODES */
+ 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0,
+ 0} },
+ {0xa3, 0xd, F_SA_LOW, {12, /* REPORT SUPPORTED TASK MAN. FUNCTIONS */
+ 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} },
+
+ {0xff, 0xffff, 0xffff, {0, /* Sentinel, keep as last element */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} },
+};
+
+/* Returns pointer to array of struct sg_opcode_info_t of SCSI commands
+ * translated to NVMe. */
+const struct sg_opcode_info_t *
+sg_get_opcode_translation(void)
+{
+ return sg_opcode_info_arr;
+}
+
+/* Given the NVMe Identify controller response and optionally the NVMe
+ * Identify namespace response (NULL otherwise), generate the SCSI VPD
+ * page 0x83 (device identification) descriptor(s) in dop. Return the
+ * number of bytes written which will not exceed max_do_len. Probably use
+ * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport
+ * protocol (tproto) should be -1 if not known, else SCSI value.
+ * N.B. Does not write total VPD page length into dop[2:3] . */
+int
+sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p,
+ const uint8_t * nvme_id_ns_p, int pdt,
+ int tproto, uint8_t * dop, int max_do_len)
+{
+ bool have_nguid, have_eui64;
+ int k, n;
+ char b[4];
+
+ if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56))
+ return 0;
+
+ memset(dop, 0, max_do_len);
+ dop[0] = 0x1f & pdt; /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */
+ dop[1] = 0x83; /* Device Identification VPD page number */
+ /* Build a T10 Vendor ID based designator (desig_id=1) for controller */
+ if (tproto >= 0) {
+ dop[4] = ((0xf & tproto) << 4) | 0x2;
+ dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */
+ } else {
+ dop[4] = 0x2; /* Prococol id=0, code_set=2 (ASCII) */
+ dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */
+ }
+ memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe " */
+ memcpy(dop + 16, nvme_id_ctl_p + 24, 40); /* MN */
+ for (k = 40; k > 0; --k) {
+ if (' ' == dop[15 + k])
+ dop[15 + k] = '_'; /* convert trailing spaces */
+ else
+ break;
+ }
+ if (40 == k)
+ --k;
+ n = 16 + 1 + k;
+ if (max_do_len < (n + 20))
+ return 0;
+ memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */
+ for (k = 20; k > 0; --k) { /* trim trailing spaces */
+ if (' ' == dop[n + k - 1])
+ dop[n + k - 1] = '\0';
+ else
+ break;
+ }
+ n += k;
+ if (0 != (n % 4))
+ n = ((n / 4) + 1) * 4; /* round up to next modulo 4 */
+ dop[7] = n - 8;
+ if (NULL == nvme_id_ns_p)
+ return n;
+
+ /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in
+ * NVME Identify for namespace. If found form a EUI and a SCSI string
+ * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */
+ have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16);
+ have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8);
+ if ((! have_nguid) && (! have_eui64))
+ return n;
+ if (have_nguid) {
+ if (max_do_len < (n + 20))
+ return n;
+ dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */
+ dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+ dop[n + 3] = 16;
+ memcpy(dop + n + 4, nvme_id_ns_p + 104, 16);
+ n += 20;
+ if (max_do_len < (n + 40))
+ return n;
+ dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */
+ dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+ dop[n + 3] = 36;
+ memcpy(dop + n + 4, "eui.", 4);
+ for (k = 0; k < 16; ++k) {
+ snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]);
+ memcpy(dop + n + 8 + (2 * k), b, 2);
+ }
+ return n + 40;
+ } else { /* have_eui64 is true, 8 byte identifier */
+ if (max_do_len < (n + 12))
+ return n;
+ dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */
+ dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */
+ dop[n + 3] = 8;
+ memcpy(dop + n + 4, nvme_id_ns_p + 120, 8);
+ n += 12;
+ if (max_do_len < (n + 24))
+ return n;
+ dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */
+ dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */
+ dop[n + 3] = 20;
+ memcpy(dop + n + 4, "eui.", 4);
+ for (k = 0; k < 8; ++k) {
+ snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]);
+ memcpy(dop + n + 8 + (2 * k), b, 2);
+ }
+ return n + 24;
+ }
+}
+
+/* Disconnect-Reconnect page for mode_sense */
+static int
+resp_disconnect_pg(uint8_t * p, int pcontrol)
+{
+ uint8_t disconnect_pg[] = {0x2, 0xe, 128, 128, 0, 10, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+
+ memcpy(p, disconnect_pg, sizeof(disconnect_pg));
+ if (1 == pcontrol)
+ memset(p + 2, 0, sizeof(disconnect_pg) - 2);
+ return sizeof(disconnect_pg);
+}
+
+static uint8_t caching_m_pg[] = {0x8, 18, 0x14, 0, 0xff, 0xff, 0, 0,
+ 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0,
+ 0, 0, 0, 0};
+
+/* Control mode page (SBC) for mode_sense */
+static int
+resp_caching_m_pg(unsigned char *p, int pcontrol, bool wce)
+{ /* Caching page for mode_sense */
+ uint8_t ch_caching_m_pg[] = {/* 0x8, 18, */ 0x4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t d_caching_m_pg[] = {0x8, 18, 0x14, 0, 0xff, 0xff, 0, 0,
+ 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0};
+
+ // if (SDEBUG_OPT_N_WCE & sdebug_opts)
+ caching_m_pg[2] &= ~0x4; /* set WCE=0 (default WCE=1) */
+ if ((0 == pcontrol) || (3 == pcontrol)) {
+ if (wce)
+ caching_m_pg[2] |= 0x4;
+ else
+ caching_m_pg[2] &= ~0x4;
+ }
+ memcpy(p, caching_m_pg, sizeof(caching_m_pg));
+ if (1 == pcontrol) {
+ if (wce)
+ ch_caching_m_pg[2] |= 0x4;
+ else
+ ch_caching_m_pg[2] &= ~0x4;
+ memcpy(p + 2, ch_caching_m_pg, sizeof(ch_caching_m_pg));
+ }
+ else if (2 == pcontrol) {
+ if (wce)
+ d_caching_m_pg[2] |= 0x4;
+ else
+ d_caching_m_pg[2] &= ~0x4;
+ memcpy(p, d_caching_m_pg, sizeof(d_caching_m_pg));
+ }
+ return sizeof(caching_m_pg);
+}
+
+static uint8_t ctrl_m_pg[] = {0xa, 10, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0x2, 0x4b};
+
+/* Control mode page for mode_sense */
+static int
+resp_ctrl_m_pg(uint8_t *p, int pcontrol)
+{
+ uint8_t ch_ctrl_m_pg[] = {/* 0xa, 10, */ 0x6, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t d_ctrl_m_pg[] = {0xa, 10, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0x2, 0x4b};
+
+ memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
+ if (1 == pcontrol)
+ memcpy(p + 2, ch_ctrl_m_pg, sizeof(ch_ctrl_m_pg));
+ else if (2 == pcontrol)
+ memcpy(p, d_ctrl_m_pg, sizeof(d_ctrl_m_pg));
+ return sizeof(ctrl_m_pg);
+}
+
+static uint8_t ctrl_ext_m_pg[] = {0x4a, 0x1, 0, 0x1c, 0, 0, 0x40, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, };
+
+/* Control Extension mode page [0xa,0x1] for mode_sense */
+static int
+resp_ctrl_ext_m_pg(uint8_t *p, int pcontrol)
+{
+ uint8_t ch_ctrl_ext_m_pg[] = {/* 0x4a, 0x1, 0, 0x1c, */ 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, };
+ uint8_t d_ctrl_ext_m_pg[] = {0x4a, 0x1, 0, 0x1c, 0, 0, 0x40, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, };
+
+ memcpy(p, ctrl_ext_m_pg, sizeof(ctrl_ext_m_pg));
+ if (1 == pcontrol)
+ memcpy(p + 4, ch_ctrl_ext_m_pg, sizeof(ch_ctrl_ext_m_pg));
+ else if (2 == pcontrol)
+ memcpy(p, d_ctrl_ext_m_pg, sizeof(d_ctrl_ext_m_pg));
+ return sizeof(ctrl_ext_m_pg);
+}
+
+static uint8_t iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0};
+
+/* Informational Exceptions control mode page for mode_sense */
+static int
+resp_iec_m_pg(uint8_t *p, int pcontrol)
+{
+ uint8_t ch_iec_m_pg[] = {/* 0x1c, 0xa, */ 0x4, 0xf, 0, 0, 0, 0, 0, 0,
+ 0x0, 0x0};
+ uint8_t d_iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0};
+
+ memcpy(p, iec_m_pg, sizeof(iec_m_pg));
+ if (1 == pcontrol)
+ memcpy(p + 2, ch_iec_m_pg, sizeof(ch_iec_m_pg));
+ else if (2 == pcontrol)
+ memcpy(p, d_iec_m_pg, sizeof(d_iec_m_pg));
+ return sizeof(iec_m_pg);
+}
+
+static uint8_t vs_ua_m_pg[] = {0x0, 0xe, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0};
+
+/* Vendor specific Unit Attention mode page for mode_sense */
+static int
+resp_vs_ua_m_pg(uint8_t *p, int pcontrol)
+{
+ uint8_t ch_vs_ua_m_pg[] = {/* 0x0, 0xe, */ 0xff, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t d_vs_ua_m_pg[] = {0x0, 0xe, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+
+ memcpy(p, vs_ua_m_pg, sizeof(vs_ua_m_pg));
+ if (1 == pcontrol)
+ memcpy(p + 2, ch_vs_ua_m_pg, sizeof(ch_vs_ua_m_pg));
+ else if (2 == pcontrol)
+ memcpy(p, d_vs_ua_m_pg, sizeof(d_vs_ua_m_pg));
+ return sizeof(vs_ua_m_pg);
+}
+
+void
+sntl_init_dev_stat(struct sg_sntl_dev_state_t * dsp)
+{
+ if (dsp) {
+ dsp->scsi_dsense = !! (0x4 & ctrl_m_pg[2]);
+ dsp->enclosure_override = vs_ua_m_pg[2];
+ }
+}
+
+
+#define SDEBUG_MAX_MSENSE_SZ 256
+
+/* Only support MODE SENSE(10). Returns the number of bytes written to dip,
+ * or -1 if error info placed in resp. */
+int
+sntl_resp_mode_sense10(const struct sg_sntl_dev_state_t * dsp,
+ const uint8_t * cdbp, uint8_t * dip, int mx_di_len,
+ struct sg_sntl_result_t * resp)
+{
+ bool dbd, llbaa, is_disk, bad_pcode;
+ int pcontrol, pcode, subpcode, bd_len, alloc_len, offset, len;
+ const uint32_t num_blocks = 0x100000; /* made up */
+ const uint32_t lb_size = 512; /* guess */
+ uint8_t dev_spec;
+ uint8_t * ap;
+ uint8_t arr[SDEBUG_MAX_MSENSE_SZ];
+
+ memset(resp, 0, sizeof(*resp));
+ dbd = !! (cdbp[1] & 0x8); /* disable block descriptors */
+ pcontrol = (cdbp[2] & 0xc0) >> 6;
+ pcode = cdbp[2] & 0x3f;
+ subpcode = cdbp[3];
+ llbaa = !!(cdbp[1] & 0x10);
+ is_disk = sg_pdt_s_eq(sg_lib_pdt_decay(dsp->pdt), PDT_DISK_ZBC);
+ if (is_disk && !dbd)
+ bd_len = llbaa ? 16 : 8;
+ else
+ bd_len = 0;
+ alloc_len = sg_get_unaligned_be16(cdbp + 7);
+ memset(arr, 0, SDEBUG_MAX_MSENSE_SZ);
+ if (0x3 == pcontrol) { /* Saving values not supported */
+ resp->asc = SAVING_PARAMS_UNSUP;
+ goto err_out;
+ }
+ /* for disks set DPOFUA bit and clear write protect (WP) bit */
+ if (is_disk)
+ dev_spec = 0x10; /* =0x90 if WP=1 implies read-only */
+ else
+ dev_spec = 0x0;
+ arr[3] = dev_spec;
+ if (16 == bd_len)
+ arr[4] = 0x1; /* set LONGLBA bit */
+ arr[7] = bd_len; /* assume 255 or less */
+ offset = 8;
+ ap = arr + offset;
+
+ if (8 == bd_len) {
+ sg_put_unaligned_be32(num_blocks, ap + 0);
+ sg_put_unaligned_be16((uint16_t)lb_size, ap + 6);
+ offset += bd_len;
+ ap = arr + offset;
+ } else if (16 == bd_len) {
+ sg_put_unaligned_be64(num_blocks, ap + 0);
+ sg_put_unaligned_be32(lb_size, ap + 12);
+ offset += bd_len;
+ ap = arr + offset;
+ }
+ bad_pcode = false;
+
+ switch (pcode) {
+ case 0x2: /* Disconnect-Reconnect page, all devices */
+ if (0x0 == subpcode)
+ len = resp_disconnect_pg(ap, pcontrol);
+ else {
+ len = 0;
+ bad_pcode = true;
+ }
+ offset += len;
+ break;
+ case 0x8: /* Caching Mode page, disk (like) devices */
+ if (! is_disk) {
+ len = 0;
+ bad_pcode = true;
+ } else if (0x0 == subpcode)
+ len = resp_caching_m_pg(ap, pcontrol, dsp->wce);
+ else {
+ len = 0;
+ bad_pcode = true;
+ }
+ offset += len;
+ break;
+ case 0xa: /* Control Mode page, all devices */
+ if (0x0 == subpcode)
+ len = resp_ctrl_m_pg(ap, pcontrol);
+ else if (0x1 == subpcode)
+ len = resp_ctrl_ext_m_pg(ap, pcontrol);
+ else {
+ len = 0;
+ bad_pcode = true;
+ }
+ offset += len;
+ break;
+ case 0x1c: /* Informational Exceptions Mode page, all devices */
+ if (0x0 == subpcode)
+ len = resp_iec_m_pg(ap, pcontrol);
+ else {
+ len = 0;
+ bad_pcode = true;
+ }
+ offset += len;
+ break;
+ case 0x3f: /* Read all Mode pages */
+ if ((0 == subpcode) || (0xff == subpcode)) {
+ len = 0;
+ len = resp_disconnect_pg(ap + len, pcontrol);
+ if (is_disk)
+ len += resp_caching_m_pg(ap + len, pcontrol, dsp->wce);
+ len += resp_ctrl_m_pg(ap + len, pcontrol);
+ if (0xff == subpcode)
+ len += resp_ctrl_ext_m_pg(ap + len, pcontrol);
+ len += resp_iec_m_pg(ap + len, pcontrol);
+ len += resp_vs_ua_m_pg(ap + len, pcontrol);
+ offset += len;
+ } else {
+ resp->asc = INVALID_FIELD_IN_CDB;
+ resp->in_byte = 3;
+ resp->in_bit = 255;
+ goto err_out;
+ }
+ break;
+ case 0x0: /* Vendor specific "Unit Attention" mode page */
+ /* all sub-page codes ?? */
+ len = resp_vs_ua_m_pg(ap, pcontrol);
+ offset += len;
+ break; /* vendor is "NVMe " (from INQUIRY field) */
+ default:
+ bad_pcode = true;
+ break;
+ }
+ if (bad_pcode) {
+ resp->asc = INVALID_FIELD_IN_CDB;
+ resp->in_byte = 2;
+ resp->in_bit = 5;
+ goto err_out;
+ }
+ sg_put_unaligned_be16(offset - 2, arr + 0);
+ len = (alloc_len < offset) ? alloc_len : offset;
+ len = (len < mx_di_len) ? len : mx_di_len;
+ memcpy(dip, arr, len);
+ return len;
+
+err_out:
+ resp->sstatus = SAM_STAT_CHECK_CONDITION;
+ resp->sk = SPC_SK_ILLEGAL_REQUEST;
+ return -1;
+}
+
+#define SDEBUG_MAX_MSELECT_SZ 512
+
+/* Only support MODE SELECT(10). Returns number of bytes used from dop,
+ * else -1 on error with sense code placed in resp. */
+int
+sntl_resp_mode_select10(struct sg_sntl_dev_state_t * dsp,
+ const uint8_t * cdbp, const uint8_t * dop, int do_len,
+ struct sg_sntl_result_t * resp)
+{
+ int pf, sp, ps, md_len, bd_len, off, spf, pg_len, rlen, param_len, mpage;
+ int sub_mpage;
+ uint8_t arr[SDEBUG_MAX_MSELECT_SZ];
+
+ memset(resp, 0, sizeof(*resp));
+ memset(arr, 0, sizeof(arr));
+ pf = cdbp[1] & 0x10;
+ sp = cdbp[1] & 0x1;
+ param_len = sg_get_unaligned_be16(cdbp + 7);
+ if ((0 == pf) || sp || (param_len > SDEBUG_MAX_MSELECT_SZ)) {
+ resp->asc = INVALID_FIELD_IN_CDB;
+ resp->in_byte = 1;
+ if (sp)
+ resp->in_bit = 0;
+ else if (0 == pf)
+ resp->in_bit = 4;
+ else {
+ resp->in_byte = 7;
+ resp->in_bit = 255;
+ }
+ goto err_out;
+ }
+ rlen = (do_len < param_len) ? do_len : param_len;
+ memcpy(arr, dop, rlen);
+ md_len = sg_get_unaligned_be16(arr + 0) + 2;
+ bd_len = sg_get_unaligned_be16(arr + 6);
+ if (md_len > 2) {
+ resp->asc = INVALID_FIELD_IN_PARAM_LIST;
+ resp->in_byte = 0;
+ resp->in_bit = 255;
+ goto err_out;
+ }
+ off = bd_len + 8;
+ mpage = arr[off] & 0x3f;
+ ps = !!(arr[off] & 0x80);
+ if (ps) {
+ resp->asc = INVALID_FIELD_IN_PARAM_LIST;
+ resp->in_byte = off;
+ resp->in_bit = 7;
+ goto err_out;
+ }
+ spf = !!(arr[off] & 0x40);
+ pg_len = spf ? (sg_get_unaligned_be16(arr + off + 2) + 4) :
+ (arr[off + 1] + 2);
+ sub_mpage = spf ? arr[off + 1] : 0;
+ if ((pg_len + off) > param_len) {
+ resp->asc = PARAMETER_LIST_LENGTH_ERR;
+ goto err_out;
+ }
+ switch (mpage) {
+ case 0x8: /* Caching Mode page */
+ if (0x0 == sub_mpage) {
+ if (caching_m_pg[1] == arr[off + 1]) {
+ memcpy(caching_m_pg + 2, arr + off + 2,
+ sizeof(caching_m_pg) - 2);
+ dsp->wce = !!(caching_m_pg[2] & 0x4);
+ dsp->wce_changed = true;
+ break;
+ }
+ }
+ goto def_case;
+ case 0xa: /* Control Mode page */
+ if (0x0 == sub_mpage) {
+ if (ctrl_m_pg[1] == arr[off + 1]) {
+ memcpy(ctrl_m_pg + 2, arr + off + 2,
+ sizeof(ctrl_m_pg) - 2);
+ dsp->scsi_dsense = !!(ctrl_m_pg[2] & 0x4);
+ break;
+ }
+ }
+ goto def_case;
+ case 0x1c: /* Informational Exceptions Mode page (SBC) */
+ if (0x0 == sub_mpage) {
+ if (iec_m_pg[1] == arr[off + 1]) {
+ memcpy(iec_m_pg + 2, arr + off + 2,
+ sizeof(iec_m_pg) - 2);
+ break;
+ }
+ }
+ goto def_case;
+ case 0x0: /* Vendor specific "Unit Attention" mode page */
+ if (vs_ua_m_pg[1] == arr[off + 1]) {
+ memcpy(vs_ua_m_pg + 2, arr + off + 2,
+ sizeof(vs_ua_m_pg) - 2);
+ dsp->enclosure_override = vs_ua_m_pg[2];
+ }
+ break;
+ default:
+def_case:
+ resp->asc = INVALID_FIELD_IN_PARAM_LIST;
+ resp->in_byte = off;
+ resp->in_bit = 5;
+ goto err_out;
+ }
+ return rlen;
+
+err_out:
+ resp->sk = SPC_SK_ILLEGAL_REQUEST;
+ resp->sstatus = SAM_STAT_CHECK_CONDITION;
+ return -1;
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) [near line 140] */
diff --git a/lib/sg_pt_dummy.c b/lib/sg_pt_dummy.c
new file mode 100644
index 00000000..e080ff8c
--- /dev/null
+++ b/lib/sg_pt_dummy.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+/* Version 1.02 20210618 */
+
+/* List of function names with external linkage that need to be defined
+ *
+ * check_pt_file_handle
+ * clear_scsi_pt_obj
+ * construct_scsi_pt_obj
+ * construct_scsi_pt_obj_with_fd
+ * destruct_scsi_pt_obj
+ * do_scsi_pt
+ * do_nvm_pt
+ * get_pt_actual_lengths
+ * get_pt_duration_ns
+ * get_pt_file_handle
+ * get_pt_nvme_nsid
+ * get_pt_req_lengths
+ * get_pt_result
+ * get_scsi_pt_cdb_buf
+ * get_scsi_pt_cdb_len
+ * get_scsi_pt_duration_ms
+ * get_scsi_pt_os_err
+ * get_scsi_pt_os_err_str
+ * get_scsi_pt_resid
+ * get_scsi_pt_result_category
+ * get_scsi_pt_sense_buf
+ * get_scsi_pt_sense_len
+ * get_scsi_pt_status_response
+ * get_scsi_pt_transport_err
+ * get_scsi_pt_transport_err_str
+ * partial_clear_scsi_pt_obj
+ * pt_device_is_nvme
+ * scsi_pt_close_device
+ * scsi_pt_open_device
+ * scsi_pt_open_flags
+ * set_pt_file_handle
+ * set_pt_metadata_xfer
+ * set_scsi_pt_cdb
+ * set_scsi_pt_data_in
+ * set_scsi_pt_data_out
+ * set_scsi_pt_flags
+ * set_scsi_pt_packet_id
+ * set_scsi_pt_sense
+ * set_scsi_pt_tag
+ * set_scsi_pt_task_attr
+ * set_scsi_pt_task_management
+ * set_scsi_pt_transport_err
+ */
+
+/* Simply defines all the functions needed by the pt interface (see sg_pt.h).
+ * They do nothing. This allows decoding of hex files (e.g. with the --in=
+ * or --inhex= option) with utilities like sg_vpd and sg_logs. */
+
+struct sg_pt_dummy {
+ int dummy;
+};
+
+struct sg_pt_base {
+ struct sg_pt_dummy impl;
+};
+
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = 0 /* O_NONBLOCK*/ ;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. The 'flags' argument is ignored in OSF-1.
+ * Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int verbose)
+{
+ if (device_name) {}
+ if (flags) {}
+ if (verbose) {}
+ errno = EINVAL;
+ return -1;
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ if (device_fd) {}
+ return 0;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int device_fd, int verbose)
+{
+ struct sg_pt_dummy * ptp;
+
+ if (device_fd) {}
+ ptp = (struct sg_pt_dummy *)malloc(sizeof(struct sg_pt_dummy));
+ if (ptp) {
+ memset(ptp, 0, sizeof(struct sg_pt_dummy));
+ } else if (verbose)
+ pr2ws("%s: malloc() out of memory\n", __func__);
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj(void)
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_dummy * ptp = &vp->impl;
+
+ if (ptp)
+ free(ptp);
+}
+
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_dummy * ptp = &vp->impl;
+
+ if (ptp) {
+ ptp->dummy = 0;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_dummy * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->dummy = 0;
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ if (vp) {}
+ if (cdb) {}
+ if (cdb_len) {}
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 6;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return NULL;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ if (vp) {}
+ if (sense) {}
+ if (max_sense_len) {}
+}
+
+/* from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_len)
+{
+ if (vp) {}
+ if (dxferp) {}
+ if (dxfer_len) {}
+}
+
+/* to device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_len)
+{
+ if (vp) {}
+ if (dxferp) {}
+ if (dxfer_len) {}
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
+{
+ if (vp) {}
+ if (pack_id) {}
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+ if (vp) {}
+ if (tag) {}
+}
+
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
+{
+ if (vp) {}
+ if (tmf_code) {}
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attrib, int priority)
+{
+ if (vp) {}
+ if (attrib) {}
+ if (priority) {}
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * vp, int flags)
+{
+ if (vp) {}
+ if (flags) {}
+}
+
+int
+do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose)
+{
+ if (vp) {}
+ if (device_fd) {}
+ if (time_secs) {}
+ if (verbose) {}
+ return 0;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ if (vp) {}
+ if (req_dinp) {}
+ if (req_doutp) {}
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ if (vp) {}
+ if (act_dinp) {}
+ if (act_doutp) {}
+}
+
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return NULL;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return 0;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ if (vp) {}
+ return false;
+}
+
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ if (vp) {}
+ if (max_b_len) {}
+ if (b) {}
+ return NULL;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ if (vp) {}
+ if (max_b_len) {}
+ if (b) {}
+ return NULL;
+}
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int verbose)
+{
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ if (verbose) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+int
+check_pt_file_handle(int device_fd, const char * device_name, int vb)
+{
+ if (device_fd) {}
+ if (device_name) {}
+ if (vb) {}
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return -1;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ if (vp) { }
+ if (dev_han) { }
+ if (vb) { }
+ return 0;
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true)
+{
+ if (vp) { }
+ if (mdxferp) { }
+ if (mdxfer_len) { }
+ if (out_true) { }
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ if (vp) { }
+ if (err) { }
+}
diff --git a/lib/sg_pt_freebsd.c b/lib/sg_pt_freebsd.c
new file mode 100644
index 00000000..e0cd9575
--- /dev/null
+++ b/lib/sg_pt_freebsd.c
@@ -0,0 +1,3168 @@
+/*
+ * Copyright (c) 2005-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* sg_pt_freebsd version 1.48 20220811 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <limits.h>
+#include <libgen.h> /* for basename */
+#include <fcntl.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h> /* from PRIx macros */
+#include <err.h>
+#include <camlib.h>
+#include <cam/scsi/scsi_message.h>
+// #include <sys/ata.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <fcntl.h>
+#include <stddef.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_unaligned.h"
+#include "sg_pt_nvme.h"
+#include "sg_pr2serr.h"
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+#include "freebsd_nvme_ioctl.h"
+#else
+#define NVME_CTRLR_PREFIX "/dev/nvme"
+#define NVME_NS_PREFIX "ns"
+#endif
+
+#define SG_NVME_NVD_PREFIX "/dev/nvd" /* >= FreeBSD 9.2 */
+#define SG_NVME_NDA_PREFIX "/dev/nda" /* >= FreeBSD 12.0, CAM compatible */
+
+#define FREEBSD_MAXDEV 64
+#define FREEBSD_FDOFFSET 16;
+
+#if __FreeBSD_version > 500000
+#define CAM_ERROR_PRINT(a, b, c, d, e) cam_error_print(a, b, c, d, e);
+#else
+#define CAM_ERROR_PRINT(a, b, c, d, e)
+#endif
+
+
+struct freebsd_dev_channel { /* one instance per open file descriptor */
+ bool is_nvme_dev; /* true if NVMe device attached, else SCSI */
+ bool is_cam_nvme; /* NVMe via /dev/nda<n> or /dev/pass<n> devices */
+ bool is_pass; /* CAM passthrough device (i.e. 'pass<n>') */
+ int unitnum; /* the SCSI unit number, NVMe controller id? */
+ uint32_t nsid;
+ // uint32_t nv_ctrlid; /* unitnum seems to have this role */
+ int nvme_fd_ns; // for non-CAM NVMe, use -1 to indicate not provided
+ int nvme_fd_ctrl; // open("/dev/nvme<n>") if needed */
+ char* devname; // from cam_get_device() or ioctl(NVME_GET_NSID)
+ struct cam_device* cam_dev;
+ uint8_t * nvme_id_ctlp;
+ uint8_t * free_nvme_id_ctlp;
+ struct sg_sntl_dev_state_t dev_stat; // owner
+};
+
+// Private table of open devices: guaranteed zero on startup since
+// part of static data.
+static struct freebsd_dev_channel *devicetable[FREEBSD_MAXDEV];
+
+#define DEF_TIMEOUT 60000 /* 60,000 milliseconds (60 seconds) */
+
+struct sg_pt_freebsd_scsi { /* context of one SCSI/NVME command (pt object) */
+ union ccb *ccb;
+ uint8_t * cdb;
+ int cdb_len;
+ uint8_t * sense;
+ int sense_len;
+ uint8_t * dxferp;
+ int dxfer_len;
+ int dxfer_dir; /* CAM_DIR_NONE, _IN, _OUT and _BOTH */
+ uint8_t * dxferip;
+ uint8_t * dxferop;
+ uint8_t * mdxferp;
+ uint32_t dxfer_ilen;
+ uint32_t dxfer_olen;
+ uint32_t mdxfer_len;
+ uint32_t nvme_result; // cdw0 from completion
+ uint16_t nvme_status; // from completion: ((sct << 8) | sc)
+ uint8_t cq_dw0_3[16];
+ int timeout_ms;
+ int scsi_status;
+ int resid;
+ int sense_resid;
+ int in_err;
+ int os_err;
+ int transport_err;
+ int dev_han; // should be >= FREEBSD_FDOFFSET then
+ // (dev_han - FREEBSD_FDOFFSET) is the
+ // index into devicetable[]
+ bool mdxfer_out;
+ bool is_nvme_dev; /* copied from owning mchanp */
+ bool nvme_our_sntl; /* true: our SNTL; false: received NVMe command */
+ struct freebsd_dev_channel * mchanp; /* associated device info */
+};
+
+struct sg_pt_base {
+ struct sg_pt_freebsd_scsi impl;
+};
+
+// static const uint32_t broadcast_nsid = SG_NVME_BROADCAST_NSID;
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+static int sg_do_nvme_pt(struct sg_pt_freebsd_scsi * ptp, int fd,
+ bool is_admin, int timeout_secs, int vb);
+#endif
+
+
+
+static struct freebsd_dev_channel *
+get_fdc_p(struct sg_pt_freebsd_scsi * ptp)
+{
+ int han = ptp->dev_han - FREEBSD_FDOFFSET;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV))
+ return NULL;
+ return devicetable[han];
+}
+
+static const struct freebsd_dev_channel *
+get_fdc_cp(const struct sg_pt_freebsd_scsi * ptp)
+{
+ int han = ptp->dev_han - FREEBSD_FDOFFSET;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV))
+ return NULL;
+ return devicetable[han];
+}
+
+#if __FreeBSD_version >= 1100000
+/* This works with /dev/nvme*, /dev/nvd* and /dev/nda* but not /dev/pass* */
+static int
+nvme_get_nsid(int fd, uint32_t *nsid, char *b, int blen, int vb)
+{
+ struct nvme_get_nsid gnsid;
+ int n_cdev = sizeof(gnsid.cdev);
+
+ if (ioctl(fd, NVME_GET_NSID, &gnsid) < 0) {
+ int err = errno;
+
+ if (vb > 2)
+ pr2ws("%s: ioctl(NVME_GET_NSID) failed, errno=%d\n", __func__,
+ err);
+ return -err;
+ }
+ if (n_cdev < blen) {
+ strncpy(b, gnsid.cdev, n_cdev);
+ b[n_cdev] = '\0';
+ } else {
+ strncpy(b, gnsid.cdev, blen);
+ b[blen - 1] = '\0';
+ }
+ if (nsid != NULL)
+ *nsid = gnsid.nsid;
+ return 0;
+}
+#endif
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int vb)
+{
+ int oflags = 0 /* O_NONBLOCK*/ ;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, vb);
+}
+
+#if __FreeBSD_version >= 1100000
+/* Get a get device CCB for the specified device, borrowed from camdd.c */
+int
+sg_cam_get_cgd(struct cam_device *device, struct ccb_getdev *cgd, int vb)
+{
+ union ccb *ccb;
+ FILE * ferrp = sg_warnings_strm ? sg_warnings_strm : stderr;
+ int retval = 0;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ if (vb)
+ pr2ws("%s: couldn't allocate CCB\n", __func__);
+ return -ENOMEM;
+ }
+ CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->cgd);
+ ccb->ccb_h.func_code = XPT_GDEV_TYPE;
+
+ if (cam_send_ccb(device, ccb) < 0) {
+ if (vb > 1) {
+ pr2ws("%s: error sending Get Device Information CCB\n", __func__);
+ CAM_ERROR_PRINT(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, ferrp);
+ }
+ retval = -ENODEV;
+ goto bailout;
+ }
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (vb > 1)
+ CAM_ERROR_PRINT(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, ferrp);
+ retval = -ENODEV;
+ goto bailout;
+ }
+ bcopy(&ccb->cgd, cgd, sizeof(struct ccb_getdev));
+bailout:
+ cam_freeccb(ccb);
+ return retval;
+}
+#endif
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. The 'oflags' is only used on NVMe devices. It is ignored on
+ * SCSI and ATA devices in FreeBSD.
+ * Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int oflags, int vb)
+{
+ bool maybe_non_cam_nvme = false;
+ bool basnam0_n = false;
+ char first_ch;
+ int k, err, dev_fd, ret, handle_idx;
+ ssize_t s;
+ struct freebsd_dev_channel *fdc_p = NULL;
+ struct cam_device* cam_dev;
+ struct stat a_stat;
+ char dev_nm[PATH_MAX];
+
+ if (vb > 6)
+ pr2ws("%s: device_name=%s, oflags=0x%x\n", __func__, device_name,
+ oflags);
+ // Search table for a free entry
+ for (k = 0; k < FREEBSD_MAXDEV; k++)
+ if (! devicetable[k])
+ break;
+
+ // If no free entry found, return error. We have max allowed number
+ // of "file descriptors" already allocated.
+ if (k == FREEBSD_MAXDEV) {
+ if (vb)
+ pr2ws("too many open file descriptors (%d)\n", FREEBSD_MAXDEV);
+ ret = -EMFILE;
+ goto err_out;
+ }
+ handle_idx = k;
+ fdc_p = (struct freebsd_dev_channel *)
+ calloc(1,sizeof(struct freebsd_dev_channel));
+ if (fdc_p == NULL) {
+ // errno already set by call to calloc()
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ fdc_p->nvme_fd_ns = -1;
+ fdc_p->nvme_fd_ctrl = -1;
+ if (! (fdc_p->devname = (char *)calloc(1, DEV_IDLEN+1))) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+ /* Don't know yet whether device_name is a SCSI, NVME(non-CAM) or
+ * NVME(CAM) device. Start by assuming it is CAM. */
+ if (cam_get_device(device_name, fdc_p->devname, DEV_IDLEN,
+ &(fdc_p->unitnum)) == -1) {
+ if (vb > 3)
+ pr2ws("%s: cam_get_device(%s) fails, should work for SCSI and "
+ "NVMe devices\n", __func__, device_name, errno);
+ ret = -EINVAL;
+ goto err_out;
+ } else if (vb > 6)
+ pr2ws("%s: cam_get_device() works, devname=%s unit=%u\n", __func__,
+ fdc_p->devname, fdc_p->unitnum);
+
+ if (! (cam_dev = cam_open_spec_device(fdc_p->devname,
+ fdc_p->unitnum, O_RDWR, NULL))) {
+ if (vb > 6) {
+ pr2ws("cam_open_spec_device: %s\n", cam_errbuf);
+ pr2ws("%s: so not CAM, but still maybe NVME\n", __func__);
+ }
+ maybe_non_cam_nvme = true;
+ } else { /* found CAM, could be SCSI or NVME(CAM) [nda driver] */
+#if __FreeBSD_version >= 1100000
+ struct ccb_getdev cgd;
+
+ fdc_p->cam_dev = cam_dev;
+ ret = sg_cam_get_cgd(cam_dev, &cgd, vb);
+ if (ret)
+ goto err_out;
+ switch (cgd.protocol) {
+ case PROTO_SCSI:
+ fdc_p->is_nvme_dev = false;
+ break;
+ case PROTO_NVME:
+ fdc_p->is_nvme_dev = true;
+ fdc_p->is_cam_nvme = true;
+ fdc_p->nsid = cam_dev->target_lun & UINT32_MAX;
+ break;
+ case PROTO_ATA:
+ case PROTO_ATAPI:
+ case PROTO_SATAPM:
+ case PROTO_SEMB: /* SATA Enclosure Management bridge */
+ if (vb) {
+ pr2ws("%s: ATA and derivative devices not supported\n",
+ __func__);
+ if (vb > 2)
+ pr2ws(" ... FreeBSD doesn't have a SAT in its kernel\n");
+ }
+ ret = -EINVAL;
+ break;
+#if __FreeBSD_version > 1200058
+ case PROTO_MMCSD:
+ if (vb)
+ pr2ws("%s: MMC and SD devices not supported\n",
+ __func__);
+ ret = -EINVAL;
+ break;
+#endif
+ default:
+ if (vb)
+ pr2ws("%s: unexpected device protocol\n", __func__);
+ ret = -EINVAL;
+ break;
+ }
+ if (ret)
+ goto err_out;
+ if (0 == memcpy(fdc_p->devname, "pass", 4))
+ fdc_p->is_pass = true;
+#else
+ ret = 0;
+ fdc_p->is_nvme_dev = false;
+#endif
+ }
+ if (maybe_non_cam_nvme) {
+ first_ch = device_name[0];
+ if (('/' != first_ch) && ('.' != first_ch)) {
+ char b[PATH_MAX];
+
+ /* Step 1: if device_name is symlink, follow it */
+ s = readlink(device_name, b, sizeof(b));
+ if (s <= 0) {
+ strncpy(b, device_name, PATH_MAX - 1);
+ b[PATH_MAX - 1] = '\0';
+ }
+ /* Step 2: if no leading '/' nor '.' given, prepend '/dev/' */
+ first_ch = b[0];
+ basnam0_n = ('n' == first_ch);
+ if (('/' != first_ch) && ('.' != first_ch))
+ snprintf(dev_nm, PATH_MAX, "%s%s", "/dev/", b);
+ else
+ strcpy(dev_nm, b);
+ } else {
+ const char * cp;
+
+ strcpy(dev_nm, device_name);
+ cp = basename(dev_nm);
+ basnam0_n = ('n' == *cp);
+ strcpy(dev_nm, device_name);
+ }
+ if (stat(dev_nm, &a_stat) < 0) {
+ err = errno;
+ if (vb)
+ pr2ws("%s: unable to stat(%s): %s; basnam0_n=%d\n",
+ __func__, dev_nm, strerror(err), basnam0_n);
+ ret = -err;
+ goto err_out;
+ }
+ if (! (S_ISCHR(a_stat.st_mode))) {
+ if (vb > 1)
+ pr2ws("%s: %s is not a char device ??\n", __func__, dev_nm);
+ ret = -ENODEV;
+ goto err_out;
+ }
+ dev_fd = open(dev_nm, oflags);
+ if (dev_fd < 0) {
+ err = errno;
+ if (vb > 1)
+ pr2ws("%s: open(%s) failed: %s (errno=%d), try SCSI/ATA\n",
+ __func__, dev_nm, strerror(err), err);
+ ret = -err;
+ goto err_out;
+ }
+#if __FreeBSD_version >= 1100000
+ ret = nvme_get_nsid(dev_fd, &fdc_p->nsid, fdc_p->devname, DEV_IDLEN,
+ vb);
+ if (ret)
+ goto err_out;
+#else
+ {
+ unsigned int u;
+
+ /* only support /dev/nvme<n> and /dev/nvme<n>ns<m> */
+ k = sscanf(dev_nm, "nvme%uns%u", &u, &fdc_p->nsid);
+ if (2 == k) {
+ char * cp = strchr(dev_nm, 's');
+
+ *(cp - 2) = '\0';
+ strcpy(fdc_p->devname, dev_nm);
+ } else if (1 == k) {
+ strncpy(fdc_p->devname, dev_nm, DEV_IDLEN);
+ fdc_p->nsid = 0;
+ } else if (vb > 1) {
+ pr2ws("%s: only support '[/dev/]nvme<n>[ns<m>]'\n", __func__);
+ goto err_out;
+ }
+ }
+#endif
+ if (vb > 6)
+ pr2ws("%s: nvme_dev_nm: %s, nsid=%u\n", __func__, fdc_p->devname,
+ fdc_p->nsid);
+ fdc_p->is_nvme_dev = true;
+ fdc_p->is_cam_nvme = false;
+ if (fdc_p->nsid > 0)
+ fdc_p->nvme_fd_ns = dev_fd;
+ else
+ fdc_p->nvme_fd_ctrl = dev_fd;
+ }
+ // return pointer to "file descriptor" table entry, properly offset.
+ devicetable[handle_idx] = fdc_p;
+ return handle_idx + FREEBSD_FDOFFSET;
+
+err_out: /* ret should be negative value (negated errno) */
+ if (fdc_p) {
+ if (fdc_p->devname)
+ free(fdc_p->devname);
+ if (fdc_p->nvme_fd_ns >= 0)
+ close(fdc_p->nvme_fd_ns);
+ if (fdc_p->nvme_fd_ctrl >= 0)
+ close(fdc_p->nvme_fd_ctrl);
+ free(fdc_p);
+ fdc_p = NULL;
+ }
+ return ret;
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_han)
+{
+ struct freebsd_dev_channel *fdc_p;
+ int han = device_han - FREEBSD_FDOFFSET;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
+ errno = ENODEV;
+ return -errno;
+ }
+ fdc_p = devicetable[han];
+ if (NULL == fdc_p) {
+ errno = ENODEV;
+ return -errno;
+ }
+ if (fdc_p->devname)
+ free(fdc_p->devname);
+ if (fdc_p->cam_dev) /* N.B. can be cam_nvme devices */
+ cam_close_device(fdc_p->cam_dev);
+ else if (fdc_p->is_nvme_dev) {
+ if (fdc_p->nvme_fd_ns >= 0)
+ close(fdc_p->nvme_fd_ns);
+ if (fdc_p->nvme_fd_ctrl >= 0)
+ close(fdc_p->nvme_fd_ctrl);
+ if (fdc_p->free_nvme_id_ctlp) {
+ free(fdc_p->free_nvme_id_ctlp);
+ fdc_p->nvme_id_ctlp = NULL;
+ fdc_p->free_nvme_id_ctlp = NULL;
+ }
+ }
+ free(fdc_p);
+ devicetable[han] = NULL;
+ errno = 0;
+ return 0;
+}
+
+/* Assumes device_han is an "open" file handle associated with some device.
+ * Returns 1 if SCSI generic pass-though device [SCSI CAM primary: nda0],
+ * returns 2 if secondary * SCSI pass-through device [SCSI CAM: pass<n>];
+ * returns 3 if non-CAM NVMe with no nsid [nvme0]; returns 4 if non-CAM
+ * NVMe device with nsid (> 0) [nvme0ns1, nvd0]; returns 5 if CAM NVMe
+ * (with or without nsid) [nda0]; or returns 0 if something else (e.g. ATA
+ * block device) or device_han < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int device_han, const char * device_name, int vb)
+{
+ struct freebsd_dev_channel *fdc_p;
+ int han = device_han - FREEBSD_FDOFFSET;
+
+ if (vb > 6)
+ pr2ws("%s: device_handle=%d, device_name: %s\n", __func__,
+ device_han, device_name);
+ if ((han < 0) || (han >= FREEBSD_MAXDEV))
+ return -ENODEV;
+ fdc_p = devicetable[han];
+ if (NULL == fdc_p)
+ return -ENODEV;
+ if (fdc_p->is_nvme_dev) {
+ if (fdc_p->is_cam_nvme)
+ return 5;
+ else if (fdc_p->nsid == 0)
+ return 3;
+ else
+ return 4; /* Something like nvme0ns1 or nvd0 */
+ } else if (fdc_p->cam_dev)
+ return fdc_p->is_pass ? 2 : 1;
+ else {
+ if (vb > 1)
+ pr2ws("%s: neither SCSI nor NVMe ... hmm, device name: %s\n",
+ __func__, device_name);
+ return 0;
+ }
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+static bool checked_ev_dsense = false;
+static bool ev_dsense = false;
+#endif
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int dev_han, int vb)
+{
+ struct sg_pt_freebsd_scsi * ptp;
+
+ ptp = (struct sg_pt_freebsd_scsi *)
+ calloc(1, sizeof(struct sg_pt_freebsd_scsi));
+ if (ptp) {
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->dev_han = (dev_han < 0) ? -1 : dev_han;
+ if (ptp->dev_han >= 0) {
+ struct freebsd_dev_channel *fdc_p;
+
+ fdc_p = get_fdc_p(ptp);
+ if (fdc_p) {
+ ptp->mchanp = fdc_p;
+#if (HAVE_NVME && (! IGNORE_NVME))
+ sntl_init_dev_stat(&fdc_p->dev_stat);
+ if (! checked_ev_dsense) {
+ ev_dsense = sg_get_initial_dsense();
+ checked_ev_dsense = true;
+ }
+ fdc_p->dev_stat.scsi_dsense = ev_dsense;
+#endif
+ } else if (vb)
+ pr2ws("%s: bad dev_han=%d\n", __func__, dev_han);
+ }
+ } else if (vb)
+ pr2ws("%s: calloc() out of memory\n", __func__);
+ return (struct sg_pt_base *)ptp;
+}
+
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_freebsd_scsi * ptp;
+
+ if (NULL == vp) {
+ pr2ws(">>>> %s: given NULL pointer\n", __func__);
+ return;
+ }
+ if ((ptp = &vp->impl)) {
+ if (ptp->ccb)
+ cam_freeccb(ptp->ccb);
+ free(vp);
+ }
+}
+
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_freebsd_scsi * ptp;
+
+ if (NULL == vp) {
+ pr2ws(">>>>> %s: NULL pointer given\n", __func__);
+ return;
+ }
+ if ((ptp = &vp->impl)) {
+ int dev_han = ptp->dev_han;
+ struct freebsd_dev_channel *fdc_p = ptp->mchanp;
+
+ if (ptp->ccb)
+ cam_freeccb(ptp->ccb);
+ memset(ptp, 0, sizeof(struct sg_pt_freebsd_scsi));
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->dev_han = dev_han;
+ ptp->mchanp = fdc_p;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->transport_err = 0;
+ ptp->scsi_status = 0;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->dxferip = NULL;
+ ptp->dxfer_ilen = 0;
+ ptp->dxferop = NULL;
+ ptp->dxfer_olen = 0;
+ ptp->nvme_result = 0;
+}
+
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ struct sg_pt_freebsd_scsi * ptp;
+
+ if (NULL == vp) {
+ if (vb)
+ pr2ws(">>>> %s: pointer to object is NULL\n", __func__);
+ return EINVAL;
+ }
+ if ((ptp = &vp->impl)) {
+ struct freebsd_dev_channel *fdc_p;
+
+ if (dev_han < 0) {
+ ptp->dev_han = -1;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ return 0;
+ }
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: dev_han (%d) is invalid\n", __func__, dev_han);
+ ptp->os_err = EINVAL;
+ return ptp->os_err;
+ }
+ ptp->os_err = 0;
+ ptp->transport_err = 0;
+ ptp->in_err = 0;
+ ptp->scsi_status = 0;
+ ptp->dev_han = dev_han;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->mchanp = fdc_p;
+ }
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp ? ptp->dev_han : -1;
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, int cdb_len)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ ptp->cdb = (uint8_t *)cdb;
+ ptp->cdb_len = cdb_len;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp->cdb_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp->cdb;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (sense) {
+ if (max_sense_len > 0)
+ memset(sense, 0, max_sense_len);
+ }
+ ptp->sense = sense;
+ ptp->sense_len = max_sense_len;
+}
+
+/* Setup for data transfer from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp->dxferip)
+ ++ptp->in_err;
+ ptp->dxferip = dxferp;
+ ptp->dxfer_ilen = dxfer_len;
+ if (dxfer_len > 0) {
+ ptp->dxferp = dxferp;
+ ptp->dxfer_len = dxfer_len;
+ if (ptp->dxfer_dir == CAM_DIR_OUT)
+ ptp->dxfer_dir = CAM_DIR_BOTH;
+ else
+ ptp->dxfer_dir = CAM_DIR_IN;
+ }
+}
+
+/* Setup for data transfer toward device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp->dxferop)
+ ++ptp->in_err;
+ ptp->dxferop = (uint8_t *)dxferp;
+ ptp->dxfer_olen = dxfer_len;
+ if (dxfer_len > 0) {
+ ptp->dxferp = (uint8_t *)dxferp;
+ ptp->dxfer_len = dxfer_len;
+ if (ptp->dxfer_dir == CAM_DIR_IN)
+ ptp->dxfer_dir = CAM_DIR_BOTH;
+ else
+ ptp->dxfer_dir = CAM_DIR_OUT;
+ }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp->mdxferp)
+ ++ptp->in_err;
+ ptp->mdxferp = mdxferp;
+ ptp->mdxfer_len = mdxfer_len;
+ if (mdxfer_len > 0)
+ ptp->mdxfer_out = out_true;
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp __attribute__ ((unused)),
+ int pack_id __attribute__ ((unused)))
+{
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag __attribute__ ((unused)))
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp,
+ int tmf_code __attribute__ ((unused)))
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp,
+ int attrib __attribute__ ((unused)),
+ int priority __attribute__ ((unused)))
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * objp, int flags)
+{
+ if (objp) { ; } /* unused, suppress warning */
+ if (flags) { ; } /* unused, suppress warning */
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers).
+ * Clears os_err field prior to active call (whose result may set it
+ * again). */
+int
+do_scsi_pt(struct sg_pt_base * vp, int dev_han, int time_secs, int vb)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+ struct freebsd_dev_channel *fdc_p;
+ FILE * ferrp = sg_warnings_strm ? sg_warnings_strm : stderr;
+ union ccb *ccb;
+
+ if (vb > 6)
+ pr2ws("%s: dev_han=%d, time_secs=%d\n", __func__, dev_han, time_secs);
+ ptp->os_err = 0;
+ if (ptp->in_err) {
+ if (vb)
+ pr2ws("Replicated or unused set_scsi_pt...\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (dev_han < 0) {
+ if (ptp->dev_han < 0) {
+ if (vb)
+ pr2ws("%s: No device file handle given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ dev_han = ptp->dev_han;
+ } else {
+ if (ptp->dev_han >= 0) {
+ if (dev_han != ptp->dev_han) {
+ if (vb)
+ pr2ws("%s: file handle given to create and this "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else
+ ptp->dev_han = dev_han;
+ }
+
+ if (NULL == ptp->cdb) {
+ if (vb)
+ pr2ws("No command (cdb) given\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ fdc_p = ptp->mchanp;
+ if (NULL == fdc_p) {
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("File descriptor bad or closed??\n");
+ ptp->os_err = ENODEV;
+ return -ptp->os_err;
+ }
+ ptp->mchanp = fdc_p;
+ }
+#if (HAVE_NVME && (! IGNORE_NVME))
+ if (fdc_p->is_nvme_dev)
+ return sg_do_nvme_pt(ptp, -1, true /* assume Admin */, time_secs, vb);
+#endif
+
+ /* SCSI CAM pass-through follows */
+ ptp->is_nvme_dev = fdc_p->is_nvme_dev;
+ if (NULL == fdc_p->cam_dev) {
+ if (vb)
+ pr2ws("No open CAM device\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ if (NULL == ptp->ccb) { /* re-use if we have one already */
+ if (! (ccb = cam_getccb(fdc_p->cam_dev))) {
+ if (vb)
+ pr2ws("cam_getccb: failed\n");
+ ptp->os_err = ENOMEM;
+ return -ptp->os_err;
+ }
+ ptp->ccb = ccb;
+ } else
+ ccb = ptp->ccb;
+
+ // clear out structure, except for header that was filled in for us
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ ptp->timeout_ms = (time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT;
+ cam_fill_csio(&ccb->csio,
+ /* retries */ 1,
+ /* cbfcnp */ NULL,
+ /* flags */ ptp->dxfer_dir,
+ /* tagaction */ MSG_SIMPLE_Q_TAG,
+ /* dataptr */ ptp->dxferp,
+ /* datalen */ ptp->dxfer_len,
+ /* senselen */ ptp->sense_len,
+ /* cdblen */ ptp->cdb_len,
+ /* timeout (millisecs) */ ptp->timeout_ms);
+ memcpy(ccb->csio.cdb_io.cdb_bytes, ptp->cdb, ptp->cdb_len);
+
+ if (cam_send_ccb(fdc_p->cam_dev, ccb) < 0) {
+ if (vb) {
+ pr2serr("%s: cam_send_ccb() error\n", __func__);
+ CAM_ERROR_PRINT(fdc_p->cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, ferrp);
+ }
+ cam_freeccb(ptp->ccb);
+ ptp->ccb = NULL;
+ ptp->os_err = EIO;
+ return -ptp->os_err;
+ }
+
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) ||
+ ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR)) {
+ ptp->scsi_status = ccb->csio.scsi_status;
+ ptp->resid = ccb->csio.resid;
+ ptp->sense_resid = ccb->csio.sense_resid;
+
+ if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status)) {
+ int len;
+
+ if (ptp->sense_resid > ptp->sense_len)
+ len = ptp->sense_len; /* crazy; ignore sense_resid */
+ else
+ len = ptp->sense_len - ptp->sense_resid;
+ if (len > 0)
+ memcpy(ptp->sense, &(ccb->csio.sense_data), len);
+ }
+ } else
+ ptp->transport_err = 1;
+
+ return 0;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if (ptp->transport_err)
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status))
+ return SCSI_PT_RESULT_SENSE;
+ else if (ptp->scsi_status)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if ((NULL == ptp) || (NULL == ptp->mchanp))
+ return 0;
+ return ((ptp->is_nvme_dev && ! ptp->nvme_our_sntl)) ? 0 : ptp->resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+ bool bidi = (ptp->dxfer_dir == CAM_DIR_BOTH);
+
+ if (req_dinp) {
+ if (ptp->dxfer_ilen > 0)
+ *req_dinp = ptp->dxfer_ilen;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if ((!bidi) && (ptp->dxfer_olen > 0))
+ *req_doutp = ptp->dxfer_olen;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+ bool bidi = (ptp->dxfer_dir == CAM_DIR_BOTH);
+
+ if (act_dinp) {
+ if (ptp->dxfer_ilen > 0)
+ *act_dinp = ptp->dxfer_ilen - ptp->resid;
+ else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if ((!bidi) && (ptp->dxfer_olen > 0))
+ *act_doutp = ptp->dxfer_olen - ptp->resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+/* Returns SCSI status value (from device that received the command). If an
+ * NVMe command was issued directly (i.e. through do_scsi_pt() then return
+ * NVMe status (i.e. ((SCT << 8) | SC)). If problem returns -1. */
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ const struct freebsd_dev_channel * fdc_p = ptp->mchanp;
+
+ if (NULL == fdc_p)
+ return -1;
+ if (ptp->is_nvme_dev && ! ptp->nvme_our_sntl)
+ return (int)ptp->nvme_status;
+ else
+ return ptp->scsi_status;
+ }
+ return -1;
+}
+
+/* For NVMe command: CDW0 from completion (32 bits); for SCSI: the status */
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ const struct freebsd_dev_channel * fdc_p = ptp->mchanp;
+
+ if (NULL == fdc_p)
+ return -1;
+ if (ptp->is_nvme_dev && ! ptp->nvme_our_sntl)
+ return ptp->nvme_result;
+ else
+ return (uint32_t)ptp->scsi_status;
+ }
+ return 0xffffffff;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp->sense_resid > ptp->sense_len)
+ return ptp->sense_len; /* strange; ignore ptp->sense_resid */
+ else
+ return ptp->sense_len - ptp->sense_resid;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp->sense;
+}
+
+/* Not implemented so return -1 . */
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ // const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return -1;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp->transport_err;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ ptp->transport_err = err;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (0 == ptp->transport_err) {
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+ return b;
+ }
+ if (ptp->mchanp && ptp->mchanp->is_nvme_dev) {
+ snprintf(b, max_b_len, "NVMe has no transport errors at present "
+ "but tranport_err=%d ??\n", ptp->transport_err);
+ return b;
+ }
+#if __FreeBSD_version > 500000
+ if (ptp->mchanp && ptp->mchanp->cam_dev)
+ cam_error_string(ptp->mchanp->cam_dev, ptp->ccb, b, max_b_len,
+ CAM_ESF_ALL, CAM_EPF_ALL);
+ else {
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+ }
+#else
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+#endif
+ return b;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp && (ptp->dev_han >= 0)) {
+ const struct freebsd_dev_channel *fdc_p;
+
+ fdc_p = get_fdc_cp(ptp);
+ if (NULL == fdc_p) {
+ pr2ws("%s: unable to find fdc_p\n", __func__);
+ errno = ENODEV;
+ return false;
+ }
+ return fdc_p->is_nvme_dev;
+ }
+ return false;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'objp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+ if (ptp && (ptp->dev_han >= 0)) {
+ const struct freebsd_dev_channel *fdc_p;
+
+ fdc_p = get_fdc_cp(ptp);
+ if (NULL == fdc_p)
+ return 0;
+ return fdc_p->nsid;
+ }
+ return 0;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+ const char * cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+
+#define SCSI_INQUIRY_OPC 0x12
+#define SCSI_MAINT_IN_OPC 0xa3
+#define SCSI_MODE_SENSE10_OPC 0x5a
+#define SCSI_MODE_SELECT10_OPC 0x55
+#define SCSI_READ10_OPC 0x28
+#define SCSI_READ16_OPC 0x88
+#define SCSI_READ_CAPACITY10_OPC 0x25
+#define SCSI_START_STOP_OPC 0x1b
+#define SCSI_SYNC_CACHE10_OPC 0x35
+#define SCSI_SYNC_CACHE16_OPC 0x91
+#define SCSI_VERIFY10_OPC 0x2f
+#define SCSI_VERIFY16_OPC 0x8f
+#define SCSI_WRITE10_OPC 0x2a
+#define SCSI_WRITE16_OPC 0x8a
+#define SCSI_WRITE_SAME10_OPC 0x41
+#define SCSI_WRITE_SAME16_OPC 0x93
+#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c
+#define SCSI_REP_SUP_OPCS_OPC 0xc
+#define SCSI_REP_SUP_TMFS_OPC 0xd
+#define SCSI_REPORT_LUNS_OPC 0xa0
+#define SCSI_REQUEST_SENSE_OPC 0x3
+#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d
+#define SCSI_TEST_UNIT_READY_OPC 0x0
+#define SCSI_SERVICE_ACT_IN_OPC 0x9e
+#define SCSI_READ_CAPACITY16_SA 0x10
+#define SCSI_SA_MSK 0x1f
+
+/* Additional Sense Code (ASC) */
+#define NO_ADDITIONAL_SENSE 0x0
+#define LOGICAL_UNIT_NOT_READY 0x4
+#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8
+#define UNRECOVERED_READ_ERR 0x11
+#define PARAMETER_LIST_LENGTH_ERR 0x1a
+#define INVALID_OPCODE 0x20
+#define LBA_OUT_OF_RANGE 0x21
+#define INVALID_FIELD_IN_CDB 0x24
+#define INVALID_FIELD_IN_PARAM_LIST 0x26
+#define UA_RESET_ASC 0x29
+#define UA_CHANGED_ASC 0x2a
+#define TARGET_CHANGED_ASC 0x3f
+#define LUNS_CHANGED_ASCQ 0x0e
+#define INSUFF_RES_ASC 0x55
+#define INSUFF_RES_ASCQ 0x3
+#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */
+#define POWER_ON_RESET_ASCQ 0x0
+#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */
+#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */
+#define CAPACITY_CHANGED_ASCQ 0x9
+#define SAVING_PARAMS_UNSUP 0x39
+#define TRANSPORT_PROBLEM 0x4b
+#define THRESHOLD_EXCEEDED 0x5d
+#define LOW_POWER_COND_ON 0x5e
+#define MISCOMPARE_VERIFY_ASC 0x1d
+#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */
+#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
+#define PCIE_ERR_ASC 0x4b
+#define PCIE_UNSUPP_REQ_ASCQ 0x13
+
+/* NVMe Admin commands */
+#define SG_NVME_AD_GET_FEATURE 0xa
+#define SG_NVME_AD_SET_FEATURE 0x9
+#define SG_NVME_AD_IDENTIFY 0x6 /* similar to SCSI INQUIRY */
+#define SG_NVME_AD_DEV_SELT_TEST 0x14
+#define SG_NVME_AD_MI_RECEIVE 0x1e /* MI: Management Interface */
+#define SG_NVME_AD_MI_SEND 0x1d /* hmmm, same opcode as SEND DIAG */
+
+/* NVMe NVM (Non-Volatile Memory) commands */
+#define SG_NVME_NVM_FLUSH 0x0 /* SCSI SYNCHRONIZE CACHE */
+#define SG_NVME_NVM_COMPARE 0x5 /* SCSI VERIFY(BYTCHK=1) */
+#define SG_NVME_NVM_READ 0x2
+#define SG_NVME_NVM_VERIFY 0xc /* SCSI VERIFY(BYTCHK=0) */
+#define SG_NVME_NVM_WRITE 0x1
+#define SG_NVME_NVM_WRITE_ZEROES 0x8 /* SCSI WRITE SAME */
+
+#define SG_NVME_RW_CDW12_FUA (1 << 30) /* Force Unit Access bit */
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+static void
+mk_sense_asc_ascq(struct sg_pt_freebsd_scsi * ptp, int sk, int asc, int ascq,
+ int vb)
+{
+ bool dsense = ptp->mchanp ? ptp->mchanp->dev_stat.scsi_dsense : false;
+ int n;
+ uint8_t * sbp = ptp->sense;
+
+ ptp->scsi_status = SAM_STAT_CHECK_CONDITION;
+ n = ptp->sense_len;
+ if ((n < 8) || ((! dsense) && (n < 14))) {
+ if (vb)
+ pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__,
+ n);
+ return;
+ } else
+ ptp->sense_resid = ptp->sense_len -
+ (dsense ? 8 : ((n < 18) ? n : 18));
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__,
+ sk, asc, ascq);
+}
+
+static void
+mk_sense_from_nvme_status(struct sg_pt_freebsd_scsi * ptp, uint16_t sct_sc,
+ int vb)
+{
+ bool ok;
+ bool dsense = ptp->mchanp ? ptp->mchanp->dev_stat.scsi_dsense : false;
+ int n;
+ uint8_t sstatus, sk, asc, ascq;
+ uint8_t * sbp = ptp->sense;
+
+ ok = sg_nvme_status2scsi(sct_sc, &sstatus, &sk, &asc, &ascq);
+ if (! ok) { /* can't find a mapping to a SCSI error, so ... */
+ sstatus = SAM_STAT_CHECK_CONDITION;
+ sk = SPC_SK_ILLEGAL_REQUEST;
+ asc = 0xb;
+ ascq = 0x0; /* asc: "WARNING" purposely vague */
+ }
+
+ ptp->scsi_status = sstatus;
+ n = ptp->sense_len;
+ if ((n < 8) || ((! dsense) && (n < 14))) {
+ if (vb)
+ pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__,
+ n);
+ return;
+ } else
+ ptp->sense_resid = ptp->sense_len -
+ (dsense ? 8 : ((n < 18) ? n : 18));
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__,
+ sk, asc, ascq);
+ if (dsense && (sct_sc > 0) && (ptp->sense_resid > 7)) {
+ sg_nvme_desc2sense(sbp, 0x4000 & sct_sc /* dnr */,
+ 0x2000 & sct_sc /* more */, 0x7ff & sct_sc);
+ ptp->sense_resid -= 8;
+ }
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_invalid_fld(struct sg_pt_freebsd_scsi * ptp, bool in_cdb,
+ int in_byte, int in_bit, int vb)
+{
+ bool ds = ptp->mchanp ? ptp->mchanp->dev_stat.scsi_dsense : false;
+ int asc, n;
+ uint8_t * sbp = (uint8_t *)ptp->sense;
+ uint8_t sks[4];
+
+ ptp->scsi_status = SAM_STAT_CHECK_CONDITION;
+ asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST;
+ n = ptp->sense_len;
+ if ((n < 8) || ((! ds) && (n < 14))) {
+ if (vb)
+ pr2ws("%s: max_response_len=%d too short, want 14 or more\n",
+ __func__, n);
+ return;
+ } else
+ ptp->sense_resid = ptp->sense_len - (ds ? 8 : ((n < 18) ? n : 18));
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(ds, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0);
+ memset(sks, 0, sizeof(sks));
+ sks[0] = 0x80;
+ if (in_cdb)
+ sks[0] |= 0x40;
+ if (in_bit >= 0) {
+ sks[0] |= 0x8;
+ sks[0] |= (0x7 & in_bit);
+ }
+ sg_put_unaligned_be16(in_byte, sks + 1);
+ if (ds) {
+ int sl = sbp[7] + 8;
+
+ sbp[7] = sl;
+ sbp[sl] = 0x2;
+ sbp[sl + 1] = 0x6;
+ memcpy(sbp + sl + 4, sks, 3);
+ } else
+ memcpy(sbp + 15, sks, 3);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n",
+ __func__, asc, in_cdb ? 'C' : 'D', in_byte,
+ ((in_bit > 0) ? (0x7 & in_bit) : 0));
+}
+
+#if 0
+static void
+nvme_cbfcn(struct cam_periph * camperp, union ccb * ccb)
+{
+ pr2ws("%s: >>>> called, camperp=%p, ccb=%p\n", __func__, camperp, ccb);
+}
+#endif
+
+/* Does actual ioctl(NVME_PASSTHROUGH_CMD) or uses NVME(CAM) interface.
+ * Returns 0 on success; negative values are Unix negated errno values;
+ * positive values are NVMe status (i.e. ((SCT << 8) | SC) ). */
+static int
+nvme_pt_low(struct sg_pt_freebsd_scsi * ptp, void * dxferp, uint32_t len,
+ bool is_admin, bool is_read, struct nvme_pt_command * npcp,
+ int time_secs, int vb)
+{
+ int err, dev_fd;
+ uint16_t sct_sc;
+ uint8_t opcode;
+ struct freebsd_dev_channel *fdc_p = ptp->mchanp;
+
+ if (vb > 6)
+ pr2ws("%s: is_read=%d, time_secs=%d, is_cam_nvme=%d, is_admin=%d\n",
+ __func__, (int)is_read, time_secs, (int)fdc_p->is_cam_nvme,
+ (int)is_admin);
+ ptp->is_nvme_dev = fdc_p->is_nvme_dev;
+ npcp->buf = dxferp;
+ npcp->len = len;
+ npcp->is_read = (uint32_t)is_read;
+ opcode = npcp->cmd.opc;
+#if __FreeBSD_version >= 1100000
+ if (fdc_p->is_cam_nvme)
+ goto cam_nvme;
+#endif
+
+ /* non-CAM NVMe processing follows */
+ if (is_admin) {
+ if (fdc_p->nvme_fd_ctrl < 0) {
+ if (vb > 4)
+ pr2ws("%s: not CAM but nvme_fd_ctrl<0, try to open "
+ "controller\n", __func__);
+ if ((fdc_p->nsid > 0) && fdc_p->devname && *fdc_p->devname) {
+ int fd;
+ char dev_nm[PATH_MAX];
+
+ if ((fdc_p->devname[0] == '/') || (fdc_p->devname[0] == '.'))
+ strncpy(dev_nm, fdc_p->devname, PATH_MAX);
+ else
+ snprintf(dev_nm, PATH_MAX, "/dev/%s", fdc_p->devname);
+ fd = open(dev_nm, O_RDWR);
+ if (fd < 0) {
+ if (vb > 1)
+ pr2ws("%s: Unable to open %s of NVMe controller: "
+ "%s\n", __func__, dev_nm, strerror(errno));
+ } else
+ fdc_p->nvme_fd_ctrl = fd;
+ }
+ if (fdc_p->nvme_fd_ctrl < 0)
+ return -EINVAL;
+ }
+ dev_fd = fdc_p->nvme_fd_ctrl;
+ } else {
+ if (fdc_p->nvme_fd_ns < 0) {
+ if (vb > 1)
+ pr2ws("%s: not CAM but nvme_fd_ns<0, inconsistent\n",
+ __func__);
+ return -EINVAL;
+ }
+ dev_fd = fdc_p->nvme_fd_ns;
+ }
+ err = ioctl(dev_fd, NVME_PASSTHROUGH_CMD, npcp);
+ if (err < 0) {
+ err = errno;
+ if (vb)
+ pr2ws("%s: ioctl(NVME_PASSTHROUGH_CMD) errno: %s\n", __func__,
+ strerror(err));
+ /* when that ioctl returns an error npcp->cpl is not populated */
+ return -err;
+ }
+
+#if __FreeBSD_version <= 1200058
+ sct_sc = ((npcp->cpl.status.sct << 8) | npcp->cpl.status.sc);
+#else
+ sct_sc = (NVME_STATUS_GET_SCT(npcp->cpl.status) << 8) |
+ NVME_STATUS_GET_SC(npcp->cpl.status);
+#endif
+ ptp->nvme_result = npcp->cpl.cdw0;
+ sg_put_unaligned_le32(npcp->cpl.cdw0,
+ ptp->cq_dw0_3 + SG_NVME_PT_CQ_RESULT);
+ sg_put_unaligned_le32(npcp->cpl.rsvd1, ptp->cq_dw0_3 + 4);
+ sg_put_unaligned_le16(npcp->cpl.sqhd, ptp->cq_dw0_3 + 8);
+ sg_put_unaligned_le16(npcp->cpl.sqid, ptp->cq_dw0_3 + 10);
+ sg_put_unaligned_le16(npcp->cpl.cid, ptp->cq_dw0_3 + 12);
+ sg_put_unaligned_le16(*((const uint16_t *)&(npcp->cpl.status)),
+ ptp->cq_dw0_3 + SG_NVME_PT_CQ_STATUS_P);
+ if (sct_sc && (vb > 1)) {
+ char nam[64];
+ char b[80];
+
+ sg_get_nvme_opcode_name(opcode, is_admin, sizeof(nam), nam);
+ pr2ws("%s: %s [0x%x], status: %s\n", __func__, nam, opcode,
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b));
+ }
+ return sct_sc;
+
+#if __FreeBSD_version >= 1100000
+cam_nvme:
+ {
+ cam_status ccb_status;
+ union ccb *ccb;
+ struct ccb_nvmeio *nviop;
+ FILE * ferrp = sg_warnings_strm ? sg_warnings_strm : stderr;
+
+ if (NULL == ptp->ccb) { /* re-use if we have one already */
+ if (! (ccb = cam_getccb(fdc_p->cam_dev))) {
+ if (vb)
+ pr2ws("%s: cam_getccb: failed\n", __func__);
+ ptp->os_err = ENOMEM;
+ return -ptp->os_err;
+ }
+ ptp->ccb = ccb;
+ } else
+ ccb = ptp->ccb;
+ nviop = &ccb->nvmeio;
+ CCB_CLEAR_ALL_EXCEPT_HDR(nviop);
+
+ memcpy(&nviop->cmd, &npcp->cmd, sizeof(nviop->cmd));
+ ptp->timeout_ms = (time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT;
+ if (is_admin)
+ cam_fill_nvmeadmin(nviop,
+ 1 /* retries */,
+ NULL,
+ is_read ? CAM_DIR_IN : CAM_DIR_OUT,
+ dxferp,
+ len,
+ ptp->timeout_ms);
+
+ else { /* NVM command set, rather than Admin */
+ if (fdc_p->nsid != npcp->cmd.nsid) {
+ if (vb)
+ pr2ws("%s: device node nsid [%u] not equal to cmd nsid "
+ "[%u]\n", __func__, fdc_p->nsid, npcp->cmd.nsid);
+ return -EINVAL;
+ }
+ cam_fill_nvmeio(nviop,
+ 1 /* retries */,
+ NULL,
+ is_read ? CAM_DIR_IN : CAM_DIR_OUT,
+ dxferp,
+ len,
+ ptp->timeout_ms);
+ }
+
+ if (cam_send_ccb(fdc_p->cam_dev, ccb) < 0) {
+ if (vb) {
+ pr2ws("%s: cam_send_ccb(NVME) %s ccb error\n", __func__,
+ (is_admin ? "Admin" : "NVM"));
+ CAM_ERROR_PRINT(fdc_p->cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, ferrp);
+ }
+ cam_freeccb(ptp->ccb);
+ ptp->ccb = NULL;
+ ptp->os_err = EIO;
+ return -ptp->os_err;
+ }
+ ccb_status = ccb->ccb_h.status & CAM_STATUS_MASK;
+ if (ccb_status == CAM_REQ_CMP) {
+ ptp->nvme_result = 0;
+ ptp->os_err = 0;
+ return 0;
+ }
+ /* error processing follows ... */
+ ptp->os_err = EIO;
+ if (vb) {
+ pr2ws("%s: ccb_status != CAM_REQ_CMP\n", __func__);
+ CAM_ERROR_PRINT(fdc_p->cam_dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, ferrp);
+ }
+#if __FreeBSD_version <= 1200058
+ sct_sc = ((nviop->cpl.status.sct << 8) | nviop->cpl.status.sc);
+#else
+ sct_sc = (NVME_STATUS_GET_SCT(nviop->cpl.status) << 8) |
+ NVME_STATUS_GET_SC(nviop->cpl.status);
+#endif
+ ptp->nvme_result = nviop->cpl.cdw0;
+ sg_put_unaligned_le32(nviop->cpl.cdw0,
+ ptp->cq_dw0_3 + SG_NVME_PT_CQ_RESULT);
+ sg_put_unaligned_le32(nviop->cpl.rsvd1, ptp->cq_dw0_3 + 4);
+ sg_put_unaligned_le16(nviop->cpl.sqhd, ptp->cq_dw0_3 + 8);
+ sg_put_unaligned_le16(nviop->cpl.sqid, ptp->cq_dw0_3 + 10);
+ sg_put_unaligned_le16(nviop->cpl.cid, ptp->cq_dw0_3 + 12);
+ sg_put_unaligned_le16(*((const uint16_t *)&(nviop->cpl.status)),
+ ptp->cq_dw0_3 + SG_NVME_PT_CQ_STATUS_P);
+ if (sct_sc && (vb > 1)) {
+ char nam[64];
+ char b[80];
+
+ sg_get_nvme_opcode_name(opcode, is_admin, sizeof(nam),
+ nam);
+ pr2ws("%s: %s [0x%x], status: %s\n", __func__, nam, opcode,
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b));
+ }
+ return sct_sc ? sct_sc : ptp->os_err;
+ }
+#endif
+ return 0;
+}
+
+static void
+sntl_check_enclosure_override(struct freebsd_dev_channel * fdc_p, int vb)
+{
+ uint8_t * up = fdc_p->nvme_id_ctlp;
+ uint8_t nvmsr;
+
+ if (NULL == up)
+ return;
+ nvmsr = up[253];
+ if (vb > 5)
+ pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr);
+ fdc_p->dev_stat.id_ctl253 = nvmsr;
+ switch (fdc_p->dev_stat.enclosure_override) {
+ case 0x0: /* no override */
+ if (0x3 == (0x3 & nvmsr)) {
+ fdc_p->dev_stat.pdt = PDT_DISK;
+ fdc_p->dev_stat.enc_serv = 1;
+ } else if (0x2 & nvmsr) {
+ fdc_p->dev_stat.pdt = PDT_SES;
+ fdc_p->dev_stat.enc_serv = 1;
+ } else if (0x1 & nvmsr) {
+ fdc_p->dev_stat.pdt = PDT_DISK;
+ fdc_p->dev_stat.enc_serv = 0;
+ } else {
+ uint32_t nn = sg_get_unaligned_le32(up + 516);
+
+ fdc_p->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN;
+ fdc_p->dev_stat.enc_serv = 0;
+ }
+ break;
+ case 0x1: /* override to SES device */
+ fdc_p->dev_stat.pdt = PDT_SES;
+ fdc_p->dev_stat.enc_serv = 1;
+ break;
+ case 0x2: /* override to disk with attached SES device */
+ fdc_p->dev_stat.pdt = PDT_DISK;
+ fdc_p->dev_stat.enc_serv = 1;
+ break;
+ case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */
+ fdc_p->dev_stat.pdt = PDT_PROCESSOR;
+ fdc_p->dev_stat.enc_serv = 1;
+ break;
+ case 0xff: /* override to normal disk */
+ fdc_p->dev_stat.pdt = PDT_DISK;
+ fdc_p->dev_stat.enc_serv = 0;
+ break;
+ default:
+ pr2ws("%s: unknown enclosure_override value: %d\n", __func__,
+ fdc_p->dev_stat.enclosure_override);
+ break;
+ }
+}
+
+static int
+sntl_do_identify(struct sg_pt_freebsd_scsi * ptp, int cns, int nsid,
+ int u_len, uint8_t * up, int time_secs, int vb)
+{
+ int err;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+
+ if (vb > 5)
+ pr2ws("%s: nsid=%d\n", __func__, nsid);
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_IDENTIFY;
+ sg_put_unaligned_le32(nsid, npc_up + SG_NVME_PT_NSID);
+ /* CNS=0x1 Identify: controller */
+ sg_put_unaligned_le32(cns, npc_up + SG_NVME_PT_CDW10);
+ sg_put_unaligned_le64((sg_uintptr_t)up, npc_up + SG_NVME_PT_ADDR);
+ sg_put_unaligned_le32(u_len, npc_up + SG_NVME_PT_DATA_LEN);
+ err = nvme_pt_low(ptp, up, u_len, true, true, &npc, time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n", __func__,
+ strerror(-err), -err);
+ return err;
+ } else { /* non-zero NVMe command status */
+ ptp->nvme_status = err;
+ return SG_LIB_NVME_STATUS;
+ }
+ }
+ return 0;
+}
+
+/* Currently only caches associated controller response (4096 bytes) */
+static int
+sntl_cache_identity(struct sg_pt_freebsd_scsi * ptp, int time_secs, int vb)
+{
+ int ret;
+ uint32_t pg_sz = sg_get_page_size();
+ struct freebsd_dev_channel * fdc_p = ptp->mchanp;
+
+ fdc_p->nvme_id_ctlp = sg_memalign(pg_sz, pg_sz,
+ &fdc_p->free_nvme_id_ctlp, vb > 3);
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ if (vb)
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ ret = sntl_do_identify(ptp, 0x1 /* CNS */, 0 /* nsid */, pg_sz,
+ fdc_p->nvme_id_ctlp, time_secs, vb);
+ if (0 == ret)
+ sntl_check_enclosure_override(fdc_p, vb);
+ return (ret < 0) ? sg_convert_errno(-ret) : ret;
+}
+
+static const char * nvme_scsi_vendor_str = "NVMe ";
+static const uint16_t inq_resp_len = 36;
+
+static int
+sntl_inq(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int time_secs,
+ int vb)
+{
+ bool evpd;
+ int res;
+ uint16_t n, alloc_len, pg_cd;
+ uint32_t pg_sz = sg_get_page_size();
+ struct freebsd_dev_channel * fdc_p;
+ uint8_t * nvme_id_ns = NULL;
+ uint8_t * free_nvme_id_ns = NULL;
+ uint8_t inq_dout[256];
+
+ if (vb > 5)
+ pr2ws("%s: starting\n", __func__);
+
+ if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */
+ mk_sense_invalid_fld(ptp, true, 1, 1, vb);
+ return 0;
+ }
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ res = sntl_cache_identity(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, ptp->nvme_status, vb);
+ return 0;
+ } else if (res) /* should be negative errno */
+ return res;
+ }
+ memset(inq_dout, 0, sizeof(inq_dout));
+ alloc_len = sg_get_unaligned_be16(cdbp + 3);
+ evpd = !!(0x1 & cdbp[1]);
+ pg_cd = cdbp[2];
+ if (evpd) { /* VPD page responses */
+ bool cp_id_ctl = false;
+
+ switch (pg_cd) {
+ case 0: /* Supported VPD pages VPD page */
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 11;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x0;
+ inq_dout[5] = 0x80;
+ inq_dout[6] = 0x83;
+ inq_dout[7] = 0x86;
+ inq_dout[8] = 0x87;
+ inq_dout[9] = 0x92;
+ inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */
+ break;
+ case 0x80: /* Serial number VPD page */
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 24;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ memcpy(inq_dout + 4, fdc_p->nvme_id_ctlp + 4, 20); /* SN */
+ break;
+ case 0x83: /* Device identification VPD page */
+ if ((fdc_p->nsid > 0) && (fdc_p->nsid < SG_NVME_BROADCAST_NSID)) {
+ nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns,
+ vb > 3);
+ if (nvme_id_ns) {
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_IDENTIFY;
+ sg_put_unaligned_le32(fdc_p->nsid,
+ npc_up + SG_NVME_PT_NSID);
+ /* CNS=0x0 Identify: namespace */
+ sg_put_unaligned_le32(0x0, npc_up + SG_NVME_PT_CDW10);
+ sg_put_unaligned_le64((sg_uintptr_t)nvme_id_ns,
+ npc_up + SG_NVME_PT_ADDR);
+ sg_put_unaligned_le32(pg_sz,
+ npc_up + SG_NVME_PT_DATA_LEN);
+ res = nvme_pt_low(ptp, nvme_id_ns, pg_sz, true, true,
+ &npc, time_secs, vb > 3);
+ if (res) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ }
+ }
+ n = sg_make_vpd_devid_for_nvme(fdc_p->nvme_id_ctlp, nvme_id_ns, 0,
+ -1, inq_dout, sizeof(inq_dout));
+ if (n > 3)
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ if (free_nvme_id_ns) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ break;
+ case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 64;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[5] = 0x1; /* SIMPSUP=1 */
+ inq_dout[7] = 0x1; /* LUICLR=1 */
+ inq_dout[13] = 0x40; /* max supported sense data length */
+ break;
+ case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 8;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x3f; /* all mode pages */
+ inq_dout[5] = 0xff; /* and their sub-pages */
+ inq_dout[6] = 0x80; /* MLUS=1, policy=shared */
+ break;
+ case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */
+ inq_dout[1] = pg_cd;
+ n = 10;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde */
+ inq_dout[1] = pg_cd;
+ sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2);
+ n = 16 + 4096;
+ cp_id_ctl = true;
+ break;
+ default: /* Point to page_code field in cdb */
+ mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+ return 0;
+ }
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < ptp->dxfer_len) ? n : ptp->dxfer_len;
+ ptp->resid = ptp->dxfer_len - n;
+ if (n > 0) {
+ if (cp_id_ctl) {
+ memcpy((uint8_t *)ptp->dxferp, inq_dout,
+ (n < 16 ? n : 16));
+ if (n > 16)
+ memcpy((uint8_t *)ptp->dxferp + 16,
+ fdc_p->nvme_id_ctlp, n - 16);
+ } else
+ memcpy((uint8_t *)ptp->dxferp, inq_dout, n);
+ }
+ }
+ } else { /* Standard INQUIRY response */
+ /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */
+ inq_dout[0] = (PDT_MASK & fdc_p->dev_stat.pdt); /* (PQ=0)<<5 */
+ /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6; rest reserved */
+ inq_dout[2] = 6; /* version: SPC-4 */
+ inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */
+ inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */
+ inq_dout[6] = fdc_p->dev_stat.enc_serv ? 0x40 : 0;
+ inq_dout[7] = 0x2; /* CMDQUE=1 */
+ memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */
+ memcpy(inq_dout + 16, fdc_p->nvme_id_ctlp + 24, 16);/* Prod <-- MN */
+ memcpy(inq_dout + 32, fdc_p->nvme_id_ctlp + 64, 4); /* Rev <-- FR */
+ if (alloc_len > 0) {
+ n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len;
+ n = (n < ptp->dxfer_len) ? n : ptp->dxfer_len;
+ if (n > 0)
+ memcpy((uint8_t *)ptp->dxferp, inq_dout, n);
+ }
+ }
+ return 0;
+}
+
+static int
+sntl_rluns(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ int res;
+ uint16_t sel_report;
+ uint32_t alloc_len, k, n, num, max_nsid;
+ struct freebsd_dev_channel * fdc_p;
+ uint8_t * rl_doutp;
+ uint8_t * up;
+
+ if (vb > 5)
+ pr2ws("%s: starting\n", __func__);
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ sel_report = cdbp[2];
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ res = sntl_cache_identity(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, ptp->nvme_status, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ max_nsid = sg_get_unaligned_le32(fdc_p->nvme_id_ctlp + 516);
+ switch (sel_report) {
+ case 0:
+ case 2:
+ num = max_nsid;
+ break;
+ case 1:
+ case 0x10:
+ case 0x12:
+ num = 0;
+ break;
+ case 0x11:
+ num = (1 == fdc_p->nsid) ? max_nsid : 0;
+ break;
+ default:
+ if (vb > 1)
+ pr2ws("%s: bad select_report value: 0x%x\n", __func__,
+ sel_report);
+ mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+ return 0;
+ }
+ rl_doutp = (uint8_t *)calloc(num + 1, 8);
+ if (NULL == rl_doutp) {
+ if (vb)
+ pr2ws("%s: calloc() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8)
+ sg_put_unaligned_be16(k, up);
+ n = num * 8;
+ sg_put_unaligned_be32(n, rl_doutp);
+ n+= 8;
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < (uint32_t)ptp->dxfer_len) ? n : (uint32_t)ptp->dxfer_len;
+ ptp->resid = ptp->dxfer_len - (int)n;
+ if (n > 0)
+ memcpy((uint8_t *)ptp->dxferp, rl_doutp, n);
+ }
+ res = 0;
+ free(rl_doutp);
+ return res;
+}
+
+static int
+sntl_tur(struct sg_pt_freebsd_scsi * ptp, int time_secs, int vb)
+{
+ int err;
+ uint32_t pow_state;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: starting\n", __func__);
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ int res = sntl_cache_identity(ptp, time_secs, vb);
+
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, ptp->nvme_status, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_GET_FEATURE;
+ sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, npc_up + SG_NVME_PT_NSID);
+ /* SEL=0 (current), Feature=2 Power Management */
+ sg_put_unaligned_le32(0x2, npc_up + SG_NVME_PT_CDW10);
+ err = nvme_pt_low(ptp, NULL, 0, true, false, &npc, time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n", __func__,
+ strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ pow_state = (0x1f & ptp->nvme_result);
+ if (vb > 3)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+#if 0 /* pow_state bounces around too much on laptop */
+ if (pow_state)
+ mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0,
+ vb);
+#endif
+ return 0;
+}
+
+static int
+sntl_req_sense(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool desc;
+ int err;
+ uint32_t pow_state, alloc_len, n;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+ uint8_t rs_dout[64];
+
+ if (vb > 5)
+ pr2ws("%s: starting\n", __func__);
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ int res = sntl_cache_identity(ptp, time_secs, vb);
+
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, ptp->nvme_status, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ desc = !!(0x1 & cdbp[1]);
+ alloc_len = cdbp[4];
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_GET_FEATURE;
+ sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, npc_up + SG_NVME_PT_NSID);
+ /* SEL=0 (current), Feature=2 Power Management */
+ sg_put_unaligned_le32(0x2, npc_up + SG_NVME_PT_CDW10);
+ err = nvme_pt_low(ptp, NULL, 0, true, false, &npc, time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n", __func__,
+ strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ pow_state = (0x1f & ptp->nvme_result);
+ if (vb > 3)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+ memset(rs_dout, 0, sizeof(rs_dout));
+ if (pow_state)
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ LOW_POWER_COND_ON_ASC, 0);
+ else
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ NO_ADDITIONAL_SENSE, 0);
+ n = desc ? 8 : 18;
+ n = (n < alloc_len) ? n : alloc_len;
+ n = (n < (uint32_t)ptp->dxfer_len) ? n : (uint32_t)ptp->dxfer_len;
+ ptp->resid = ptp->dxfer_len - (int)n;
+ if (n > 0)
+ memcpy((uint8_t *)ptp->dxferp, rs_dout, n);
+ return 0;
+}
+
+static int
+sntl_mode_ss(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]);
+ int n, len;
+ uint8_t * bp;
+ struct freebsd_dev_channel * fdc_p;
+ struct sg_sntl_result_t sntl_result;
+
+ if (vb > 5)
+ pr2ws("%s: mse%s\n", __func__, (is_msense ? "nse" : "lect"));
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ if (NULL == fdc_p->nvme_id_ctlp) {
+ int res = sntl_cache_identity(ptp, time_secs, vb);
+
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, ptp->nvme_status, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ if (is_msense) { /* MODE SENSE(10) */
+ len = ptp->dxfer_len;
+ bp = ptp->dxferp;
+ n = sntl_resp_mode_sense10(&fdc_p->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ ptp->resid = (n >= 0) ? len - n : len;
+ } else { /* MODE SELECT(10) */
+ uint8_t pre_enc_ov = fdc_p->dev_stat.enclosure_override;
+
+ len = ptp->dxfer_len;
+ bp = ptp->dxferp;
+ n = sntl_resp_mode_select10(&fdc_p->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ if (pre_enc_ov != fdc_p->dev_stat.enclosure_override)
+ sntl_check_enclosure_override(fdc_p, vb); /* ENC_OV has changed */
+ }
+ if (n < 0) {
+ int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit :
+ -1;
+ if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) &&
+ (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) {
+ if (INVALID_FIELD_IN_CDB == sntl_result.asc)
+ mk_sense_invalid_fld(ptp, true, sntl_result.in_byte, in_bit,
+ vb);
+ else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc)
+ mk_sense_invalid_fld(ptp, false, sntl_result.in_byte, in_bit,
+ vb);
+ else
+ mk_sense_asc_ascq(ptp, sntl_result.sk, sntl_result.asc,
+ sntl_result.ascq, vb);
+ } else if (vb)
+ pr2ws("%s: error but no sense?? n=%d\n", __func__, n);
+ }
+ return 0;
+}
+
+/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI
+ * has a special command (SES Send) to tunnel through pages to an
+ * enclosure. The NVMe enclosure is meant to understand the SES
+ * (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_senddiag(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool pf, self_test;
+ int err;
+ uint8_t st_cd, dpg_cd;
+ uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst;
+ const uint8_t * dop;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ st_cd = 0x7 & (cdbp[1] >> 5);
+ pf = !! (0x4 & cdbp[1]);
+ self_test = !! (0x10 & cdbp[1]);
+ if (vb > 5)
+ pr2ws("%s: pf=%d, self_test=%d, st_code=%d\n", __func__, (int)pf,
+ (int)self_test, (int)st_cd);
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ if (self_test || st_cd) {
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_DEV_SELT_TEST;
+ /* just this namespace (if there is one) and controller */
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ switch (st_cd) {
+ case 0: /* Here if self_test is set, do short self-test */
+ case 1: /* Background short */
+ case 5: /* Foreground short */
+ nvme_dst = 1;
+ break;
+ case 2: /* Background extended */
+ case 6: /* Foreground extended */
+ nvme_dst = 2;
+ break;
+ case 4: /* Abort self-test */
+ nvme_dst = 0xf;
+ break;
+ default:
+ pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd);
+ mk_sense_invalid_fld(ptp, true, 1, 7, vb);
+ return 0;
+ }
+ sg_put_unaligned_le32(nvme_dst, npc_up + SG_NVME_PT_CDW10);
+ err = nvme_pt_low(ptp, NULL, 0x0, true, false, &npc, time_secs, vb);
+ goto do_low;
+ }
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ dout_len = ptp->dxfer_len;
+ if (pf) {
+ if (0 == alloc_len) {
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: PF bit set bit param_list_len=0\n", __func__);
+ return 0;
+ }
+ } else { /* PF bit clear */
+ if (alloc_len) {
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: param_list_len>0 but PF clear\n", __func__);
+ return 0;
+ } else
+ return 0; /* nothing to do */
+ if (dout_len > 0) {
+ if (vb)
+ pr2ws("%s: dout given but PF clear\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ }
+ if (dout_len < 4) {
+ if (vb)
+ pr2ws("%s: dout length (%u bytes) too short\n", __func__,
+ dout_len);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ n = dout_len;
+ n = (n < alloc_len) ? n : alloc_len;
+ dop = (const uint8_t *)ptp->dxferp;
+ if (! sg_is_aligned(dop, 0)) {
+ if (vb)
+ pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)ptp->dxferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ dpg_cd = dop[0];
+ dpg_len = sg_get_unaligned_be16(dop + 2) + 4;
+ /* should we allow for more than one D_PG is dout ?? */
+ n = (n < dpg_len) ? n : dpg_len; /* not yet ... */
+
+ if (vb)
+ pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n",
+ __func__, dpg_cd, dpg_len);
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_MI_SEND;
+ sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferp,
+ npc_up + SG_NVME_PT_ADDR);
+ /* NVMe 4k page size. Maybe determine this? */
+ /* dout_len > 0x1000, is this a problem?? */
+ sg_put_unaligned_le32(0x1000, npc_up + SG_NVME_PT_DATA_LEN);
+ /* NVMe Message Header */
+ sg_put_unaligned_le32(0x0804, npc_up + SG_NVME_PT_CDW10);
+ /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */
+ sg_put_unaligned_le32(0x9, npc_up + SG_NVME_PT_CDW11);
+ /* data-out length I hope */
+ sg_put_unaligned_le32(n, npc_up + SG_NVME_PT_CDW13);
+ err = nvme_pt_low(ptp, ptp->dxferp, 0x1000, true, false, &npc, time_secs,
+ vb);
+do_low:
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1)
+ * NVMe-MI has a special command (SES Receive) to read pages through a
+ * tunnel from an enclosure. The NVMe enclosure is meant to understand the
+ * SES (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_recvdiag(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool pcv;
+ int err;
+ uint8_t dpg_cd;
+ uint32_t alloc_len, n, din_len;
+ const uint8_t * dip;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ pcv = !! (0x1 & cdbp[1]);
+ dpg_cd = cdbp[2];
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ if (vb > 5)
+ pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__,
+ dpg_cd, (int)pcv, alloc_len);
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ din_len = ptp->dxfer_len;
+ if (pcv) {
+ if (0 == alloc_len) {
+ /* T10 says not an error, hmmm */
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: PCV bit set bit but alloc_len=0\n", __func__);
+ return 0;
+ }
+ } else { /* PCV bit clear */
+ if (alloc_len) {
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: alloc_len>0 but PCV clear\n", __func__);
+ return 0;
+ } else
+ return 0; /* nothing to do */
+ if (din_len > 0) {
+ if (vb)
+ pr2ws("%s: din given but PCV clear\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ }
+ n = din_len;
+ n = (n < alloc_len) ? n : alloc_len;
+ dip = (const uint8_t *)ptp->dxferp;
+ if (! sg_is_aligned(dip, 0)) {
+ if (vb)
+ pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)ptp->dxferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ if (vb)
+ pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__,
+ dpg_cd);
+ memset(npc_up, 0, sizeof(npc));
+ npc_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_MI_RECEIVE;
+ sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferp,
+ npc_up + SG_NVME_PT_ADDR);
+ /* NVMe 4k page size. Maybe determine this? */
+ /* dout_len > 0x1000, is this a problem?? */
+ sg_put_unaligned_le32(0x1000, npc_up + SG_NVME_PT_DATA_LEN);
+ /* NVMe Message Header */
+ sg_put_unaligned_le32(0x0804, npc_up + SG_NVME_PT_CDW10);
+ /* nvme_mi_ses_receive */
+ sg_put_unaligned_le32(0x8, npc_up + SG_NVME_PT_CDW11);
+ sg_put_unaligned_le32(dpg_cd, npc_up + SG_NVME_PT_CDW12);
+ /* data-in length I hope */
+ sg_put_unaligned_le32(n, npc_up + SG_NVME_PT_CDW13);
+ err = nvme_pt_low(ptp, ptp->dxferp, 0x1000, true, true, &npc, time_secs,
+ vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = din_len - n;
+ return 0;
+}
+
+#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH 0x100 /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP 0x200
+
+static int
+sntl_rep_opcodes(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool rctd;
+ uint8_t reporting_opts, req_opcode, supp;
+ uint16_t req_sa;
+ uint32_t alloc_len, offset, a_len;
+ uint32_t pg_sz = sg_get_page_size();
+ int len, count, bump;
+ const struct sg_opcode_info_t *oip;
+ uint8_t *arr;
+ uint8_t *free_arr;
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */
+ reporting_opts = cdbp[2] & 0x7;
+ req_opcode = cdbp[3];
+ req_sa = sg_get_unaligned_be16(cdbp + 4);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4 || alloc_len > 0xffff) {
+ mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+ return 0;
+ }
+ a_len = pg_sz - 72;
+ arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3);
+ if (NULL == arr) {
+ if (vb)
+ pr2ws("%s: calloc() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ switch (reporting_opts) {
+ case 0: /* all commands */
+ count = 0;
+ bump = rctd ? 20 : 8;
+ for (offset = 4, oip = sg_get_opcode_translation();
+ (oip->flags != 0xffff) && (offset < a_len); ++oip) {
+ if (F_INV_OP & oip->flags)
+ continue;
+ ++count;
+ arr[offset] = oip->opcode;
+ sg_put_unaligned_be16(oip->sa, arr + offset + 2);
+ if (rctd)
+ arr[offset + 5] |= 0x2;
+ if (FF_SA & oip->flags)
+ arr[offset + 5] |= 0x1;
+ sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
+ if (rctd)
+ sg_put_unaligned_be16(0xa, arr + offset + 8);
+ offset += bump;
+ }
+ sg_put_unaligned_be32(count * bump, arr + 0);
+ break;
+ case 1: /* one command: opcode only */
+ case 2: /* one command: opcode plus service action */
+ case 3: /* one command: if sa==0 then opcode only else opcode+sa */
+ for (oip = sg_get_opcode_translation(); oip->flags != 0xffff; ++oip) {
+ if ((req_opcode == oip->opcode) && (req_sa == oip->sa))
+ break;
+ }
+ if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) {
+ supp = 1;
+ offset = 4;
+ } else {
+ if (1 == reporting_opts) {
+ if (FF_SA & oip->flags) {
+ mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ req_sa = 0;
+ } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) {
+ mk_sense_invalid_fld(ptp, true, 4, -1, vb);
+ free(free_arr);
+ return 0;
+ }
+ if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode))
+ supp = 3;
+ else if (0 == (FF_SA & oip->flags))
+ supp = 1;
+ else if (req_sa != oip->sa)
+ supp = 1;
+ else
+ supp = 3;
+ if (3 == supp) {
+ uint16_t u = oip->len_mask[0];
+ int k;
+
+ sg_put_unaligned_be16(u, arr + 2);
+ arr[4] = oip->opcode;
+ for (k = 1; k < u; ++k)
+ arr[4 + k] = (k < 16) ?
+ oip->len_mask[k] : 0xff;
+ offset = 4 + u;
+ } else
+ offset = 4;
+ }
+ arr[1] = (rctd ? 0x80 : 0) | supp;
+ if (rctd) {
+ sg_put_unaligned_be16(0xa, arr + offset);
+ offset += 12;
+ }
+ break;
+ default:
+ mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ offset = (offset < a_len) ? offset : a_len;
+ len = (offset < alloc_len) ? offset : alloc_len;
+ ptp->resid = ptp->dxfer_len - (int)len;
+ if (len > 0)
+ memcpy((uint8_t *)ptp->dxferp, arr, len);
+ free(free_arr);
+ return 0;
+}
+
+static int
+sntl_rep_tmfs(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool repd;
+ uint32_t alloc_len, len;
+ uint8_t arr[16];
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ memset(arr, 0, sizeof(arr));
+ repd = !!(cdbp[2] & 0x80);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4) {
+ mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+ return 0;
+ }
+ arr[0] = 0xc8; /* ATS | ATSS | LURS */
+ arr[1] = 0x1; /* ITNRS */
+ if (repd) {
+ arr[3] = 0xc;
+ len = 16;
+ } else
+ len = 4;
+
+ len = (len < alloc_len) ? len : alloc_len;
+ ptp->resid = ptp->dxfer_len - (int)len;
+ if (len > 0)
+ memcpy((uint8_t *)ptp->dxferp, arr, len);
+ return 0;
+}
+
+static int
+sntl_rread(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_read10 = (SCSI_READ10_OPC == cdbp[0]);
+ bool have_fua = !!(cdbp[1] & 0x8);
+ int err;
+ uint32_t nblks_t10 = 0; /* 'control' in upper 16 bits */
+ uint64_t lba;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: fua=%d\n", __func__, (int)have_fua);
+ fdc_p = get_fdc_p(ptp);
+ memset(&npc, 0, sizeof(npc));
+ npc.cmd.opc = SG_NVME_NVM_READ;
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ if (is_read10) {
+ lba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ lba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ --nblks_t10; /* crazy "0's based" counts */
+ sg_put_unaligned_le64(lba, npc_up + SG_NVME_PT_CDW10); /* fills W11 too */
+ if (have_fua)
+ nblks_t10 |= SG_NVME_RW_CDW12_FUA;
+ sg_put_unaligned_le32(nblks_t10, npc_up + SG_NVME_PT_CDW12);
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+
+ err = nvme_pt_low(ptp, ptp->dxferp, ptp->dxfer_len, false, true, &npc,
+ time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = 0; /* hoping */
+ return 0;
+}
+
+static int
+sntl_write(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_write10 = (SCSI_WRITE10_OPC == cdbp[0]);
+ bool have_fua = !!(cdbp[1] & 0x8);
+ int err;
+ uint32_t nblks_t10 = 0;
+ uint64_t lba;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: fua=%d, time_secs=%d\n", __func__, (int)have_fua,
+ time_secs);
+ fdc_p = get_fdc_p(ptp);
+ memset(&npc, 0, sizeof(npc));
+ npc.cmd.opc = SG_NVME_NVM_WRITE;
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ if (is_write10) {
+ lba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ lba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ --nblks_t10;
+ sg_put_unaligned_le64(lba, npc_up + SG_NVME_PT_CDW10); /* fills W11 too */
+ if (have_fua)
+ nblks_t10 |= SG_NVME_RW_CDW12_FUA;
+ sg_put_unaligned_le32(nblks_t10, npc_up + SG_NVME_PT_CDW12);
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+
+ err = nvme_pt_low(ptp, ptp->dxferp, ptp->dxfer_len, false, false, &npc,
+ time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = 0;
+ return 0;
+}
+
+static int
+sntl_verify(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_verify10 = (SCSI_VERIFY10_OPC == cdbp[0]);
+ uint8_t bytchk = (cdbp[1] >> 1) & 0x3;
+ int err;
+ uint32_t nblks_t10 = 0;
+ uint64_t lba;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: bytchk=%d, time_secs=%d\n", __func__, bytchk, time_secs);
+ if (bytchk > 1) {
+ mk_sense_invalid_fld(ptp, true, 1, 2, vb);
+ return 0;
+ }
+ fdc_p = get_fdc_p(ptp);
+ memset(&npc, 0, sizeof(npc));
+ npc.cmd.opc = bytchk ? SG_NVME_NVM_COMPARE : SG_NVME_NVM_VERIFY;
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ if (is_verify10) {
+ lba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ lba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ --nblks_t10;
+ sg_put_unaligned_le64(lba, npc_up + SG_NVME_PT_CDW10); /* fills W11 too */
+ sg_put_unaligned_le32(nblks_t10, npc_up + SG_NVME_PT_CDW12);
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+
+ err = nvme_pt_low(ptp, ptp->dxferp, ptp->dxfer_len, false, false, &npc,
+ time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = 0;
+ return 0;
+}
+
+static int
+sntl_write_same(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_ws10 = (SCSI_WRITE_SAME10_OPC == cdbp[0]);
+ bool ndob = is_ws10 ? false : !!(0x1 & cdbp[1]);
+ int err;
+ int nblks_t10 = 0;
+ uint64_t lba;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: ndob=%d, time_secs=%d\n", __func__, (int)ndob, time_secs);
+ if (! ndob) {
+ int flbas, index, lbafx, lbads, lbsize;
+ uint8_t * up;
+ uint8_t * dp;
+
+ dp = ptp->dxferp;
+ up = ptp->mchanp->nvme_id_ctlp;
+ if ((dp == NULL) || (up == NULL))
+ return sg_convert_errno(ENOMEM);
+ flbas = up[26]; /* NVME FLBAS field from Identify */
+ index = 128 + (4 * (flbas & 0xf));
+ lbafx = sg_get_unaligned_le32(up + index);
+ lbads = (lbafx >> 16) & 0xff; /* bits 16 to 23 inclusive, pow2 */
+ lbsize = 1 << lbads;
+ if (! sg_all_zeros(dp, lbsize)) {
+ mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, PCIE_ERR_ASC,
+ PCIE_UNSUPP_REQ_ASCQ, vb);
+ return 0;
+ }
+ /* so given single LB full of zeros, can translate .... */
+ }
+ fdc_p = ptp->mchanp;
+ memset(&npc, 0, sizeof(npc));
+ npc.cmd.opc = SG_NVME_NVM_WRITE_ZEROES;
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ if (is_ws10) {
+ lba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ uint32_t num = sg_get_unaligned_be32(cdbp + 10);
+
+ lba = sg_get_unaligned_be64(cdbp + 2);
+ if (num > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ } else
+ nblks_t10 = num;
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ --nblks_t10;
+ sg_put_unaligned_le64(lba, npc_up + SG_NVME_PT_CDW10); /* fills W11 too */
+ sg_put_unaligned_le32(nblks_t10, npc_up + SG_NVME_PT_CDW12);
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+
+ err = nvme_pt_low(ptp, ptp->dxferp, ptp->dxfer_len, false, false, &npc,
+ time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = 0;
+ return 0;
+}
+
+static int
+sntl_sync_cache(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool immed = !!(0x2 & cdbp[1]);
+ int err;
+ struct nvme_pt_command npc;
+ uint8_t * npc_up = (uint8_t *)&npc;
+ struct freebsd_dev_channel * fdc_p;
+
+ if (vb > 5)
+ pr2ws("%s: immed=%d, time_secs=%d\n", __func__, (int)immed,
+ time_secs);
+ fdc_p = ptp->mchanp;
+ memset(&npc, 0, sizeof(npc));
+ npc.cmd.opc = SG_NVME_NVM_FLUSH;
+ sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID);
+ if (vb > 4)
+ pr2ws("%s: immed bit, lba and num_lbs fields ignored\n", __func__);
+ err = nvme_pt_low(ptp, ptp->dxferp, ptp->dxfer_len, false, false, &npc,
+ time_secs, vb);
+ if (err) {
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ } else {
+ ptp->nvme_status = err;
+ mk_sense_from_nvme_status(ptp, err, vb);
+ return 0;
+ }
+ }
+ ptp->resid = 0;
+ return 0;
+}
+
+static int
+sntl_start_stop(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool immed = !!(0x1 & cdbp[1]);
+
+ if (vb > 5)
+ pr2ws("%s: immed=%d, time_secs=%d, ignore\n", __func__, (int)immed,
+ time_secs);
+ if (ptp) { } /* suppress warning */
+ return 0;
+}
+
+/* Note that the "Returned logical block address" (RLBA) field in the SCSI
+ * READ CAPACITY (10+16) command's response provides the address of the _last_
+ * LBA (counting origin 0) which will be one less that the "size" in the
+ * NVMe Identify command response's NSZE field. One problem is that in
+ * some situations NSZE can be zero: temporarily set RLBA field to 0
+ * (implying a 1 LB logical units size) pending further research. The LBLIB
+ * is the "Logical Block Length In Bytes" field in the RCAP response. */
+static int
+sntl_readcap(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_rcap10 = (SCSI_READ_CAPACITY10_OPC == cdbp[0]);
+ int res, n, len, alloc_len, dps;
+ uint8_t flbas, index, lbads; /* NVMe: 2**LBADS --> Logical Block size */
+ uint32_t lbafx; /* NVME: LBAF0...LBAF15, each 16 bytes */
+ uint32_t pg_sz = sg_get_page_size();
+ uint64_t nsze;
+ uint8_t * bp;
+ uint8_t * up;
+ uint8_t * free_up = NULL;
+ struct freebsd_dev_channel * fdc_p;
+ uint8_t resp[32];
+
+ if (vb > 5)
+ pr2ws("%s: RCAP%d\n", __func__, (is_rcap10 ? 10 : 16));
+ fdc_p = ptp->mchanp;
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__);
+ return -EINVAL;
+ }
+ up = sg_memalign(pg_sz, pg_sz, &free_up, false);
+ if (NULL == up) {
+ if (vb)
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = sntl_do_identify(ptp, 0x0 /* CNS */, fdc_p->nsid, pg_sz, up,
+ time_secs, vb);
+ if (res < 0) {
+ res = sg_convert_errno(-res);
+ goto fini;
+ }
+ memset(resp, 0, sizeof(resp));
+ nsze = sg_get_unaligned_le64(up + 0);
+ flbas = up[26]; /* NVME FLBAS field from Identify, want LBAF[flbas] */
+ index = 128 + (4 * (flbas & 0xf));
+ lbafx = sg_get_unaligned_le32(up + index);
+ lbads = (lbafx >> 16) & 0xff; /* bits 16 to 23 inclusive, pow2 */
+ if (is_rcap10) {
+ alloc_len = 8; /* implicit, not in cdb */
+ if (nsze > 0xffffffff)
+ sg_put_unaligned_be32(0xffffffff, resp + 0);
+ else if (0 == nsze) /* no good answer here */
+ sg_put_unaligned_be32(0, resp + 0); /* SCSI RLBA field */
+ else
+ sg_put_unaligned_be32((uint32_t)(nsze - 1), resp + 0);
+ sg_put_unaligned_be32(1 << lbads, resp + 4); /* SCSI LBLIB field */
+ } else {
+ alloc_len = sg_get_unaligned_be32(cdbp + 10);
+ dps = up[29];
+ if (0x7 & dps) {
+ resp[12] = 0x1;
+ n = (0x7 & dps) - 1;
+ if (n > 0)
+ resp[12] |= (n + n);
+ }
+ if (0 == nsze) /* no good answer here */
+ sg_put_unaligned_be64(0, resp + 0);
+ else
+ sg_put_unaligned_be64(nsze - 1, resp + 0);
+ sg_put_unaligned_be32(1 << lbads, resp + 8); /* SCSI LBLIB field */
+ }
+ len = ptp->dxfer_len;
+ bp = ptp->dxferp;
+ n = 32;
+ n = (n < alloc_len) ? n : alloc_len;
+ n = (n < len) ? n : len;
+ ptp->resid = len - n;
+ if (n > 0)
+ memcpy(bp, resp, n);
+fini:
+ if (free_up)
+ free(free_up);
+ return res;
+}
+
+/* Executes NVMe Admin command (or at least forwards it to lower layers).
+ * Depending on the device, this could be NVME(via CAM) or NVME(non-CAM).
+ * is_admin will be overridden if the SNTL functions are called.
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package. */
+static int
+sg_do_nvme_pt(struct sg_pt_freebsd_scsi * ptp, int fd, bool is_admin,
+ int time_secs, int vb)
+{
+ bool scsi_cdb, in_xfer;
+ int n, err, len, io_len;
+ uint16_t sct_sc, sa;
+ uint8_t * dxferp;
+ uint8_t * npc_up;
+ struct freebsd_dev_channel * fdc_p;
+ const uint8_t * cdbp;
+ struct nvme_pt_command npc;
+
+ npc_up = (uint8_t *)&npc;
+ if (vb > 6)
+ pr2ws("%s: fd=%d, is_admin=%d\n", __func__, fd, (int)is_admin);
+ if (! ptp->cdb) {
+ if (vb)
+ pr2ws("%s: No NVMe command given (set_scsi_pt_cdb())\n",
+ __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ fdc_p = ptp->mchanp;
+ if (fd < 0) {
+ if (NULL == fdc_p) {
+ if (vb)
+ pr2ws("%s: no device handle in object or fd ?\n", __func__);
+ return -EINVAL;
+ }
+ /* no fd, but have fdc_p so that is okay */
+ } else {
+ int han = fd - FREEBSD_FDOFFSET;
+
+ if ((han < 0) || (han >= FREEBSD_MAXDEV)) {
+ if (vb)
+ pr2ws("%s: argument 'fd' is bad\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (NULL == devicetable[han]) {
+ if (vb)
+ pr2ws("%s: argument 'fd' is bad (2)\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (fdc_p && (fdc_p != devicetable[han])) {
+ if (vb)
+ pr2ws("%s: different device handle in object and fd ?\n",
+ __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (NULL == fdc_p) {
+ ptp->dev_han = fd;
+ fdc_p = devicetable[han];
+ }
+ }
+
+ ptp->is_nvme_dev = fdc_p->is_nvme_dev;
+ n = ptp->cdb_len;
+ cdbp = (const uint8_t *)ptp->cdb;
+ if (vb > 3)
+ pr2ws("%s: opcode=0x%x, fd=%d\n", __func__, cdbp[0], fd);
+ scsi_cdb = sg_is_scsi_cdb(cdbp, n);
+ /* nvme_our_sntl is false when NVMe command (64 byte) has been given */
+ ptp->nvme_our_sntl = scsi_cdb;
+ if (scsi_cdb) {
+ switch (cdbp[0]) {
+ case SCSI_INQUIRY_OPC:
+ return sntl_inq(ptp, cdbp, time_secs, vb);
+ case SCSI_REPORT_LUNS_OPC:
+ return sntl_rluns(ptp, cdbp, time_secs, vb);
+ case SCSI_TEST_UNIT_READY_OPC:
+ return sntl_tur(ptp, time_secs, vb);
+ case SCSI_REQUEST_SENSE_OPC:
+ return sntl_req_sense(ptp, cdbp, time_secs, vb);
+ case SCSI_READ10_OPC:
+ case SCSI_READ16_OPC:
+ return sntl_rread(ptp, cdbp, time_secs, vb);
+ case SCSI_WRITE10_OPC:
+ case SCSI_WRITE16_OPC:
+ return sntl_write(ptp, cdbp, time_secs, vb);
+ case SCSI_START_STOP_OPC:
+ return sntl_start_stop(ptp, cdbp, time_secs, vb);
+ case SCSI_SEND_DIAGNOSTIC_OPC:
+ return sntl_senddiag(ptp, cdbp, time_secs, vb);
+ case SCSI_RECEIVE_DIAGNOSTIC_OPC:
+ return sntl_recvdiag(ptp, cdbp, time_secs, vb);
+ case SCSI_MODE_SENSE10_OPC:
+ case SCSI_MODE_SELECT10_OPC:
+ return sntl_mode_ss(ptp, cdbp, time_secs, vb);
+ case SCSI_READ_CAPACITY10_OPC:
+ return sntl_readcap(ptp, cdbp, time_secs, vb);
+ case SCSI_VERIFY10_OPC:
+ case SCSI_VERIFY16_OPC:
+ return sntl_verify(ptp, cdbp, time_secs, vb);
+ case SCSI_WRITE_SAME10_OPC:
+ case SCSI_WRITE_SAME16_OPC:
+ return sntl_write_same(ptp, cdbp, time_secs, vb);
+ case SCSI_SYNC_CACHE10_OPC:
+ case SCSI_SYNC_CACHE16_OPC:
+ return sntl_sync_cache(ptp, cdbp, time_secs, vb);
+ case SCSI_SERVICE_ACT_IN_OPC:
+ if (SCSI_READ_CAPACITY16_SA == (cdbp[1] & SCSI_SA_MSK))
+ return sntl_readcap(ptp, cdbp, time_secs, vb);
+ goto fini;
+ case SCSI_MAINT_IN_OPC:
+ sa = SCSI_SA_MSK & cdbp[1]; /* service action */
+ if (SCSI_REP_SUP_OPCS_OPC == sa)
+ return sntl_rep_opcodes(ptp, cdbp, time_secs, vb);
+ else if (SCSI_REP_SUP_TMFS_OPC == sa)
+ return sntl_rep_tmfs(ptp, cdbp, time_secs, vb);
+ /* fall through */
+ default:
+fini:
+ if (vb > 2) {
+ char b[64];
+
+ sg_get_command_name(cdbp, -1, sizeof(b), b);
+ pr2ws("%s: no translation to NVMe for SCSI %s command\n",
+ __func__, b);
+ }
+ mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE,
+ 0, vb);
+ return 0;
+ }
+ }
+
+ /* NVMe command given to pass-through */
+ if (vb > 4)
+ pr2ws("%s: NVMe pass-through command, admin=%d\n", __func__,
+ is_admin);
+ len = (int)sizeof(npc.cmd);
+ n = (n < len) ? n : len;
+ if (n < 64) {
+ if (vb)
+ pr2ws("%s: command length of %d bytes is too short\n", __func__,
+ n);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ memcpy(npc_up, (const uint8_t *)ptp->cdb, n);
+ if (n < len) /* zero out rest of 'npc' */
+ memset(npc_up + n, 0, len - n);
+ in_xfer = false;
+ io_len = 0;
+ dxferp = NULL;
+ if (ptp->dxfer_ilen > 0) {
+ in_xfer = true;
+ io_len = ptp->dxfer_ilen;
+ dxferp = ptp->dxferip;
+ sg_put_unaligned_le32(ptp->dxfer_ilen, npc_up + SG_NVME_PT_DATA_LEN);
+ sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferip,
+ npc_up + SG_NVME_PT_ADDR);
+ } else if (ptp->dxfer_olen > 0) {
+ in_xfer = false;
+ io_len = ptp->dxfer_olen;
+ dxferp = ptp->dxferop;
+ sg_put_unaligned_le32(ptp->dxfer_olen, npc_up + SG_NVME_PT_DATA_LEN);
+ sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferop,
+ npc_up + SG_NVME_PT_ADDR);
+ }
+ err = nvme_pt_low(ptp, dxferp, io_len, is_admin, in_xfer, &npc, time_secs,
+ vb);
+ if (err < 0) {
+ if (vb > 1)
+ pr2ws("%s: nvme_pt_low() failed: %s (errno=%d)\n",
+ __func__, strerror(-err), -err);
+ return err;
+ }
+ sct_sc = err; /* ((SCT << 8) | SC) which may be 0 */
+ ptp->nvme_status = sct_sc;
+ if (ptp->sense && (ptp->sense_len > 0)) {
+ uint32_t k = sizeof(ptp->cq_dw0_3);
+
+ if ((int)k < ptp->sense_len)
+ ptp->sense_resid = ptp->sense_len - (int)k;
+ else {
+ k = ptp->sense_len;
+ ptp->sense_resid = 0;
+ }
+ memcpy(ptp->sense, ptp->cq_dw0_3, k);
+ }
+ if (in_xfer)
+ ptp->resid = 0; /* Just hoping ... */
+ return sct_sc ? SG_LIB_NVME_STATUS : 0;
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+/* Requires pass-through file to be open and associated with vp */
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int vb)
+{
+ struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+ struct freebsd_dev_channel *fdc_p;
+
+ if (vb && (submq != 0))
+ pr2ws("%s: warning, uses submit queue 0\n", __func__);
+ fdc_p = ptp->mchanp;
+ if (NULL == fdc_p) {
+ fdc_p = get_fdc_p(ptp);
+ if (NULL == fdc_p) {
+ if (vb > 2)
+ pr2ws("%s: no open file associated with pt object\n",
+ __func__);
+ return -EINVAL;
+ }
+ ptp->mchanp = fdc_p;
+ }
+ return sg_do_nvme_pt(ptp, -1, false, timeout_secs, vb);
+}
+
+#else /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int vb)
+{
+ if (vb) {
+ pr2ws("%s: not supported, ", __func__);
+#ifdef HAVE_NVME
+ pr2ws("HAVE_NVME, ");
+#else
+ pr2ws("don't HAVE_NVME, ");
+#endif
+
+#ifdef IGNORE_NVME
+ pr2ws("IGNORE_NVME");
+#else
+ pr2ws("don't IGNORE_NVME");
+#endif
+ }
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/lib/sg_pt_haiku.c b/lib/sg_pt_haiku.c
new file mode 100644
index 00000000..6b1ed222
--- /dev/null
+++ b/lib/sg_pt_haiku.c
@@ -0,0 +1,572 @@
+/*
+ * Copyright (c) 2017 Leorize.
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <device/CAM.h>
+#include <scsi.h>
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+
+#if defined(__GNUC__) || defined(__clang__)
+static int pr2ws(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#else
+static int pr2ws(const char * fmt, ...);
+#endif
+
+static int
+pr2ws(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+struct sg_pt_haiku_scsi {
+ raw_device_command raw_command;
+ size_t data_len;
+ int in_err;
+ int os_err;
+ int dev_fd;
+};
+
+struct sg_pt_base {
+ struct sg_pt_haiku_scsi impl;
+};
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = O_NONBLOCK;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */
+/* together. The 'flags' argument is advisory and may be ignored. */
+/* Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int verbose)
+{
+ int fd;
+
+ if (verbose > 1) {
+ pr2ws("open %s with flags=0x%x\n", device_name, flags);
+ }
+ fd = open(device_name, flags);
+ if (fd < 0)
+ fd = -errno;
+ return fd;
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ int res;
+
+ res = close(device_fd);
+ if (res < 0)
+ res = -errno;
+ return res;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int device_fd, int verbose)
+{
+ struct sg_pt_haiku_scsi * ptp;
+
+ /* The following 2 lines are temporary. It is to avoid a NULL pointer
+ * crash when an old utility is used with a newer library built after
+ * the sg_warnings_strm cleanup */
+ if (NULL == sg_warnings_strm)
+ sg_warnings_strm = stderr;
+
+ ptp = (struct sg_pt_haiku_scsi *)
+ calloc(1, sizeof(struct sg_pt_haiku_scsi));
+ if (ptp) {
+ ptp->raw_command.flags = B_RAW_DEVICE_REPORT_RESIDUAL;
+ ptp->dev_fd = device_fd;
+ } else if (verbose)
+ pr2ws("%s: malloc() out of memory\n", __func__);
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp)
+ free(ptp);
+}
+
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+
+ int fd = ptp->dev_fd;
+ memset(ptp, 0, sizeof(struct sg_pt_haiku_scsi));
+ ptp->dev_fd = fd;
+ ptp->raw_command.flags = B_RAW_DEVICE_REPORT_RESIDUAL;
+ }
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const unsigned char * cdb,
+ int cdb_len)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ for (int i = 0; i < 16; ++i)
+ if (ptp->raw_command.command[i])
+ ++ptp->in_err;
+ memcpy(ptp->raw_command.command, cdb, cdb_len);
+ ptp->raw_command.command_length = (uint8_t)cdb_len;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, unsigned char * sense,
+ int max_sense_len)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->raw_command.sense_data)
+ ++ptp->in_err;
+ memset(sense, 0, max_sense_len);
+ ptp->raw_command.sense_data = sense;
+ ptp->raw_command.sense_data_length = max_sense_len;
+}
+
+/* Setup for data transfer from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, unsigned char * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->raw_command.data)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->raw_command.data = dxferp;
+ ptp->raw_command.data_length = dxfer_len;
+ ptp->data_len = dxfer_len;
+ ptp->raw_command.flags |= B_RAW_DEVICE_DATA_IN;
+ }
+}
+
+/* Setup for data transfer toward device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const unsigned char * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->raw_command.data)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->raw_command.data = (unsigned char *)dxferp;
+ ptp->raw_command.data_length = dxfer_len;
+ ptp->data_len = dxfer_len;
+ ptp->raw_command.flags &= ~B_RAW_DEVICE_DATA_IN;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp __attribute__ ((unused)),
+ int pack_id __attribute__ ((unused)))
+{
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag __attribute__ ((unused)))
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp,
+ int tmf_code __attribute__ ((unused)))
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp,
+ int attrib __attribute__ ((unused)),
+ int priority __attribute__ ((unused)))
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * vp __attribute__ ((unused)),
+ int flags __attribute__ ((unused)))
+{
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers).
+ * Clears os_err field prior to active call (whose result may set it
+ * again). */
+int
+do_scsi_pt(struct sg_pt_base * vp, int fd, int timeout_secs, int verbose)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ ptp->os_err = 0;
+ if (ptp->in_err) {
+ if (verbose)
+ pr2ws("Replicated or unused set_scsi_pt...\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (fd >= 0) {
+ if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+ if (verbose)
+ pr2ws("%s: file descriptor given to create() and here "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ ptp->dev_fd = fd;
+ } else if (ptp->dev_fd < 0) {
+ if (verbose)
+ pr2ws("%s: invalid file descriptors\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ } else
+ fd = ptp->dev_fd;
+
+ if (NULL == ptp->raw_command.command) {
+ if (verbose)
+ pr2ws("No SCSI command (cdb) given\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ /* raw_command.timeout is in microseconds */
+ ptp->raw_command.timeout = ((timeout_secs > 0) ? (timeout_secs * 1000000) :
+ CAM_TIME_DEFAULT);
+
+ if (ioctl(fd, B_RAW_DEVICE_COMMAND, &ptp->raw_command) < 0) {
+ ptp->os_err = errno;
+ if (verbose > 1)
+ pr2ws("ioctl(B_RAW_DEVICE_COMMAND) failed: %s (errno=%d)\n",
+ safe_strerror(ptp->os_err), ptp->os_err);
+ return -ptp->os_err;
+ }
+
+ return SCSI_PT_DO_START_OK;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ int cam_status_masked;
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ cam_status_masked = ptp->raw_command.cam_status & CAM_STATUS_MASK;
+ if (cam_status_masked != CAM_REQ_CMP &&
+ cam_status_masked != CAM_REQ_CMP_ERR)
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if ((SAM_STAT_CHECK_CONDITION == ptp->raw_command.scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == ptp->raw_command.scsi_status))
+ return SCSI_PT_RESULT_SENSE;
+ else if (ptp->raw_command.scsi_status)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ /* For various reasons Haiku return data_len - data_resid */
+ return ptp->data_len - ptp->raw_command.data_length;
+}
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return ptp->raw_command.scsi_status;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)ptp->raw_command.sense_data;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return ptp->raw_command.sense_data_length;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp __attribute__ ((unused)),
+ int max_b_len, char * b)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ const char *cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if ((ptp->raw_command.cam_status & CAM_STATUS_MASK) != CAM_REQ_CMP ||
+ (ptp->raw_command.cam_status & CAM_STATUS_MASK) != CAM_REQ_CMP_ERR)
+ return ptp->raw_command.cam_status & CAM_STATUS_MASK;
+
+ return 0;
+}
+
+char *
+get_scsi_pt_transport_err_str(
+ const struct sg_pt_base * vp __attribute__ ((unused)),
+ int max_b_len, char * b)
+{
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return -1;
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->data_len = 0;
+ ptp->raw_command.cam_status = 0;
+ ptp->raw_command.data_length = 0;
+}
+
+bool pt_device_is_nvme(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+check_pt_file_handle(int device_fd, const char * device_name, int vb)
+{
+ if (device_fd) {}
+ if (device_name) {}
+ if (vb) {}
+ return 1; /* guess it is a SCSI generic pass-though device */
+}
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int verbose)
+{
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ if (verbose) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->data_len == 0) {
+ if (act_dinp)
+ *act_dinp = 0;
+ if (act_doutp)
+ *act_doutp = 0;
+ } else if (act_dinp && (ptp->raw_command.flags & B_RAW_DEVICE_DATA_IN)) {
+ /* "For various reasons Haiku return data_len - data_resid" */
+ *act_dinp = ptp->raw_command.data_length;
+ if (act_doutp)
+ *act_doutp = 0;
+ } else if (act_doutp &&
+ !(ptp->raw_command.flags & B_RAW_DEVICE_DATA_IN)) {
+ *act_doutp = ptp->raw_command.data_length;
+ if (act_dinp)
+ *act_dinp = 0;
+ } else {
+ if (act_dinp)
+ *act_dinp = 0;
+ if (act_doutp)
+ *act_doutp = 0;
+ }
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return ptp->dev_fd;
+}
+
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (ptp->data_len == 0) {
+ if (req_dinp)
+ *req_dinp = 0;
+ if (req_doutp)
+ *req_doutp = 0;
+ } else if (req_dinp && (ptp->raw_command.flags & B_RAW_DEVICE_DATA_IN)) {
+ /* "For various reasons Haiku return data_len - data_resid" */
+ *req_dinp = ptp->data_len;
+ if (req_doutp)
+ *req_doutp = 0;
+ } else if (req_doutp &&
+ !(ptp->raw_command.flags & B_RAW_DEVICE_DATA_IN)) {
+ *req_doutp = ptp->data_len;
+ if (req_dinp)
+ *req_dinp = 0;
+ } else {
+ if (req_dinp)
+ *req_dinp = 0;
+ if (req_doutp)
+ *req_doutp = 0;
+ }
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ return (uint32_t)get_scsi_pt_status_response(vp);
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)ptp->raw_command.command;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ return (int)ptp->raw_command.command_length;
+}
+
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ struct sg_pt_haiku_scsi * ptp = &vp->impl;
+
+ if (vb) {}
+ ptp->dev_fd = (dev_han < 0) ? -1 : dev_han;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ return 0;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ if (vp) { }
+ if (err) { }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true)
+{
+ if (vp) { }
+ if (mdxferp) { }
+ if (mdxfer_len) { }
+ if (out_true) { }
+}
diff --git a/lib/sg_pt_linux.c b/lib/sg_pt_linux.c
new file mode 100644
index 00000000..6d282f50
--- /dev/null
+++ b/lib/sg_pt_linux.c
@@ -0,0 +1,1181 @@
+/*
+ * Copyright (c) 2005-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* sg_pt_linux version 1.54 20210923 */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h> /* to define 'major' */
+#ifndef major
+#include <sys/types.h>
+#endif
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <linux/major.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+#include "sg_pt_linux.h"
+#include "sg_pr2serr.h"
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+#ifndef BLOCK_EXT_MAJOR
+#define BLOCK_EXT_MAJOR 259
+#endif
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs (60 seconds) */
+
+/* sg driver displayed format: [x]xyyzz --> [x]x.[y]y.zz */
+#define SG_LINUX_SG_VER_V4_BASE 40000 /* lowest sg driver version with
+ * v4 interface */
+#define SG_LINUX_SG_VER_V4_FULL 40030 /* lowest version with full v4
+ * interface */
+
+static const char * linux_host_bytes[] = {
+ "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+ "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+ "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+ "DID_IMM_RETRY", "DID_REQUEUE" /* 0xd */,
+ "DID_TRANSPORT_DISRUPTED", "DID_TRANSPORT_FAILFAST",
+ "DID_TARGET_FAILURE" /* 0x10 */,
+ "DID_NEXUS_FAILURE (reservation conflict)",
+ "DID_ALLOC_FAILURE",
+ "DID_MEDIUM_ERROR",
+ "DID_TRANSPORT_MARGINAL", /*0x14 */
+};
+
+/* These where made obsolete around lk 5.12.0 . Only DRIVER_SENSE [0x8] is
+ * defined in scsi/sg.h for backward comaptibility */
+static const char * linux_driver_bytes[] = {
+ "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+ "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+ "DRIVER_SENSE"
+};
+
+#if 0
+
+static const char * linux_driver_suggests[] = {
+ "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+ "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+ "SUGGEST_SENSE"
+};
+
+#endif
+
+/*
+ * These defines are for constants that should be visible in the
+ * /usr/include/scsi directory (brought in by sg_linux_inc.h).
+ * Redefined and aliased here to decouple this code from
+ * sg_io_linux.h N.B. the SUGGEST_* constants are no longer used.
+ */
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0 /* Suggest mask is obsolete */
+#endif
+#ifndef DRIVER_SENSE
+#define DRIVER_SENSE 0x08
+#endif
+#define SG_LIB_DRIVER_MASK DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK SUGGEST_MASK
+#define SG_LIB_DRIVER_SENSE DRIVER_SENSE
+
+bool sg_bsg_nvme_char_major_checked = false;
+int sg_bsg_major = 0;
+volatile int sg_nvme_char_major = 0;
+
+bool sg_checked_version_num = false;
+int sg_driver_version_num = 0;
+bool sg_duration_set_nano = false;
+
+long sg_lin_page_size = 4096; /* default, overridden with correct value */
+
+
+/* This function only needs to be called once (unless a NVMe controller
+ * can be hot-plugged into system in which case it should be called
+ * (again) after that event). */
+void
+sg_find_bsg_nvme_char_major(int verbose)
+{
+ bool got_one = false;
+ int n;
+ const char * proc_devices = "/proc/devices";
+ char * cp;
+ FILE *fp;
+ char a[128];
+ char b[128];
+
+ sg_lin_page_size = sysconf(_SC_PAGESIZE);
+ if (NULL == (fp = fopen(proc_devices, "r"))) {
+ if (verbose)
+ pr2ws("fopen %s failed: %s\n", proc_devices, strerror(errno));
+ return;
+ }
+ while ((cp = fgets(b, sizeof(b), fp))) {
+ if ((1 == sscanf(b, "%126s", a)) &&
+ (0 == memcmp(a, "Character", 9)))
+ break;
+ }
+ while (cp && (cp = fgets(b, sizeof(b), fp))) {
+ if (2 == sscanf(b, "%d %126s", &n, a)) {
+ if (0 == strcmp("bsg", a)) {
+ sg_bsg_major = n;
+ if (got_one)
+ break;
+ got_one = true;
+ } else if (0 == strcmp("nvme", a)) {
+ sg_nvme_char_major = n;
+ if (got_one)
+ break;
+ got_one = true;
+ }
+ } else
+ break;
+ }
+ if (verbose > 3) {
+ if (cp) {
+ if (sg_bsg_major > 0)
+ pr2ws("found sg_bsg_major=%d\n", sg_bsg_major);
+ if (sg_nvme_char_major > 0)
+ pr2ws("found sg_nvme_char_major=%d\n", sg_nvme_char_major);
+ } else
+ pr2ws("found no bsg not nvme char device in %s\n", proc_devices);
+ }
+ fclose(fp);
+}
+
+/* Assumes that sg_find_bsg_nvme_char_major() has already been called. Returns
+ * true if dev_fd is a scsi generic pass-through device. If yields
+ * *is_nvme_p = true with *nsid_p = 0 then dev_fd is a NVMe char device.
+ * If yields *nsid_p > 0 then dev_fd is a NVMe block device. */
+static bool
+check_file_type(int dev_fd, struct stat * dev_statp, bool * is_bsg_p,
+ bool * is_nvme_p, uint32_t * nsid_p, int * os_err_p,
+ int verbose)
+{
+ bool is_nvme = false;
+ bool is_sg = false;
+ bool is_bsg = false;
+ bool is_block = false;
+ int os_err = 0;
+ int major_num;
+ uint32_t nsid = 0; /* invalid NSID */
+
+ if (dev_fd >= 0) {
+ if (fstat(dev_fd, dev_statp) < 0) {
+ os_err = errno;
+ if (verbose)
+ pr2ws("%s: fstat() failed: %s (errno=%d)\n", __func__,
+ safe_strerror(os_err), os_err);
+ goto skip_out;
+ }
+ major_num = (int)SG_DEV_MAJOR(dev_statp->st_rdev);
+ if (S_ISCHR(dev_statp->st_mode)) {
+ if (SCSI_GENERIC_MAJOR == major_num)
+ is_sg = true;
+ else if (sg_bsg_major == major_num)
+ is_bsg = true;
+ else if (sg_nvme_char_major == major_num)
+ is_nvme = true;
+ } else if (S_ISBLK(dev_statp->st_mode)) {
+ is_block = true;
+ if (BLOCK_EXT_MAJOR == major_num) {
+ is_nvme = true;
+ nsid = ioctl(dev_fd, NVME_IOCTL_ID, NULL);
+ if (SG_NVME_BROADCAST_NSID == nsid) { /* means ioctl error */
+ os_err = errno;
+ if (verbose)
+ pr2ws("%s: ioctl(NVME_IOCTL_ID) failed: %s "
+ "(errno=%d)\n", __func__, safe_strerror(os_err),
+ os_err);
+ } else
+ os_err = 0;
+ }
+ }
+ } else {
+ os_err = EBADF;
+ if (verbose)
+ pr2ws("%s: invalid file descriptor (%d)\n", __func__, dev_fd);
+ }
+skip_out:
+ if (verbose > 3) {
+ pr2ws("%s: file descriptor is ", __func__);
+ if (is_sg)
+ pr2ws("sg device\n");
+ else if (is_bsg)
+ pr2ws("bsg device\n");
+ else if (is_nvme && (0 == nsid))
+ pr2ws("NVMe char device\n");
+ else if (is_nvme)
+ pr2ws("NVMe block device, nsid=%lld\n",
+ ((uint32_t)-1 == nsid) ? -1LL : (long long)nsid);
+ else if (is_block)
+ pr2ws("block device\n");
+ else
+ pr2ws("undetermined device, could be regular file\n");
+ }
+ if (is_bsg_p)
+ *is_bsg_p = is_bsg;
+ if (is_nvme_p)
+ *is_nvme_p = is_nvme;
+ if (nsid_p)
+ *nsid_p = nsid;
+ if (os_err_p)
+ *os_err_p = os_err;
+ return is_sg;
+}
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int dev_fd, const char * device_name, int verbose)
+{
+ if (verbose > 4)
+ pr2ws("%s: dev_fd=%d, device_name: %s\n", __func__, dev_fd,
+ device_name);
+ /* Linux doesn't need device_name to determine which pass-through */
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (dev_fd >= 0) {
+ bool is_sg, is_bsg, is_nvme;
+ int err;
+ uint32_t nsid;
+ struct stat a_stat;
+
+ is_sg = check_file_type(dev_fd, &a_stat, &is_bsg, &is_nvme, &nsid,
+ &err, verbose);
+ if (err)
+ return -err;
+ else if (is_sg)
+ return 1;
+ else if (is_bsg)
+ return 2;
+ else if (is_nvme && (0 == nsid))
+ return 3;
+ else if (is_nvme)
+ return 4;
+ else
+ return 0;
+ } else
+ return 0;
+}
+
+/*
+ * We make a runtime decision whether to use the sg v3 interface or the sg
+ * v4 interface (currently exclusively used by the bsg driver). If all the
+ * following are true we use sg v4 which is only currently supported on bsg
+ * device nodes:
+ * a) there is a bsg entry in the /proc/devices file
+ * b) the device node given to scsi_pt_open() is a char device
+ * c) the char major number of the device node given to scsi_pt_open()
+ * matches the char major number of the bsg entry in /proc/devices
+ * Otherwise the sg v3 interface is used.
+ *
+ * Note that in either case we prepare the data in a sg v4 structure. If
+ * the runtime tests indicate that the v3 interface is needed then
+ * do_scsi_pt_v3() transfers the input data into a v3 structure and
+ * then the output data is transferred back into a sg v4 structure.
+ * That implementation detail could change in the future.
+ *
+ * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is
+ * available and major() macro [N.B. lower case] is not available.
+ */
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */
+/* together. The 'flags' argument is advisory and may be ignored. */
+/* Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int verbose)
+{
+ int fd;
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (verbose > 1) {
+ pr2ws("open %s with flags=0x%x\n", device_name, flags);
+ }
+ fd = open(device_name, flags);
+ if (fd < 0) {
+ fd = -errno;
+ if (verbose > 1)
+ pr2ws("%s: open(%s, 0x%x) failed: %s\n", __func__, device_name,
+ flags, safe_strerror(-fd));
+ }
+ return fd;
+}
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = O_NONBLOCK;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ int res;
+
+ res = close(device_fd);
+ if (res < 0)
+ res = -errno;
+ return res;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+static bool checked_ev_dsense = false;
+static bool ev_dsense = false;
+#endif
+
+
+/* Caller should additionally call get_scsi_pt_os_err() after this call */
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int dev_fd, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp;
+
+ ptp = (struct sg_pt_linux_scsi *)
+ calloc(1, sizeof(struct sg_pt_linux_scsi));
+ if (ptp) {
+ int err;
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+ sntl_init_dev_stat(&ptp->dev_stat);
+ if (! checked_ev_dsense) {
+ ev_dsense = sg_get_initial_dsense();
+ checked_ev_dsense = true;
+ }
+ ptp->dev_stat.scsi_dsense = ev_dsense;
+#endif
+ err = set_pt_file_handle((struct sg_pt_base *)ptp, dev_fd, verbose);
+ if ((0 == err) && (! ptp->is_nvme)) {
+ ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+ ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+ ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+ }
+ } else if (verbose)
+ pr2ws("%s: calloc() failed, out of memory?\n", __func__);
+
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1 /* dev_fd */, 0 /* verbose */);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+
+ if (NULL == vp)
+ pr2ws(">>>>>>> Warning: %s called with NULL pointer\n", __func__);
+ else {
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->free_nvme_id_ctlp) {
+ free(ptp->free_nvme_id_ctlp);
+ ptp->free_nvme_id_ctlp = NULL;
+ ptp->nvme_id_ctlp = NULL;
+ }
+ if (vp)
+ free(vp);
+ }
+}
+
+/* Remembers previous device file descriptor */
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ bool is_sg, is_bsg, is_nvme;
+ int fd;
+ uint32_t nvme_nsid;
+ struct sg_sntl_dev_state_t dev_stat;
+
+ fd = ptp->dev_fd;
+ is_sg = ptp->is_sg;
+ is_bsg = ptp->is_bsg;
+ is_nvme = ptp->is_nvme;
+ nvme_nsid = ptp->nvme_nsid;
+ dev_stat = ptp->dev_stat;
+ if (ptp->free_nvme_id_ctlp)
+ free(ptp->free_nvme_id_ctlp);
+ memset(ptp, 0, sizeof(struct sg_pt_linux_scsi));
+ ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+ ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+ ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+ ptp->dev_fd = fd;
+ ptp->is_sg = is_sg;
+ ptp->is_bsg = is_bsg;
+ ptp->is_nvme = is_nvme;
+ ptp->nvme_our_sntl = false;
+ ptp->nvme_nsid = nvme_nsid;
+ ptp->dev_stat = dev_stat;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->io_hdr.device_status = 0;
+ ptp->io_hdr.transport_status = 0;
+ ptp->io_hdr.driver_status = 0;
+ ptp->io_hdr.din_xferp = 0;
+ ptp->io_hdr.din_xfer_len = 0;
+ ptp->io_hdr.dout_xferp = 0;
+ ptp->io_hdr.dout_xfer_len = 0;
+ ptp->nvme_result = 0;
+}
+
+#ifndef SG_SET_GET_EXTENDED
+
+/* If both sei_wr_mask and sei_rd_mask are 0, this ioctl does nothing */
+struct sg_extended_info {
+ uint32_t sei_wr_mask; /* OR-ed SG_SEIM_* user->driver values */
+ uint32_t sei_rd_mask; /* OR-ed SG_SEIM_* driver->user values */
+ uint32_t ctl_flags_wr_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ uint32_t ctl_flags_rd_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ uint32_t ctl_flags; /* bit values OR-ed, see SG_CTL_FLAGM_* */
+ uint32_t read_value; /* write SG_SEIRV_*, read back related */
+
+ uint32_t reserved_sz; /* data/sgl size of pre-allocated request */
+ uint32_t tot_fd_thresh; /* total data/sgat for this fd, 0: no limit */
+ uint32_t minor_index; /* rd: kernel's sg device minor number */
+ uint32_t share_fd; /* SHARE_FD and CHG_SHARE_FD use this */
+ uint32_t sgat_elem_sz; /* sgat element size (must be power of 2) */
+ uint8_t pad_to_96[52]; /* pad so struct is 96 bytes long */
+};
+
+#define SG_IOCTL_MAGIC_NUM 0x22
+
+#define SG_SET_GET_EXTENDED _IOWR(SG_IOCTL_MAGIC_NUM, 0x51, \
+ struct sg_extended_info)
+
+#define SG_SEIM_CTL_FLAGS 0x1
+
+#define SG_CTL_FLAGM_TIME_IN_NS 0x1
+
+#endif
+
+/* Forget any previous dev_fd and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_fd should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_fd, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ struct stat a_stat;
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ ptp->dev_fd = dev_fd;
+ if (dev_fd >= 0) {
+ ptp->is_sg = check_file_type(dev_fd, &a_stat, &ptp->is_bsg,
+ &ptp->is_nvme, &ptp->nvme_nsid,
+ &ptp->os_err, verbose);
+ if (ptp->is_sg && (! sg_checked_version_num)) {
+ if (ioctl(dev_fd, SG_GET_VERSION_NUM, &ptp->sg_version) < 0) {
+ ptp->sg_version = 0;
+ if (verbose > 3)
+ pr2ws("%s: ioctl(SG_GET_VERSION_NUM) failed: errno: %d "
+ "[%s]\n", __func__, errno, safe_strerror(errno));
+ } else { /* got version number */
+ sg_driver_version_num = ptp->sg_version;
+ sg_checked_version_num = true;
+ }
+ if (verbose > 4) {
+ int ver = ptp->sg_version;
+
+ if (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE) {
+#ifdef IGNORE_LINUX_SGV4
+ pr2ws("%s: sg driver version %d.%02d.%02d but config "
+ "override back to v3\n", __func__, ver / 10000,
+ (ver / 100) % 100, ver % 100);
+#else
+ pr2ws("%s: sg driver version %d.%02d.%02d so choose v4\n",
+ __func__, ver / 10000, (ver / 100) % 100,
+ ver % 100);
+#endif
+ } else if (verbose > 5)
+ pr2ws("%s: sg driver version %d.%02d.%02d so choose v3\n",
+ __func__, ver / 10000, (ver / 100) % 100,
+ ver % 100);
+ }
+ } else if (ptp->is_sg)
+ ptp->sg_version = sg_driver_version_num;
+
+ if (ptp->is_sg && (ptp->sg_version >= SG_LINUX_SG_VER_V4_FULL) &&
+ getenv("SG3_UTILS_LINUX_NANO")) {
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip = &sei;
+
+ memset(seip, 0, sizeof(*seip));
+ /* try to override default of milliseconds */
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(dev_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ if (verbose > 2)
+ pr2ws("%s: unable to override milli --> nanoseconds: "
+ "%s\n", __func__, safe_strerror(errno));
+ } else {
+ if (! sg_duration_set_nano)
+ sg_duration_set_nano = true;
+ if (verbose > 5)
+ pr2ws("%s: dev_fd=%d, succeeding in setting durations "
+ "to nanoseconds\n", __func__, dev_fd);
+ }
+ } else if (ptp->is_sg && (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE)
+ && getenv("SG3_UTILS_LINUX_NANO")) {
+ if (verbose > 2)
+ pr2ws("%s: dev_fd=%d, ignored SG3_UTILS_LINUX_NANO\nbecause "
+ "base version sg version 4 driver\n", __func__, dev_fd);
+ }
+ } else {
+ ptp->is_sg = false;
+ ptp->is_bsg = false;
+ ptp->is_nvme = false;
+ ptp->nvme_our_sntl = false;
+ ptp->nvme_nsid = 0;
+ ptp->os_err = 0;
+ }
+ return ptp->os_err;
+}
+
+int
+sg_linux_get_sg_version(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->sg_version;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->dev_fd;
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
+ ptp->io_hdr.request_len = cdb_len;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.request_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (sense) {
+ if (max_sense_len > 0)
+ memset(sense, 0, max_sense_len);
+ }
+ ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
+ ptp->io_hdr.max_response_len = max_sense_len;
+}
+
+/* Setup for data transfer from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_ilen)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->io_hdr.din_xferp)
+ ++ptp->in_err;
+ if (dxfer_ilen > 0) {
+ ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp;
+ ptp->io_hdr.din_xfer_len = dxfer_ilen;
+ }
+}
+
+/* Setup for data transfer toward device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_olen)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->io_hdr.dout_xferp)
+ ++ptp->in_err;
+ if (dxfer_olen > 0) {
+ ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp;
+ ptp->io_hdr.dout_xfer_len = dxfer_olen;
+ }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * dxferp,
+ uint32_t dxfer_len, bool out_true)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (dxfer_len > 0) {
+ ptp->mdxferp = dxferp;
+ ptp->mdxfer_len = dxfer_len;
+ ptp->mdxfer_out = out_true;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_extra = pack_id; /* was placed in spare_in */
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_tag = tag;
+}
+
+/* Note that task management function codes are transport specific */
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.subprotocol = 1; /* SCSI task management function */
+ ptp->tmf_request[0] = (uint8_t)tmf_code; /* assume it fits */
+ ptp->io_hdr.request = (__u64)(sg_uintptr_t)(&(ptp->tmf_request[0]));
+ ptp->io_hdr.request_len = 1;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_attr = attribute;
+ ptp->io_hdr.request_priority = priority;
+}
+
+#ifndef BSG_FLAG_Q_AT_TAIL
+#define BSG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef BSG_FLAG_Q_AT_HEAD
+#define BSG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+/* Need this later if translated to v3 interface */
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+void
+set_scsi_pt_flags(struct sg_pt_base * vp, int flags)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ /* default action of bsg driver (sg v4) is QUEUE_AT_HEAD */
+ /* default action of block layer SG_IO ioctl is QUEUE_AT_TAIL */
+ if (SCSI_PT_FLAGS_QUEUE_AT_HEAD & flags) { /* favour AT_HEAD */
+ ptp->io_hdr.flags |= BSG_FLAG_Q_AT_HEAD;
+ ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_TAIL;
+ } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) {
+ ptp->io_hdr.flags |= BSG_FLAG_Q_AT_TAIL;
+ ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_HEAD;
+ }
+}
+
+/* If supported it is the number of bytes requested to transfer less the
+ * number actually transferred. This it typically important for data-in
+ * transfers. For data-out (only) transfers, the 'dout_req_len -
+ * dout_act_len' is returned. For bidi transfer the "din" residual is
+ * returned. */
+/* N.B. Returns din_resid and ignores dout_resid */
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if ((NULL == ptp) || (ptp->is_nvme && ! ptp->nvme_our_sntl))
+ return 0;
+ else if ((ptp->io_hdr.din_xfer_len > 0) &&
+ (ptp->io_hdr.dout_xfer_len > 0))
+ return ptp->io_hdr.din_resid;
+ else if (ptp->io_hdr.dout_xfer_len > 0)
+ return ptp->io_hdr.dout_resid;
+ return ptp->io_hdr.din_resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (req_dinp) {
+ if (ptp->io_hdr.din_xfer_len > 0)
+ *req_dinp = ptp->io_hdr.din_xfer_len;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if (ptp->io_hdr.dout_xfer_len > 0)
+ *req_doutp = ptp->io_hdr.dout_xfer_len;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (act_dinp) {
+ if (ptp->io_hdr.din_xfer_len > 0) {
+ int res = ptp->io_hdr.din_xfer_len - ptp->io_hdr.din_resid;
+
+ *act_dinp = (res > 0) ? res : 0;
+ } else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if (ptp->io_hdr.dout_xfer_len > 0)
+ *act_doutp = ptp->io_hdr.dout_xfer_len - ptp->io_hdr.dout_resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return 0;
+ return (int)((ptp->is_nvme && ! ptp->nvme_our_sntl) ?
+ ptp->nvme_status : ptp->io_hdr.device_status);
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return 0;
+ return (ptp->is_nvme && ! ptp->nvme_our_sntl) ?
+ ptp->nvme_result : ptp->io_hdr.device_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.response_len;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return sg_duration_set_nano ? (ptp->io_hdr.duration / 1000) :
+ ptp->io_hdr.duration;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return sg_duration_set_nano ? (uint32_t)ptp->io_hdr.duration : 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.transport_status;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.transport_status = err;
+}
+
+/* Returns b which will contain a null char terminated string (if
+ * max_b_len > 0). Combined driver and transport (called "host" in Linux
+ * kernel) statuses */
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ int ds = ptp->io_hdr.driver_status;
+ int hs = ptp->io_hdr.transport_status;
+ int n, m;
+ char * cp = b;
+ int driv;
+ const char * driv_cp = "invalid";
+
+ if (max_b_len < 1)
+ return b;
+ m = max_b_len;
+ n = 0;
+ if (hs) {
+ if ((hs < 0) || (hs >= (int)SG_ARRAY_SIZE(linux_host_bytes)))
+ n = snprintf(cp, m, "Host_status=0x%02x is invalid\n", hs);
+ else
+ n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs,
+ linux_host_bytes[hs]);
+ }
+ m -= n;
+ if (m < 1) {
+ b[max_b_len - 1] = '\0';
+ return b;
+ }
+ cp += n;
+ if (ds) {
+ driv = ds & SG_LIB_DRIVER_MASK;
+ if (driv < (int)SG_ARRAY_SIZE(linux_driver_bytes))
+ driv_cp = linux_driver_bytes[driv];
+ n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp);
+ m -= n;
+ }
+ if (m < 1)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK;
+ int scsi_st = ptp->io_hdr.device_status & 0x7e;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if (ptp->io_hdr.transport_status)
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st))
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if ((SG_LIB_DRIVER_SENSE == dr_st) ||
+ (SAM_STAT_CHECK_CONDITION == scsi_st) ||
+ (SAM_STAT_COMMAND_TERMINATED == scsi_st))
+ return SCSI_PT_RESULT_SENSE;
+ else if (scsi_st)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ const char * cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->is_nvme;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->nvme_nsid;
+}
+
+/* Executes SCSI command using sg v3 interface */
+static int
+do_scsi_pt_v3(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+ int verbose)
+{
+ struct sg_io_hdr v3_hdr;
+
+ memset(&v3_hdr, 0, sizeof(v3_hdr));
+ /* convert v4 to v3 header */
+ v3_hdr.interface_id = 'S';
+ v3_hdr.dxfer_direction = SG_DXFER_NONE;
+ v3_hdr.cmdp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request;
+ v3_hdr.cmd_len = (uint8_t)ptp->io_hdr.request_len;
+ if (ptp->io_hdr.din_xfer_len > 0) {
+ if (ptp->io_hdr.dout_xfer_len > 0) {
+ if (verbose)
+ pr2ws("sgv3 doesn't support bidi\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ v3_hdr.dxferp = (void *)(long)ptp->io_hdr.din_xferp;
+ v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.din_xfer_len;
+ v3_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ } else if (ptp->io_hdr.dout_xfer_len > 0) {
+ v3_hdr.dxferp = (void *)(long)ptp->io_hdr.dout_xferp;
+ v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.dout_xfer_len;
+ v3_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ }
+ if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+ v3_hdr.sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+ v3_hdr.mx_sb_len = (uint8_t)ptp->io_hdr.max_response_len;
+ }
+ v3_hdr.pack_id = (int)ptp->io_hdr.request_extra;
+ if (BSG_FLAG_Q_AT_HEAD & ptp->io_hdr.flags)
+ v3_hdr.flags |= SG_FLAG_Q_AT_HEAD; /* favour AT_HEAD */
+ else if (BSG_FLAG_Q_AT_TAIL & ptp->io_hdr.flags)
+ v3_hdr.flags |= SG_FLAG_Q_AT_TAIL;
+
+ if (NULL == v3_hdr.cmdp) {
+ if (verbose)
+ pr2ws("No SCSI command (cdb) given [v3]\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ /* io_hdr.timeout is in milliseconds, if greater than zero */
+ v3_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+ /* Finally do the v3 SG_IO ioctl */
+ if (ioctl(fd, SG_IO, &v3_hdr) < 0) {
+ ptp->os_err = errno;
+ if (verbose > 1)
+ pr2ws("ioctl(SG_IO v3) failed: %s (errno=%d)\n",
+ safe_strerror(ptp->os_err), ptp->os_err);
+ return -ptp->os_err;
+ }
+ ptp->io_hdr.device_status = (__u32)v3_hdr.status;
+ ptp->io_hdr.driver_status = (__u32)v3_hdr.driver_status;
+ ptp->io_hdr.transport_status = (__u32)v3_hdr.host_status;
+ ptp->io_hdr.response_len = (__u32)v3_hdr.sb_len_wr;
+ ptp->io_hdr.duration = (__u32)v3_hdr.duration;
+ ptp->io_hdr.din_resid = (__s32)v3_hdr.resid;
+ /* v3_hdr.info not passed back since no mapping defined (yet) */
+ return 0;
+}
+
+/* Executes SCSI command using sg v4 interface */
+static int
+do_scsi_pt_v4(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+ int verbose)
+{
+ if (0 == ptp->io_hdr.request) {
+ if (verbose)
+ pr2ws("No SCSI command (cdb) given [v4]\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ /* io_hdr.timeout is in milliseconds, if greater than zero */
+ ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+ if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) {
+ ptp->os_err = errno;
+ if (verbose > 1)
+ pr2ws("ioctl(SG_IO v4) failed: %s (errno=%d)\n",
+ safe_strerror(ptp->os_err), ptp->os_err);
+ return -ptp->os_err;
+ }
+ return 0;
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers).
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package. */
+int
+do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ bool have_checked_for_type = (ptp->dev_fd >= 0);
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (ptp->in_err) {
+ if (verbose)
+ pr2ws("Replicated or unused set_scsi_pt... functions\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (fd >= 0) {
+ if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+ if (verbose)
+ pr2ws("%s: file descriptor given to create() and here "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ ptp->dev_fd = fd;
+ } else if (ptp->dev_fd < 0) {
+ if (verbose)
+ pr2ws("%s: invalid file descriptors\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ } else
+ fd = ptp->dev_fd;
+ if (! have_checked_for_type) {
+ int err = set_pt_file_handle(vp, ptp->dev_fd, verbose);
+
+ if (err)
+ return -ptp->os_err;
+ }
+ if (ptp->os_err)
+ return -ptp->os_err;
+ if (verbose > 5)
+ pr2ws("%s: is_nvme=%d, is_sg=%d, is_bsg=%d\n", __func__,
+ (int)ptp->is_nvme, (int)ptp->is_sg, (int)ptp->is_bsg);
+ if (ptp->is_nvme)
+ return sg_do_nvme_pt(vp, -1, time_secs, verbose);
+ else if (ptp->is_sg) {
+#ifdef IGNORE_LINUX_SGV4
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+#else
+ if (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE)
+ return do_scsi_pt_v4(ptp, fd, time_secs, verbose);
+ else
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+#endif
+ } else if (sg_bsg_major <= 0)
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+ else if (ptp->is_bsg)
+ return do_scsi_pt_v4(ptp, fd, time_secs, verbose);
+ else
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+
+ pr2ws("%s: Should never reach this point\n", __func__);
+ return 0;
+}
diff --git a/lib/sg_pt_linux_nvme.c b/lib/sg_pt_linux_nvme.c
new file mode 100644
index 00000000..5a710f9d
--- /dev/null
+++ b/lib/sg_pt_linux_nvme.c
@@ -0,0 +1,1935 @@
+/*
+ * Copyright (c) 2017-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * The code to use the NVMe Management Interface (MI) SES pass-through
+ * was provided by WDC in November 2017.
+ */
+
+/*
+ * Copyright 2017, Western Digital Corporation
+ *
+ * Written by Berck Nash
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * Based on the NVM-Express command line utility, which bore the following
+ * notice:
+ *
+ * Copyright (c) 2014-2015, Intel Corporation.
+ *
+ * Written by Keith Busch <keith.busch@intel.com>
+ *
+ * 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
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+/* sg_pt_linux_nvme version 1.18 20210601 */
+
+/* This file contains a small "SPC-only" SNTL to support the SES pass-through
+ * of SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS through NVME-MI
+ * SES Send and SES Receive. */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h> /* to define 'major' */
+#ifndef major
+#include <sys/types.h>
+#endif
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <linux/major.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+#include "sg_pt_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#define SCSI_INQUIRY_OPC 0x12
+#define SCSI_REPORT_LUNS_OPC 0xa0
+#define SCSI_TEST_UNIT_READY_OPC 0x0
+#define SCSI_REQUEST_SENSE_OPC 0x3
+#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d
+#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c
+#define SCSI_MAINT_IN_OPC 0xa3
+#define SCSI_READ10_OPC 0x28
+#define SCSI_READ16_OPC 0x88
+#define SCSI_REP_SUP_OPCS_OPC 0xc
+#define SCSI_REP_SUP_TMFS_OPC 0xd
+#define SCSI_MODE_SENSE10_OPC 0x5a
+#define SCSI_MODE_SELECT10_OPC 0x55
+#define SCSI_READ_CAPACITY10_OPC 0x25
+#define SCSI_START_STOP_OPC 0x1b
+#define SCSI_SYNC_CACHE10_OPC 0x35
+#define SCSI_SYNC_CACHE16_OPC 0x91
+#define SCSI_VERIFY10_OPC 0x2f
+#define SCSI_VERIFY16_OPC 0x8f
+#define SCSI_WRITE10_OPC 0x2a
+#define SCSI_WRITE16_OPC 0x8a
+#define SCSI_WRITE_SAME10_OPC 0x41
+#define SCSI_WRITE_SAME16_OPC 0x93
+#define SCSI_SERVICE_ACT_IN_OPC 0x9e
+#define SCSI_READ_CAPACITY16_SA 0x10
+#define SCSI_SA_MSK 0x1f
+
+/* Additional Sense Code (ASC) */
+#define NO_ADDITIONAL_SENSE 0x0
+#define LOGICAL_UNIT_NOT_READY 0x4
+#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8
+#define UNRECOVERED_READ_ERR 0x11
+#define PARAMETER_LIST_LENGTH_ERR 0x1a
+#define INVALID_OPCODE 0x20
+#define LBA_OUT_OF_RANGE 0x21
+#define INVALID_FIELD_IN_CDB 0x24
+#define INVALID_FIELD_IN_PARAM_LIST 0x26
+#define UA_RESET_ASC 0x29
+#define UA_CHANGED_ASC 0x2a
+#define TARGET_CHANGED_ASC 0x3f
+#define LUNS_CHANGED_ASCQ 0x0e
+#define INSUFF_RES_ASC 0x55
+#define INSUFF_RES_ASCQ 0x3
+#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */
+#define POWER_ON_RESET_ASCQ 0x0
+#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */
+#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */
+#define CAPACITY_CHANGED_ASCQ 0x9
+#define SAVING_PARAMS_UNSUP 0x39
+#define TRANSPORT_PROBLEM 0x4b
+#define THRESHOLD_EXCEEDED 0x5d
+#define LOW_POWER_COND_ON 0x5e
+#define MISCOMPARE_VERIFY_ASC 0x1d
+#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */
+#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
+#define PCIE_ERR_ASC 0x4b
+#define PCIE_UNSUPP_REQ_ASCQ 0x13
+
+/* NVMe Admin commands */
+#define SG_NVME_AD_GET_FEATURE 0xa
+#define SG_NVME_AD_SET_FEATURE 0x9
+#define SG_NVME_AD_IDENTIFY 0x6 /* similar to SCSI INQUIRY */
+#define SG_NVME_AD_DEV_SELT_TEST 0x14
+#define SG_NVME_AD_MI_RECEIVE 0x1e /* MI: Management Interface */
+#define SG_NVME_AD_MI_SEND 0x1d /* hmmm, same opcode as SEND DIAG */
+
+/* NVMe NVM (Non-Volatile Memory) commands */
+#define SG_NVME_NVM_FLUSH 0x0 /* SCSI SYNCHRONIZE CACHE */
+#define SG_NVME_NVM_COMPARE 0x5 /* SCSI VERIFY(BYTCHK=1) */
+#define SG_NVME_NVM_READ 0x2
+#define SG_NVME_NVM_VERIFY 0xc /* SCSI VERIFY(BYTCHK=0) */
+#define SG_NVME_NVM_WRITE 0x1
+#define SG_NVME_NVM_WRITE_ZEROES 0x8 /* SCSI WRITE SAME */
+
+#define SG_NVME_RW_CONTROL_FUA (1 << 14) /* Force Unit Access bit */
+
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5)
+ * to the name of its associated char device (e.g. /dev/nvme0). If this
+ * occurs true is returned and the char device name is placed in 'b' (as
+ * long as b_len is sufficient). Otherwise false is returned. */
+bool
+sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len,
+ char * b)
+{
+ uint32_t n, tlen;
+ const char * cp;
+ char buff[8];
+
+ if ((NULL == b) || (b_len < 5))
+ return false; /* degenerate cases */
+ cp = strstr(nvme_block_devname, "nvme");
+ if (NULL == cp)
+ return false; /* expected to find "nvme" in given name */
+ if (1 != sscanf(cp, "nvme%u", &n))
+ return false; /* didn't find valid "nvme<number>" */
+ snprintf(buff, sizeof(buff), "%u", n);
+ tlen = (cp - nvme_block_devname) + 4 + strlen(buff);
+ if ((tlen + 1) > b_len)
+ return false; /* b isn't long enough to fit output */
+ memcpy(b, nvme_block_devname, tlen);
+ b[tlen] = '\0';
+ return true;
+}
+
+static void
+mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq,
+ int vb)
+{
+ bool dsense = !! ptp->dev_stat.scsi_dsense;
+ int n;
+ uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+
+ ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION;
+ n = ptp->io_hdr.max_response_len;
+ if ((n < 8) || ((! dsense) && (n < 14))) {
+ if (vb)
+ pr2ws("%s: max_response_len=%d too short, want 14 or more\n",
+ __func__, n);
+ return;
+ } else
+ ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18);
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk,
+ asc, ascq);
+}
+
+static void
+mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb)
+{
+ bool ok;
+ bool dsense = !! ptp->dev_stat.scsi_dsense;
+ int n;
+ uint8_t sstatus, sk, asc, ascq;
+ uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+
+ ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq);
+ if (! ok) { /* can't find a mapping to a SCSI error, so ... */
+ sstatus = SAM_STAT_CHECK_CONDITION;
+ sk = SPC_SK_ILLEGAL_REQUEST;
+ asc = 0xb;
+ ascq = 0x0; /* asc: "WARNING" purposely vague */
+ }
+
+ ptp->io_hdr.device_status = sstatus;
+ n = ptp->io_hdr.max_response_len;
+ if ((n < 8) || ((! dsense) && (n < 14))) {
+ pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n);
+ return;
+ } else
+ ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18);
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (dsense && (ptp->nvme_status > 0))
+ sg_nvme_desc2sense(sbp, ptp->nvme_stat_dnr, ptp->nvme_stat_more,
+ ptp->nvme_status);
+ if (vb > 3)
+ pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n",
+ __func__, sstatus, sk, asc, ascq);
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_invalid_fld(struct sg_pt_linux_scsi * ptp, bool in_cdb, int in_byte,
+ int in_bit, int vb)
+{
+ bool dsense = !! ptp->dev_stat.scsi_dsense;
+ int asc, n;
+ uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+ uint8_t sks[4];
+
+ ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION;
+ asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST;
+ n = ptp->io_hdr.max_response_len;
+ if ((n < 8) || ((! dsense) && (n < 14))) {
+ if (vb)
+ pr2ws("%s: max_response_len=%d too short, want 14 or more\n",
+ __func__, n);
+ return;
+ } else
+ ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18);
+
+ memset(sbp, 0, n);
+ sg_build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0);
+ memset(sks, 0, sizeof(sks));
+ sks[0] = 0x80;
+ if (in_cdb)
+ sks[0] |= 0x40;
+ if (in_bit >= 0) {
+ sks[0] |= 0x8;
+ sks[0] |= (0x7 & in_bit);
+ }
+ sg_put_unaligned_be16(in_byte, sks + 1);
+ if (dsense) {
+ int sl = sbp[7] + 8;
+
+ sbp[7] = sl;
+ sbp[sl] = 0x2;
+ sbp[sl + 1] = 0x6;
+ memcpy(sbp + sl + 4, sks, 3);
+ } else
+ memcpy(sbp + 15, sks, 3);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n",
+ __func__, asc, in_cdb ? 'C' : 'D', in_byte,
+ ((in_bit > 0) ? (0x7 & in_bit) : 0));
+}
+
+/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero
+ * NVMe status (from the completion queue) with the value placed in
+ * ptp->nvme_status. If Unix error from ioctl then return negated value
+ * (equivalent -errno from basic Unix system functions like open()).
+ * CDW0 from the completion queue is placed in ptp->nvme_result in the
+ * absence of a Unix error. If time_secs is negative it is treated as
+ * a timeout in milliseconds (of abs(time_secs) ). */
+static int
+sg_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp,
+ struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read,
+ int time_secs, int vb)
+{
+ const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd);
+ int res;
+ uint32_t n;
+ uint16_t sct_sc;
+ const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE;
+ char nam[64];
+
+ if (vb)
+ sg_get_nvme_opcode_name(*up, true /* ADMIN */, sizeof(nam), nam);
+ else
+ nam[0] = '\0';
+ cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs);
+ ptp->os_err = 0;
+ if (vb > 2) {
+ pr2ws("NVMe Admin command: %s\n", nam);
+ hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+ if ((vb > 4) && (! is_read) && dp) {
+ uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+ if (len > 0) {
+ n = len;
+ if ((len < 512) || (vb > 5))
+ pr2ws("\nData-out buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+ n = 512;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ }
+ res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp);
+ if (res < 0) { /* OS error (errno negated) */
+ ptp->os_err = -res;
+ if (vb > 1) {
+ pr2ws("%s: ioctl for %s [0x%x] failed: %s "
+ "(errno=%d)\n", __func__, nam, *up, strerror(-res), -res);
+ }
+ return res;
+ }
+
+ /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */
+ ptp->nvme_result = cmdp->result;
+ if ((! ptp->nvme_our_sntl) && ptp->io_hdr.response &&
+ (ptp->io_hdr.max_response_len > 3)) {
+ /* build 32 byte "sense" buffer */
+ uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+ uint16_t st = (uint16_t)res;
+
+ n = ptp->io_hdr.max_response_len;
+ n = (n < 32) ? n : 32;
+ memset(sbp, 0 , n);
+ ptp->io_hdr.response_len = n;
+ sg_put_unaligned_le32(cmdp->result,
+ sbp + SG_NVME_PT_CQ_RESULT);
+ if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */
+ sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P);
+ }
+ /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */
+ sct_sc = 0x7ff & res; /* 11 bits */
+ ptp->nvme_status = sct_sc;
+ ptp->nvme_stat_dnr = !!(0x4000 & res);
+ ptp->nvme_stat_more = !!(0x2000 & res);
+ if (sct_sc) { /* when non-zero, treat as command error */
+ if (vb > 1) {
+ char b[80];
+
+ pr2ws("%s: ioctl for %s [0x%x] failed, status: %s [0x%x]\n",
+ __func__, nam, *up,
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc);
+ }
+ return SG_LIB_NVME_STATUS; /* == SCSI_PT_DO_NVME_STATUS */
+ }
+ if ((vb > 4) && is_read && dp) {
+ uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN);
+
+ if (len > 0) {
+ n = len;
+ if ((len < 1024) || (vb > 5))
+ pr2ws("\nData-in buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+ n = 1024;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ return 0;
+}
+
+/* see NVME MI document, NVMSR is NVM Subsystem Report */
+static void
+sntl_check_enclosure_override(struct sg_pt_linux_scsi * ptp, int vb)
+{
+ uint8_t * up = ptp->nvme_id_ctlp;
+ uint8_t nvmsr;
+
+ if (NULL == up)
+ return;
+ nvmsr = up[253];
+ if (vb > 5)
+ pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr);
+ ptp->dev_stat.id_ctl253 = nvmsr;
+ switch (ptp->dev_stat.enclosure_override) {
+ case 0x0: /* no override */
+ if (0x3 == (0x3 & nvmsr)) {
+ ptp->dev_stat.pdt = PDT_DISK;
+ ptp->dev_stat.enc_serv = 1;
+ } else if (0x2 & nvmsr) {
+ ptp->dev_stat.pdt = PDT_SES;
+ ptp->dev_stat.enc_serv = 1;
+ } else if (0x1 & nvmsr) {
+ ptp->dev_stat.pdt = PDT_DISK;
+ ptp->dev_stat.enc_serv = 0;
+ } else {
+ uint32_t nn = sg_get_unaligned_le32(up + 516);
+
+ ptp->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN;
+ ptp->dev_stat.enc_serv = 0;
+ }
+ break;
+ case 0x1: /* override to SES device */
+ ptp->dev_stat.pdt = PDT_SES;
+ ptp->dev_stat.enc_serv = 1;
+ break;
+ case 0x2: /* override to disk with attached SES device */
+ ptp->dev_stat.pdt = PDT_DISK;
+ ptp->dev_stat.enc_serv = 1;
+ break;
+ case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */
+ ptp->dev_stat.pdt = PDT_PROCESSOR;
+ ptp->dev_stat.enc_serv = 1;
+ break;
+ case 0xff: /* override to normal disk */
+ ptp->dev_stat.pdt = PDT_DISK;
+ ptp->dev_stat.enc_serv = 0;
+ break;
+ default:
+ pr2ws("%s: unknown enclosure_override value: %d\n", __func__,
+ ptp->dev_stat.enclosure_override);
+ break;
+ }
+}
+
+static int
+sntl_do_identify(struct sg_pt_linux_scsi * ptp, int cns, int nsid,
+ int time_secs, int u_len, uint8_t * up, int vb)
+{
+ struct sg_nvme_passthru_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = SG_NVME_AD_IDENTIFY;
+ cmd.nsid = nsid;
+ cmd.cdw10 = cns;
+ cmd.addr = (uint64_t)(sg_uintptr_t)up;
+ cmd.data_len = u_len;
+ return sg_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb);
+}
+
+/* Currently only caches associated identify controller response (4096 bytes).
+ * Returns 0 on success; otherwise a positive value is returned */
+static int
+sntl_cache_identify(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+ int ret;
+ uint32_t pg_sz = sg_get_page_size();
+ uint8_t * up;
+
+ up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, false);
+ ptp->nvme_id_ctlp = up;
+ if (NULL == up) {
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = sntl_do_identify(ptp, 0x1 /* CNS */, 0 /* nsid */, time_secs,
+ pg_sz, up, vb);
+ if (0 == ret)
+ sntl_check_enclosure_override(ptp, vb);
+ return (ret < 0) ? sg_convert_errno(-ret) : ret;
+}
+
+/* If nsid==0 then set cmdp->nsid to SG_NVME_BROADCAST_NSID. */
+static int
+sntl_get_features(struct sg_pt_linux_scsi * ptp, int feature_id, int select,
+ uint32_t nsid, uint64_t din_addr, int time_secs, int vb)
+{
+ int res;
+ struct sg_nvme_passthru_cmd cmd;
+ struct sg_nvme_passthru_cmd * cmdp = &cmd;
+
+ if (vb > 4)
+ pr2ws("%s: feature_id=0x%x, select=%d\n", __func__, feature_id,
+ select);
+ memset(cmdp, 0, sizeof(*cmdp));
+ cmdp->opcode = SG_NVME_AD_GET_FEATURE;
+ cmdp->nsid = nsid ? nsid : SG_NVME_BROADCAST_NSID;
+ select &= 0x7;
+ feature_id &= 0xff;
+ cmdp->cdw10 = (select << 8) | feature_id;
+ if (din_addr)
+ cmdp->addr = din_addr;
+ cmdp->timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+ res = sg_nvme_admin_cmd(ptp, cmdp, NULL, false, time_secs, vb);
+ if (res)
+ return res;
+ ptp->os_err = 0;
+ ptp->nvme_status = 0;
+ return 0;
+}
+
+static const char * nvme_scsi_vendor_str = "NVMe ";
+static const uint16_t inq_resp_len = 36;
+
+static int
+sntl_inq(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs,
+ int vb)
+{
+ bool evpd;
+ int res;
+ uint16_t n, alloc_len, pg_cd;
+ uint32_t pg_sz = sg_get_page_size();
+ uint8_t * nvme_id_ns = NULL;
+ uint8_t * free_nvme_id_ns = NULL;
+ uint8_t inq_dout[256];
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+ if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */
+ mk_sense_invalid_fld(ptp, true, 1, 1, vb);
+ return 0;
+ }
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res) /* should be negative errno */
+ return res;
+ }
+ memset(inq_dout, 0, sizeof(inq_dout));
+ alloc_len = sg_get_unaligned_be16(cdbp + 3);
+ evpd = !!(0x1 & cdbp[1]);
+ pg_cd = cdbp[2];
+ if (evpd) { /* VPD page responses */
+ bool cp_id_ctl = false;
+
+ switch (pg_cd) {
+ case 0:
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 11;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x0;
+ inq_dout[5] = 0x80;
+ inq_dout[6] = 0x83;
+ inq_dout[7] = 0x86;
+ inq_dout[8] = 0x87;
+ inq_dout[9] = 0x92;
+ inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */
+ break;
+ case 0x80:
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 24;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20); /* SN */
+ break;
+ case 0x83:
+ if ((ptp->nvme_nsid > 0) &&
+ (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) {
+ nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns,
+ false);
+ if (nvme_id_ns) {
+ /* CNS=0x0 Identify namespace */
+ res = sntl_do_identify(ptp, 0x0, ptp->nvme_nsid,
+ time_secs, pg_sz, nvme_id_ns, vb);
+ if (res) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ }
+ }
+ n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns,
+ 0 /* pdt */, -1 /*tproto */,
+ inq_dout, sizeof(inq_dout));
+ if (n > 3)
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ if (free_nvme_id_ns) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ break;
+ case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 64;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[5] = 0x1; /* SIMPSUP=1 */
+ inq_dout[7] = 0x1; /* LUICLR=1 */
+ inq_dout[13] = 0x40; /* max supported sense data length */
+ break;
+ case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 8;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x3f; /* all mode pages */
+ inq_dout[5] = 0xff; /* and their sub-pages */
+ inq_dout[6] = 0x80; /* MLUS=1, policy=shared */
+ break;
+ case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */
+ inq_dout[1] = pg_cd;
+ n = 10;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde (vendor (sg3_utils) specific) */
+ inq_dout[1] = pg_cd;
+ sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2);
+ n = 16 + 4096;
+ cp_id_ctl = true;
+ break;
+ default: /* Point to page_code field in cdb */
+ mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+ return 0;
+ }
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+ if (n > 0) {
+ uint8_t * dp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+
+ if (cp_id_ctl) {
+ memcpy(dp, inq_dout, (n < 16 ? n : 16));
+ if (n > 16)
+ memcpy(dp + 16, ptp->nvme_id_ctlp, n - 16);
+ } else
+ memcpy(dp, inq_dout, n);
+ }
+ }
+ } else { /* Standard INQUIRY response */
+ /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */
+ inq_dout[0] = (0x1f & ptp->dev_stat.pdt); /* (PQ=0)<<5 */
+ /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6 | (HOT_PLUG=0)<<4; */
+ inq_dout[2] = 6; /* version: SPC-4 */
+ inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */
+ inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */
+ inq_dout[6] = ptp->dev_stat.enc_serv ? 0x40 : 0;
+ inq_dout[7] = 0x2; /* CMDQUE=1 */
+ memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */
+ memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */
+ memcpy(inq_dout + 32, ptp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */
+ if (alloc_len > 0) {
+ n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len;
+ n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+ if (n > 0)
+ memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp,
+ inq_dout, n);
+ }
+ }
+ return 0;
+}
+
+static int
+sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs,
+ int vb)
+{
+ int res;
+ uint16_t sel_report;
+ uint32_t alloc_len, k, n, num, max_nsid;
+ uint8_t * rl_doutp;
+ uint8_t * up;
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+ sel_report = cdbp[2];
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516);
+ switch (sel_report) {
+ case 0:
+ case 2:
+ num = max_nsid;
+ break;
+ case 1:
+ case 0x10:
+ case 0x12:
+ num = 0;
+ break;
+ case 0x11:
+ num = (1 == ptp->nvme_nsid) ? max_nsid : 0;
+ break;
+ default:
+ if (vb > 1)
+ pr2ws("%s: bad select_report value: 0x%x\n", __func__,
+ sel_report);
+ mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+ return 0;
+ }
+ rl_doutp = (uint8_t *)calloc(num + 1, 8);
+ if (NULL == rl_doutp) {
+ pr2ws("%s: calloc() failed to get memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8)
+ sg_put_unaligned_be16(k, up);
+ n = num * 8;
+ sg_put_unaligned_be32(n, rl_doutp);
+ n+= 8;
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+ if (n > 0)
+ memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, rl_doutp,
+ n);
+ }
+ res = 0;
+ free(rl_doutp);
+ return res;
+}
+
+static int
+sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb)
+{
+ int res;
+ uint32_t pow_state;
+
+ if (vb > 5)
+ pr2ws("%s: start\n", __func__);
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ res = sntl_get_features(ptp, 2 /* Power Management */, 0 /* current */,
+ 0, 0, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ pow_state = (0x1f & ptp->nvme_result);
+ if (vb > 5)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+#if 0 /* pow_state bounces around too much on laptop */
+ if (pow_state)
+ mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0,
+ vb);
+#endif
+ return 0;
+}
+
+static int
+sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool desc;
+ int res;
+ uint32_t pow_state, alloc_len, n;
+ uint8_t rs_dout[64];
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ desc = !!(0x1 & cdbp[1]);
+ alloc_len = cdbp[4];
+ res = sntl_get_features(ptp, 0x2 /* Power Management */, 0 /* current */,
+ 0, 0, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ ptp->io_hdr.response_len = 0;
+ pow_state = (0x1f & ptp->nvme_result);
+ if (vb > 5)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+ memset(rs_dout, 0, sizeof(rs_dout));
+ if (pow_state)
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ LOW_POWER_COND_ON_ASC, 0);
+ else
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ NO_ADDITIONAL_SENSE, 0);
+ n = desc ? 8 : 18;
+ n = (n < alloc_len) ? n : alloc_len;
+ n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n;
+ if (n > 0)
+ memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, rs_dout, n);
+ return 0;
+}
+
+static uint8_t pc_t10_2_select[] = {0, 3, 1, 2};
+
+/* For MODE SENSE(10) and MODE SELECT(10). 6 byte variants not supported */
+static int
+sntl_mode_ss(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]);
+ int res, n, len;
+ uint8_t * bp;
+ struct sg_sntl_result_t sntl_result;
+
+ if (vb > 5)
+ pr2ws("%s: mode se%s\n", __func__, (is_msense ? "nse" : "lect"));
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ if (is_msense) { /* MODE SENSE(10) */
+ uint8_t pc_t10 = (cdbp[2] >> 6) & 0x3;
+ int mp_t10 = (cdbp[2] & 0x3f);
+
+ if ((0x3f == mp_t10) || (0x8 /* caching mpage */ == mp_t10)) {
+ /* 0x6 is "Volatile write cache" feature id */
+ res = sntl_get_features(ptp, 0x6, pc_t10_2_select[pc_t10], 0,
+ 0, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ ptp->dev_stat.wce = !!(0x1 & ptp->nvme_result);
+ }
+ len = ptp->io_hdr.din_xfer_len;
+ bp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ n = sntl_resp_mode_sense10(&ptp->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ ptp->io_hdr.din_resid = (n >= 0) ? len - n : len;
+ } else { /* MODE SELECT(10) */
+ bool sp = !!(0x1 & cdbp[1]); /* Save Page indication */
+ uint8_t pre_enc_ov = ptp->dev_stat.enclosure_override;
+
+ len = ptp->io_hdr.dout_xfer_len;
+ bp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ ptp->dev_stat.wce_changed = false;
+ n = sntl_resp_mode_select10(&ptp->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ if (ptp->dev_stat.wce_changed) {
+ uint32_t nsid = ptp->nvme_nsid;
+ struct sg_nvme_passthru_cmd cmd;
+ struct sg_nvme_passthru_cmd * cmdp = &cmd;
+
+ ptp->dev_stat.wce_changed = false;
+ memset(cmdp, 0, sizeof(*cmdp));
+ cmdp->opcode = SG_NVME_AD_SET_FEATURE;
+ cmdp->nsid = nsid ? nsid : SG_NVME_BROADCAST_NSID;
+ cmdp->cdw10 = 0x6; /* "Volatile write cache" feature id */
+ if (sp)
+ cmdp->cdw10 |= (1U << 31);
+ cmdp->cdw11 = (uint32_t)ptp->dev_stat.wce;
+ cmdp->timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs);
+ res = sg_nvme_admin_cmd(ptp, cmdp, NULL, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ ptp->os_err = 0;
+ ptp->nvme_status = 0;
+ }
+ if (pre_enc_ov != ptp->dev_stat.enclosure_override)
+ sntl_check_enclosure_override(ptp, vb); /* ENC_OV has changed */
+ }
+ if (n < 0) {
+ int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit :
+ -1;
+ if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) &&
+ (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) {
+ if (INVALID_FIELD_IN_CDB == sntl_result.asc)
+ mk_sense_invalid_fld(ptp, true, sntl_result.in_byte, in_bit,
+ vb);
+ else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc)
+ mk_sense_invalid_fld(ptp, false, sntl_result.in_byte, in_bit,
+ vb);
+ else
+ mk_sense_asc_ascq(ptp, sntl_result.sk, sntl_result.asc,
+ sntl_result.ascq, vb);
+ } else
+ pr2ws("%s: error but no sense?? n=%d\n", __func__, n);
+ }
+ return 0;
+}
+
+/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI
+ * has a special command (SES Send) to tunnel through pages to an
+ * enclosure. The NVMe enclosure is meant to understand the SES
+ * (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool pf, self_test;
+ int res;
+ uint8_t st_cd, dpg_cd;
+ uint32_t alloc_len, n, dout_len, dpg_len;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint8_t * dop;
+ struct sg_nvme_passthru_cmd cmd;
+ uint8_t * cmd_up = (uint8_t *)&cmd;
+
+ st_cd = 0x7 & (cdbp[1] >> 5);
+ self_test = !! (0x4 & cdbp[1]);
+ pf = !! (0x10 & cdbp[1]);
+ if (vb > 5)
+ pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf,
+ (int)self_test, (int)st_cd);
+ if (self_test || st_cd) {
+ uint32_t nvme_dst;
+
+ memset(cmd_up, 0, sizeof(cmd));
+ cmd_up[SG_NVME_PT_OPCODE] = SG_NVME_AD_DEV_SELT_TEST;
+ /* just this namespace (if there is one) and controller */
+ sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID);
+ switch (st_cd) {
+ case 0: /* Here if self_test is set, do short self-test */
+ case 1: /* Background short */
+ case 5: /* Foreground short */
+ nvme_dst = 1;
+ break;
+ case 2: /* Background extended */
+ case 6: /* Foreground extended */
+ nvme_dst = 2;
+ break;
+ case 4: /* Abort self-test */
+ nvme_dst = 0xf;
+ break;
+ default:
+ pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd);
+ mk_sense_invalid_fld(ptp, true, 1, 7, vb);
+ return 0;
+ }
+ sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10);
+ res = sg_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ }
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ dout_len = ptp->io_hdr.dout_xfer_len;
+ if (pf) {
+ if (0 == alloc_len) {
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: PF bit set bit param_list_len=0\n", __func__);
+ return 0;
+ }
+ } else { /* PF bit clear */
+ if (alloc_len) {
+ mk_sense_invalid_fld(ptp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: param_list_len>0 but PF clear\n", __func__);
+ return 0;
+ } else
+ return 0; /* nothing to do */
+ }
+ if (dout_len < 4) {
+ if (vb)
+ pr2ws("%s: dout length (%u bytes) too short\n", __func__,
+ dout_len);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ n = dout_len;
+ n = (n < alloc_len) ? n : alloc_len;
+ dop = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ if (! sg_is_aligned(dop, pg_sz)) { /* is dop page aligned ? */
+ if (vb)
+ pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)ptp->io_hdr.dout_xferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ dpg_cd = dop[0];
+ dpg_len = sg_get_unaligned_be16(dop + 2) + 4;
+ /* should we allow for more than one D_PG is dout ?? */
+ n = (n < dpg_len) ? n : dpg_len; /* not yet ... */
+
+ if (vb)
+ pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n",
+ __func__, dpg_cd, dpg_len);
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = SG_NVME_AD_MI_SEND;
+ cmd.addr = (uint64_t)(sg_uintptr_t)dop;
+ cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */
+ /* dout_len > 0x1000, is this a problem?? */
+ cmd.cdw10 = 0x0804; /* NVMe Message Header */
+ cmd.cdw11 = 0x9; /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */
+ cmd.cdw13 = n;
+ res = sg_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ }
+ return res;
+}
+
+/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1)
+ * NVMe-MI has a special command (SES Receive) to read pages through a
+ * tunnel from an enclosure. The NVMe enclosure is meant to understand the
+ * SES (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool pcv;
+ int res;
+ uint8_t dpg_cd;
+ uint32_t alloc_len, n, din_len;
+ uint32_t pg_sz = sg_get_page_size();
+ uint8_t * dip;
+ struct sg_nvme_passthru_cmd cmd;
+
+ pcv = !! (0x1 & cdbp[1]);
+ dpg_cd = cdbp[2];
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ if (vb > 5)
+ pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__,
+ dpg_cd, (int)pcv, alloc_len);
+ din_len = ptp->io_hdr.din_xfer_len;
+ n = din_len;
+ n = (n < alloc_len) ? n : alloc_len;
+ dip = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ if (! sg_is_aligned(dip, pg_sz)) {
+ if (vb)
+ pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)ptp->io_hdr.din_xferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ if (vb)
+ pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__,
+ dpg_cd);
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = SG_NVME_AD_MI_RECEIVE;
+ cmd.addr = (uint64_t)(sg_uintptr_t)dip;
+ cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */
+ /* din_len > 0x1000, is this a problem?? */
+ cmd.cdw10 = 0x0804; /* NVMe Message Header */
+ cmd.cdw11 = 0x8; /* nvme_mi_ses_receive */
+ cmd.cdw12 = dpg_cd;
+ cmd.cdw13 = n;
+ res = sg_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ ptp->io_hdr.din_resid = din_len - n;
+ return res;
+}
+
+#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH 0x100 /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP 0x200
+
+static int
+sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool rctd;
+ uint8_t reporting_opts, req_opcode, supp;
+ uint16_t req_sa;
+ uint32_t alloc_len, offset, a_len;
+ uint32_t pg_sz = sg_get_page_size();
+ int len, count, bump;
+ const struct sg_opcode_info_t *oip;
+ uint8_t *arr;
+ uint8_t *free_arr;
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */
+ reporting_opts = cdbp[2] & 0x7;
+ req_opcode = cdbp[3];
+ req_sa = sg_get_unaligned_be16(cdbp + 4);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4 || alloc_len > 0xffff) {
+ mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+ return 0;
+ }
+ a_len = pg_sz - 72;
+ arr = sg_memalign(pg_sz, pg_sz, &free_arr, false);
+ if (NULL == arr) {
+ pr2ws("%s: calloc() failed to get memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ switch (reporting_opts) {
+ case 0: /* all commands */
+ count = 0;
+ bump = rctd ? 20 : 8;
+ for (offset = 4, oip = sg_get_opcode_translation();
+ (oip->flags != 0xffff) && (offset < a_len); ++oip) {
+ if (F_INV_OP & oip->flags)
+ continue;
+ ++count;
+ arr[offset] = oip->opcode;
+ sg_put_unaligned_be16(oip->sa, arr + offset + 2);
+ if (rctd)
+ arr[offset + 5] |= 0x2;
+ if (FF_SA & oip->flags)
+ arr[offset + 5] |= 0x1;
+ sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
+ if (rctd)
+ sg_put_unaligned_be16(0xa, arr + offset + 8);
+ offset += bump;
+ }
+ sg_put_unaligned_be32(count * bump, arr + 0);
+ break;
+ case 1: /* one command: opcode only */
+ case 2: /* one command: opcode plus service action */
+ case 3: /* one command: if sa==0 then opcode only else opcode+sa */
+ for (oip = sg_get_opcode_translation(); oip->flags != 0xffff; ++oip) {
+ if ((req_opcode == oip->opcode) && (req_sa == oip->sa))
+ break;
+ }
+ if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) {
+ supp = 1;
+ offset = 4;
+ } else {
+ if (1 == reporting_opts) {
+ if (FF_SA & oip->flags) {
+ mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ req_sa = 0;
+ } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) {
+ mk_sense_invalid_fld(ptp, true, 4, -1, vb);
+ free(free_arr);
+ return 0;
+ }
+ if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode))
+ supp = 3;
+ else if (0 == (FF_SA & oip->flags))
+ supp = 1;
+ else if (req_sa != oip->sa)
+ supp = 1;
+ else
+ supp = 3;
+ if (3 == supp) {
+ uint16_t u;
+ int k;
+
+ u = oip->len_mask[0];
+ sg_put_unaligned_be16(u, arr + 2);
+ arr[4] = oip->opcode;
+ for (k = 1; k < u; ++k)
+ arr[4 + k] = (k < 16) ?
+ oip->len_mask[k] : 0xff;
+ offset = 4 + u;
+ } else
+ offset = 4;
+ }
+ arr[1] = (rctd ? 0x80 : 0) | supp;
+ if (rctd) {
+ sg_put_unaligned_be16(0xa, arr + offset);
+ offset += 12;
+ }
+ break;
+ default:
+ mk_sense_invalid_fld(ptp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ offset = (offset < a_len) ? offset : a_len;
+ len = (offset < alloc_len) ? offset : alloc_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+ if (len > 0)
+ memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, arr, len);
+ free(free_arr);
+ return 0;
+}
+
+static int
+sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool repd;
+ uint32_t alloc_len, len;
+ uint8_t arr[16];
+
+ if (vb > 5)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ memset(arr, 0, sizeof(arr));
+ repd = !!(cdbp[2] & 0x80);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4) {
+ mk_sense_invalid_fld(ptp, true, 6, -1, vb);
+ return 0;
+ }
+ arr[0] = 0xc8; /* ATS | ATSS | LURS */
+ arr[1] = 0x1; /* ITNRS */
+ if (repd) {
+ arr[3] = 0xc;
+ len = 16;
+ } else
+ len = 4;
+
+ len = (len < alloc_len) ? len : alloc_len;
+ ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len;
+ if (len > 0)
+ memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, arr, len);
+ return 0;
+}
+
+/* Note that the "Returned logical block address" (RLBA) field in the SCSI
+ * READ CAPACITY (10+16) command's response provides the address of the _last_
+ * LBA (counting origin 0) which will be one less that the "size" in the
+ * NVMe Identify command response's NSZE field. One problem is that in
+ * some situations NSZE can be zero: temporarily set RLBA field to 0
+ * (implying a 1 LB logical units size) pending further research. The LBLIB
+ * is the "Logical Block Length In Bytes" field in the RCAP response. */
+static int
+sntl_readcap(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_rcap10 = (SCSI_READ_CAPACITY10_OPC == cdbp[0]);
+ int res, n, len, alloc_len, dps;
+ uint8_t flbas, index, lbads; /* NVMe: 2**LBADS --> Logical Block size */
+ uint32_t lbafx; /* NVME: LBAF0...LBAF15, each 16 bytes */
+ uint32_t pg_sz = sg_get_page_size();
+ uint64_t nsze;
+ uint8_t * bp;
+ uint8_t * up;
+ uint8_t * free_up = NULL;
+ uint8_t resp[32];
+
+ if (vb > 5)
+ pr2ws("%s: RCAP%d, time_secs=%d\n", __func__,
+ (is_rcap10 ? 10 : 16), time_secs);
+ up = sg_memalign(pg_sz, pg_sz, &free_up, false);
+ if (NULL == up) {
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = sntl_do_identify(ptp, 0x0 /* CNS */, ptp->nvme_nsid, time_secs,
+ pg_sz, up, vb);
+ if (res < 0) {
+ res = sg_convert_errno(-res);
+ goto fini;
+ }
+ memset(resp, 0, sizeof(resp));
+ nsze = sg_get_unaligned_le64(up + 0);
+ flbas = up[26]; /* NVME FLBAS field from Identify, want LBAF[flbas] */
+ index = 128 + (4 * (flbas & 0xf));
+ lbafx = sg_get_unaligned_le32(up + index);
+ lbads = (lbafx >> 16) & 0xff; /* bits 16 to 23 inclusive, pow2 */
+ if (is_rcap10) {
+ alloc_len = 8; /* implicit, not in cdb */
+ if (nsze > 0xffffffff)
+ sg_put_unaligned_be32(0xffffffff, resp + 0);
+ else if (0 == nsze) /* no good answer here */
+ sg_put_unaligned_be32(0, resp + 0); /* SCSI RLBA field */
+ else
+ sg_put_unaligned_be32((uint32_t)(nsze - 1), resp + 0);
+ sg_put_unaligned_be32(1 << lbads, resp + 4); /* SCSI LBLIB field */
+ } else {
+ alloc_len = sg_get_unaligned_be32(cdbp + 10);
+ dps = up[29];
+ if (0x7 & dps) {
+ resp[12] = 0x1;
+ n = (0x7 & dps) - 1;
+ if (n > 0)
+ resp[12] |= (n + n);
+ }
+ if (0 == nsze) /* no good answer here */
+ sg_put_unaligned_be64(0, resp + 0);
+ else
+ sg_put_unaligned_be64(nsze - 1, resp + 0);
+ sg_put_unaligned_be32(1 << lbads, resp + 8); /* SCSI LBLIB field */
+ }
+ len = ptp->io_hdr.din_xfer_len;
+ bp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ n = 32;
+ n = (n < alloc_len) ? n : alloc_len;
+ n = (n < len) ? n : len;
+ ptp->io_hdr.din_resid = len - n;
+ if (n > 0)
+ memcpy(bp, resp, n);
+fini:
+ if (free_up)
+ free(free_up);
+ return res;
+}
+
+static int
+do_nvm_pt_low(struct sg_pt_linux_scsi * ptp,
+ struct sg_nvme_passthru_cmd *cmdp, void * dp, int dlen,
+ bool is_read, int time_secs, int vb)
+{
+ const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd);
+ int res;
+ uint32_t n;
+ uint16_t sct_sc;
+ const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE;
+ char nam[64];
+
+ if (vb)
+ sg_get_nvme_opcode_name(*up, false /* NVM */ , sizeof(nam), nam);
+ else
+ nam[0] = '\0';
+ cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs);
+ ptp->os_err = 0;
+ if (vb > 2) {
+ pr2ws("NVMe NVM command: %s\n", nam);
+ hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+ if ((vb > 4) && (! is_read) && dp) {
+ if (dlen > 0) {
+ n = dlen;
+ if ((dlen < 512) || (vb > 5))
+ pr2ws("\nData-out buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+ n = 512;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ }
+ res = ioctl(ptp->dev_fd, NVME_IOCTL_IO_CMD, cmdp);
+ if (res < 0) { /* OS error (errno negated) */
+ ptp->os_err = -res;
+ if (vb > 1) {
+ pr2ws("%s: ioctl for %s [0x%x] failed: %s "
+ "(errno=%d)\n", __func__, nam, *up, strerror(-res), -res);
+ }
+ return res;
+ }
+
+ /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */
+ ptp->nvme_result = cmdp->result;
+ if ((! ptp->nvme_our_sntl) && ptp->io_hdr.response &&
+ (ptp->io_hdr.max_response_len > 3)) {
+ /* build 32 byte "sense" buffer */
+ uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+ uint16_t st = (uint16_t)res;
+
+ n = ptp->io_hdr.max_response_len;
+ n = (n < 32) ? n : 32;
+ memset(sbp, 0 , n);
+ ptp->io_hdr.response_len = n;
+ sg_put_unaligned_le32(cmdp->result,
+ sbp + SG_NVME_PT_CQ_RESULT);
+ if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */
+ sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P);
+ }
+ /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */
+ sct_sc = 0x7ff & res; /* 11 bits */
+ ptp->nvme_status = sct_sc;
+ ptp->nvme_stat_dnr = !!(0x4000 & res);
+ ptp->nvme_stat_more = !!(0x2000 & res);
+ if (sct_sc) { /* when non-zero, treat as command error */
+ if (vb > 1) {
+ char b[80];
+
+ pr2ws("%s: ioctl for %s [0x%x] failed, status: %s [0x%x]\n",
+ __func__, nam, *up,
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc);
+ }
+ return SG_LIB_NVME_STATUS; /* == SCSI_PT_DO_NVME_STATUS */
+ }
+ if ((vb > 4) && is_read && dp) {
+ if (dlen > 0) {
+ n = dlen;
+ if ((dlen < 1024) || (vb > 5))
+ pr2ws("\nData-in buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+ n = 1024;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ return 0;
+}
+
+/* Since ptp can be a char device (e.g. /dev/nvme0) or a blocks device
+ * (e.g. /dev/nvme0n1 or /dev/nvme0n1p3) use NVME_IOCTL_IO_CMD which is
+ * common to both (and takes a timeout). The difficult is that
+ * NVME_IOCTL_IO_CMD takes a nvme_passthru_cmd object point. */
+static int
+sntl_do_nvm_cmd(struct sg_pt_linux_scsi * ptp, struct sg_nvme_user_io * iop,
+ uint32_t dlen, bool is_read, int time_secs, int vb)
+{
+
+ struct sg_nvme_passthru_cmd nvme_pt_cmd;
+ struct sg_nvme_passthru_cmd *cmdp = &nvme_pt_cmd;
+ void * dp = (void *)(sg_uintptr_t)iop->addr;
+
+ memset(cmdp, 0, sizeof(*cmdp));
+ cmdp->opcode = iop->opcode;
+ cmdp->flags = iop->flags;
+ cmdp->nsid = ptp->nvme_nsid;
+ cmdp->addr = iop->addr;
+ cmdp->data_len = dlen;
+ cmdp->cdw10 = iop->slba & 0xffffffff;
+ cmdp->cdw11 = (iop->slba >> 32) & 0xffffffff;
+ cmdp->cdw12 = iop->nblocks; /* lower 16 bits already "0's based" count */
+
+ return do_nvm_pt_low(ptp, cmdp, dp, dlen, is_read, time_secs, vb);
+}
+
+static int
+sntl_rread(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_read10 = (SCSI_READ10_OPC == cdbp[0]);
+ bool have_fua = !!(cdbp[1] & 0x8);
+ int res;
+ uint32_t nblks_t10 = 0;
+ struct sg_nvme_user_io io;
+ struct sg_nvme_user_io * iop = &io;
+
+ if (vb > 5)
+ pr2ws("%s: fua=%d, time_secs=%d\n", __func__, (int)have_fua,
+ time_secs);
+ memset(iop, 0, sizeof(*iop));
+ iop->opcode = SG_NVME_NVM_READ;
+ if (is_read10) {
+ iop->slba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ iop->slba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ iop->nblocks = nblks_t10 - 1; /* crazy "0's based" */
+ if (have_fua)
+ iop->control |= SG_NVME_RW_CONTROL_FUA;
+ iop->addr = (uint64_t)ptp->io_hdr.din_xferp;
+ res = sntl_do_nvm_cmd(ptp, iop, ptp->io_hdr.din_xfer_len,
+ true /* is_read */, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ return res;
+}
+
+static int
+sntl_write(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_write10 = (SCSI_WRITE10_OPC == cdbp[0]);
+ bool have_fua = !!(cdbp[1] & 0x8);
+ int res;
+ uint32_t nblks_t10 = 0;
+ struct sg_nvme_user_io io;
+ struct sg_nvme_user_io * iop = &io;
+
+ if (vb > 5)
+ pr2ws("%s: fua=%d, time_secs=%d\n", __func__, (int)have_fua,
+ time_secs);
+ memset(iop, 0, sizeof(*iop));
+ iop->opcode = SG_NVME_NVM_WRITE;
+ if (is_write10) {
+ iop->slba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ iop->slba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ iop->nblocks = nblks_t10 - 1;
+ if (have_fua)
+ iop->control |= SG_NVME_RW_CONTROL_FUA;
+ iop->addr = (uint64_t)ptp->io_hdr.dout_xferp;
+ res = sntl_do_nvm_cmd(ptp, iop, ptp->io_hdr.dout_xfer_len, false,
+ time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ return res;
+}
+
+static int
+sntl_verify(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_verify10 = (SCSI_VERIFY10_OPC == cdbp[0]);
+ uint8_t bytchk = (cdbp[1] >> 1) & 0x3;
+ uint32_t dlen = 0;
+ int res;
+ uint32_t nblks_t10 = 0;
+ struct sg_nvme_user_io io;
+ struct sg_nvme_user_io * iop = &io;
+
+ if (vb > 5)
+ pr2ws("%s: bytchk=%d, time_secs=%d\n", __func__, bytchk, time_secs);
+ if (bytchk > 1) {
+ mk_sense_invalid_fld(ptp, true, 1, 2, vb);
+ return 0;
+ }
+ memset(iop, 0, sizeof(*iop));
+ iop->opcode = bytchk ? SG_NVME_NVM_COMPARE : SG_NVME_NVM_VERIFY;
+ if (is_verify10) {
+ iop->slba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ iop->slba = sg_get_unaligned_be64(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be32(cdbp + 10);
+ if (nblks_t10 > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ }
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ iop->nblocks = nblks_t10 - 1;
+ if (bytchk) {
+ iop->addr = (uint64_t)ptp->io_hdr.dout_xferp;
+ dlen = ptp->io_hdr.dout_xfer_len;
+ }
+ res = sntl_do_nvm_cmd(ptp, iop, dlen, false, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ return res;
+}
+
+static int
+sntl_write_same(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool is_ws10 = (SCSI_WRITE_SAME10_OPC == cdbp[0]);
+ bool ndob = is_ws10 ? false : !!(0x1 & cdbp[1]);
+ int res;
+ int nblks_t10 = 0;
+ struct sg_nvme_user_io io;
+ struct sg_nvme_user_io * iop = &io;
+
+ if (vb > 5)
+ pr2ws("%s: ndob=%d, time_secs=%d\n", __func__, (int)ndob, time_secs);
+ if (! ndob) {
+ int flbas, index, lbafx, lbads, lbsize;
+ uint8_t * up;
+ uint8_t * dp;
+
+ dp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ if (dp == NULL)
+ return sg_convert_errno(ENOMEM);
+ if (NULL == ptp->nvme_id_ctlp) {
+ res = sntl_cache_identify(ptp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ up = ptp->nvme_id_ctlp;
+ flbas = up[26]; /* NVME FLBAS field from Identify */
+ index = 128 + (4 * (flbas & 0xf));
+ lbafx = sg_get_unaligned_le32(up + index);
+ lbads = (lbafx >> 16) & 0xff; /* bits 16 to 23 inclusive, pow2 */
+ lbsize = 1 << lbads;
+ if (! sg_all_zeros(dp, lbsize)) {
+ mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, PCIE_ERR_ASC,
+ PCIE_UNSUPP_REQ_ASCQ, vb);
+ return 0;
+ }
+ /* so given single LB full of zeros, can translate .... */
+ }
+ memset(iop, 0, sizeof(*iop));
+ iop->opcode = SG_NVME_NVM_WRITE_ZEROES;
+ if (is_ws10) {
+ iop->slba = sg_get_unaligned_be32(cdbp + 2);
+ nblks_t10 = sg_get_unaligned_be16(cdbp + 7);
+ } else {
+ uint32_t num = sg_get_unaligned_be32(cdbp + 10);
+
+ iop->slba = sg_get_unaligned_be64(cdbp + 2);
+ if (num > (UINT16_MAX + 1)) {
+ mk_sense_invalid_fld(ptp, true, 11, -1, vb);
+ return 0;
+ } else
+ nblks_t10 = num;
+ }
+ if (0 == nblks_t10) { /* NOP in SCSI */
+ if (vb > 4)
+ pr2ws("%s: nblks_t10 is 0, a NOP in SCSI, can't map to NVMe\n",
+ __func__);
+ return 0;
+ }
+ iop->nblocks = nblks_t10 - 1;
+ res = sntl_do_nvm_cmd(ptp, iop, 0, false, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ return res;
+}
+
+static int
+sntl_sync_cache(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool immed = !!(0x2 & cdbp[1]);
+ struct sg_nvme_user_io io;
+ struct sg_nvme_user_io * iop = &io;
+ int res;
+
+ if (vb > 5)
+ pr2ws("%s: immed=%d, time_secs=%d\n", __func__, (int)immed,
+ time_secs);
+ memset(iop, 0, sizeof(*iop));
+ iop->opcode = SG_NVME_NVM_FLUSH;
+ if (vb > 4)
+ pr2ws("%s: immed bit, lba and num_lbs fields ignored\n", __func__);
+ res = sntl_do_nvm_cmd(ptp, iop, 0, false, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(ptp, vb);
+ return 0;
+ }
+ return res;
+}
+
+static int
+sntl_start_stop(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp,
+ int time_secs, int vb)
+{
+ bool immed = !!(0x1 & cdbp[1]);
+
+ if (vb > 5)
+ pr2ws("%s: immed=%d, time_secs=%d, ignore\n", __func__, (int)immed,
+ time_secs);
+ if (ptp) { } /* suppress warning */
+ return 0;
+}
+
+/* Executes NVMe Admin command (or at least forwards it to lower layers).
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package.
+ * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds
+ * is used. */
+int
+sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
+{
+ bool scsi_cdb;
+ bool is_read = false;
+ int n, len, hold_dev_fd;
+ uint16_t sa;
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ struct sg_nvme_passthru_cmd cmd;
+ const uint8_t * cdbp;
+ void * dp = NULL;
+
+ if (! ptp->io_hdr.request) {
+ if (vb)
+ pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ hold_dev_fd = ptp->dev_fd;
+ if (fd >= 0) {
+ if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+ if (vb)
+ pr2ws("%s: file descriptor given to create() and here "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ ptp->dev_fd = fd;
+ } else if (ptp->dev_fd < 0) {
+ if (vb)
+ pr2ws("%s: invalid file descriptors\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ n = ptp->io_hdr.request_len;
+ cdbp = (const uint8_t *)(sg_uintptr_t)ptp->io_hdr.request;
+ if (vb > 4)
+ pr2ws("%s: opcode=0x%x, fd=%d (dev_fd=%d), time_secs=%d\n", __func__,
+ cdbp[0], fd, hold_dev_fd, time_secs);
+ scsi_cdb = sg_is_scsi_cdb(cdbp, n);
+ /* direct NVMe command (i.e. 64 bytes long) or SNTL */
+ ptp->nvme_our_sntl = scsi_cdb;
+ if (scsi_cdb) {
+ switch (cdbp[0]) {
+ case SCSI_INQUIRY_OPC:
+ return sntl_inq(ptp, cdbp, time_secs, vb);
+ case SCSI_REPORT_LUNS_OPC:
+ return sntl_rluns(ptp, cdbp, time_secs, vb);
+ case SCSI_TEST_UNIT_READY_OPC:
+ return sntl_tur(ptp, time_secs, vb);
+ case SCSI_REQUEST_SENSE_OPC:
+ return sntl_req_sense(ptp, cdbp, time_secs, vb);
+ case SCSI_READ10_OPC:
+ case SCSI_READ16_OPC:
+ return sntl_rread(ptp, cdbp, time_secs, vb);
+ case SCSI_WRITE10_OPC:
+ case SCSI_WRITE16_OPC:
+ return sntl_write(ptp, cdbp, time_secs, vb);
+ case SCSI_START_STOP_OPC:
+ return sntl_start_stop(ptp, cdbp, time_secs, vb);
+ case SCSI_SEND_DIAGNOSTIC_OPC:
+ return sntl_senddiag(ptp, cdbp, time_secs, vb);
+ case SCSI_RECEIVE_DIAGNOSTIC_OPC:
+ return sntl_recvdiag(ptp, cdbp, time_secs, vb);
+ case SCSI_MODE_SENSE10_OPC:
+ case SCSI_MODE_SELECT10_OPC:
+ return sntl_mode_ss(ptp, cdbp, time_secs, vb);
+ case SCSI_READ_CAPACITY10_OPC:
+ return sntl_readcap(ptp, cdbp, time_secs, vb);
+ case SCSI_VERIFY10_OPC:
+ case SCSI_VERIFY16_OPC:
+ return sntl_verify(ptp, cdbp, time_secs, vb);
+ case SCSI_WRITE_SAME10_OPC:
+ case SCSI_WRITE_SAME16_OPC:
+ return sntl_write_same(ptp, cdbp, time_secs, vb);
+ case SCSI_SYNC_CACHE10_OPC:
+ case SCSI_SYNC_CACHE16_OPC:
+ return sntl_sync_cache(ptp, cdbp, time_secs, vb);
+ case SCSI_SERVICE_ACT_IN_OPC:
+ if (SCSI_READ_CAPACITY16_SA == (cdbp[1] & SCSI_SA_MSK))
+ return sntl_readcap(ptp, cdbp, time_secs, vb);
+ goto fini;
+ case SCSI_MAINT_IN_OPC:
+ sa = SCSI_SA_MSK & cdbp[1]; /* service action */
+ if (SCSI_REP_SUP_OPCS_OPC == sa)
+ return sntl_rep_opcodes(ptp, cdbp, time_secs, vb);
+ else if (SCSI_REP_SUP_TMFS_OPC == sa)
+ return sntl_rep_tmfs(ptp, cdbp, time_secs, vb);
+ /* fall through */
+ default:
+fini:
+ if (vb > 2) {
+ char b[64];
+
+ sg_get_command_name(cdbp, -1, sizeof(b), b);
+ pr2ws("%s: no translation to NVMe for SCSI %s command\n",
+ __func__, b);
+ }
+ mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE,
+ 0, vb);
+ return 0;
+ }
+ }
+ len = (int)sizeof(cmd);
+ n = (n < len) ? n : len;
+ if (n < 64) {
+ if (vb)
+ pr2ws("%s: command length of %d bytes is too short\n", __func__,
+ n);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ memcpy(&cmd, (const uint8_t *)(sg_uintptr_t)ptp->io_hdr.request, n);
+ if (n < len) /* zero out rest of 'cmd' */
+ memset((uint8_t *)&cmd + n, 0, len - n);
+ if (ptp->io_hdr.din_xfer_len > 0) {
+ cmd.data_len = ptp->io_hdr.din_xfer_len;
+ dp = (void *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ is_read = true;
+ } else if (ptp->io_hdr.dout_xfer_len > 0) {
+ cmd.data_len = ptp->io_hdr.dout_xfer_len;
+ dp = (void *)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ is_read = false;
+ }
+ return sg_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb);
+}
+
+#else /* (HAVE_NVME && (! IGNORE_NVME)) [around line 140] */
+
+int
+sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
+{
+ if (vb) {
+ pr2ws("%s: not supported, ", __func__);
+#ifdef HAVE_NVME
+ pr2ws("HAVE_NVME, ");
+#else
+ pr2ws("don't HAVE_NVME, ");
+#endif
+
+#ifdef IGNORE_NVME
+ pr2ws("IGNORE_NVME");
+#else
+ pr2ws("don't IGNORE_NVME");
+#endif
+ pr2ws("\n");
+ }
+ if (vp) { ; } /* suppress warning */
+ if (fd) { ; } /* suppress warning */
+ if (time_secs) { ; } /* suppress warning */
+ return -ENOTTY; /* inappropriate ioctl error */
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int vb)
+{
+ bool is_read = false;
+ int dlen;
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ struct sg_nvme_passthru_cmd cmd;
+ uint8_t * cmdp = (uint8_t *)&cmd;
+ void * dp = NULL;
+
+ if (vb && (submq != 0))
+ pr2ws("%s: warning, uses submit queue 0\n", __func__);
+ if (ptp->dev_fd < 0) {
+ if (vb > 1)
+ pr2ws("%s: no NVMe file descriptor given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (! ptp->is_nvme) {
+ if (vb > 1)
+ pr2ws("%s: file descriptor is not NVMe device\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if ((! ptp->io_hdr.request) || (64 != ptp->io_hdr.request_len)) {
+ if (vb > 1)
+ pr2ws("%s: no NVMe 64 byte command present\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (sizeof(cmd) > 64)
+ memset(cmdp + 64, 0, sizeof(cmd) - 64);
+ memcpy(cmdp, (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request, 64);
+ ptp->nvme_our_sntl = false;
+
+ dlen = ptp->io_hdr.din_xfer_len;
+ if (dlen > 0) {
+ is_read = true;
+ dp = (void *)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+ } else {
+ dlen = ptp->io_hdr.dout_xfer_len;
+ if (dlen > 0)
+ dp = (void *)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+ }
+ return do_nvm_pt_low(ptp, &cmd, dp, dlen, is_read, timeout_secs, vb);
+}
+
+#else /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int vb)
+{
+ if (vb) {
+ pr2ws("%s: not supported, ", __func__);
+#ifdef HAVE_NVME
+ pr2ws("HAVE_NVME, ");
+#else
+ pr2ws("don't HAVE_NVME, ");
+#endif
+
+#ifdef IGNORE_NVME
+ pr2ws("IGNORE_NVME");
+#else
+ pr2ws("don't IGNORE_NVME");
+#endif
+ }
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
diff --git a/lib/sg_pt_osf1.c b/lib/sg_pt_osf1.c
new file mode 100644
index 00000000..38e32cfc
--- /dev/null
+++ b/lib/sg_pt_osf1.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (c) 2005-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <io/common/devgetinfo.h>
+#include <io/common/iotypes.h>
+#include <io/cam/cam.h>
+#include <io/cam/uagt.h>
+#include <io/cam/rzdisk.h>
+#include <io/cam/scsi_opcodes.h>
+#include <io/cam/scsi_all.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+/* Version 2.04 20210617 */
+
+#define OSF1_MAXDEV 64
+
+#ifndef CAM_DIR_BOTH
+#define CAM_DIR_BOTH 0x0 /* copy value from FreeBSD */
+#endif
+
+struct osf1_dev_channel {
+ int bus;
+ int tgt;
+ int lun;
+};
+
+// Private table of open devices: guaranteed zero on startup since
+// part of static data.
+static struct osf1_dev_channel *devicetable[OSF1_MAXDEV] SG_C_CPP_ZERO_INIT;
+static char *cam_dev = "/dev/cam";
+static int camfd;
+static int camopened = 0;
+
+struct sg_pt_osf1_scsi {
+ uint8_t * cdb;
+ int cdb_len;
+ uint8_t * sense;
+ int sense_len;
+ uint8_t * dxferp;
+ int dxfer_len;
+ int dxfer_dir;
+ int scsi_status;
+ int resid;
+ int sense_resid;
+ int in_err;
+ int os_err;
+ int transport_err;
+ bool is_nvme;
+ int dev_fd;
+};
+
+struct sg_pt_base {
+ struct sg_pt_osf1_scsi impl;
+};
+
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = 0 /* O_NONBLOCK*/ ;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. The 'flags' argument is ignored in OSF-1.
+ * Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int verbose)
+{
+ struct osf1_dev_channel *fdchan;
+ int fd, k;
+
+ if (!camopened) {
+ camfd = open(cam_dev, O_RDWR, 0);
+ if (camfd < 0)
+ return -1;
+ camopened++;
+ }
+
+ // Search table for a free entry
+ for (k = 0; k < OSF1_MAXDEV; k++)
+ if (! devicetable[k])
+ break;
+
+ if (k == OSF1_MAXDEV) {
+ if (verbose)
+ pr2ws("too many open devices (%d)\n", OSF1_MAXDEV);
+ errno=EMFILE;
+ return -1;
+ }
+
+ fdchan = (struct osf1_dev_channel *)calloc(1,
+ sizeof(struct osf1_dev_channel));
+ if (fdchan == NULL) {
+ // errno already set by call to malloc()
+ return -1;
+ }
+
+ fd = open(device_name, O_RDONLY|O_NONBLOCK);
+ if (fd > 0) {
+ device_info_t devinfo;
+ bzero(&devinfo, sizeof(devinfo));
+ if (ioctl(fd, DEVGETINFO, &devinfo) == 0) {
+ fdchan->bus = devinfo.v1.businfo.bus.scsi.bus_num;
+ fdchan->tgt = devinfo.v1.businfo.bus.scsi.tgt_id;
+ fdchan->lun = devinfo.v1.businfo.bus.scsi.lun;
+ }
+ close (fd);
+ } else {
+ free(fdchan);
+ return -1;
+ }
+
+ devicetable[k] = fdchan;
+ return k;
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ struct osf1_dev_channel *fdchan;
+ int i;
+
+ if ((device_fd < 0) || (device_fd >= OSF1_MAXDEV)) {
+ errno = ENODEV;
+ return -1;
+ }
+
+ fdchan = devicetable[device_fd];
+ if (NULL == fdchan) {
+ errno = ENODEV;
+ return -1;
+ }
+
+ free(fdchan);
+ devicetable[device_fd] = NULL;
+
+ for (i = 0; i < OSF1_MAXDEV; i++) {
+ if (devicetable[i])
+ break;
+ }
+ if (i == OSF1_MAXDEV) {
+ close(camfd);
+ camopened = 0;
+ }
+ return 0;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int device_fd, int verbose)
+{
+ struct sg_pt_osf1_scsi * ptp;
+
+ ptp = (struct sg_pt_osf1_scsi *)malloc(sizeof(struct sg_pt_osf1_scsi));
+ if (ptp) {
+ bzero(ptp, sizeof(struct sg_pt_osf1_scsi));
+ ptp->dev_fd = (device_fd < 0) ? -1 : device_fd;
+ ptp->is_nvme = false;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ } else if (verbose)
+ pr2ws("%s: malloc() out of memory\n", __func__);
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj(void)
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (ptp)
+ free(ptp);
+}
+
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ bool is_nvme;
+ int dev_fd;
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ is_nvme = ptp->is_nvme;
+ dev_fd = ptp->dev_fd;
+ bzero(ptp, sizeof(struct sg_pt_osf1_scsi));
+ ptp->dev_fd = dev_fd;
+ ptp->is_nvme = is_nvme;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->transport_err = 0;
+ ptp->scsi_status = 0;
+ ptp->dxfer_dir = CAM_DIR_NONE;
+ ptp->dxferp = NULL;
+ ptp->dxfer_len = 0;
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ ptp->cdb = (uint8_t *)cdb;
+ ptp->cdb_len = cdb_len;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->cdb_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->cdb;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (sense) {
+ if (max_sense_len > 0)
+ bzero(sense, max_sense_len);
+ }
+ ptp->sense = sense;
+ ptp->sense_len = max_sense_len;
+}
+
+/* from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (ptp->dxferp)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->dxferp = dxferp;
+ ptp->dxfer_len = dxfer_len;
+ ptp->dxfer_dir = CAM_DIR_IN;
+ }
+}
+
+/* to device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (ptp->dxferp)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->dxferp = (uint8_t *)dxferp;
+ ptp->dxfer_len = dxfer_len;
+ ptp->dxfer_dir = CAM_DIR_OUT;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
+{
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attrib, int priority)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * objp, int flags)
+{
+ /* do nothing, suppress warnings */
+ objp = objp;
+ flags = flags;
+}
+
+static int
+release_sim(struct sg_pt_base *vp, int device_fd, int verbose) {
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ struct osf1_dev_channel *fdchan = devicetable[device_fd];
+ UAGT_CAM_CCB uagt;
+ CCB_RELSIM relsim;
+ int retval;
+
+ bzero(&uagt, sizeof(uagt));
+ bzero(&relsim, sizeof(relsim));
+
+ uagt.uagt_ccb = (CCB_HEADER *) &relsim;
+ uagt.uagt_ccblen = sizeof(relsim);
+
+ relsim.cam_ch.cam_ccb_len = sizeof(relsim);
+ relsim.cam_ch.cam_func_code = XPT_REL_SIMQ;
+ relsim.cam_ch.cam_flags = CAM_DIR_IN | CAM_DIS_CALLBACK;
+ relsim.cam_ch.cam_path_id = fdchan->bus;
+ relsim.cam_ch.cam_target_id = fdchan->tgt;
+ relsim.cam_ch.cam_target_lun = fdchan->lun;
+
+ retval = ioctl(camfd, UAGT_CAM_IO, &uagt);
+ if (retval < 0) {
+ if (verbose)
+ pr2ws("CAM ioctl error (Release SIM Queue)\n");
+ }
+ return retval;
+}
+
+int
+do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ struct osf1_dev_channel *fdchan;
+ int len, retval;
+ CCB_SCSIIO ccb;
+ UAGT_CAM_CCB uagt;
+ uint8_t sensep[ADDL_SENSE_LENGTH];
+
+
+ ptp->os_err = 0;
+ if (ptp->in_err) {
+ if (verbose)
+ pr2ws("Replicated or unused set_scsi_pt...\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (device_fd < 0) {
+ if (ptp->dev_fd < 0) {
+ if (verbose)
+ pr2ws("%s: No device file descriptor given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else {
+ if (ptp->dev_fd >= 0) {
+ if (device_fd != ptp->dev_fd) {
+ if (verbose)
+ pr2ws("%s: file descriptor given to create and this "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else
+ ptp->dev_fd = device_fd;
+ }
+ if (NULL == ptp->cdb) {
+ if (verbose)
+ pr2ws("No command (cdb) given\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ if ((ptp->dev_fd < 0) || (ptp->dev_fd >= OSF1_MAXDEV)) {
+ if (verbose)
+ pr2ws("Bad file descriptor\n");
+ ptp->os_err = ENODEV;
+ return -ptp->os_err;
+ }
+ fdchan = devicetable[ptp->dev_fd];
+ if (NULL == fdchan) {
+ if (verbose)
+ pr2ws("File descriptor closed??\n");
+ ptp->os_err = ENODEV;
+ return -ptp->os_err;
+ }
+ if (0 == camopened) {
+ if (verbose)
+ pr2ws("No open CAM device\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ bzero(&uagt, sizeof(uagt));
+ bzero(&ccb, sizeof(ccb));
+
+ uagt.uagt_ccb = (CCB_HEADER *) &ccb;
+ uagt.uagt_ccblen = sizeof(ccb);
+ uagt.uagt_snsbuf = ccb.cam_sense_ptr = ptp->sense ? ptp->sense : sensep;
+ uagt.uagt_snslen = ccb.cam_sense_len = ptp->sense ? ptp->sense_len :
+ sizeof sensep;
+ uagt.uagt_buffer = ccb.cam_data_ptr = ptp->dxferp;
+ uagt.uagt_buflen = ccb.cam_dxfer_len = ptp->dxfer_len;
+
+ ccb.cam_timeout = time_secs;
+ ccb.cam_ch.my_addr = (CCB_HEADER *) &ccb;
+ ccb.cam_ch.cam_ccb_len = sizeof(ccb);
+ ccb.cam_ch.cam_func_code = XPT_SCSI_IO;
+ ccb.cam_ch.cam_flags = ptp->dxfer_dir;
+ ccb.cam_cdb_len = ptp->cdb_len;
+ memcpy(ccb.cam_cdb_io.cam_cdb_bytes, ptp->cdb, ptp->cdb_len);
+ ccb.cam_ch.cam_path_id = fdchan->bus;
+ ccb.cam_ch.cam_target_id = fdchan->tgt;
+ ccb.cam_ch.cam_target_lun = fdchan->lun;
+
+ if (ioctl(camfd, UAGT_CAM_IO, &uagt) < 0) {
+ if (verbose)
+ pr2ws("CAN I/O Error\n");
+ ptp->os_err = EIO;
+ return -ptp->os_err;
+ }
+
+ if (((ccb.cam_ch.cam_status & CAM_STATUS_MASK) == CAM_REQ_CMP) ||
+ ((ccb.cam_ch.cam_status & CAM_STATUS_MASK) == CAM_REQ_CMP_ERR)) {
+ ptp->scsi_status = ccb.cam_scsi_status;
+ ptp->resid = ccb.cam_resid;
+ if (ptp->sense)
+ ptp->sense_resid = ccb.cam_sense_resid;
+ } else {
+ ptp->transport_err = 1;
+ }
+
+ /* If the SIM queue is frozen, release SIM queue. */
+ if (ccb.cam_ch.cam_status & CAM_SIM_QFRZN)
+ release_sim(vp, ptp->dev_fd, verbose);
+
+ return 0;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if (ptp->transport_err)
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status))
+ return SCSI_PT_RESULT_SENSE;
+ else if (ptp->scsi_status)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ bool bidi = (ptp->dxfer_dir == CAM_DIR_BOTH);
+
+ if (req_dinp) {
+ if (ptp->dxfer_len > 0)
+ *req_dinp = ptp->dxfer_len;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if ((!bidi) && (ptp->dxfer_len > 0))
+ *req_doutp = ptp->dxfer_len;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ bool bidi = (ptp->dxfer_dir == CAM_DIR_BOTH);
+
+ if (act_dinp) {
+ if (ptp->dxfer_len > 0)
+ *act_dinp = ptp->dxfer_len - ptp->resid;
+ else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if ((!bidi) && (ptp->dxfer_len > 0))
+ *act_doutp = ptp->dxfer_len - ptp->resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->scsi_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ int len;
+
+ len = ptp->sense_len - ptp->sense_resid;
+ return (len > 0) ? len : 0;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->sense;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
+{
+ // const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return -1;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->transport_err;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp ? ptp->is_nvme : false;
+}
+
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (0 == ptp->transport_err) {
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+ return b;
+ }
+ strncpy(b, "no transport error available", max_b_len);
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+ const char * cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int verbose)
+{
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ if (verbose) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+int
+check_pt_file_handle(int device_fd, const char * device_name, int vb)
+{
+ if (device_fd) {}
+ if (device_name) {}
+ if (vb) {}
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ return ptp->dev_fd;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+ if (vb) {}
+ ptp->dev_fd = (dev_han < 0) ? -1 : dev_han;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->is_nvme = false;
+ return 0;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ if (vp) { }
+ if (err) { }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true)
+{
+ if (vp) { }
+ if (mdxferp) { }
+ if (mdxfer_len) { }
+ if (out_true) { }
+}
diff --git a/lib/sg_pt_solaris.c b/lib/sg_pt_solaris.c
new file mode 100644
index 00000000..e6cfa575
--- /dev/null
+++ b/lib/sg_pt_solaris.c
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2007-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* sg_pt_solaris version 1.15 20210617 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/param.h>
+
+/* Solaris headers */
+#include <sys/scsi/generic/commands.h>
+#include <sys/scsi/generic/status.h>
+#include <sys/scsi/impl/types.h>
+#include <sys/scsi/impl/uscsi.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+
+
+#define DEF_TIMEOUT 60 /* 60 seconds */
+
+struct sg_pt_solaris_scsi {
+ struct uscsi_cmd uscsi;
+ int max_sense_len;
+ int in_err;
+ int os_err;
+ bool is_nvme;
+ int dev_fd;
+};
+
+struct sg_pt_base {
+ struct sg_pt_solaris_scsi impl;
+};
+
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = 0 /* O_NONBLOCK*/ ;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. The 'flags' argument is ignored in Solaris.
+ * Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags_arg, int verbose)
+{
+ int oflags = O_NONBLOCK | O_RDWR;
+ int fd;
+
+ flags_arg = flags_arg; /* ignore flags argument, suppress warning */
+ if (verbose > 1) {
+ fprintf(sg_warnings_strm ? sg_warnings_strm : stderr,
+ "open %s with flags=0x%x\n", device_name, oflags);
+ }
+ fd = open(device_name, oflags);
+ if (fd < 0)
+ fd = -errno;
+ return fd;
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ int res;
+
+ res = close(device_fd);
+ if (res < 0)
+ res = -errno;
+ return res;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int dev_fd, int verbose)
+{
+ struct sg_pt_solaris_scsi * ptp;
+
+ ptp = (struct sg_pt_solaris_scsi *)
+ calloc(1, sizeof(struct sg_pt_solaris_scsi));
+ if (ptp) {
+ ptp->dev_fd = (dev_fd < 0) ? -1 : dev_fd;
+ ptp->is_nvme = false;
+ ptp->uscsi.uscsi_timeout = DEF_TIMEOUT;
+ /* Comment in Illumos suggest USCSI_ISOLATE and USCSI_DIAGNOSE (both)
+ * seem to mean "don't retry" which is what we want. */
+ ptp->uscsi.uscsi_flags = USCSI_ISOLATE | USCSI_DIAGNOSE |
+ USCSI_RQENABLE;
+ } else if (verbose)
+ fprintf(sg_warnings_strm ? sg_warnings_strm : stderr,
+ "%s: calloc() out of memory\n", __func__);
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (ptp)
+ free(ptp);
+}
+
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ bool is_nvme;
+ int dev_fd;
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ is_nvme = ptp->is_nvme;
+ dev_fd = ptp->dev_fd;
+ memset(ptp, 0, sizeof(struct sg_pt_solaris_scsi));
+ ptp->dev_fd = dev_fd;
+ ptp->is_nvme = is_nvme;
+ ptp->uscsi.uscsi_timeout = DEF_TIMEOUT;
+ ptp->uscsi.uscsi_flags = USCSI_ISOLATE | USCSI_DIAGNOSE |
+ USCSI_RQENABLE;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->uscsi.uscsi_status = 0;
+ ptp->uscsi.uscsi_bufaddr = NULL;
+ ptp->uscsi.uscsi_buflen = 0;
+ ptp->uscsi.uscsi_flags = USCSI_ISOLATE | USCSI_DIAGNOSE |
+ USCSI_RQENABLE;
+ }
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ ptp->uscsi.uscsi_cdb = (char *)cdb;
+ ptp->uscsi.uscsi_cdblen = cdb_len;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp->uscsi.uscsi_cdblen;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)ptp->uscsi.uscsi_cdb;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (sense && (max_sense_len > 0))
+ memset(sense, 0, max_sense_len);
+ ptp->uscsi.uscsi_rqbuf = (char *)sense;
+ ptp->uscsi.uscsi_rqlen = max_sense_len;
+ ptp->max_sense_len = max_sense_len;
+}
+
+/* from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (ptp->uscsi.uscsi_bufaddr)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->uscsi.uscsi_bufaddr = (char *)dxferp;
+ ptp->uscsi.uscsi_buflen = dxfer_len;
+ ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_DIAGNOSE |
+ USCSI_RQENABLE;
+ }
+}
+
+/* to device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (ptp->uscsi.uscsi_bufaddr)
+ ++ptp->in_err;
+ if (dxfer_len > 0) {
+ ptp->uscsi.uscsi_bufaddr = (char *)dxferp;
+ ptp->uscsi.uscsi_buflen = dxfer_len;
+ ptp->uscsi.uscsi_flags = USCSI_WRITE | USCSI_ISOLATE | USCSI_DIAGNOSE |
+ USCSI_RQENABLE;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
+{
+ // struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ vp = vp; /* ignore and suppress warning */
+ pack_id = pack_id; /* ignore and suppress warning */
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+ // struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ vp = vp; /* ignore and suppress warning */
+ tag = tag; /* ignore and suppress warning */
+}
+
+/* Note that task management function codes are transport specific */
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+ tmf_code = tmf_code; /* dummy to silence compiler */
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ ++ptp->in_err;
+ attribute = attribute; /* dummy to silence compiler */
+ priority = priority; /* dummy to silence compiler */
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * objp, int flags)
+{
+ /* do nothing, suppress warnings */
+ objp = objp;
+ flags = flags;
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers).
+ * Clears os_err field prior to active call (whose result may set it
+ * again). */
+int
+do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ FILE * ferr = sg_warnings_strm ? sg_warnings_strm : stderr;
+
+ ptp->os_err = 0;
+ if (ptp->in_err) {
+ if (verbose)
+ fprintf(ferr, "Replicated or unused set_scsi_pt... functions\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (fd < 0) {
+ if (ptp->dev_fd < 0) {
+ if (verbose)
+ fprintf(ferr, "%s: No device file descriptor given\n",
+ __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else {
+ if (ptp->dev_fd >= 0) {
+ if (fd != ptp->dev_fd) {
+ if (verbose)
+ fprintf(ferr, "%s: file descriptor given to create and "
+ "this differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ } else
+ ptp->dev_fd = fd;
+ }
+ if (NULL == ptp->uscsi.uscsi_cdb) {
+ if (verbose)
+ fprintf(ferr, "%s: No SCSI command (cdb) given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (time_secs > 0)
+ ptp->uscsi.uscsi_timeout = time_secs;
+
+ if (ioctl(ptp->dev_fd, USCSICMD, &ptp->uscsi)) {
+ ptp->os_err = errno;
+ if ((EIO == ptp->os_err) && ptp->uscsi.uscsi_status) {
+ ptp->os_err = 0;
+ return 0;
+ }
+ if (verbose)
+ fprintf(ferr, "%s: ioctl(USCSICMD) failed with os_err (errno) "
+ "= %d\n", __func__, ptp->os_err);
+ return -ptp->os_err;
+ }
+ return 0;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ int scsi_st = ptp->uscsi.uscsi_status;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if ((SAM_STAT_CHECK_CONDITION == scsi_st) ||
+ (SAM_STAT_COMMAND_TERMINATED == scsi_st))
+ return SCSI_PT_RESULT_SENSE;
+ else if (scsi_st)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return (uint32_t)ptp->uscsi.uscsi_status;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp->uscsi.uscsi_resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ int dxfer_len = ptp->uscsi.uscsi_buflen;
+ int flags = ptp->uscsi.uscsi_flags;
+
+ if (req_dinp) {
+ if ((dxfer_len > 0) && (USCSI_READ & flags))
+ *req_dinp = dxfer_len;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if ((dxfer_len > 0) && (USCSI_WRITE & flags))
+ *req_doutp = dxfer_len;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ int dxfer_len = ptp->uscsi.uscsi_buflen;
+ int flags = ptp->uscsi.uscsi_flags;
+
+ if (act_dinp) {
+ if ((dxfer_len > 0) && (USCSI_READ & flags))
+ *act_dinp = dxfer_len - ptp->uscsi.uscsi_resid;
+ else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if ((dxfer_len > 0) && (USCSI_WRITE & flags))
+ *act_doutp = dxfer_len - ptp->uscsi.uscsi_resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp->uscsi.uscsi_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ int res;
+
+ if (ptp->max_sense_len > 0) {
+ res = ptp->max_sense_len - ptp->uscsi.uscsi_rqresid;
+ return (res > 0) ? res : 0;
+ }
+ return 0;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)ptp->uscsi.uscsi_rqbuf;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
+{
+ // const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ vp = vp; /* ignore and suppress warning */
+ return -1; /* not available */
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ // const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (vp) { ; } /* ignore and suppress warning */
+ return 0;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ // const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (vp) { ; } /* ignore and suppress warning */
+ if (err) { ; } /* ignore and suppress warning */
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp ? ptp->is_nvme : false;
+}
+
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ // const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ vp = vp; /* ignore and suppress warning */
+ if (max_b_len > 0)
+ b[0] = '\0';
+
+ return b;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+ const char * cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int verbose)
+{
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ if (verbose) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
+
+int
+check_pt_file_handle(int device_fd, const char * device_name, int vb)
+{
+ if (device_fd) {}
+ if (device_name) {}
+ if (vb) {}
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ return ptp->dev_fd;
+}
+
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ if (vp) { }
+ return 0;
+}
+
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+ if (vb) {}
+ ptp->dev_fd = (dev_han < 0) ? -1 : dev_han;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->is_nvme = false;
+ return 0;
+}
diff --git a/lib/sg_pt_win32.c b/lib/sg_pt_win32.c
new file mode 100644
index 00000000..170e33c3
--- /dev/null
+++ b/lib/sg_pt_win32.c
@@ -0,0 +1,3155 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* sg_pt_win32 version 1.34 20210503 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_unaligned.h"
+#include "sg_pt.h"
+#include "sg_pt_win32.h"
+#include "sg_pt_nvme.h"
+#include "sg_pr2serr.h"
+
+
+/* Comment the following line out to use the pre-W10 NVMe pass-through */
+#define W10_NVME_NON_PASSTHRU 1
+
+#ifndef O_EXCL
+// #define O_EXCL 0x80 // cygwin ??
+// #define O_EXCL 0x80 // Linux
+#define O_EXCL 0x400 // mingw
+#warning "O_EXCL not defined"
+#endif
+
+#define SCSI_INQUIRY_OPC 0x12
+#define SCSI_REPORT_LUNS_OPC 0xa0
+#define SCSI_TEST_UNIT_READY_OPC 0x0
+#define SCSI_REQUEST_SENSE_OPC 0x3
+#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d
+#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c
+#define SCSI_MAINT_IN_OPC 0xa3
+#define SCSI_REP_SUP_OPCS_OPC 0xc
+#define SCSI_REP_SUP_TMFS_OPC 0xd
+#define SCSI_MODE_SENSE10_OPC 0x5a
+#define SCSI_MODE_SELECT10_OPC 0x55
+
+/* Additional Sense Code (ASC) */
+#define NO_ADDITIONAL_SENSE 0x0
+#define LOGICAL_UNIT_NOT_READY 0x4
+#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8
+#define UNRECOVERED_READ_ERR 0x11
+#define PARAMETER_LIST_LENGTH_ERR 0x1a
+#define INVALID_OPCODE 0x20
+#define LBA_OUT_OF_RANGE 0x21
+#define INVALID_FIELD_IN_CDB 0x24
+#define INVALID_FIELD_IN_PARAM_LIST 0x26
+#define UA_RESET_ASC 0x29
+#define UA_CHANGED_ASC 0x2a
+#define TARGET_CHANGED_ASC 0x3f
+#define LUNS_CHANGED_ASCQ 0x0e
+#define INSUFF_RES_ASC 0x55
+#define INSUFF_RES_ASCQ 0x3
+#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */
+#define POWER_ON_RESET_ASCQ 0x0
+#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */
+#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */
+#define CAPACITY_CHANGED_ASCQ 0x9
+#define SAVING_PARAMS_UNSUP 0x39
+#define TRANSPORT_PROBLEM 0x4b
+#define THRESHOLD_EXCEEDED 0x5d
+#define LOW_POWER_COND_ON 0x5e
+#define MISCOMPARE_VERIFY_ASC 0x1d
+#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */
+#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
+
+/* Use the Microsoft SCSI Pass Through (SPT) interface. It has two
+ * variants: "SPT" where data is double buffered; and "SPTD" where data
+ * pointers to the user space are passed to the OS. Only Windows
+ * 2000 and later (i.e. not 95,98 or ME).
+ * There is no ASPI interface which relies on a dll from adaptec.
+ * This code uses cygwin facilities and is built in a cygwin
+ * shell. It can be run in a normal DOS shell if the cygwin1.dll
+ * file is put in an appropriate place.
+ * This code can build in a MinGW environment.
+ *
+ * N.B. MSDN says that the "SPT" interface (i.e. double buffered)
+ * should be used for small amounts of data (it says "< 16 KB").
+ * The direct variant (i.e. IOCTL_SCSI_PASS_THROUGH_DIRECT) should
+ * be used for larger amounts of data but the buffer needs to be
+ * "cache aligned". Is that 16 byte alignment or greater?
+ *
+ * This code will default to indirect (i.e. double buffered) access
+ * unless the WIN32_SPT_DIRECT preprocessor constant is defined in
+ * config.h . In version 1.12 runtime selection of direct and indirect
+ * access was added; the default is still determined by the
+ * WIN32_SPT_DIRECT preprocessor constant.
+ */
+
+#define DEF_TIMEOUT 60 /* 60 seconds */
+#define MAX_OPEN_SIMULT 8
+#define WIN32_FDOFFSET 32
+
+union STORAGE_DEVICE_DESCRIPTOR_DATA {
+ STORAGE_DEVICE_DESCRIPTOR desc;
+ char raw[256];
+};
+
+union STORAGE_DEVICE_UID_DATA {
+ STORAGE_DEVICE_UNIQUE_IDENTIFIER desc;
+ char raw[1060];
+};
+
+
+struct sg_pt_handle {
+ bool in_use;
+ bool not_claimed;
+ bool checked_handle;
+ bool bus_type_failed;
+ bool is_nvme;
+ bool got_physical_drive;
+ HANDLE fh;
+ char adapter[32]; /* for example: '\\.\scsi3' */
+ int bus; /* a.k.a. PathId in MS docs */
+ int target;
+ int lun;
+ int scsi_pdt; /* Peripheral Device Type, PDT_ALL if not known */
+ // uint32_t nvme_nsid; /* how do we find this given file handle ?? */
+ int verbose; /* tunnel verbose through to scsi_pt_close_device */
+ char dname[20];
+ struct sg_sntl_dev_state_t dev_stat; // owner
+};
+
+/* Start zeroed but need to zeroed before use because could be re-use */
+static struct sg_pt_handle handle_arr[MAX_OPEN_SIMULT];
+
+struct sg_pt_win32_scsi {
+ bool is_nvme;
+ bool nvme_direct; /* false: our SNTL; true: received NVMe command */
+ bool mdxfer_out; /* direction of metadata xfer, true->data-out */
+ bool have_nvme_cmd;
+ bool is_read;
+ int sense_len;
+ int scsi_status;
+ int resid;
+ int sense_resid;
+ int in_err;
+ int os_err; /* pseudo unix error */
+ int transport_err; /* windows error number */
+ int dev_fd; /* -1 for no "file descriptor" given */
+ uint32_t nvme_nsid; /* 1 to 0xfffffffe are possibly valid, 0
+ * implies dev_fd is not a NVMe device
+ * (is_nvme=false) or has no storage (e.g.
+ * enclosure rather than disk) */
+ uint32_t nvme_result; /* DW0 from completion queue */
+ uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue,
+ * note: the DNR+More bit are not there.
+ * The whole 16 byte completion q entry is
+ * sent back as sense data */
+ uint32_t dxfer_len;
+ uint32_t mdxfer_len;
+ uint8_t * dxferp;
+ uint8_t * mdxferp; /* NVMe has metadata buffer */
+ uint8_t * sensep;
+ uint8_t * nvme_id_ctlp;
+ uint8_t * free_nvme_id_ctlp;
+ struct sg_sntl_dev_state_t * dev_statp; /* points to handle's dev_stat */
+ uint8_t nvme_cmd[64];
+ union {
+ SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb_d;
+ /* Last entry in structure so data buffer can be extended */
+ SCSI_PASS_THROUGH_WITH_BUFFERS swb_i;
+ };
+};
+
+/* embed pointer so can change on fly if (non-direct) data buffer
+ * is not big enough */
+struct sg_pt_base {
+ struct sg_pt_win32_scsi * implp;
+};
+
+#ifdef WIN32_SPT_DIRECT
+static int spt_direct = 1;
+#else
+static int spt_direct = 0;
+#endif
+
+static int nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb);
+
+
+/* Request SPT direct interface when state_direct is 1, state_direct set
+ * to 0 for the SPT indirect interface. */
+void
+scsi_pt_win32_direct(int state_direct)
+{
+ spt_direct = state_direct;
+}
+
+/* Returns current SPT interface state, 1 for direct, 0 for indirect */
+int
+scsi_pt_win32_spt_state(void)
+{
+ return spt_direct;
+}
+
+static const char *
+bus_type_str(int bt)
+{
+ switch (bt)
+ {
+ case BusTypeUnknown:
+ return "Unknown";
+ case BusTypeScsi:
+ return "Scsi";
+ case BusTypeAtapi:
+ return "Atapi";
+ case BusTypeAta:
+ return "Ata";
+ case BusType1394:
+ return "1394";
+ case BusTypeSsa:
+ return "Ssa";
+ case BusTypeFibre:
+ return "Fibre";
+ case BusTypeUsb:
+ return "Usb";
+ case BusTypeRAID:
+ return "RAID";
+ case BusTypeiScsi:
+ return "iScsi";
+ case BusTypeSas:
+ return "Sas";
+ case BusTypeSata:
+ return "Sata";
+ case BusTypeSd:
+ return "Sd";
+ case BusTypeMmc:
+ return "Mmc";
+ case BusTypeVirtual:
+ return "Virt";
+ case BusTypeFileBackedVirtual:
+ return "FBVir";
+#ifdef BusTypeSpaces
+ case BusTypeSpaces:
+#else
+ case 0x10:
+#endif
+ return "Spaces";
+#ifdef BusTypeNvme
+ case BusTypeNvme:
+#else
+ case 0x11:
+#endif
+ return "NVMe";
+#ifdef BusTypeSCM
+ case BusTypeSCM:
+#else
+ case 0x12:
+#endif
+ return "SCM";
+#ifdef BusTypeUfs
+ case BusTypeUfs:
+#else
+ case 0x13:
+#endif
+ return "Ufs";
+ case 0x14:
+ return "Max";
+ case 0x7f:
+ return "Max Reserved";
+ default:
+ return "_unknown";
+ }
+}
+
+static char *
+get_err_str(DWORD err, int max_b_len, char * b)
+{
+ LPVOID lpMsgBuf;
+ int k, num, ch;
+
+ memset(b, 0, max_b_len);
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+ num = lstrlen((LPCTSTR)lpMsgBuf);
+ if (num < 1)
+ return b;
+ num = (num < max_b_len) ? num : (max_b_len - 1);
+ for (k = 0; k < num; ++k) {
+ ch = *((LPCTSTR)lpMsgBuf + k);
+ if ((ch >= 0x0) && (ch < 0x7f))
+ b[k] = ch & 0x7f;
+ else
+ b[k] = '?';
+ }
+ return b;
+}
+
+/* Returns pointer to sg_pt_handle object given Unix like device_fd. If
+ * device_fd is invalid or not open returns NULL. If psp is non-NULL and
+ * NULL is returned then ENODEV is placed in psp->os_err. */
+static struct sg_pt_handle *
+get_open_pt_handle(struct sg_pt_win32_scsi * psp, int device_fd, bool vbb)
+{
+ int index = device_fd - WIN32_FDOFFSET;
+ struct sg_pt_handle * shp;
+
+ if ((index < 0) || (index >= WIN32_FDOFFSET)) {
+ if (vbb)
+ pr2ws("Bad file descriptor\n");
+ if (psp)
+ psp->os_err = EBADF;
+ return NULL;
+ }
+ shp = handle_arr + index;
+ if (! shp->in_use) {
+ if (vbb)
+ pr2ws("File descriptor closed??\n");
+ if (psp)
+ psp->os_err = ENODEV;
+ return NULL;
+ }
+ return shp;
+}
+
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int vb)
+{
+ int oflags = 0 /* O_NONBLOCK*/ ;
+
+ oflags |= (read_only ? 0 : 0); /* was ... ? O_RDONLY : O_RDWR) */
+ return scsi_pt_open_flags(device_name, oflags, vb);
+}
+
+/*
+ * Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed
+ * together. The 'flags' argument is ignored in Windows.
+ * Returns >= 0 if successful, otherwise returns negated errno.
+ * Optionally accept leading "\\.\". If given something of the form
+ * "SCSI<num>:<bus>,<target>,<lun>" where the values in angle brackets
+ * are integers, then will attempt to open "\\.\SCSI<num>:" and save the
+ * other three values for the DeviceIoControl call. The trailing ".<lun>"
+ * is optionally and if not given 0 is assumed. Since "PhysicalDrive"
+ * is a lot of keystrokes, "PD" is accepted and converted to the longer
+ * form.
+ */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int vb)
+{
+ bool got_scsi_name = false;
+ int len, k, adapter_num, bus, target, lun, off, index, num, pd_num;
+ int share_mode;
+ struct sg_pt_handle * shp;
+ char buff[8];
+
+ share_mode = (O_EXCL & flags) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE);
+ /* lock */
+ for (k = 0; k < MAX_OPEN_SIMULT; k++)
+ if (! handle_arr[k].in_use)
+ break;
+ if (k == MAX_OPEN_SIMULT) {
+ if (vb)
+ pr2ws("too many open handles (%d)\n", MAX_OPEN_SIMULT);
+ return -EMFILE;
+ } else {
+ /* clear any previous contents */
+ memset(handle_arr + k, 0, sizeof(struct sg_pt_handle));
+ handle_arr[k].in_use = true;
+ }
+ /* unlock */
+ index = k;
+ shp = handle_arr + index;
+#if (HAVE_NVME && (! IGNORE_NVME))
+ sntl_init_dev_stat(&shp->dev_stat);
+#endif
+ adapter_num = 0;
+ bus = 0; /* also known as 'PathId' in MS docs */
+ target = 0;
+ lun = 0;
+ len = (int)strlen(device_name);
+ k = (int)sizeof(shp->dname);
+ if (len < k)
+ strcpy(shp->dname, device_name);
+ else if (len == k)
+ memcpy(shp->dname, device_name, k - 1);
+ else /* trim on left */
+ memcpy(shp->dname, device_name + (len - k), k - 1);
+ shp->dname[k - 1] = '\0';
+ if ((len > 4) && (0 == strncmp("\\\\.\\", device_name, 4)))
+ off = 4;
+ else
+ off = 0;
+ if (len > (off + 2)) {
+ buff[0] = toupper((int)device_name[off + 0]);
+ buff[1] = toupper((int)device_name[off + 1]);
+ if (0 == strncmp("PD", buff, 2)) {
+ num = sscanf(device_name + off + 2, "%d", &pd_num);
+ if (1 == num)
+ shp->got_physical_drive = true;
+ }
+ if (! shp->got_physical_drive) {
+ buff[2] = toupper((int)device_name[off + 2]);
+ buff[3] = toupper((int)device_name[off + 3]);
+ if (0 == strncmp("SCSI", buff, 4)) {
+ num = sscanf(device_name + off + 4, "%d:%d,%d,%d",
+ &adapter_num, &bus, &target, &lun);
+ if (num < 3) {
+ if (vb)
+ pr2ws("expected format like: "
+ "'SCSI<port>:<bus>,<target>[,<lun>]'\n");
+ shp->in_use = false;
+ return -EINVAL;
+ }
+ got_scsi_name = true;
+ }
+ }
+ }
+ shp->bus = bus;
+ shp->target = target;
+ shp->lun = lun;
+ shp->scsi_pdt = PDT_ALL;
+ shp->verbose = vb;
+ memset(shp->adapter, 0, sizeof(shp->adapter));
+ memcpy(shp->adapter, "\\\\.\\", 4);
+ if (shp->got_physical_drive)
+ snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5,
+ "PhysicalDrive%d", pd_num);
+ else if (got_scsi_name)
+ snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5, "SCSI%d:",
+ adapter_num);
+ else
+ snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5, "%s",
+ device_name + off);
+ if (vb > 4)
+ pr2ws("%s: CreateFile('%s'), bus=%d, target=%d, lun=%d\n", __func__,
+ shp->adapter, bus, target, lun);
+#if 1
+ shp->fh = CreateFile(shp->adapter, GENERIC_READ | GENERIC_WRITE,
+ share_mode, NULL, OPEN_EXISTING, 0, NULL);
+#endif
+
+#if 0
+ shp->fh = CreateFileA(shp->adapter, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0);
+ // No GENERIC_READ/WRITE access required, works without admin rights (W10)
+ shp->fh = CreateFileA(shp->adapter, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, (HANDLE)0);
+#endif
+ if (shp->fh == INVALID_HANDLE_VALUE) {
+ if (vb) {
+ uint32_t err = (uint32_t)GetLastError();
+ char b[128];
+
+ pr2ws("%s: CreateFile error: %s [%u]\n", __func__,
+ get_err_str(err, sizeof(b), b), err);
+ }
+ shp->in_use = false;
+ return -ENODEV;
+ }
+ return index + WIN32_FDOFFSET;
+}
+
+/* Returns 0 if successful. If device_id seems wild returns -ENODEV,
+ * other errors return 0. If CloseHandle() fails and verbose > 0 then
+ * outputs warning with value from GetLastError(). The verbose value
+ * defaults to zero and is potentially set from the most recent call
+ * to scsi_pt_open_device() or do_scsi_pt(). */
+int
+scsi_pt_close_device(int device_fd)
+{
+ struct sg_pt_handle * shp = get_open_pt_handle(NULL, device_fd, false);
+
+ if (NULL == shp)
+ return -ENODEV;
+ if ((! CloseHandle(shp->fh)) && shp->verbose)
+ pr2ws("Windows CloseHandle error=%u\n", (unsigned int)GetLastError());
+ shp->bus = 0;
+ shp->target = 0;
+ shp->lun = 0;
+ memset(shp->adapter, 0, sizeof(shp->adapter));
+ shp->in_use = false;
+ shp->verbose = 0;
+ shp->dname[0] = '\0';
+ return 0;
+}
+
+/* Attempt to return device's SCSI peripheral device type (pdt), a number
+ * between 0 (disks) and 31 (not given) by calling IOCTL_SCSI_GET_INQUIRY_DATA
+ * on the adapter. Returns -EIO on error and -999 if not found. */
+static int
+get_scsi_pdt(struct sg_pt_handle *shp, int vb)
+{
+ const int alloc_sz = 8192;
+ int j;
+ int ret = -999;
+ BOOL ok;
+ ULONG dummy;
+ DWORD err;
+ BYTE wbus;
+ uint8_t * inqBuf;
+ uint8_t * free_inqBuf;
+ char b[128];
+
+ if (vb > 2)
+ pr2ws("%s: enter, adapter: %s\n", __func__, shp->adapter);
+ inqBuf = sg_memalign(alloc_sz, 0 /* page size */, &free_inqBuf, false);
+ if (NULL == inqBuf) {
+ pr2ws("%s: unable to allocate %d bytes\n", __func__, alloc_sz);
+ return -ENOMEM;
+ }
+ ok = DeviceIoControl(shp->fh, IOCTL_SCSI_GET_INQUIRY_DATA,
+ NULL, 0, inqBuf, alloc_sz, &dummy, NULL);
+ if (ok) {
+ PSCSI_ADAPTER_BUS_INFO ai;
+ PSCSI_BUS_DATA pbd;
+ PSCSI_INQUIRY_DATA pid;
+ int num_lus, off;
+
+ ai = (PSCSI_ADAPTER_BUS_INFO)inqBuf;
+ for (wbus = 0; wbus < ai->NumberOfBusses; ++wbus) {
+ pbd = ai->BusData + wbus;
+ num_lus = pbd->NumberOfLogicalUnits;
+ off = pbd->InquiryDataOffset;
+ for (j = 0; j < num_lus; ++j) {
+ if ((off < (int)sizeof(SCSI_ADAPTER_BUS_INFO)) ||
+ (off > (alloc_sz - (int)sizeof(SCSI_INQUIRY_DATA))))
+ break;
+ pid = (PSCSI_INQUIRY_DATA)(inqBuf + off);
+ if ((shp->bus == pid->PathId) &&
+ (shp->target == pid->TargetId) &&
+ (shp->lun == pid->Lun)) { /* got match */
+ shp->scsi_pdt = pid->InquiryData[0] & PDT_MASK;
+ shp->not_claimed = ! pid->DeviceClaimed;
+ shp->checked_handle = true;
+ shp->bus_type_failed = false;
+ if (vb > 3)
+ pr2ws("%s: found, scsi_pdt=%d, claimed=%d, "
+ "target=%d, lun=%d\n", __func__, shp->scsi_pdt,
+ pid->DeviceClaimed, shp->target, shp->lun);
+ ret = shp->scsi_pdt;
+ goto fini;
+ }
+ off = pid->NextInquiryDataOffset;
+ }
+ }
+ } else {
+ err = GetLastError();
+ if (vb > 1)
+ pr2ws("%s: IOCTL_SCSI_GET_INQUIRY_DATA failed err=%u\n\t%s",
+ shp->adapter, (unsigned int)err,
+ get_err_str(err, sizeof(b), b));
+ ret = -EIO;
+ }
+fini:
+ if (free_inqBuf)
+ free(free_inqBuf);
+ return ret; /* no match after checking all PathIds, Targets and LUs */
+}
+
+/* Returns 0 on success, negated errno if error */
+static int
+get_bus_type(struct sg_pt_handle *shp, const char *dname,
+ STORAGE_BUS_TYPE * btp, int vb)
+{
+ DWORD num_out, err;
+ STORAGE_BUS_TYPE bt;
+ union STORAGE_DEVICE_DESCRIPTOR_DATA sddd;
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty,
+ PropertyStandardQuery, {0} };
+ char b[256];
+
+ memset(&sddd, 0, sizeof(sddd));
+ if (! DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), &sddd, sizeof(sddd),
+ &num_out, NULL)) {
+ if (vb > 2) {
+ err = GetLastError();
+ pr2ws("%s IOCTL_STORAGE_QUERY_PROPERTY(Devprop) failed, "
+ "Error: %s [%u]\n", dname, get_err_str(err, sizeof(b), b),
+ (uint32_t)err);
+ }
+ shp->bus_type_failed = true;
+ return -EIO;
+ }
+ bt = sddd.desc.BusType;
+ if (vb > 2) {
+ pr2ws("%s: Bus type: %s\n", __func__, bus_type_str((int)bt));
+ if (vb > 3) {
+ pr2ws("Storage Device Descriptor Data:\n");
+ hex2stderr((const uint8_t *)&sddd, num_out, 0);
+ }
+ }
+ if (shp) {
+ shp->checked_handle = true;
+ shp->bus_type_failed = false;
+ shp->is_nvme = (BusTypeNvme == bt);
+ }
+ if (btp)
+ *btp = bt;
+ return 0;
+}
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int device_fd, const char * device_name, int vb)
+{
+ int res;
+ STORAGE_BUS_TYPE bt;
+ const char * dnp = device_name;
+ struct sg_pt_handle * shp;
+
+ if (vb > 3)
+ pr2ws("%s: device_name: %s\n", __func__, dnp);
+ shp = get_open_pt_handle(NULL, device_fd, vb > 1);
+ if (NULL == shp) {
+ pr2ws("%s: device_fd (%s) bad or not in_use ??\n", __func__,
+ dnp ? dnp : "");
+ return -ENODEV;
+ }
+ if (shp->bus_type_failed) {
+ if (vb > 2)
+ pr2ws("%s: skip because get_bus_type() has failed\n", __func__);
+ return 0;
+ }
+ dnp = dnp ? dnp : shp->dname;
+ res = get_bus_type(shp, dnp, &bt, vb);
+ if (res < 0) {
+ if (! shp->got_physical_drive) {
+ res = get_scsi_pdt(shp, vb);
+ if (res >= 0)
+ return 1;
+ }
+ return res;
+ }
+ return (BusTypeNvme == bt) ? 3 : 1;
+ /* NVMe "char" ?? device, could be enclosure: 3 */
+ /* SCSI generic pass-though device: 1 */
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+static bool checked_ev_dsense = false;
+static bool ev_dsense = false;
+#endif
+
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int dev_fd, int vb)
+{
+ int res;
+ struct sg_pt_win32_scsi * psp;
+ struct sg_pt_base * vp = NULL;
+ struct sg_pt_handle * shp = NULL;
+
+ if (dev_fd >= 0) {
+ shp = get_open_pt_handle(NULL, dev_fd, vb > 1);
+ if (NULL == shp) {
+ if (vb)
+ pr2ws("%s: dev_fd is not open\n", __func__);
+ return NULL;
+ }
+ if (! (shp->bus_type_failed || shp->checked_handle)) {
+ res = get_bus_type(shp, shp->dname, NULL, vb);
+ if (res < 0) {
+ if (! shp->got_physical_drive)
+ res = get_scsi_pdt(shp, vb);
+ if ((res < 0) && (vb > 1))
+ pr2ws("%s: get_bus_type() errno=%d, continue\n", __func__,
+ -res);
+ }
+ }
+ }
+ psp = (struct sg_pt_win32_scsi *)calloc(sizeof(struct sg_pt_win32_scsi),
+ 1);
+ if (psp) {
+ psp->dev_fd = (dev_fd < 0) ? -1 : dev_fd;
+ if (shp) {
+ psp->is_nvme = shp->is_nvme;
+ psp->dev_statp = &shp->dev_stat;
+#if (HAVE_NVME && (! IGNORE_NVME))
+ sntl_init_dev_stat(psp->dev_statp);
+ if (! checked_ev_dsense) {
+ ev_dsense = sg_get_initial_dsense();
+ checked_ev_dsense = true;
+ }
+ shp->dev_stat.scsi_dsense = ev_dsense;
+#endif
+ }
+ if (psp->is_nvme) {
+ ; /* should be 'psp->nvme_nsid = shp->nvme_nsid' */
+ } else if (spt_direct) {
+ psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_d.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT;
+ } else {
+ psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_i.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT;
+ }
+ vp = (struct sg_pt_base *)malloc(sizeof(struct sg_pt_win32_scsi *));
+ /* yes, allocating the size of a pointer (4 or 8 bytes) */
+ if (vp)
+ vp->implp = psp;
+ else
+ free(psp);
+ }
+ if ((NULL == vp) && vb)
+ pr2ws("%s: about to return NULL, space problem\n", __func__);
+ return vp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj(void)
+{
+ return construct_scsi_pt_obj_with_fd(-1, 0);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ if (vp) {
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (psp) {
+ free(psp);
+ }
+ free(vp);
+ }
+}
+
+/* Forget any previous dev_han and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_han should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb)
+{
+ int res;
+ struct sg_pt_win32_scsi * psp;
+
+ if (NULL == vp) {
+ if (vb)
+ pr2ws(">>>> %s: pointer to object is NULL\n", __func__);
+ return EINVAL;
+ }
+ if ((psp = vp->implp)) {
+ struct sg_pt_handle * shp;
+
+ if (dev_han < 0) {
+ psp->dev_fd = -1;
+ psp->is_nvme = false;
+ psp->nvme_nsid = 0;
+ return 0;
+ }
+ shp = get_open_pt_handle(psp, dev_han, vb > 1);
+ if (NULL == shp) {
+ if (vb)
+ pr2ws("%s: dev_han (%d) is invalid\n", __func__, dev_han);
+ psp->os_err = EINVAL;
+ return psp->os_err;
+ }
+ psp->os_err = 0;
+ psp->transport_err = 0;
+ psp->in_err = 0;
+ psp->scsi_status = 0;
+ psp->dev_fd = dev_han;
+ if (! (shp->bus_type_failed || shp->checked_handle)) {
+ res = get_bus_type(shp, shp->dname, NULL, vb);
+ if (res < 0) {
+ res = get_scsi_pdt(shp, vb);
+ if (res >= 0) /* clears shp->bus_type_failed on success */
+ psp->os_err = 0;
+ }
+ if ((res < 0) && (vb > 2))
+ pr2ws("%s: get_bus_type() errno=%d\n", __func__, -res);
+ }
+ if (shp->bus_type_failed)
+ psp->os_err = EIO;
+ if (psp->os_err)
+ return psp->os_err;
+ psp->is_nvme = shp->is_nvme;
+ psp->nvme_nsid = 0; /* should be 'psp->nvme_nsid = shp->nvme_nsid' */
+ psp->dev_statp = &shp->dev_stat;
+ }
+ return 0;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp;
+
+ if (vp) {
+ psp = vp->implp;
+ return psp ? psp->dev_fd : -1;
+ }
+ return -1;
+}
+
+/* Keep state information such as dev_fd and nvme_nsid */
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ bool is_nvme;
+ int dev_fd;
+ uint32_t nvme_nsid;
+ struct sg_pt_win32_scsi * psp = vp->implp;
+ struct sg_sntl_dev_state_t * dsp;
+
+ if (psp) {
+ dev_fd = psp->dev_fd;
+ is_nvme = psp->is_nvme;
+ nvme_nsid = psp->nvme_nsid;
+ dsp = psp->dev_statp;
+ memset(psp, 0, sizeof(struct sg_pt_win32_scsi));
+ if (spt_direct) {
+ psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_d.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT;
+ } else {
+ psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_i.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT;
+ }
+ psp->dev_fd = dev_fd;
+ psp->is_nvme = is_nvme;
+ psp->nvme_nsid = nvme_nsid;
+ psp->dev_statp = dsp;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (NULL == psp)
+ return;
+ psp->in_err = 0;
+ psp->os_err = 0;
+ psp->transport_err = 0;
+ psp->scsi_status = 0;
+ if (spt_direct) {
+ psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_d.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT;
+ } else {
+ psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+ psp->swb_i.spt.SenseInfoOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+ psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT;
+ }
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ bool scsi_cdb = sg_is_scsi_cdb(cdb, cdb_len);
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (! scsi_cdb) {
+ psp->have_nvme_cmd = true;
+ memcpy(psp->nvme_cmd, cdb, cdb_len);
+ } else if (spt_direct) {
+ if (cdb_len > (int)sizeof(psp->swb_d.spt.Cdb)) {
+ ++psp->in_err;
+ return;
+ }
+ memcpy(psp->swb_d.spt.Cdb, cdb, cdb_len);
+ psp->swb_d.spt.CdbLength = cdb_len;
+ } else {
+ if (cdb_len > (int)sizeof(psp->swb_i.spt.Cdb)) {
+ ++psp->in_err;
+ return;
+ }
+ memcpy(psp->swb_i.spt.Cdb, cdb, cdb_len);
+ psp->swb_i.spt.CdbLength = cdb_len;
+ }
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return spt_direct ? psp->swb_d.spt.CdbLength : psp->swb_i.spt.CdbLength;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (spt_direct) {
+ if (psp->swb_d.spt.CdbLength > 0)
+ return (uint8_t *)(psp->swb_d.spt.Cdb);
+ else
+ return NULL;
+ } else {
+ if (psp->swb_i.spt.CdbLength > 0)
+ return (uint8_t *)(psp->swb_i.spt.Cdb);
+ else
+ return NULL;
+ }
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, int sense_len)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (sense && (sense_len > 0))
+ memset(sense, 0, sense_len);
+ psp->sensep = sense;
+ psp->sense_len = sense_len;
+}
+
+/* from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (psp->dxferp)
+ ++psp->in_err;
+ if (dxfer_len > 0) {
+ psp->dxferp = dxferp;
+ psp->dxfer_len = (uint32_t)dxfer_len;
+ psp->is_read = true;
+ if (spt_direct)
+ psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ else
+ psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_IN;
+ }
+}
+
+/* to device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_len)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (psp->dxferp)
+ ++psp->in_err;
+ if (dxfer_len > 0) {
+ psp->dxferp = (uint8_t *)dxferp;
+ psp->dxfer_len = (uint32_t)dxfer_len;
+ if (spt_direct)
+ psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ else
+ psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_OUT;
+ }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp,
+ uint32_t mdxfer_len, bool out_true)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (psp->mdxferp)
+ ++psp->in_err;
+ if (mdxfer_len > 0) {
+ psp->mdxferp = mdxferp;
+ psp->mdxfer_len = mdxfer_len;
+ psp->mdxfer_out = out_true;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp __attribute__ ((unused)),
+ int pack_id __attribute__ ((unused)))
+{
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag __attribute__ ((unused)))
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ ++psp->in_err;
+}
+
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp,
+ int tmf_code __attribute__ ((unused)))
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ ++psp->in_err;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp,
+ int attrib __attribute__ ((unused)),
+ int priority __attribute__ ((unused)))
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ ++psp->in_err;
+}
+
+void
+set_scsi_pt_flags(struct sg_pt_base * objp, int flags)
+{
+ /* do nothing, suppress warnings */
+ objp = objp;
+ flags = flags;
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers)
+ * using direct interface. Clears os_err field prior to active call (whose
+ * result may set it again). */
+static int
+scsi_pt_direct(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ BOOL status;
+ DWORD returned;
+
+ psp->os_err = 0;
+ if (0 == psp->swb_d.spt.CdbLength) {
+ if (vb)
+ pr2ws("%s: No command (cdb) given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ psp->swb_d.spt.Length = sizeof (SCSI_PASS_THROUGH_DIRECT);
+ psp->swb_d.spt.PathId = shp->bus;
+ psp->swb_d.spt.TargetId = shp->target;
+ psp->swb_d.spt.Lun = shp->lun;
+ psp->swb_d.spt.TimeOutValue = time_secs;
+ psp->swb_d.spt.DataTransferLength = psp->dxfer_len;
+ if (vb > 4) {
+ pr2ws(" spt_direct, adapter: %s Length=%d ScsiStatus=%d PathId=%d "
+ "TargetId=%d Lun=%d\n", shp->adapter,
+ (int)psp->swb_d.spt.Length, (int)psp->swb_d.spt.ScsiStatus,
+ (int)psp->swb_d.spt.PathId, (int)psp->swb_d.spt.TargetId,
+ (int)psp->swb_d.spt.Lun);
+ pr2ws(" CdbLength=%d SenseInfoLength=%d DataIn=%d "
+ "DataTransferLength=%u\n",
+ (int)psp->swb_d.spt.CdbLength,
+ (int)psp->swb_d.spt.SenseInfoLength,
+ (int)psp->swb_d.spt.DataIn,
+ (unsigned int)psp->swb_d.spt.DataTransferLength);
+ pr2ws(" TimeOutValue=%u SenseInfoOffset=%u\n",
+ (unsigned int)psp->swb_d.spt.TimeOutValue,
+ (unsigned int)psp->swb_d.spt.SenseInfoOffset);
+ }
+ psp->swb_d.spt.DataBuffer = psp->dxferp;
+ status = DeviceIoControl(shp->fh, IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &psp->swb_d,
+ sizeof(psp->swb_d),
+ &psp->swb_d,
+ sizeof(psp->swb_d),
+ &returned,
+ NULL);
+ if (! status) {
+ unsigned int u;
+
+ u = (unsigned int)GetLastError();
+ if (vb) {
+ char b[128];
+
+ pr2ws("%s: DeviceIoControl: %s [%u]\n", __func__,
+ get_err_str(u, sizeof(b), b), u);
+ }
+ psp->transport_err = (int)u;
+ psp->os_err = EIO;
+ return 0; /* let app find transport error */
+ }
+
+ psp->scsi_status = psp->swb_d.spt.ScsiStatus;
+ if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status))
+ memcpy(psp->sensep, psp->swb_d.ucSenseBuf, psp->sense_len);
+ else
+ psp->sense_len = 0;
+ psp->sense_resid = 0;
+ if ((psp->dxfer_len > 0) && (psp->swb_d.spt.DataTransferLength > 0))
+ psp->resid = psp->dxfer_len - psp->swb_d.spt.DataTransferLength;
+ else
+ psp->resid = 0;
+
+ return 0;
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers) using
+ * indirect interface. Clears os_err field prior to active call (whose
+ * result may set it again). */
+static int
+scsi_pt_indirect(struct sg_pt_base * vp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ BOOL status;
+ DWORD returned;
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ psp->os_err = 0;
+ if (0 == psp->swb_i.spt.CdbLength) {
+ if (vb)
+ pr2ws("%s: No command (cdb) given\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (psp->dxfer_len > (int)sizeof(psp->swb_i.ucDataBuf)) {
+ int extra = psp->dxfer_len - (int)sizeof(psp->swb_i.ucDataBuf);
+ struct sg_pt_win32_scsi * epsp;
+
+ if (vb > 4)
+ pr2ws("spt_indirect: dxfer_len (%d) too large for initial data\n"
+ " buffer (%d bytes), try enlarging\n", psp->dxfer_len,
+ (int)sizeof(psp->swb_i.ucDataBuf));
+ epsp = (struct sg_pt_win32_scsi *)
+ calloc(sizeof(struct sg_pt_win32_scsi) + extra, 1);
+ if (NULL == epsp) {
+ pr2ws("%s: failed to enlarge data buffer to %d bytes\n", __func__,
+ psp->dxfer_len);
+ psp->os_err = ENOMEM;
+ return -psp->os_err;
+ }
+ memcpy(epsp, psp, sizeof(struct sg_pt_win32_scsi));
+ free(psp);
+ vp->implp = epsp;
+ psp = epsp;
+ }
+ psp->swb_i.spt.Length = sizeof (SCSI_PASS_THROUGH);
+ psp->swb_i.spt.DataBufferOffset =
+ offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
+ psp->swb_i.spt.PathId = shp->bus;
+ psp->swb_i.spt.TargetId = shp->target;
+ psp->swb_i.spt.Lun = shp->lun;
+ psp->swb_i.spt.TimeOutValue = time_secs;
+ psp->swb_i.spt.DataTransferLength = psp->dxfer_len;
+ if (vb > 4) {
+ pr2ws(" spt_indirect, adapter: %s Length=%d ScsiStatus=%d PathId=%d "
+ "TargetId=%d Lun=%d\n", shp->adapter,
+ (int)psp->swb_i.spt.Length, (int)psp->swb_i.spt.ScsiStatus,
+ (int)psp->swb_i.spt.PathId, (int)psp->swb_i.spt.TargetId,
+ (int)psp->swb_i.spt.Lun);
+ pr2ws(" CdbLength=%d SenseInfoLength=%d DataIn=%d "
+ "DataTransferLength=%u\n",
+ (int)psp->swb_i.spt.CdbLength,
+ (int)psp->swb_i.spt.SenseInfoLength,
+ (int)psp->swb_i.spt.DataIn,
+ (unsigned int)psp->swb_i.spt.DataTransferLength);
+ pr2ws(" TimeOutValue=%u DataBufferOffset=%u "
+ "SenseInfoOffset=%u\n",
+ (unsigned int)psp->swb_i.spt.TimeOutValue,
+ (unsigned int)psp->swb_i.spt.DataBufferOffset,
+ (unsigned int)psp->swb_i.spt.SenseInfoOffset);
+ }
+ if ((psp->dxfer_len > 0) &&
+ (SCSI_IOCTL_DATA_OUT == psp->swb_i.spt.DataIn))
+ memcpy(psp->swb_i.ucDataBuf, psp->dxferp, psp->dxfer_len);
+ status = DeviceIoControl(shp->fh, IOCTL_SCSI_PASS_THROUGH,
+ &psp->swb_i,
+ sizeof(psp->swb_i),
+ &psp->swb_i,
+ sizeof(psp->swb_i),
+ &returned,
+ NULL);
+ if (! status) {
+ uint32_t u = (uint32_t)GetLastError();
+
+ if (vb) {
+ char b[128];
+
+ pr2ws("%s: DeviceIoControl: %s [%u]\n", __func__,
+ get_err_str(u, sizeof(b), b), u);
+ }
+ psp->transport_err = (int)u;
+ psp->os_err = EIO;
+ return 0; /* let app find transport error */
+ }
+ if ((psp->dxfer_len > 0) && (SCSI_IOCTL_DATA_IN == psp->swb_i.spt.DataIn))
+ memcpy(psp->dxferp, psp->swb_i.ucDataBuf, psp->dxfer_len);
+
+ psp->scsi_status = psp->swb_i.spt.ScsiStatus;
+ if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status))
+ memcpy(psp->sensep, psp->swb_i.ucSenseBuf, psp->sense_len);
+ else
+ psp->sense_len = 0;
+ psp->sense_resid = 0;
+ if ((psp->dxfer_len > 0) && (psp->swb_i.spt.DataTransferLength > 0))
+ psp->resid = psp->dxfer_len - psp->swb_i.spt.DataTransferLength;
+ else
+ psp->resid = 0;
+
+ return 0;
+}
+
+/* Executes SCSI or NVME command (or at least forwards it to lower layers).
+ * Clears os_err field prior to active call (whose result may set it
+ * again). Returns 0 on success, positive SCSI_PT_DO_* errors for syntax
+ * like errors and negated errnos for OS errors. For Windows its errors
+ * are placed in psp->transport_err and a errno is simulated. */
+int
+do_scsi_pt(struct sg_pt_base * vp, int dev_fd, int time_secs, int vb)
+{
+ int res;
+ struct sg_pt_win32_scsi * psp = vp->implp;
+ struct sg_pt_handle * shp;
+
+ if (! (vp && ((psp = vp->implp)))) {
+ if (vb)
+ pr2ws("%s: NULL 1st argument to this function\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ psp->os_err = 0;
+ if (dev_fd >= 0) {
+ if ((psp->dev_fd >= 0) && (dev_fd != psp->dev_fd)) {
+ if (vb)
+ pr2ws("%s: file descriptor given to create() and here "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ psp->dev_fd = dev_fd;
+ } else if (psp->dev_fd < 0) { /* so no dev_fd in ctor */
+ if (vb)
+ pr2ws("%s: missing device file descriptor\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ } else
+ dev_fd = psp->dev_fd;
+ shp = get_open_pt_handle(psp, dev_fd, vb > 3);
+ if (NULL == shp)
+ return -psp->os_err;
+
+ if (! (shp->bus_type_failed || shp->checked_handle)) {
+ res = get_bus_type(shp, shp->dname, NULL, vb);
+ if (res < 0) {
+ res = get_scsi_pdt(shp, vb);
+ if (res >= 0) /* clears shp->bus_type_failed on success */
+ psp->os_err = 0;
+ }
+ if ((res < 0) && (vb > 2))
+ pr2ws("%s: get_bus_type() errno=%d\n", __func__, -res);
+ }
+ if (shp->bus_type_failed)
+ psp->os_err = EIO;
+ if (psp->os_err)
+ return -psp->os_err;
+ psp->is_nvme = shp->is_nvme;
+ psp->dev_statp = &shp->dev_stat;
+
+ if (psp->is_nvme)
+ return nvme_pt(psp, shp, time_secs, vb);
+ else if (spt_direct)
+ return scsi_pt_direct(psp, shp, time_secs, vb);
+ else
+ return scsi_pt_indirect(vp, shp, time_secs, vb);
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (psp->transport_err) /* give transport error highest priority */
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if (psp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) ||
+ (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status))
+ return SCSI_PT_RESULT_SENSE;
+ else if (psp->scsi_status)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp->resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (req_dinp) {
+ if (psp->is_read && (psp->dxfer_len > 0))
+ *req_dinp = psp->dxfer_len;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if ((! psp->is_read) && (psp->dxfer_len > 0))
+ *req_doutp = psp->dxfer_len;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (act_dinp) {
+ if (psp->is_read && (psp->dxfer_len > 0))
+ *act_dinp = psp->dxfer_len - psp->resid;
+ else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if ((! psp->is_read) && (psp->dxfer_len > 0))
+ *act_doutp = psp->dxfer_len - psp->resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (NULL == psp)
+ return 0;
+ return psp->nvme_direct ? (int)psp->nvme_status : psp->scsi_status;
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ if (NULL == psp)
+ return 0;
+ return psp->nvme_direct ? psp->nvme_result : (uint32_t)psp->scsi_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+ int len;
+
+ len = psp->sense_len - psp->sense_resid;
+ return (len > 0) ? len : 0;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp->sensep;
+}
+
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ // const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return -1;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp __attribute__ ((unused)))
+{
+ return 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp->transport_err;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ struct sg_pt_win32_scsi * psp = vp->implp;
+
+ psp->transport_err = err;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp->os_err;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp ? psp->is_nvme : false;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+
+ return psp->nvme_nsid;
+}
+
+/* Use the transport_err for Windows errors. */
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ struct sg_pt_win32_scsi * psp = (struct sg_pt_win32_scsi *)vp->implp;
+
+ if ((max_b_len < 2) || (NULL == psp) || (NULL == b)) {
+ if (b && (max_b_len > 0))
+ b[0] = '\0';
+ return b;
+ }
+ return get_err_str(psp->transport_err, max_b_len, b);
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_win32_scsi * psp = vp->implp;
+ const char * cp;
+
+ cp = safe_strerror(psp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+static void
+mk_sense_asc_ascq(struct sg_pt_win32_scsi * psp, int sk, int asc, int ascq,
+ int vb)
+{
+ bool dsense = psp->dev_statp->scsi_dsense;
+ int slen = psp->sense_len;
+ int n;
+ uint8_t * sbp = (uint8_t *)psp->sensep;
+
+ psp->scsi_status = SAM_STAT_CHECK_CONDITION;
+ if ((slen < 8) || ((! dsense) && (slen < 14))) {
+ if (vb)
+ pr2ws("%s: sense_len=%d too short, want 14 or more\n",
+ __func__, slen);
+ return;
+ }
+ if (dsense)
+ n = (slen > 32) ? 32 : slen;
+ else
+ n = (slen < 18) ? slen : 18;
+ psp->sense_resid = (slen > n) ? (slen - n) : 0;
+ memset(sbp, 0, slen);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk,
+ asc, ascq);
+}
+
+static void
+mk_sense_from_nvme_status(struct sg_pt_win32_scsi * psp, int vb)
+{
+ bool ok;
+ bool dsense = psp->dev_statp->scsi_dsense;
+ int n;
+ int slen = psp->sense_len;
+ uint8_t sstatus, sk, asc, ascq;
+ uint8_t * sbp = (uint8_t *)psp->sensep;
+
+ ok = sg_nvme_status2scsi(psp->nvme_status, &sstatus, &sk, &asc, &ascq);
+ if (! ok) { /* can't find a mapping to a SCSI error, so ... */
+ sstatus = SAM_STAT_CHECK_CONDITION;
+ sk = SPC_SK_ILLEGAL_REQUEST;
+ asc = 0xb;
+ ascq = 0x0; /* asc: "WARNING" purposely vague */
+ }
+
+ psp->scsi_status = sstatus;
+ if ((slen < 8) || ((! dsense) && (slen < 14))) {
+ if (vb)
+ pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__,
+ slen);
+ return;
+ }
+ if (dsense)
+ n = (slen > 32) ? 32 : slen;
+ else
+ n = (slen < 18) ? slen : 18;
+ psp->sense_resid = (slen > n) ? slen - n : 0;
+ memset(sbp, 0, slen);
+ sg_build_sense_buffer(dsense, sbp, sk, asc, ascq);
+ if (dsense && (psp->nvme_status > 0))
+ sg_nvme_desc2sense(sbp, false /* dnr */, false /* more */,
+ psp->nvme_status);
+ if (vb > 3)
+ pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n",
+ __func__, sstatus, sk, asc, ascq);
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_invalid_fld(struct sg_pt_win32_scsi * psp, bool in_cdb, int in_byte,
+ int in_bit, int vb)
+{
+ bool dsense = psp->dev_statp->scsi_dsense;
+ int sl, asc, n;
+ int slen = psp->sense_len;
+ uint8_t * sbp = (uint8_t *)psp->sensep;
+ uint8_t sks[4];
+
+ psp->scsi_status = SAM_STAT_CHECK_CONDITION;
+ asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST;
+ if ((slen < 8) || ((! dsense) && (slen < 14))) {
+ if (vb)
+ pr2ws("%s: max_response_len=%d too short, want 14 or more\n",
+ __func__, slen);
+ return;
+ }
+ if (dsense)
+ n = (slen > 32) ? 32 : slen;
+ else
+ n = (slen < 18) ? slen : 18;
+ psp->sense_resid = (slen > n) ? (slen - n) : 0;
+ memset(sbp, 0, slen);
+ sg_build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0);
+ memset(sks, 0, sizeof(sks));
+ sks[0] = 0x80;
+ if (in_cdb)
+ sks[0] |= 0x40;
+ if (in_bit >= 0) {
+ sks[0] |= 0x8;
+ sks[0] |= (0x7 & in_bit);
+ }
+ sg_put_unaligned_be16(in_byte, sks + 1);
+ if (dsense) {
+ sl = sbp[7] + 8;
+ sbp[7] = sl;
+ sbp[sl] = 0x2;
+ sbp[sl + 1] = 0x6;
+ memcpy(sbp + sl + 4, sks, 3);
+ } else
+ memcpy(sbp + 15, sks, 3);
+ if (vb > 3)
+ pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n",
+ __func__, asc, in_cdb ? 'C' : 'D', in_byte,
+ ((in_bit > 0) ? (0x7 & in_bit) : 0));
+}
+
+#if W10_NVME_NON_PASSTHRU /* W10 and later, no real pass-through ?? */
+
+#ifndef NVME_MAX_LOG_SIZE
+#define NVME_MAX_LOG_SIZE 4096
+#endif
+
+static int
+nvme_identify(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb)
+{
+ bool id_ctrl;
+ int res = 0;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint32_t cdw10, nsid, n;
+ const uint8_t * bp;
+ BOOL result;
+ PVOID buffer = NULL;
+ uint8_t * free_buffer = NULL;
+ ULONG bufferLength = 0;
+ ULONG returnedLength = 0;
+ STORAGE_PROPERTY_QUERY * query = NULL;
+ STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL;
+ STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL;
+
+ nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID);
+ cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10);
+ id_ctrl = (0x1 == cdw10);
+ n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen;
+ bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) +
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n;
+ buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false);
+ if (buffer == NULL) {
+ res = sg_convert_errno(ENOMEM);
+ if (vb > 1)
+ pr2ws("%s: unable to allocate memory\n", __func__);
+ psp->os_err = res;
+ return -res;
+ }
+ query = (STORAGE_PROPERTY_QUERY *)buffer;
+
+ query->PropertyId = id_ctrl ? StorageAdapterProtocolSpecificProperty :
+ StorageDeviceProtocolSpecificProperty;
+ query->QueryType = PropertyStandardQuery;
+ protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer;
+ protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *)
+ query->AdditionalParameters;
+
+ protocolData->ProtocolType = ProtocolTypeNvme;
+ protocolData->DataType = NVMeDataTypeIdentify;
+ protocolData->ProtocolDataRequestValue = cdw10;
+ if (! id_ctrl)
+ protocolData->ProtocolDataRequestSubValue = nsid;
+ protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
+ protocolData->ProtocolDataLength = dlen;
+
+ result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY,
+ buffer, bufferLength, buffer, bufferLength,
+ &returnedLength, (OVERLAPPED*)0);
+ if ((! result) || (0 == returnedLength)) {
+ n = (uint32_t)GetLastError();
+ psp->transport_err = n;
+ psp->os_err = EIO; /* simulate Unix error, */
+ if (vb > 2) {
+ char b[128];
+
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_%s) failed: %s "
+ "[%u]\n", __func__, (id_ctrl ? "ctrl" : "ns"),
+ get_err_str(n, sizeof(b), b), n);
+ }
+ res = -psp->os_err;
+ goto err_out;
+ }
+ if (dlen > 0) {
+ protocolData = &protocolDataDescr->ProtocolSpecificData;
+ bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset;
+ memcpy(dp, bp, dlen);
+ if (0 == psp->nvme_nsid) {
+ uint32_t nn = sg_get_unaligned_le32(bp + 516);
+
+ if (1 == nn) /* if physical drive has only 1 namespace */
+ psp->nvme_nsid = 1; /* then its nsid must be 1 */
+ /* N.B. Need better get_nsid_from _handle technique when 2 or
+ * more namespaces. Suggestions? */
+ }
+ }
+ psp->nvme_status = 0;
+ psp->nvme_result =
+ protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData;
+ if (vb > 3)
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, "
+ "returnedLength=%u\n", __func__, (uint32_t)returnedLength);
+ res = 0;
+err_out:
+ if (free_buffer)
+ free(free_buffer);
+ return res;
+}
+
+static int
+nvme_get_features(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb)
+{
+ int res = 0;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint32_t cdw10, nsid, n;
+ const uint8_t * bp;
+ BOOL result;
+ PVOID buffer = NULL;
+ uint8_t * free_buffer = NULL;
+ ULONG bufferLength = 0;
+ ULONG returnedLength = 0;
+ STORAGE_PROPERTY_QUERY * query = NULL;
+ STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL;
+ STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL;
+
+ nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID);
+ cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10);
+ n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen;
+ bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) +
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n;
+ buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false);
+ if (buffer == NULL) {
+ res = sg_convert_errno(ENOMEM);
+ if (vb > 1)
+ pr2ws("%s: unable to allocate memory\n", __func__);
+ psp->os_err = res;
+ return -res;
+ }
+ query = (STORAGE_PROPERTY_QUERY *)buffer;
+
+ query->PropertyId = StorageDeviceProtocolSpecificProperty;
+ query->QueryType = PropertyStandardQuery;
+ protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer;
+ protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *)
+ query->AdditionalParameters;
+
+ protocolData->ProtocolType = ProtocolTypeNvme;
+ protocolData->DataType = NVMeDataTypeFeature; /* Get Features */
+ protocolData->ProtocolDataRequestValue = cdw10;
+ protocolData->ProtocolDataRequestSubValue = nsid;
+ protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
+ protocolData->ProtocolDataLength = dlen;
+
+ result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY,
+ buffer, bufferLength, buffer, bufferLength,
+ &returnedLength, (OVERLAPPED*)0);
+ if ((! result) || (0 == returnedLength)) {
+ n = (uint32_t)GetLastError();
+ psp->transport_err = n;
+ psp->os_err = EIO; /* simulate Unix error, */
+ if (vb > 2) {
+ char b[128];
+
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) failed: %s "
+ "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n);
+ }
+ res = -psp->os_err;
+ goto err_out;
+ }
+ if (dlen > 0) {
+ protocolData = &protocolDataDescr->ProtocolSpecificData;
+ bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset;
+ memcpy(dp, bp, dlen);
+ }
+ psp->nvme_status = 0;
+ psp->nvme_result =
+ protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData;
+ if (vb > 3)
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, "
+ "returnedLength=%u\n", __func__, (uint32_t)returnedLength);
+ res = 0;
+err_out:
+ if (free_buffer)
+ free(free_buffer);
+ return res;
+}
+
+static int
+nvme_get_log_page(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb)
+{
+ int res = 0;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint32_t cdw10, nsid, n;
+ const uint8_t * bp;
+ BOOL result;
+ PVOID buffer = NULL;
+ uint8_t * free_buffer = NULL;
+ ULONG bufferLength = 0;
+ ULONG returnedLength = 0;
+ STORAGE_PROPERTY_QUERY * query = NULL;
+ STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL;
+ STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL;
+
+ nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID);
+ cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10);
+ n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen;
+ bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) +
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n;
+ buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false);
+ if (buffer == NULL) {
+ res = sg_convert_errno(ENOMEM);
+ if (vb > 1)
+ pr2ws("%s: unable to allocate memory\n", __func__);
+ psp->os_err = res;
+ return -res;
+ }
+ query = (STORAGE_PROPERTY_QUERY *)buffer;
+
+ query->PropertyId = StorageDeviceProtocolSpecificProperty;
+ query->QueryType = PropertyStandardQuery;
+ protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer;
+ protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *)
+ query->AdditionalParameters;
+
+ protocolData->ProtocolType = ProtocolTypeNvme;
+ protocolData->DataType = NVMeDataTypeLogPage; /* Get Log Page */
+ protocolData->ProtocolDataRequestValue = cdw10;
+ protocolData->ProtocolDataRequestSubValue = nsid;
+ protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
+ protocolData->ProtocolDataLength = dlen;
+
+ result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY,
+ buffer, bufferLength, buffer, bufferLength,
+ &returnedLength, (OVERLAPPED*)0);
+ if ((! result) || (0 == returnedLength)) {
+ n = (uint32_t)GetLastError();
+ psp->transport_err = n;
+ psp->os_err = EIO; /* simulate Unix error, */
+ if (vb > 2) {
+ char b[128];
+
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) failed: %s "
+ "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n);
+ }
+ res = -psp->os_err;
+ goto err_out;
+ }
+ if (dlen > 0) {
+ protocolData = &protocolDataDescr->ProtocolSpecificData;
+ bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset;
+ memcpy(dp, bp, dlen);
+ }
+ psp->nvme_status = 0;
+ psp->nvme_result =
+ protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData;
+ if (vb > 3)
+ pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, "
+ "returnedLength=%u\n", __func__, (uint32_t)returnedLength);
+ res = 0;
+err_out:
+ if (free_buffer)
+ free(free_buffer);
+ return res;
+}
+
+static int
+nvme_real_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, bool is_read,
+ int time_secs, int vb)
+{
+ int res = 0;
+ const uint32_t cmd_len = 64;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint32_t n, k;
+ uint32_t rd_off = 0;
+ uint32_t slen = psp->sense_len;
+ uint8_t * bp;
+ uint8_t * sbp = psp->sensep;
+ BOOL ok;
+ PVOID buffer = NULL;
+ uint8_t * free_buffer = NULL;
+ ULONG bufferLength = 0;
+ ULONG returnLength = 0;
+ STORAGE_PROTOCOL_COMMAND * protoCmdp;
+ const NVME_ERROR_INFO_LOG * neilp;
+
+ n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen;
+ bufferLength = offsetof(STORAGE_PROTOCOL_COMMAND, Command) +
+ cmd_len +
+ sizeof(NVME_ERROR_INFO_LOG) + n;
+ buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false);
+ if (buffer == NULL) {
+ res = sg_convert_errno(ENOMEM);
+ if (vb > 1)
+ pr2ws("%s: unable to allocate memory\n", __func__);
+ psp->os_err = res;
+ return -res;
+ }
+ protoCmdp = (STORAGE_PROTOCOL_COMMAND *)buffer;
+ protoCmdp->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;
+ protoCmdp->Length = sizeof(STORAGE_PROTOCOL_COMMAND);
+ protoCmdp->ProtocolType = ProtocolTypeNvme;
+ /* without *_ADAPTER_REQUEST flag, goes to device */
+ protoCmdp->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;
+ /* protoCmdp->Flags = 0; */
+ protoCmdp->CommandLength = cmd_len;
+ protoCmdp->ErrorInfoLength = sizeof(NVME_ERROR_INFO_LOG);
+ if (dlen > 0) {
+ if (is_read)
+ protoCmdp->DataFromDeviceTransferLength = dlen;
+ else
+ protoCmdp->DataToDeviceTransferLength = dlen;
+ }
+ protoCmdp->TimeOutValue = (time_secs > 0) ? time_secs : DEF_TIMEOUT;
+ protoCmdp->ErrorInfoOffset =
+ offsetof(STORAGE_PROTOCOL_COMMAND, Command) + cmd_len;
+ n = protoCmdp->ErrorInfoOffset + protoCmdp->ErrorInfoLength;
+ if (is_read) {
+ protoCmdp->DataFromDeviceBufferOffset = n;
+ rd_off = n;
+ } else
+ protoCmdp->DataToDeviceBufferOffset = n;
+ protoCmdp->CommandSpecific =
+ STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;
+ memcpy(protoCmdp->Command, cmdp, cmd_len);
+ if ((dlen > 0) && (! is_read)) {
+ bp = (uint8_t *)protoCmdp + n;
+ memcpy(bp, dp, dlen);
+ }
+
+ ok = DeviceIoControl(shp->fh, IOCTL_STORAGE_PROTOCOL_COMMAND,
+ buffer, bufferLength, buffer, bufferLength,
+ &returnLength, (OVERLAPPED*)0);
+ if (! ok) {
+ n = (uint32_t)GetLastError();
+ psp->transport_err = n;
+ psp->os_err = EIO; /* simulate Unix error, */
+ if (vb > 2) {
+ char b[128];
+
+ pr2ws("%s: IOCTL_STORAGE_PROTOCOL_COMMAND failed: %s "
+ "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n);
+ pr2ws(" ... ReturnStatus=0x%x, ReturnLength=%u\n",
+ (uint32_t)protoCmdp->ReturnStatus, (uint32_t)returnLength);
+ }
+ res = -psp->os_err;
+ goto err_out;
+ }
+ bp = (uint8_t *)protoCmdp + protoCmdp->ErrorInfoOffset;
+ neilp = (const NVME_ERROR_INFO_LOG *)bp;
+ /* Shift over top of Phase tag bit */
+ psp->nvme_status = 0x3ff & (neilp->Status.AsUshort >> 1);
+ if ((dlen > 0) && is_read) {
+ bp = (uint8_t *)protoCmdp + rd_off;
+ memcpy(dp, bp, dlen);
+ }
+ psp->nvme_result = protoCmdp->FixedProtocolReturnData;
+ if (psp->nvme_direct && sbp && (slen > 3)) {
+ /* build 16 byte "sense" buffer from completion queue entry */
+ n = (slen < 16) ? slen : 16;
+ memset(sbp, 0 , n);
+ psp->sense_resid = (slen > 16) ? (slen - 16) : 0;
+ sg_put_unaligned_le32(psp->nvme_result, sbp + SG_NVME_PT_CQ_DW0);
+ if (n > 11) {
+ k = neilp->SQID;
+ sg_put_unaligned_le32((k << 16), sbp + SG_NVME_PT_CQ_DW2);
+ if (n > 15) {
+ k = ((uint32_t)neilp->Status.AsUshort << 16) | neilp->CMDID;
+ sg_put_unaligned_le32(k, sbp + SG_NVME_PT_CQ_DW3);
+ }
+ }
+ }
+ if (vb > 3)
+ pr2ws("%s: opcode=0x%x, status=0x%x, result=0x%x\n",
+ __func__, cmdp[0], psp->nvme_status, psp->nvme_result);
+ res = psp->nvme_status ? SG_LIB_NVME_STATUS : 0;
+err_out:
+ if (free_buffer)
+ free(free_buffer);
+ return res;
+}
+
+static int
+do_nvme_admin_cmd(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen,
+ bool is_read, int time_secs, int vb)
+{
+ const uint32_t cmd_len = 64;
+ int res;
+ uint32_t n;
+ uint8_t opcode;
+
+ psp->os_err = 0;
+ psp->transport_err = 0;
+ if (NULL == cmdp) {
+ if (! psp->have_nvme_cmd)
+ return SCSI_PT_DO_BAD_PARAMS;
+ cmdp = psp->nvme_cmd;
+ is_read = psp->is_read;
+ dlen = psp->dxfer_len;
+ dp = psp->dxferp;
+ }
+ if (vb > 2) {
+ pr2ws("NVMe is_read=%s, dlen=%u, command:\n",
+ (is_read ? "true" : "false"), dlen);
+ hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+ if ((vb > 3) && (! is_read) && dp) {
+ if (dlen > 0) {
+ n = dlen;
+ if ((dlen < 512) || (vb > 5))
+ pr2ws("\nData-out buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+ n = 512;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ }
+ opcode = cmdp[0];
+ switch (opcode) { /* The matches below are cached by W10 */
+ case 0x6: /* Identify (controller + namespace */
+ res = nvme_identify(psp, shp, cmdp, dp, dlen, vb);
+ if (res)
+ goto err_out;
+ break;
+ case 0xa: /* Get features */
+ res = nvme_get_features(psp, shp, cmdp, dp, dlen, vb);
+ if (res)
+ goto err_out;
+ break;
+ case 0x2: /* Get Log Page */
+ res = nvme_get_log_page(psp, shp, cmdp, dp, dlen, vb);
+ if (res)
+ goto err_out;
+ break;
+ default:
+ res = nvme_real_pt(psp, shp, cmdp, dp, dlen, is_read, time_secs, vb);
+ if (res)
+ goto err_out;
+ break;
+ /* IOCTL_STORAGE_PROTOCOL_COMMAND base pass-through goes here */
+ res = -EINVAL;
+ goto err_out;
+ }
+
+ if ((vb > 3) && is_read && dp && (dlen > 0)) {
+ n = dlen;
+ if ((dlen < 1024) || (vb > 5))
+ pr2ws("\nData-in buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+ n = 1024;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+err_out:
+ return res;
+}
+
+#else /* W10_NVME_NON_PASSTHRU */
+
+/* If cmdp is NULL then dp, dlen and is_read are ignored, those values are
+ * obtained from psp. Returns 0 for success. Returns SG_LIB_NVME_STATUS if
+ * there is non-zero NVMe status (SCT|SC from the completion queue) with the
+ * value placed in psp->nvme_status. If Unix error from ioctl then return
+ * negated value (equivalent -errno from basic Unix system functions like
+ * open()). CDW0 from the completion queue is placed in psp->nvme_result in
+ * the absence of an error.
+ * The following code is based on os_win32.cpp in smartmontools:
+ * Copyright (C) 2004-17 Christian Franke
+ * The code is licensed with a GPL-2. */
+static int
+do_nvme_admin_cmd(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cmdp, uint8_t * dp, uint32_t dlen,
+ bool is_read, int time_secs, int vb)
+{
+ const uint32_t cmd_len = 64;
+ int res;
+ uint32_t n, alloc_len;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint32_t slen = psp->sense_len;
+ uint8_t * sbp = psp->sensep;
+ NVME_PASS_THROUGH_IOCTL * pthru;
+ uint8_t * free_pthru;
+ DWORD num_out = 0;
+ BOOL ok;
+
+ psp->os_err = 0;
+ psp->transport_err = 0;
+ if (NULL == cmdp) {
+ if (! psp->have_nvme_cmd)
+ return SCSI_PT_DO_BAD_PARAMS;
+ cmdp = psp->nvme_cmd;
+ is_read = psp->is_read;
+ dlen = psp->dxfer_len;
+ dp = psp->dxferp;
+ }
+ if (vb > 2) {
+ pr2ws("NVMe is_read=%s, dlen=%u, command:\n",
+ (is_read ? "true" : "false"), dlen);
+ hex2stderr((const uint8_t *)cmdp, cmd_len, 1);
+ if ((vb > 3) && (! is_read) && dp) {
+ if (dlen > 0) {
+ n = dlen;
+ if ((dlen < 512) || (vb > 5))
+ pr2ws("\nData-out buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n);
+ n = 512;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ }
+ alloc_len = sizeof(NVME_PASS_THROUGH_IOCTL) + dlen;
+ pthru = (NVME_PASS_THROUGH_IOCTL *)sg_memalign(alloc_len, pg_sz,
+ &free_pthru, false);
+ if (NULL == pthru) {
+ res = sg_convert_errno(ENOMEM);
+ if (vb > 1)
+ pr2ws("%s: unable to allocate memory\n", __func__);
+ psp->os_err = res;
+ return -res;
+ }
+ if (dp && (dlen > 0) && (! is_read))
+ memcpy(pthru->DataBuffer, dp, dlen); /* dout-out buffer */
+ /* Set NVMe command */
+ pthru->SrbIoCtrl.HeaderLength = sizeof(SRB_IO_CONTROL);
+ memcpy(pthru->SrbIoCtrl.Signature, NVME_SIG_STR, sizeof(NVME_SIG_STR)-1);
+ pthru->SrbIoCtrl.Timeout = (time_secs > 0) ? time_secs : DEF_TIMEOUT;
+ pthru->SrbIoCtrl.ControlCode = NVME_PASS_THROUGH_SRB_IO_CODE;
+ pthru->SrbIoCtrl.ReturnCode = 0;
+ pthru->SrbIoCtrl.Length = alloc_len - sizeof(SRB_IO_CONTROL);
+
+ memcpy(pthru->NVMeCmd, cmdp, cmd_len);
+ if (dlen > 0)
+ pthru->Direction = is_read ? 2 : 1;
+ else
+ pthru->Direction = 0;
+ pthru->ReturnBufferLen = alloc_len;
+ shp = get_open_pt_handle(psp, psp->dev_fd, vb > 1);
+ if (NULL == shp) {
+ res = -psp->os_err; /* -ENODEV */
+ goto err_out;
+ }
+
+ ok = DeviceIoControl(shp->fh, IOCTL_SCSI_MINIPORT, pthru, alloc_len,
+ pthru, alloc_len, &num_out, (OVERLAPPED*)0);
+ if (! ok) {
+ n = (uint32_t)GetLastError();
+ psp->transport_err = n;
+ psp->os_err = EIO; /* simulate Unix error, */
+ if (vb > 2) {
+ char b[128];
+
+ pr2ws("%s: IOCTL_SCSI_MINIPORT failed: %s [%u]\n", __func__,
+ get_err_str(n, sizeof(b), b), n);
+ }
+ }
+ /* nvme_status is SCT|SC, therefore it excludes DNR+More */
+ psp->nvme_status = 0x3ff & (pthru->CplEntry[3] >> 17);
+ if (psp->nvme_status && (vb > 1)) {
+ uint16_t s = psp->nvme_status;
+ char b[80];
+
+ pr2ws("%s: opcode=0x%x failed: NVMe status: %s [0x%x]\n", __func__,
+ cmdp[0], sg_get_nvme_cmd_status_str(s, sizeof(b), b), s);
+ }
+ psp->nvme_result = sg_get_unaligned_le32(pthru->CplEntry + 0);
+
+ psp->sense_resid = 0;
+ if (psp->nvme_direct && sbp && (slen > 3)) {
+ /* build 16 byte "sense" buffer */
+ n = (slen < 16) ? slen : 16;
+ memset(sbp, 0 , n);
+ psp->sense_resid = (slen > 16) ? (slen - 16) : 0;
+ sg_put_unaligned_le32(pthru->CplEntry[0], sbp + SG_NVME_PT_CQ_DW0);
+ if (n > 7) {
+ sg_put_unaligned_le32(pthru->CplEntry[1],
+ sbp + SG_NVME_PT_CQ_DW1);
+ if (n > 11) {
+ sg_put_unaligned_le32(pthru->CplEntry[2],
+ sbp + SG_NVME_PT_CQ_DW2);
+ if (n > 15)
+ sg_put_unaligned_le32(pthru->CplEntry[3],
+ sbp + SG_NVME_PT_CQ_DW3);
+ }
+ }
+ }
+ if (! ok) {
+ res = -psp->os_err;
+ goto err_out;
+ } else if (psp->nvme_status) {
+ res = SG_LIB_NVME_STATUS;
+ goto err_out;
+ }
+
+ if (dp && (dlen > 0) && is_read) {
+ memcpy(dp, pthru->DataBuffer, dlen); /* data-in buffer */
+ if (vb > 3) {
+ n = dlen;
+ if ((dlen < 1024) || (vb > 5))
+ pr2ws("\nData-in buffer (%u bytes):\n", n);
+ else {
+ pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n);
+ n = 1024;
+ }
+ hex2stderr((const uint8_t *)dp, n, 0);
+ }
+ }
+ res = 0;
+err_out:
+ if (free_pthru)
+ free(free_pthru);
+ return res;
+}
+
+#endif /* W10_NVME_NON_PASSTHRU */
+
+
+static void
+sntl_check_enclosure_override(struct sg_pt_win32_scsi * psp,
+ struct sg_pt_handle * shp, int vb)
+{
+ uint8_t * up = psp->nvme_id_ctlp;
+ uint8_t nvmsr;
+
+ if (NULL == up)
+ return;
+ nvmsr = up[253];
+ if (vb > 3)
+ pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr);
+ shp->dev_stat.id_ctl253 = nvmsr;
+ switch (shp->dev_stat.enclosure_override) {
+ case 0x0: /* no override */
+ if (0x3 & nvmsr) {
+ shp->dev_stat.pdt = PDT_DISK;
+ shp->dev_stat.enc_serv = 1;
+ } else if (0x2 & nvmsr) {
+ shp->dev_stat.pdt = PDT_SES;
+ shp->dev_stat.enc_serv = 1;
+ } else if (0x1 & nvmsr) {
+ shp->dev_stat.pdt = PDT_DISK;
+ shp->dev_stat.enc_serv = 0;
+ } else {
+ uint32_t nn = sg_get_unaligned_le32(up + 516);
+
+ shp->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN;
+ shp->dev_stat.enc_serv = 0;
+ }
+ break;
+ case 0x1: /* override to SES device */
+ shp->dev_stat.pdt = PDT_SES;
+ shp->dev_stat.enc_serv = 1;
+ break;
+ case 0x2: /* override to disk with attached SES device */
+ shp->dev_stat.pdt = PDT_DISK;
+ shp->dev_stat.enc_serv = 1;
+ break;
+ case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */
+ shp->dev_stat.pdt = PDT_PROCESSOR;
+ shp->dev_stat.enc_serv = 1;
+ break;
+ case 0xff: /* override to normal disk */
+ shp->dev_stat.pdt = PDT_DISK;
+ shp->dev_stat.enc_serv = 0;
+ break;
+ default:
+ pr2ws("%s: unknown enclosure_override value: %d\n", __func__,
+ shp->dev_stat.enclosure_override);
+ break;
+ }
+}
+
+/* Returns 0 on success; otherwise a positive value is returned */
+static int
+sntl_cache_identity(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ static const bool is_read = true;
+ const uint32_t pg_sz = sg_get_page_size();
+ int ret;
+ uint8_t * up;
+ uint8_t * cmdp;
+
+ up = sg_memalign(((pg_sz < 4096) ? 4096 : pg_sz), pg_sz,
+ &psp->free_nvme_id_ctlp, false);
+ psp->nvme_id_ctlp = up;
+ if (NULL == up) {
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ cmdp = psp->nvme_cmd;
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[0] = 0x6; /* Identify */
+ /* leave nsid as 0, should it be broadcast (0xffffffff) ? */
+ /* CNS=0x1 Identify controller: */
+ sg_put_unaligned_le32(0x1, cmdp + SG_NVME_PT_CDW10);
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)up, cmdp + SG_NVME_PT_ADDR);
+ sg_put_unaligned_le32(pg_sz, cmdp + SG_NVME_PT_DATA_LEN);
+ ret = do_nvme_admin_cmd(psp, shp, cmdp, up, 4096, is_read, time_secs,
+ vb);
+ if (0 == ret)
+ sntl_check_enclosure_override(psp, shp, vb);
+ return ret;
+}
+
+
+static const char * nvme_scsi_vendor_str = "NVMe ";
+static const uint16_t inq_resp_len = 36;
+
+static int
+sntl_inq(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool evpd;
+ bool cp_id_ctl = false;
+ int res;
+ uint16_t n, alloc_len, pg_cd;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint8_t * nvme_id_ns = NULL;
+ uint8_t * free_nvme_id_ns = NULL;
+ uint8_t inq_dout[256];
+ uint8_t * cmdp;
+
+ if (vb > 3)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */
+ mk_sense_invalid_fld(psp, true, 1, 1, vb);
+ return 0;
+ }
+ if (NULL == psp->nvme_id_ctlp) {
+ res = sntl_cache_identity(psp, shp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else if (res) /* should be negative errno */
+ return res;
+ }
+ memset(inq_dout, 0, sizeof(inq_dout));
+ alloc_len = sg_get_unaligned_be16(cdbp + 3);
+ evpd = !!(0x1 & cdbp[1]);
+ pg_cd = cdbp[2];
+ if (evpd) { /* VPD page responses */
+ switch (pg_cd) {
+ case 0:
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 11;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x0;
+ inq_dout[5] = 0x80;
+ inq_dout[6] = 0x83;
+ inq_dout[7] = 0x86;
+ inq_dout[8] = 0x87;
+ inq_dout[9] = 0x92;
+ inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */
+ break;
+ case 0x80:
+ /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+ inq_dout[1] = pg_cd;
+ n = 24;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ memcpy(inq_dout + 4, psp->nvme_id_ctlp + 4, 20); /* SN */
+ break;
+ case 0x83:
+ if ((psp->nvme_nsid > 0) &&
+ (psp->nvme_nsid < SG_NVME_BROADCAST_NSID)) {
+ nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns,
+ false);
+ if (nvme_id_ns) {
+ cmdp = psp->nvme_cmd;
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0x6; /* Identify */
+ sg_put_unaligned_le32(psp->nvme_nsid,
+ cmdp + SG_NVME_PT_NSID);
+ /* CNS=0x0 Identify controller: */
+ sg_put_unaligned_le32(0x0, cmdp + SG_NVME_PT_CDW10);
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)nvme_id_ns,
+ cmdp + SG_NVME_PT_ADDR);
+ sg_put_unaligned_le32(pg_sz, cmdp + SG_NVME_PT_DATA_LEN);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, nvme_id_ns, pg_sz,
+ true, time_secs, vb > 3);
+ if (res) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ }
+ }
+ n = sg_make_vpd_devid_for_nvme(psp->nvme_id_ctlp, nvme_id_ns,
+ 0 /* pdt */, -1 /*tproto */,
+ inq_dout, sizeof(inq_dout));
+ if (n > 3)
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ if (free_nvme_id_ns) {
+ free(free_nvme_id_ns);
+ free_nvme_id_ns = NULL;
+ nvme_id_ns = NULL;
+ }
+ break;
+ case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 64;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[5] = 0x1; /* SIMPSUP=1 */
+ inq_dout[7] = 0x1; /* LUICLR=1 */
+ inq_dout[13] = 0x40; /* max supported sense data length */
+ break;
+ case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */
+ inq_dout[1] = pg_cd;
+ n = 8;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[4] = 0x3f; /* all mode pages */
+ inq_dout[5] = 0xff; /* and their sub-pages */
+ inq_dout[6] = 0x80; /* MLUS=1, policy=shared */
+ break;
+ case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */
+ inq_dout[1] = pg_cd;
+ n = 10;
+ sg_put_unaligned_be16(n - 4, inq_dout + 2);
+ inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde */
+ inq_dout[1] = pg_cd;
+ sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2);
+ n = 16 + 4096;
+ cp_id_ctl = true;
+ break;
+ default: /* Point to page_code field in cdb */
+ mk_sense_invalid_fld(psp, true, 2, 7, vb);
+ return 0;
+ }
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < psp->dxfer_len) ? n : psp->dxfer_len;
+ psp->resid = psp->dxfer_len - n;
+ if (n > 0) {
+ if (cp_id_ctl) {
+ memcpy(psp->dxferp, inq_dout, (n < 16 ? n : 16));
+ if (n > 16)
+ memcpy(psp->dxferp + 16,
+ psp->nvme_id_ctlp, n - 16);
+ } else
+ memcpy(psp->dxferp, inq_dout, n);
+ }
+ }
+ } else { /* Standard INQUIRY response */
+ /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */
+ inq_dout[0] = (PDT_MASK & shp->dev_stat.pdt); /* (PQ=0)<<5 */
+ /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6; rest reserved */
+ inq_dout[2] = 6; /* version: SPC-4 */
+ inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */
+ inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */
+ inq_dout[6] = shp->dev_stat.enc_serv ? 0x40 : 0;
+ inq_dout[7] = 0x2; /* CMDQUE=1 */
+ memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */
+ memcpy(inq_dout + 16, psp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */
+ memcpy(inq_dout + 32, psp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */
+ if (alloc_len > 0) {
+ n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len;
+ n = (n < psp->dxfer_len) ? n : psp->dxfer_len;
+ psp->resid = psp->dxfer_len - n;
+ if (n > 0)
+ memcpy(psp->dxferp, inq_dout, n);
+ }
+ }
+ return 0;
+}
+
+static int
+sntl_rluns(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ int res;
+ uint16_t sel_report;
+ uint32_t alloc_len, k, n, num, max_nsid;
+ uint8_t * rl_doutp;
+ uint8_t * up;
+
+ if (vb > 3)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+
+ sel_report = cdbp[2];
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (NULL == psp->nvme_id_ctlp) {
+ res = sntl_cache_identity(psp, shp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ max_nsid = sg_get_unaligned_le32(psp->nvme_id_ctlp + 516);
+ switch (sel_report) {
+ case 0:
+ case 2:
+ num = max_nsid;
+ break;
+ case 1:
+ case 0x10:
+ case 0x12:
+ num = 0;
+ break;
+ case 0x11:
+ num = (1 == psp->nvme_nsid) ? max_nsid : 0;
+ break;
+ default:
+ if (vb > 1)
+ pr2ws("%s: bad select_report value: 0x%x\n", __func__,
+ sel_report);
+ mk_sense_invalid_fld(psp, true, 2, 7, vb);
+ return 0;
+ }
+ rl_doutp = (uint8_t *)calloc(num + 1, 8);
+ if (NULL == rl_doutp) {
+ pr2ws("%s: calloc() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8)
+ sg_put_unaligned_be16(k, up);
+ n = num * 8;
+ sg_put_unaligned_be32(n, rl_doutp);
+ n+= 8;
+ if (alloc_len > 0) {
+ n = (alloc_len < n) ? alloc_len : n;
+ n = (n < psp->dxfer_len) ? n : psp->dxfer_len;
+ psp->resid = psp->dxfer_len - n;
+ if (n > 0)
+ memcpy(psp->dxferp, rl_doutp, n);
+ }
+ res = 0;
+ free(rl_doutp);
+ return res;
+}
+
+static int
+sntl_tur(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ int res;
+ uint32_t pow_state;
+ uint8_t * cmdp;
+
+ if (vb > 4)
+ pr2ws("%s: enter\n", __func__);
+ if (NULL == psp->nvme_id_ctlp) {
+ res = sntl_cache_identity(psp, shp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ cmdp = psp->nvme_cmd;
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0xa; /* Get features */
+ sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, cmdp + SG_NVME_PT_NSID);
+ /* SEL=0 (current), Feature=2 Power Management */
+ sg_put_unaligned_le32(0x2, cmdp + SG_NVME_PT_CDW10);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else
+ return res;
+ } else {
+ psp->os_err = 0;
+ psp->nvme_status = 0;
+ }
+ pow_state = (0x1f & psp->nvme_result);
+ if (vb > 3)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+#if 0 /* pow_state bounces around too much on laptop */
+ if (pow_state)
+ mk_sense_asc_ascq(psp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0,
+ vb);
+#endif
+ return 0;
+}
+
+static int
+sntl_req_sense(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool desc;
+ int res;
+ uint32_t pow_state, alloc_len, n;
+ uint8_t rs_dout[64];
+ uint8_t * cmdp;
+
+ if (vb > 3)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ if (NULL == psp->nvme_id_ctlp) {
+ res = sntl_cache_identity(psp, shp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ desc = !!(0x1 & cdbp[1]);
+ alloc_len = cdbp[4];
+ cmdp = psp->nvme_cmd;
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0xa; /* Get features */
+ sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, cmdp + SG_NVME_PT_NSID);
+ /* SEL=0 (current), Feature=2 Power Management */
+ sg_put_unaligned_le32(0x2, cmdp + SG_NVME_PT_CDW10);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else
+ return res;
+ } else {
+ psp->os_err = 0;
+ psp->nvme_status = 0;
+ }
+ psp->sense_resid = psp->sense_len;
+ pow_state = (0x1f & psp->nvme_result);
+ if (vb > 3)
+ pr2ws("%s: pow_state=%u\n", __func__, pow_state);
+ memset(rs_dout, 0, sizeof(rs_dout));
+ if (pow_state)
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ LOW_POWER_COND_ON_ASC, 0);
+ else
+ sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE,
+ NO_ADDITIONAL_SENSE, 0);
+ n = desc ? 8 : 18;
+ n = (n < alloc_len) ? n : alloc_len;
+ n = (n < psp->dxfer_len) ? n : psp->dxfer_len;
+ psp->resid = psp->dxfer_len - n;
+ if (n > 0)
+ memcpy(psp->dxferp, rs_dout, n);
+ return 0;
+}
+
+static int
+sntl_mode_ss(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]);
+ int res, n, len;
+ uint8_t * bp;
+ struct sg_sntl_result_t sntl_result;
+
+ if (vb > 3)
+ pr2ws("%s: mse%s, time_secs=%d\n", __func__,
+ (is_msense ? "nse" : "lect"), time_secs);
+ if (NULL == psp->nvme_id_ctlp) {
+ res = sntl_cache_identity(psp, shp, time_secs, vb);
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else if (res)
+ return res;
+ }
+ if (is_msense) { /* MODE SENSE(10) */
+ len = psp->dxfer_len;
+ bp = psp->dxferp;
+ n = sntl_resp_mode_sense10(&shp->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ psp->resid = (n >= 0) ? len - n : len;
+ } else { /* MODE SELECT(10) */
+ uint8_t pre_enc_ov = shp->dev_stat.enclosure_override;
+
+ len = psp->dxfer_len;
+ bp = psp->dxferp;
+ n = sntl_resp_mode_select10(&shp->dev_stat, cdbp, bp, len,
+ &sntl_result);
+ if (pre_enc_ov != shp->dev_stat.enclosure_override)
+ sntl_check_enclosure_override(psp, shp, vb); /* ENC_OV changed */
+ }
+ if (n < 0) {
+ int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit :
+ -1;
+ if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) &&
+ (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) {
+ if (INVALID_FIELD_IN_CDB == sntl_result.asc)
+ mk_sense_invalid_fld(psp, true, sntl_result.in_byte, in_bit,
+ vb);
+ else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc)
+ mk_sense_invalid_fld(psp, false, sntl_result.in_byte, in_bit,
+ vb);
+ else
+ mk_sense_asc_ascq(psp, sntl_result.sk, sntl_result.asc,
+ sntl_result.ascq, vb);
+ } else
+ pr2ws("%s: error but no sense?? n=%d\n", __func__, n);
+ }
+ return 0;
+}
+
+/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI
+ * has a special command (SES Send) to tunnel through pages to an
+ * enclosure. The NVMe enclosure is meant to understand the SES
+ * (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_senddiag(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool pf, self_test;
+ int res;
+ uint8_t st_cd, dpg_cd;
+ uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst;
+ uint8_t * dop;
+ uint8_t * cmdp;
+
+ st_cd = 0x7 & (cdbp[1] >> 5);
+ self_test = !! (0x4 & cdbp[1]);
+ pf = !! (0x10 & cdbp[1]);
+ if (vb > 3)
+ pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf,
+ (int)self_test, (int)st_cd);
+ cmdp = psp->nvme_cmd;
+ if (self_test || st_cd) {
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */
+ /* just this namespace (if there is one) and controller */
+ sg_put_unaligned_le32(psp->nvme_nsid, cmdp + SG_NVME_PT_NSID);
+ switch (st_cd) {
+ case 0: /* Here if self_test is set, do short self-test */
+ case 1: /* Background short */
+ case 5: /* Foreground short */
+ nvme_dst = 1;
+ break;
+ case 2: /* Background extended */
+ case 6: /* Foreground extended */
+ nvme_dst = 2;
+ break;
+ case 4: /* Abort self-test */
+ nvme_dst = 0xf;
+ break;
+ default:
+ pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd);
+ mk_sense_invalid_fld(psp, true, 1, 7, vb);
+ return 0;
+ }
+ sg_put_unaligned_le32(nvme_dst, cmdp + SG_NVME_PT_CDW10);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs,
+ vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ }
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ dout_len = psp->dxfer_len;
+ if (pf) {
+ if (0 == alloc_len) {
+ mk_sense_invalid_fld(psp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: PF bit set bit param_list_len=0\n", __func__);
+ return 0;
+ }
+ } else { /* PF bit clear */
+ if (alloc_len) {
+ mk_sense_invalid_fld(psp, true, 3, 7, vb);
+ if (vb)
+ pr2ws("%s: param_list_len>0 but PF clear\n", __func__);
+ return 0;
+ } else
+ return 0; /* nothing to do */
+ if (dout_len > 0) {
+ if (vb)
+ pr2ws("%s: dout given but PF clear\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ }
+ if (dout_len < 4) {
+ if (vb)
+ pr2ws("%s: dout length (%u bytes) too short\n", __func__,
+ dout_len);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ n = dout_len;
+ n = (n < alloc_len) ? n : alloc_len;
+ dop = psp->dxferp;
+ if (! sg_is_aligned(dop, 0)) { /* page aligned ? */
+ if (vb)
+ pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)(sg_uintptr_t)psp->dxferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ dpg_cd = dop[0];
+ dpg_len = sg_get_unaligned_be16(dop + 2) + 4;
+ /* should we allow for more than one D_PG is dout ?? */
+ n = (n < dpg_len) ? n : dpg_len; /* not yet ... */
+
+ if (vb)
+ pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n",
+ __func__, dpg_cd, dpg_len);
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0x1d; /* MI Send */
+ /* And 0x1d is same opcode as the SCSI SEND DIAGNOSTIC command */
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dop,
+ cmdp + SG_NVME_PT_ADDR);
+ /* NVMe 4k page size. Maybe determine this? */
+ /* N.B. Maybe n > 0x1000, is this a problem?? */
+ sg_put_unaligned_le32(0x1000, cmdp + SG_NVME_PT_DATA_LEN);
+ /* NVMe Message Header */
+ sg_put_unaligned_le32(0x0804, cmdp + SG_NVME_PT_CDW10);
+ /* NVME-MI SES Send; (0x8 -> NVME-MI SES Receive) */
+ sg_put_unaligned_le32(0x9, cmdp + SG_NVME_PT_CDW11);
+ /* 'n' is number of bytes SEND DIAGNOSTIC dpage */
+ sg_put_unaligned_le32(n, cmdp + SG_NVME_PT_CDW13);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, dop, n, false, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ }
+ }
+ return res;
+}
+
+/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1)
+ * NVMe-MI has a special command (SES Receive) to read pages through a
+ * tunnel from an enclosure. The NVMe enclosure is meant to understand the
+ * SES (SCSI Enclosure Services) use of diagnostics pages that are
+ * related to SES. */
+static int
+sntl_recvdiag(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool pcv;
+ int res;
+ uint8_t dpg_cd;
+ uint32_t alloc_len, n, din_len;
+ uint8_t * dip;
+ uint8_t * cmdp;
+
+ pcv = !! (0x1 & cdbp[1]);
+ dpg_cd = cdbp[2];
+ alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */
+ if (vb > 3)
+ pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__,
+ dpg_cd, (int)pcv, alloc_len);
+ din_len = psp->dxfer_len;
+ n = (din_len < alloc_len) ? din_len : alloc_len;
+ dip = psp->dxferp;
+ if (! sg_is_aligned(dip, 0)) { /* page aligned ? */
+ if (vb)
+ pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__,
+ (uint64_t)(sg_uintptr_t)psp->dxferp);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+
+ if (vb)
+ pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__,
+ dpg_cd);
+ cmdp = psp->nvme_cmd;
+ memset(cmdp, 0, sizeof(psp->nvme_cmd));
+ cmdp[SG_NVME_PT_OPCODE] = 0x1e; /* MI Receive */
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dip,
+ cmdp + SG_NVME_PT_ADDR);
+ /* NVMe 4k page size. Maybe determine this? */
+ /* N.B. Maybe n > 0x1000, is this a problem?? */
+ sg_put_unaligned_le32(0x1000, cmdp + SG_NVME_PT_DATA_LEN);
+ /* NVMe Message Header */
+ sg_put_unaligned_le32(0x0804, cmdp + SG_NVME_PT_CDW10);
+ /* NVME-MI SES Receive */
+ sg_put_unaligned_le32(0x8, cmdp + SG_NVME_PT_CDW11);
+ /* Diagnostic page code */
+ sg_put_unaligned_le32(dpg_cd, cmdp + SG_NVME_PT_CDW12);
+ /* 'n' is number of bytes expected in diagnostic page */
+ sg_put_unaligned_le32(n, cmdp + SG_NVME_PT_CDW13);
+ res = do_nvme_admin_cmd(psp, shp, cmdp, dip, n, true, time_secs, vb);
+ if (0 != res) {
+ if (SG_LIB_NVME_STATUS == res) {
+ mk_sense_from_nvme_status(psp, vb);
+ return 0;
+ } else
+ return res;
+ }
+ psp->resid = din_len - n;
+ return res;
+}
+
+#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */
+#define F_SA_HIGH 0x100 /* as used by variable length cdbs */
+#define FF_SA (F_SA_HIGH | F_SA_LOW)
+#define F_INV_OP 0x200
+
+static int
+sntl_rep_opcodes(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool rctd;
+ uint8_t reporting_opts, req_opcode, supp;
+ uint16_t req_sa, u;
+ uint32_t alloc_len, offset, a_len;
+ const uint32_t pg_sz = sg_get_page_size();
+ int k, len, count, bump;
+ const struct sg_opcode_info_t *oip;
+ uint8_t *arr;
+ uint8_t *free_arr;
+
+ if (vb > 3)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ if (shp) { ; } /* suppress warning */
+ rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */
+ reporting_opts = cdbp[2] & 0x7;
+ req_opcode = cdbp[3];
+ req_sa = sg_get_unaligned_be16(cdbp + 4);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4 || alloc_len > 0xffff) {
+ mk_sense_invalid_fld(psp, true, 6, -1, vb);
+ return 0;
+ }
+ a_len = pg_sz - 72;
+ arr = sg_memalign(pg_sz, pg_sz, &free_arr, false);
+ if (NULL == arr) {
+ pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ switch (reporting_opts) {
+ case 0: /* all commands */
+ count = 0;
+ bump = rctd ? 20 : 8;
+ for (offset = 4, oip = sg_get_opcode_translation();
+ (oip->flags != 0xffff) && (offset < a_len); ++oip) {
+ if (F_INV_OP & oip->flags)
+ continue;
+ ++count;
+ arr[offset] = oip->opcode;
+ sg_put_unaligned_be16(oip->sa, arr + offset + 2);
+ if (rctd)
+ arr[offset + 5] |= 0x2;
+ if (FF_SA & oip->flags)
+ arr[offset + 5] |= 0x1;
+ sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6);
+ if (rctd)
+ sg_put_unaligned_be16(0xa, arr + offset + 8);
+ offset += bump;
+ }
+ sg_put_unaligned_be32(count * bump, arr + 0);
+ break;
+ case 1: /* one command: opcode only */
+ case 2: /* one command: opcode plus service action */
+ case 3: /* one command: if sa==0 then opcode only else opcode+sa */
+ for (oip = sg_get_opcode_translation(); oip->flags != 0xffff; ++oip) {
+ if ((req_opcode == oip->opcode) && (req_sa == oip->sa))
+ break;
+ }
+ if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) {
+ supp = 1;
+ offset = 4;
+ } else {
+ if (1 == reporting_opts) {
+ if (FF_SA & oip->flags) {
+ mk_sense_invalid_fld(psp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ req_sa = 0;
+ } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) {
+ mk_sense_invalid_fld(psp, true, 4, -1, vb);
+ free(free_arr);
+ return 0;
+ }
+ if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode))
+ supp = 3;
+ else if (0 == (FF_SA & oip->flags))
+ supp = 1;
+ else if (req_sa != oip->sa)
+ supp = 1;
+ else
+ supp = 3;
+ if (3 == supp) {
+ u = oip->len_mask[0];
+ sg_put_unaligned_be16(u, arr + 2);
+ arr[4] = oip->opcode;
+ for (k = 1; k < u; ++k)
+ arr[4 + k] = (k < 16) ?
+ oip->len_mask[k] : 0xff;
+ offset = 4 + u;
+ } else
+ offset = 4;
+ }
+ arr[1] = (rctd ? 0x80 : 0) | supp;
+ if (rctd) {
+ sg_put_unaligned_be16(0xa, arr + offset);
+ offset += 12;
+ }
+ break;
+ default:
+ mk_sense_invalid_fld(psp, true, 2, 2, vb);
+ free(free_arr);
+ return 0;
+ }
+ offset = (offset < a_len) ? offset : a_len;
+ len = (offset < alloc_len) ? offset : alloc_len;
+ psp->resid = psp->dxfer_len - len;
+ if (len > 0)
+ memcpy(psp->dxferp, arr, len);
+ free(free_arr);
+ return 0;
+}
+
+static int
+sntl_rep_tmfs(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ const uint8_t * cdbp, int time_secs, int vb)
+{
+ bool repd;
+ uint32_t alloc_len, len;
+ uint8_t arr[16];
+
+ if (vb > 3)
+ pr2ws("%s: time_secs=%d\n", __func__, time_secs);
+ if (shp) { ; } /* suppress warning */
+ memset(arr, 0, sizeof(arr));
+ repd = !!(cdbp[2] & 0x80);
+ alloc_len = sg_get_unaligned_be32(cdbp + 6);
+ if (alloc_len < 4) {
+ mk_sense_invalid_fld(psp, true, 6, -1, vb);
+ return 0;
+ }
+ arr[0] = 0xc8; /* ATS | ATSS | LURS */
+ arr[1] = 0x1; /* ITNRS */
+ if (repd) {
+ arr[3] = 0xc;
+ len = 16;
+ } else
+ len = 4;
+
+ len = (len < alloc_len) ? len : alloc_len;
+ psp->resid = psp->dxfer_len - len;
+ if (len > 0)
+ memcpy(psp->dxferp, arr, len);
+ return 0;
+}
+
+/* Executes NVMe Admin command (or at least forwards it to lower layers).
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package.
+ * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds
+ * is used. */
+static int
+nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ bool scsi_cdb = false;
+ uint32_t cmd_len = 0;
+ uint16_t sa;
+ const uint8_t * cdbp = NULL;
+
+ if (psp->have_nvme_cmd) {
+ cdbp = psp->nvme_cmd;
+ cmd_len = 64;
+ psp->nvme_direct = true;
+ } else if (spt_direct) {
+ if (psp->swb_d.spt.CdbLength > 0) {
+ cdbp = psp->swb_d.spt.Cdb;
+ cmd_len = psp->swb_d.spt.CdbLength;
+ scsi_cdb = true;
+ psp->nvme_direct = false;
+ }
+ } else {
+ if (psp->swb_i.spt.CdbLength > 0) {
+ cdbp = psp->swb_i.spt.Cdb;
+ cmd_len = psp->swb_i.spt.CdbLength;
+ scsi_cdb = true;
+ psp->nvme_direct = false;
+ }
+ }
+ if (NULL == cdbp) {
+ if (vb)
+ pr2ws("%s: Missing NVMe or SCSI command (set_scsi_pt_cdb())"
+ " cmd_len=%u\n", __func__, cmd_len);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (vb > 3)
+ pr2ws("%s: opcode=0x%x, cmd_len=%u, fdev_name: %s, dlen=%u\n",
+ __func__, cdbp[0], cmd_len, shp->dname, psp->dxfer_len);
+ /* direct NVMe command (i.e. 64 bytes long) or SNTL */
+ if (scsi_cdb) {
+ switch (cdbp[0]) {
+ case SCSI_INQUIRY_OPC:
+ return sntl_inq(psp, shp, cdbp, time_secs, vb);
+ case SCSI_REPORT_LUNS_OPC:
+ return sntl_rluns(psp, shp, cdbp, time_secs, vb);
+ case SCSI_TEST_UNIT_READY_OPC:
+ return sntl_tur(psp, shp, time_secs, vb);
+ case SCSI_REQUEST_SENSE_OPC:
+ return sntl_req_sense(psp, shp, cdbp, time_secs, vb);
+ case SCSI_SEND_DIAGNOSTIC_OPC:
+ return sntl_senddiag(psp, shp, cdbp, time_secs, vb);
+ case SCSI_RECEIVE_DIAGNOSTIC_OPC:
+ return sntl_recvdiag(psp, shp, cdbp, time_secs, vb);
+ case SCSI_MODE_SENSE10_OPC:
+ case SCSI_MODE_SELECT10_OPC:
+ return sntl_mode_ss(psp, shp, cdbp, time_secs, vb);
+ case SCSI_MAINT_IN_OPC:
+ sa = 0x1f & cdbp[1]; /* service action */
+ if (SCSI_REP_SUP_OPCS_OPC == sa)
+ return sntl_rep_opcodes(psp, shp, cdbp, time_secs,
+ vb);
+ else if (SCSI_REP_SUP_TMFS_OPC == sa)
+ return sntl_rep_tmfs(psp, shp, cdbp, time_secs, vb);
+ /* fall through */
+ default:
+ if (vb > 2) {
+ char b[64];
+
+ sg_get_command_name(cdbp, -1, sizeof(b), b);
+ pr2ws("%s: no translation to NVMe for SCSI %s command\n",
+ __func__, b);
+ }
+ mk_sense_asc_ascq(psp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE,
+ 0, vb);
+ return 0;
+ }
+ }
+ if(psp->dxfer_len > 0) {
+ uint8_t * cmdp = psp->nvme_cmd;
+
+ sg_put_unaligned_le32(psp->dxfer_len, cmdp + SG_NVME_PT_DATA_LEN);
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)psp->dxferp,
+ cmdp + SG_NVME_PT_ADDR);
+ if (vb > 2)
+ pr2ws("%s: NVMe command, dlen=%u, dxferp=0x%p\n", __func__,
+ psp->dxfer_len, psp->dxferp);
+ }
+ return do_nvme_admin_cmd(psp, shp, NULL, NULL, 0, true, time_secs, vb);
+}
+
+#else /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+static int
+nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp,
+ int time_secs, int vb)
+{
+ if (vb)
+ pr2ws("%s: not supported [time_secs=%d]\n", __func__, time_secs);
+ if (psp) { ; } /* suppress warning */
+ if (shp) { ; } /* suppress warning */
+ return -ENOTTY; /* inappropriate ioctl error */
+}
+
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+int
+do_nvm_pt(struct sg_pt_base * vp, int submq, int timeout_secs, int verbose)
+{
+ if (vp) { }
+ if (submq) { }
+ if (timeout_secs) { }
+ if (verbose) { }
+ return SCSI_PT_DO_NOT_SUPPORTED;
+}
diff --git a/ltmain.sh b/ltmain.sh
new file mode 100755
index 00000000..8fb8700e
--- /dev/null
+++ b/ltmain.sh
@@ -0,0 +1,11448 @@
+#! /usr/bin/env sh
+## DO NOT EDIT - This file generated from ./build-aux/ltmain.in
+## by inline-source v2019-02-19.15
+
+# libtool (GNU libtool) 2.4.7
+# Provide generalized library-building support services.
+# Written by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+
+# Copyright (C) 1996-2019, 2021-2022 Free Software Foundation, Inc.
+# This is free software; see the source for copying conditions. There is NO
+# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# GNU Libtool 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 of the License, or
+# (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+PROGRAM=libtool
+PACKAGE=libtool
+VERSION="2.4.7 Debian-2.4.7-4"
+package_revision=2.4.7
+
+
+## ------ ##
+## Usage. ##
+## ------ ##
+
+# Run './libtool --help' for help with using this script from the
+# command line.
+
+
+## ------------------------------- ##
+## User overridable command paths. ##
+## ------------------------------- ##
+
+# After configure completes, it has a better idea of some of the
+# shell tools we need than the defaults used by the functions shared
+# with bootstrap, so set those here where they can still be over-
+# ridden by the user, but otherwise take precedence.
+
+: ${AUTOCONF="autoconf"}
+: ${AUTOMAKE="automake"}
+
+
+## -------------------------- ##
+## Source external libraries. ##
+## -------------------------- ##
+
+# Much of our low-level functionality needs to be sourced from external
+# libraries, which are installed to $pkgauxdir.
+
+# Set a version string for this script.
+scriptversion=2019-02-19.15; # UTC
+
+# General shell script boiler plate, and helper functions.
+# Written by Gary V. Vaughan, 2004
+
+# This is free software. There is NO warranty; not even for
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Copyright (C) 2004-2019, 2021 Bootstrap Authors
+#
+# This file is dual licensed under the terms of the MIT license
+# <https://opensource.org/license/MIT>, and GPL version 2 or later
+# <http://www.gnu.org/licenses/gpl-2.0.html>. You must apply one of
+# these licenses when using or redistributing this software or any of
+# the files within it. See the URLs above, or the file `LICENSE`
+# included in the Bootstrap distribution for the full license texts.
+
+# Please report bugs or propose patches to:
+# <https://github.com/gnulib-modules/bootstrap/issues>
+
+
+## ------ ##
+## Usage. ##
+## ------ ##
+
+# Evaluate this file near the top of your script to gain access to
+# the functions and variables defined here:
+#
+# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh
+#
+# If you need to override any of the default environment variable
+# settings, do that before evaluating this file.
+
+
+## -------------------- ##
+## Shell normalisation. ##
+## -------------------- ##
+
+# Some shells need a little help to be as Bourne compatible as possible.
+# Before doing anything else, make sure all that help has been provided!
+
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac
+fi
+
+# NLS nuisances: We save the old values in case they are required later.
+_G_user_locale=
+_G_safe_locale=
+for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+do
+ eval "if test set = \"\${$_G_var+set}\"; then
+ save_$_G_var=\$$_G_var
+ $_G_var=C
+ export $_G_var
+ _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\"
+ _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\"
+ fi"
+done
+# These NLS vars are set unconditionally (bootstrap issue #24). Unset those
+# in case the environment reset is needed later and the $save_* variant is not
+# defined (see the code above).
+LC_ALL=C
+LANGUAGE=C
+export LANGUAGE LC_ALL
+
+# Make sure IFS has a sensible default
+sp=' '
+nl='
+'
+IFS="$sp $nl"
+
+# There are apparently some retarded systems that use ';' as a PATH separator!
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# func_unset VAR
+# --------------
+# Portably unset VAR.
+# In some shells, an 'unset VAR' statement leaves a non-zero return
+# status if VAR is already unset, which might be problematic if the
+# statement is used at the end of a function (thus poisoning its return
+# value) or when 'set -e' is active (causing even a spurious abort of
+# the script in this case).
+func_unset ()
+{
+ { eval $1=; (eval unset $1) >/dev/null 2>&1 && eval unset $1 || : ; }
+}
+
+
+# Make sure CDPATH doesn't cause `cd` commands to output the target dir.
+func_unset CDPATH
+
+# Make sure ${,E,F}GREP behave sanely.
+func_unset GREP_OPTIONS
+
+
+## ------------------------- ##
+## Locate command utilities. ##
+## ------------------------- ##
+
+
+# func_executable_p FILE
+# ----------------------
+# Check that FILE is an executable regular file.
+func_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+}
+
+
+# func_path_progs PROGS_LIST CHECK_FUNC [PATH]
+# --------------------------------------------
+# Search for either a program that responds to --version with output
+# containing "GNU", or else returned by CHECK_FUNC otherwise, by
+# trying all the directories in PATH with each of the elements of
+# PROGS_LIST.
+#
+# CHECK_FUNC should accept the path to a candidate program, and
+# set $func_check_prog_result if it truncates its output less than
+# $_G_path_prog_max characters.
+func_path_progs ()
+{
+ _G_progs_list=$1
+ _G_check_func=$2
+ _G_PATH=${3-"$PATH"}
+
+ _G_path_prog_max=0
+ _G_path_prog_found=false
+ _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:}
+ for _G_dir in $_G_PATH; do
+ IFS=$_G_save_IFS
+ test -z "$_G_dir" && _G_dir=.
+ for _G_prog_name in $_G_progs_list; do
+ for _exeext in '' .EXE; do
+ _G_path_prog=$_G_dir/$_G_prog_name$_exeext
+ func_executable_p "$_G_path_prog" || continue
+ case `"$_G_path_prog" --version 2>&1` in
+ *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;;
+ *) $_G_check_func $_G_path_prog
+ func_path_progs_result=$func_check_prog_result
+ ;;
+ esac
+ $_G_path_prog_found && break 3
+ done
+ done
+ done
+ IFS=$_G_save_IFS
+ test -z "$func_path_progs_result" && {
+ echo "no acceptable sed could be found in \$PATH" >&2
+ exit 1
+ }
+}
+
+
+# We want to be able to use the functions in this file before configure
+# has figured out where the best binaries are kept, which means we have
+# to search for them ourselves - except when the results are already set
+# where we skip the searches.
+
+# Unless the user overrides by setting SED, search the path for either GNU
+# sed, or the sed that truncates its output the least.
+test -z "$SED" && {
+ _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+ for _G_i in 1 2 3 4 5 6 7; do
+ _G_sed_script=$_G_sed_script$nl$_G_sed_script
+ done
+ echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed
+ _G_sed_script=
+
+ func_check_prog_sed ()
+ {
+ _G_path_prog=$1
+
+ _G_count=0
+ printf 0123456789 >conftest.in
+ while :
+ do
+ cat conftest.in conftest.in >conftest.tmp
+ mv conftest.tmp conftest.in
+ cp conftest.in conftest.nl
+ echo '' >> conftest.nl
+ "$_G_path_prog" -f conftest.sed <conftest.nl >conftest.out 2>/dev/null || break
+ diff conftest.out conftest.nl >/dev/null 2>&1 || break
+ _G_count=`expr $_G_count + 1`
+ if test "$_G_count" -gt "$_G_path_prog_max"; then
+ # Best one so far, save it but keep looking for a better one
+ func_check_prog_result=$_G_path_prog
+ _G_path_prog_max=$_G_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test 10 -lt "$_G_count" && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out
+ }
+
+ func_path_progs "sed gsed" func_check_prog_sed "$PATH:/usr/xpg4/bin"
+ rm -f conftest.sed
+ SED=$func_path_progs_result
+}
+
+
+# Unless the user overrides by setting GREP, search the path for either GNU
+# grep, or the grep that truncates its output the least.
+test -z "$GREP" && {
+ func_check_prog_grep ()
+ {
+ _G_path_prog=$1
+
+ _G_count=0
+ _G_path_prog_max=0
+ printf 0123456789 >conftest.in
+ while :
+ do
+ cat conftest.in conftest.in >conftest.tmp
+ mv conftest.tmp conftest.in
+ cp conftest.in conftest.nl
+ echo 'GREP' >> conftest.nl
+ "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' <conftest.nl >conftest.out 2>/dev/null || break
+ diff conftest.out conftest.nl >/dev/null 2>&1 || break
+ _G_count=`expr $_G_count + 1`
+ if test "$_G_count" -gt "$_G_path_prog_max"; then
+ # Best one so far, save it but keep looking for a better one
+ func_check_prog_result=$_G_path_prog
+ _G_path_prog_max=$_G_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test 10 -lt "$_G_count" && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out
+ }
+
+ func_path_progs "grep ggrep" func_check_prog_grep "$PATH:/usr/xpg4/bin"
+ GREP=$func_path_progs_result
+}
+
+
+## ------------------------------- ##
+## User overridable command paths. ##
+## ------------------------------- ##
+
+# All uppercase variable names are used for environment variables. These
+# variables can be overridden by the user before calling a script that
+# uses them if a suitable command of that name is not already available
+# in the command search PATH.
+
+: ${CP="cp -f"}
+: ${ECHO="printf %s\n"}
+: ${EGREP="$GREP -E"}
+: ${FGREP="$GREP -F"}
+: ${LN_S="ln -s"}
+: ${MAKE="make"}
+: ${MKDIR="mkdir"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+: ${SHELL="${CONFIG_SHELL-/bin/sh}"}
+
+
+## -------------------- ##
+## Useful sed snippets. ##
+## -------------------- ##
+
+sed_dirname='s|/[^/]*$||'
+sed_basename='s|^.*/||'
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Same as above, but do not quote variable references.
+sed_double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution that turns a string into a regex matching for the
+# string literally.
+sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g'
+
+# Sed substitution that converts a w32 file name or path
+# that contains forward slashes, into one that contains
+# (escaped) backslashes. A very naive implementation.
+sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g'
+
+# Re-'\' parameter expansions in output of sed_double_quote_subst that
+# were '\'-ed in input to the same. If an odd number of '\' preceded a
+# '$' in input to sed_double_quote_subst, that '$' was protected from
+# expansion. Since each input '\' is now two '\'s, look for any number
+# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'.
+_G_bs='\\'
+_G_bs2='\\\\'
+_G_bs4='\\\\\\\\'
+_G_dollar='\$'
+sed_double_backslash="\
+ s/$_G_bs4/&\\
+/g
+ s/^$_G_bs2$_G_dollar/$_G_bs&/
+ s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g
+ s/\n//g"
+
+# require_check_ifs_backslash
+# ---------------------------
+# Check if we can use backslash as IFS='\' separator, and set
+# $check_ifs_backshlash_broken to ':' or 'false'.
+require_check_ifs_backslash=func_require_check_ifs_backslash
+func_require_check_ifs_backslash ()
+{
+ _G_save_IFS=$IFS
+ IFS='\'
+ _G_check_ifs_backshlash='a\\b'
+ for _G_i in $_G_check_ifs_backshlash
+ do
+ case $_G_i in
+ a)
+ check_ifs_backshlash_broken=false
+ ;;
+ '')
+ break
+ ;;
+ *)
+ check_ifs_backshlash_broken=:
+ break
+ ;;
+ esac
+ done
+ IFS=$_G_save_IFS
+ require_check_ifs_backslash=:
+}
+
+
+## ----------------- ##
+## Global variables. ##
+## ----------------- ##
+
+# Except for the global variables explicitly listed below, the following
+# functions in the '^func_' namespace, and the '^require_' namespace
+# variables initialised in the 'Resource management' section, sourcing
+# this file will not pollute your global namespace with anything
+# else. There's no portable way to scope variables in Bourne shell
+# though, so actually running these functions will sometimes place
+# results into a variable named after the function, and often use
+# temporary variables in the '^_G_' namespace. If you are careful to
+# avoid using those namespaces casually in your sourcing script, things
+# should continue to work as you expect. And, of course, you can freely
+# overwrite any of the functions or variables defined here before
+# calling anything to customize them.
+
+EXIT_SUCCESS=0
+EXIT_FAILURE=1
+EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing.
+EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake.
+
+# Allow overriding, eg assuming that you follow the convention of
+# putting '$debug_cmd' at the start of all your functions, you can get
+# bash to show function call trace with:
+#
+# debug_cmd='echo "${FUNCNAME[0]} $*" >&2' bash your-script-name
+debug_cmd=${debug_cmd-":"}
+exit_cmd=:
+
+# By convention, finish your script with:
+#
+# exit $exit_status
+#
+# so that you can set exit_status to non-zero if you want to indicate
+# something went wrong during execution without actually bailing out at
+# the point of failure.
+exit_status=$EXIT_SUCCESS
+
+# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh
+# is ksh but when the shell is invoked as "sh" and the current value of
+# the _XPG environment variable is not equal to 1 (one), the special
+# positional parameter $0, within a function call, is the name of the
+# function.
+progpath=$0
+
+# The name of this program.
+progname=`$ECHO "$progpath" |$SED "$sed_basename"`
+
+# Make sure we have an absolute progpath for reexecution:
+case $progpath in
+ [\\/]*|[A-Za-z]:\\*) ;;
+ *[\\/]*)
+ progdir=`$ECHO "$progpath" |$SED "$sed_dirname"`
+ progdir=`cd "$progdir" && pwd`
+ progpath=$progdir/$progname
+ ;;
+ *)
+ _G_IFS=$IFS
+ IFS=${PATH_SEPARATOR-:}
+ for progdir in $PATH; do
+ IFS=$_G_IFS
+ test -x "$progdir/$progname" && break
+ done
+ IFS=$_G_IFS
+ test -n "$progdir" || progdir=`pwd`
+ progpath=$progdir/$progname
+ ;;
+esac
+
+
+## ----------------- ##
+## Standard options. ##
+## ----------------- ##
+
+# The following options affect the operation of the functions defined
+# below, and should be set appropriately depending on run-time para-
+# meters passed on the command line.
+
+opt_dry_run=false
+opt_quiet=false
+opt_verbose=false
+
+# Categories 'all' and 'none' are always available. Append any others
+# you will pass as the first argument to func_warning from your own
+# code.
+warning_categories=
+
+# By default, display warnings according to 'opt_warning_types'. Set
+# 'warning_func' to ':' to elide all warnings, or func_fatal_error to
+# treat the next displayed warning as a fatal error.
+warning_func=func_warn_and_continue
+
+# Set to 'all' to display all warnings, 'none' to suppress all
+# warnings, or a space delimited list of some subset of
+# 'warning_categories' to display only the listed warnings.
+opt_warning_types=all
+
+
+## -------------------- ##
+## Resource management. ##
+## -------------------- ##
+
+# This section contains definitions for functions that each ensure a
+# particular resource (a file, or a non-empty configuration variable for
+# example) is available, and if appropriate to extract default values
+# from pertinent package files. Call them using their associated
+# 'require_*' variable to ensure that they are executed, at most, once.
+#
+# It's entirely deliberate that calling these functions can set
+# variables that don't obey the namespace limitations obeyed by the rest
+# of this file, in order that that they be as useful as possible to
+# callers.
+
+
+# require_term_colors
+# -------------------
+# Allow display of bold text on terminals that support it.
+require_term_colors=func_require_term_colors
+func_require_term_colors ()
+{
+ $debug_cmd
+
+ test -t 1 && {
+ # COLORTERM and USE_ANSI_COLORS environment variables take
+ # precedence, because most terminfo databases neglect to describe
+ # whether color sequences are supported.
+ test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"}
+
+ if test 1 = "$USE_ANSI_COLORS"; then
+ # Standard ANSI escape sequences
+ tc_reset=''
+ tc_bold=''; tc_standout=''
+ tc_red=''; tc_green=''
+ tc_blue=''; tc_cyan=''
+ else
+ # Otherwise trust the terminfo database after all.
+ test -n "`tput sgr0 2>/dev/null`" && {
+ tc_reset=`tput sgr0`
+ test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold`
+ tc_standout=$tc_bold
+ test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso`
+ test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1`
+ test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2`
+ test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4`
+ test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5`
+ }
+ fi
+ }
+
+ require_term_colors=:
+}
+
+
+## ----------------- ##
+## Function library. ##
+## ----------------- ##
+
+# This section contains a variety of useful functions to call in your
+# scripts. Take note of the portable wrappers for features provided by
+# some modern shells, which will fall back to slower equivalents on
+# less featureful shells.
+
+
+# func_append VAR VALUE
+# ---------------------
+# Append VALUE onto the existing contents of VAR.
+
+ # We should try to minimise forks, especially on Windows where they are
+ # unreasonably slow, so skip the feature probes when bash or zsh are
+ # being used:
+ if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then
+ : ${_G_HAVE_ARITH_OP="yes"}
+ : ${_G_HAVE_XSI_OPS="yes"}
+ # The += operator was introduced in bash 3.1
+ case $BASH_VERSION in
+ [12].* | 3.0 | 3.0*) ;;
+ *)
+ : ${_G_HAVE_PLUSEQ_OP="yes"}
+ ;;
+ esac
+ fi
+
+ # _G_HAVE_PLUSEQ_OP
+ # Can be empty, in which case the shell is probed, "yes" if += is
+ # useable or anything else if it does not work.
+ test -z "$_G_HAVE_PLUSEQ_OP" \
+ && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \
+ && _G_HAVE_PLUSEQ_OP=yes
+
+if test yes = "$_G_HAVE_PLUSEQ_OP"
+then
+ # This is an XSI compatible shell, allowing a faster implementation...
+ eval 'func_append ()
+ {
+ $debug_cmd
+
+ eval "$1+=\$2"
+ }'
+else
+ # ...otherwise fall back to using expr, which is often a shell builtin.
+ func_append ()
+ {
+ $debug_cmd
+
+ eval "$1=\$$1\$2"
+ }
+fi
+
+
+# func_append_quoted VAR VALUE
+# ----------------------------
+# Quote VALUE and append to the end of shell variable VAR, separated
+# by a space.
+if test yes = "$_G_HAVE_PLUSEQ_OP"; then
+ eval 'func_append_quoted ()
+ {
+ $debug_cmd
+
+ func_quote_arg pretty "$2"
+ eval "$1+=\\ \$func_quote_arg_result"
+ }'
+else
+ func_append_quoted ()
+ {
+ $debug_cmd
+
+ func_quote_arg pretty "$2"
+ eval "$1=\$$1\\ \$func_quote_arg_result"
+ }
+fi
+
+
+# func_append_uniq VAR VALUE
+# --------------------------
+# Append unique VALUE onto the existing contents of VAR, assuming
+# entries are delimited by the first character of VALUE. For example:
+#
+# func_append_uniq options " --another-option option-argument"
+#
+# will only append to $options if " --another-option option-argument "
+# is not already present somewhere in $options already (note spaces at
+# each end implied by leading space in second argument).
+func_append_uniq ()
+{
+ $debug_cmd
+
+ eval _G_current_value='`$ECHO $'$1'`'
+ _G_delim=`expr "$2" : '\(.\)'`
+
+ case $_G_delim$_G_current_value$_G_delim in
+ *"$2$_G_delim"*) ;;
+ *) func_append "$@" ;;
+ esac
+}
+
+
+# func_arith TERM...
+# ------------------
+# Set func_arith_result to the result of evaluating TERMs.
+ test -z "$_G_HAVE_ARITH_OP" \
+ && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \
+ && _G_HAVE_ARITH_OP=yes
+
+if test yes = "$_G_HAVE_ARITH_OP"; then
+ eval 'func_arith ()
+ {
+ $debug_cmd
+
+ func_arith_result=$(( $* ))
+ }'
+else
+ func_arith ()
+ {
+ $debug_cmd
+
+ func_arith_result=`expr "$@"`
+ }
+fi
+
+
+# func_basename FILE
+# ------------------
+# Set func_basename_result to FILE with everything up to and including
+# the last / stripped.
+if test yes = "$_G_HAVE_XSI_OPS"; then
+ # If this shell supports suffix pattern removal, then use it to avoid
+ # forking. Hide the definitions single quotes in case the shell chokes
+ # on unsupported syntax...
+ _b='func_basename_result=${1##*/}'
+ _d='case $1 in
+ */*) func_dirname_result=${1%/*}$2 ;;
+ * ) func_dirname_result=$3 ;;
+ esac'
+
+else
+ # ...otherwise fall back to using sed.
+ _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`'
+ _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"`
+ if test "X$func_dirname_result" = "X$1"; then
+ func_dirname_result=$3
+ else
+ func_append func_dirname_result "$2"
+ fi'
+fi
+
+eval 'func_basename ()
+{
+ $debug_cmd
+
+ '"$_b"'
+}'
+
+
+# func_dirname FILE APPEND NONDIR_REPLACEMENT
+# -------------------------------------------
+# Compute the dirname of FILE. If nonempty, add APPEND to the result,
+# otherwise set result to NONDIR_REPLACEMENT.
+eval 'func_dirname ()
+{
+ $debug_cmd
+
+ '"$_d"'
+}'
+
+
+# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT
+# --------------------------------------------------------
+# Perform func_basename and func_dirname in a single function
+# call:
+# dirname: Compute the dirname of FILE. If nonempty,
+# add APPEND to the result, otherwise set result
+# to NONDIR_REPLACEMENT.
+# value returned in "$func_dirname_result"
+# basename: Compute filename of FILE.
+# value retuned in "$func_basename_result"
+# For efficiency, we do not delegate to the functions above but instead
+# duplicate the functionality here.
+eval 'func_dirname_and_basename ()
+{
+ $debug_cmd
+
+ '"$_b"'
+ '"$_d"'
+}'
+
+
+# func_echo ARG...
+# ----------------
+# Echo program name prefixed message.
+func_echo ()
+{
+ $debug_cmd
+
+ _G_message=$*
+
+ func_echo_IFS=$IFS
+ IFS=$nl
+ for _G_line in $_G_message; do
+ IFS=$func_echo_IFS
+ $ECHO "$progname: $_G_line"
+ done
+ IFS=$func_echo_IFS
+}
+
+
+# func_echo_all ARG...
+# --------------------
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+ $ECHO "$*"
+}
+
+
+# func_echo_infix_1 INFIX ARG...
+# ------------------------------
+# Echo program name, followed by INFIX on the first line, with any
+# additional lines not showing INFIX.
+func_echo_infix_1 ()
+{
+ $debug_cmd
+
+ $require_term_colors
+
+ _G_infix=$1; shift
+ _G_indent=$_G_infix
+ _G_prefix="$progname: $_G_infix: "
+ _G_message=$*
+
+ # Strip color escape sequences before counting printable length
+ for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan"
+ do
+ test -n "$_G_tc" && {
+ _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"`
+ _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"`
+ }
+ done
+ _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes
+
+ func_echo_infix_1_IFS=$IFS
+ IFS=$nl
+ for _G_line in $_G_message; do
+ IFS=$func_echo_infix_1_IFS
+ $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2
+ _G_prefix=$_G_indent
+ done
+ IFS=$func_echo_infix_1_IFS
+}
+
+
+# func_error ARG...
+# -----------------
+# Echo program name prefixed message to standard error.
+func_error ()
+{
+ $debug_cmd
+
+ $require_term_colors
+
+ func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2
+}
+
+
+# func_fatal_error ARG...
+# -----------------------
+# Echo program name prefixed message to standard error, and exit.
+func_fatal_error ()
+{
+ $debug_cmd
+
+ func_error "$*"
+ exit $EXIT_FAILURE
+}
+
+
+# func_grep EXPRESSION FILENAME
+# -----------------------------
+# Check whether EXPRESSION matches any line of FILENAME, without output.
+func_grep ()
+{
+ $debug_cmd
+
+ $GREP "$1" "$2" >/dev/null 2>&1
+}
+
+
+# func_len STRING
+# ---------------
+# Set func_len_result to the length of STRING. STRING may not
+# start with a hyphen.
+ test -z "$_G_HAVE_XSI_OPS" \
+ && (eval 'x=a/b/c;
+ test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \
+ && _G_HAVE_XSI_OPS=yes
+
+if test yes = "$_G_HAVE_XSI_OPS"; then
+ eval 'func_len ()
+ {
+ $debug_cmd
+
+ func_len_result=${#1}
+ }'
+else
+ func_len ()
+ {
+ $debug_cmd
+
+ func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len`
+ }
+fi
+
+
+# func_mkdir_p DIRECTORY-PATH
+# ---------------------------
+# Make sure the entire path to DIRECTORY-PATH is available.
+func_mkdir_p ()
+{
+ $debug_cmd
+
+ _G_directory_path=$1
+ _G_dir_list=
+
+ if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then
+
+ # Protect directory names starting with '-'
+ case $_G_directory_path in
+ -*) _G_directory_path=./$_G_directory_path ;;
+ esac
+
+ # While some portion of DIR does not yet exist...
+ while test ! -d "$_G_directory_path"; do
+ # ...make a list in topmost first order. Use a colon delimited
+ # list incase some portion of path contains whitespace.
+ _G_dir_list=$_G_directory_path:$_G_dir_list
+
+ # If the last portion added has no slash in it, the list is done
+ case $_G_directory_path in */*) ;; *) break ;; esac
+
+ # ...otherwise throw away the child directory and loop
+ _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"`
+ done
+ _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'`
+
+ func_mkdir_p_IFS=$IFS; IFS=:
+ for _G_dir in $_G_dir_list; do
+ IFS=$func_mkdir_p_IFS
+ # mkdir can fail with a 'File exist' error if two processes
+ # try to create one of the directories concurrently. Don't
+ # stop in that case!
+ $MKDIR "$_G_dir" 2>/dev/null || :
+ done
+ IFS=$func_mkdir_p_IFS
+
+ # Bail out if we (or some other process) failed to create a directory.
+ test -d "$_G_directory_path" || \
+ func_fatal_error "Failed to create '$1'"
+ fi
+}
+
+
+# func_mktempdir [BASENAME]
+# -------------------------
+# Make a temporary directory that won't clash with other running
+# libtool processes, and avoids race conditions if possible. If
+# given, BASENAME is the basename for that directory.
+func_mktempdir ()
+{
+ $debug_cmd
+
+ _G_template=${TMPDIR-/tmp}/${1-$progname}
+
+ if test : = "$opt_dry_run"; then
+ # Return a directory name, but don't create it in dry-run mode
+ _G_tmpdir=$_G_template-$$
+ else
+
+ # If mktemp works, use that first and foremost
+ _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null`
+
+ if test ! -d "$_G_tmpdir"; then
+ # Failing that, at least try and use $RANDOM to avoid a race
+ _G_tmpdir=$_G_template-${RANDOM-0}$$
+
+ func_mktempdir_umask=`umask`
+ umask 0077
+ $MKDIR "$_G_tmpdir"
+ umask $func_mktempdir_umask
+ fi
+
+ # If we're not in dry-run mode, bomb out on failure
+ test -d "$_G_tmpdir" || \
+ func_fatal_error "cannot create temporary directory '$_G_tmpdir'"
+ fi
+
+ $ECHO "$_G_tmpdir"
+}
+
+
+# func_normal_abspath PATH
+# ------------------------
+# Remove doubled-up and trailing slashes, "." path components,
+# and cancel out any ".." path components in PATH after making
+# it an absolute path.
+func_normal_abspath ()
+{
+ $debug_cmd
+
+ # These SED scripts presuppose an absolute path with a trailing slash.
+ _G_pathcar='s|^/\([^/]*\).*$|\1|'
+ _G_pathcdr='s|^/[^/]*||'
+ _G_removedotparts=':dotsl
+ s|/\./|/|g
+ t dotsl
+ s|/\.$|/|'
+ _G_collapseslashes='s|/\{1,\}|/|g'
+ _G_finalslash='s|/*$|/|'
+
+ # Start from root dir and reassemble the path.
+ func_normal_abspath_result=
+ func_normal_abspath_tpath=$1
+ func_normal_abspath_altnamespace=
+ case $func_normal_abspath_tpath in
+ "")
+ # Empty path, that just means $cwd.
+ func_stripname '' '/' "`pwd`"
+ func_normal_abspath_result=$func_stripname_result
+ return
+ ;;
+ # The next three entries are used to spot a run of precisely
+ # two leading slashes without using negated character classes;
+ # we take advantage of case's first-match behaviour.
+ ///*)
+ # Unusual form of absolute path, do nothing.
+ ;;
+ //*)
+ # Not necessarily an ordinary path; POSIX reserves leading '//'
+ # and for example Cygwin uses it to access remote file shares
+ # over CIFS/SMB, so we conserve a leading double slash if found.
+ func_normal_abspath_altnamespace=/
+ ;;
+ /*)
+ # Absolute path, do nothing.
+ ;;
+ *)
+ # Relative path, prepend $cwd.
+ func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath
+ ;;
+ esac
+
+ # Cancel out all the simple stuff to save iterations. We also want
+ # the path to end with a slash for ease of parsing, so make sure
+ # there is one (and only one) here.
+ func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+ -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"`
+ while :; do
+ # Processed it all yet?
+ if test / = "$func_normal_abspath_tpath"; then
+ # If we ascended to the root using ".." the result may be empty now.
+ if test -z "$func_normal_abspath_result"; then
+ func_normal_abspath_result=/
+ fi
+ break
+ fi
+ func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \
+ -e "$_G_pathcar"`
+ func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+ -e "$_G_pathcdr"`
+ # Figure out what to do with it
+ case $func_normal_abspath_tcomponent in
+ "")
+ # Trailing empty path component, ignore it.
+ ;;
+ ..)
+ # Parent dir; strip last assembled component from result.
+ func_dirname "$func_normal_abspath_result"
+ func_normal_abspath_result=$func_dirname_result
+ ;;
+ *)
+ # Actual path component, append it.
+ func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent"
+ ;;
+ esac
+ done
+ # Restore leading double-slash if one was found on entry.
+ func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result
+}
+
+
+# func_notquiet ARG...
+# --------------------
+# Echo program name prefixed message only when not in quiet mode.
+func_notquiet ()
+{
+ $debug_cmd
+
+ $opt_quiet || func_echo ${1+"$@"}
+
+ # A bug in bash halts the script if the last line of a function
+ # fails when set -e is in force, so we need another command to
+ # work around that:
+ :
+}
+
+
+# func_relative_path SRCDIR DSTDIR
+# --------------------------------
+# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR.
+func_relative_path ()
+{
+ $debug_cmd
+
+ func_relative_path_result=
+ func_normal_abspath "$1"
+ func_relative_path_tlibdir=$func_normal_abspath_result
+ func_normal_abspath "$2"
+ func_relative_path_tbindir=$func_normal_abspath_result
+
+ # Ascend the tree starting from libdir
+ while :; do
+ # check if we have found a prefix of bindir
+ case $func_relative_path_tbindir in
+ $func_relative_path_tlibdir)
+ # found an exact match
+ func_relative_path_tcancelled=
+ break
+ ;;
+ $func_relative_path_tlibdir*)
+ # found a matching prefix
+ func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir"
+ func_relative_path_tcancelled=$func_stripname_result
+ if test -z "$func_relative_path_result"; then
+ func_relative_path_result=.
+ fi
+ break
+ ;;
+ *)
+ func_dirname $func_relative_path_tlibdir
+ func_relative_path_tlibdir=$func_dirname_result
+ if test -z "$func_relative_path_tlibdir"; then
+ # Have to descend all the way to the root!
+ func_relative_path_result=../$func_relative_path_result
+ func_relative_path_tcancelled=$func_relative_path_tbindir
+ break
+ fi
+ func_relative_path_result=../$func_relative_path_result
+ ;;
+ esac
+ done
+
+ # Now calculate path; take care to avoid doubling-up slashes.
+ func_stripname '' '/' "$func_relative_path_result"
+ func_relative_path_result=$func_stripname_result
+ func_stripname '/' '/' "$func_relative_path_tcancelled"
+ if test -n "$func_stripname_result"; then
+ func_append func_relative_path_result "/$func_stripname_result"
+ fi
+
+ # Normalisation. If bindir is libdir, return '.' else relative path.
+ if test -n "$func_relative_path_result"; then
+ func_stripname './' '' "$func_relative_path_result"
+ func_relative_path_result=$func_stripname_result
+ fi
+
+ test -n "$func_relative_path_result" || func_relative_path_result=.
+
+ :
+}
+
+
+# func_quote_portable EVAL ARG
+# ----------------------------
+# Internal function to portably implement func_quote_arg. Note that we still
+# keep attention to performance here so we as much as possible try to avoid
+# calling sed binary (so far O(N) complexity as long as func_append is O(1)).
+func_quote_portable ()
+{
+ $debug_cmd
+
+ $require_check_ifs_backslash
+
+ func_quote_portable_result=$2
+
+ # one-time-loop (easy break)
+ while true
+ do
+ if $1; then
+ func_quote_portable_result=`$ECHO "$2" | $SED \
+ -e "$sed_double_quote_subst" -e "$sed_double_backslash"`
+ break
+ fi
+
+ # Quote for eval.
+ case $func_quote_portable_result in
+ *[\\\`\"\$]*)
+ # Fallback to sed for $func_check_bs_ifs_broken=:, or when the string
+ # contains the shell wildcard characters.
+ case $check_ifs_backshlash_broken$func_quote_portable_result in
+ :*|*[\[\*\?]*)
+ func_quote_portable_result=`$ECHO "$func_quote_portable_result" \
+ | $SED "$sed_quote_subst"`
+ break
+ ;;
+ esac
+
+ func_quote_portable_old_IFS=$IFS
+ for _G_char in '\' '`' '"' '$'
+ do
+ # STATE($1) PREV($2) SEPARATOR($3)
+ set start "" ""
+ func_quote_portable_result=dummy"$_G_char$func_quote_portable_result$_G_char"dummy
+ IFS=$_G_char
+ for _G_part in $func_quote_portable_result
+ do
+ case $1 in
+ quote)
+ func_append func_quote_portable_result "$3$2"
+ set quote "$_G_part" "\\$_G_char"
+ ;;
+ start)
+ set first "" ""
+ func_quote_portable_result=
+ ;;
+ first)
+ set quote "$_G_part" ""
+ ;;
+ esac
+ done
+ done
+ IFS=$func_quote_portable_old_IFS
+ ;;
+ *) ;;
+ esac
+ break
+ done
+
+ func_quote_portable_unquoted_result=$func_quote_portable_result
+ case $func_quote_portable_result in
+ # double-quote args containing shell metacharacters to delay
+ # word splitting, command substitution and variable expansion
+ # for a subsequent eval.
+ # many bourne shells cannot handle close brackets correctly
+ # in scan sets, so we specify it separately.
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ func_quote_portable_result=\"$func_quote_portable_result\"
+ ;;
+ esac
+}
+
+
+# func_quotefast_eval ARG
+# -----------------------
+# Quote one ARG (internal). This is equivalent to 'func_quote_arg eval ARG',
+# but optimized for speed. Result is stored in $func_quotefast_eval.
+if test xyes = `(x=; printf -v x %q yes; echo x"$x") 2>/dev/null`; then
+ printf -v _GL_test_printf_tilde %q '~'
+ if test '\~' = "$_GL_test_printf_tilde"; then
+ func_quotefast_eval ()
+ {
+ printf -v func_quotefast_eval_result %q "$1"
+ }
+ else
+ # Broken older Bash implementations. Make those faster too if possible.
+ func_quotefast_eval ()
+ {
+ case $1 in
+ '~'*)
+ func_quote_portable false "$1"
+ func_quotefast_eval_result=$func_quote_portable_result
+ ;;
+ *)
+ printf -v func_quotefast_eval_result %q "$1"
+ ;;
+ esac
+ }
+ fi
+else
+ func_quotefast_eval ()
+ {
+ func_quote_portable false "$1"
+ func_quotefast_eval_result=$func_quote_portable_result
+ }
+fi
+
+
+# func_quote_arg MODEs ARG
+# ------------------------
+# Quote one ARG to be evaled later. MODEs argument may contain zero or more
+# specifiers listed below separated by ',' character. This function returns two
+# values:
+# i) func_quote_arg_result
+# double-quoted (when needed), suitable for a subsequent eval
+# ii) func_quote_arg_unquoted_result
+# has all characters that are still active within double
+# quotes backslashified. Available only if 'unquoted' is specified.
+#
+# Available modes:
+# ----------------
+# 'eval' (default)
+# - escape shell special characters
+# 'expand'
+# - the same as 'eval'; but do not quote variable references
+# 'pretty'
+# - request aesthetic output, i.e. '"a b"' instead of 'a\ b'. This might
+# be used later in func_quote to get output like: 'echo "a b"' instead
+# of 'echo a\ b'. This is slower than default on some shells.
+# 'unquoted'
+# - produce also $func_quote_arg_unquoted_result which does not contain
+# wrapping double-quotes.
+#
+# Examples for 'func_quote_arg pretty,unquoted string':
+#
+# string | *_result | *_unquoted_result
+# ------------+-----------------------+-------------------
+# " | \" | \"
+# a b | "a b" | a b
+# "a b" | "\"a b\"" | \"a b\"
+# * | "*" | *
+# z="${x-$y}" | "z=\"\${x-\$y}\"" | z=\"\${x-\$y}\"
+#
+# Examples for 'func_quote_arg pretty,unquoted,expand string':
+#
+# string | *_result | *_unquoted_result
+# --------------+---------------------+--------------------
+# z="${x-$y}" | "z=\"${x-$y}\"" | z=\"${x-$y}\"
+func_quote_arg ()
+{
+ _G_quote_expand=false
+ case ,$1, in
+ *,expand,*)
+ _G_quote_expand=:
+ ;;
+ esac
+
+ case ,$1, in
+ *,pretty,*|*,expand,*|*,unquoted,*)
+ func_quote_portable $_G_quote_expand "$2"
+ func_quote_arg_result=$func_quote_portable_result
+ func_quote_arg_unquoted_result=$func_quote_portable_unquoted_result
+ ;;
+ *)
+ # Faster quote-for-eval for some shells.
+ func_quotefast_eval "$2"
+ func_quote_arg_result=$func_quotefast_eval_result
+ ;;
+ esac
+}
+
+
+# func_quote MODEs ARGs...
+# ------------------------
+# Quote all ARGs to be evaled later and join them into single command. See
+# func_quote_arg's description for more info.
+func_quote ()
+{
+ $debug_cmd
+ _G_func_quote_mode=$1 ; shift
+ func_quote_result=
+ while test 0 -lt $#; do
+ func_quote_arg "$_G_func_quote_mode" "$1"
+ if test -n "$func_quote_result"; then
+ func_append func_quote_result " $func_quote_arg_result"
+ else
+ func_append func_quote_result "$func_quote_arg_result"
+ fi
+ shift
+ done
+}
+
+
+# func_stripname PREFIX SUFFIX NAME
+# ---------------------------------
+# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+if test yes = "$_G_HAVE_XSI_OPS"; then
+ eval 'func_stripname ()
+ {
+ $debug_cmd
+
+ # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are
+ # positional parameters, so assign one to ordinary variable first.
+ func_stripname_result=$3
+ func_stripname_result=${func_stripname_result#"$1"}
+ func_stripname_result=${func_stripname_result%"$2"}
+ }'
+else
+ func_stripname ()
+ {
+ $debug_cmd
+
+ case $2 in
+ .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;;
+ *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;;
+ esac
+ }
+fi
+
+
+# func_show_eval CMD [FAIL_EXP]
+# -----------------------------
+# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is
+# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it.
+func_show_eval ()
+{
+ $debug_cmd
+
+ _G_cmd=$1
+ _G_fail_exp=${2-':'}
+
+ func_quote_arg pretty,expand "$_G_cmd"
+ eval "func_notquiet $func_quote_arg_result"
+
+ $opt_dry_run || {
+ eval "$_G_cmd"
+ _G_status=$?
+ if test 0 -ne "$_G_status"; then
+ eval "(exit $_G_status); $_G_fail_exp"
+ fi
+ }
+}
+
+
+# func_show_eval_locale CMD [FAIL_EXP]
+# ------------------------------------
+# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is
+# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it. Use the saved locale for evaluation.
+func_show_eval_locale ()
+{
+ $debug_cmd
+
+ _G_cmd=$1
+ _G_fail_exp=${2-':'}
+
+ $opt_quiet || {
+ func_quote_arg expand,pretty "$_G_cmd"
+ eval "func_echo $func_quote_arg_result"
+ }
+
+ $opt_dry_run || {
+ eval "$_G_user_locale
+ $_G_cmd"
+ _G_status=$?
+ eval "$_G_safe_locale"
+ if test 0 -ne "$_G_status"; then
+ eval "(exit $_G_status); $_G_fail_exp"
+ fi
+ }
+}
+
+
+# func_tr_sh
+# ----------
+# Turn $1 into a string suitable for a shell variable name.
+# Result is stored in $func_tr_sh_result. All characters
+# not in the set a-zA-Z0-9_ are replaced with '_'. Further,
+# if $1 begins with a digit, a '_' is prepended as well.
+func_tr_sh ()
+{
+ $debug_cmd
+
+ case $1 in
+ [0-9]* | *[!a-zA-Z0-9_]*)
+ func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'`
+ ;;
+ * )
+ func_tr_sh_result=$1
+ ;;
+ esac
+}
+
+
+# func_verbose ARG...
+# -------------------
+# Echo program name prefixed message in verbose mode only.
+func_verbose ()
+{
+ $debug_cmd
+
+ $opt_verbose && func_echo "$*"
+
+ :
+}
+
+
+# func_warn_and_continue ARG...
+# -----------------------------
+# Echo program name prefixed warning message to standard error.
+func_warn_and_continue ()
+{
+ $debug_cmd
+
+ $require_term_colors
+
+ func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2
+}
+
+
+# func_warning CATEGORY ARG...
+# ----------------------------
+# Echo program name prefixed warning message to standard error. Warning
+# messages can be filtered according to CATEGORY, where this function
+# elides messages where CATEGORY is not listed in the global variable
+# 'opt_warning_types'.
+func_warning ()
+{
+ $debug_cmd
+
+ # CATEGORY must be in the warning_categories list!
+ case " $warning_categories " in
+ *" $1 "*) ;;
+ *) func_internal_error "invalid warning category '$1'" ;;
+ esac
+
+ _G_category=$1
+ shift
+
+ case " $opt_warning_types " in
+ *" $_G_category "*) $warning_func ${1+"$@"} ;;
+ esac
+}
+
+
+# func_sort_ver VER1 VER2
+# -----------------------
+# 'sort -V' is not generally available.
+# Note this deviates from the version comparison in automake
+# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a
+# but this should suffice as we won't be specifying old
+# version formats or redundant trailing .0 in bootstrap.conf.
+# If we did want full compatibility then we should probably
+# use m4_version_compare from autoconf.
+func_sort_ver ()
+{
+ $debug_cmd
+
+ printf '%s\n%s\n' "$1" "$2" \
+ | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n
+}
+
+# func_lt_ver PREV CURR
+# ---------------------
+# Return true if PREV and CURR are in the correct order according to
+# func_sort_ver, otherwise false. Use it like this:
+#
+# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..."
+func_lt_ver ()
+{
+ $debug_cmd
+
+ test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q`
+}
+
+
+# Local variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC"
+# time-stamp-time-zone: "UTC"
+# End:
+#! /bin/sh
+
+# A portable, pluggable option parser for Bourne shell.
+# Written by Gary V. Vaughan, 2010
+
+# This is free software. There is NO warranty; not even for
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Copyright (C) 2010-2019, 2021 Bootstrap Authors
+#
+# This file is dual licensed under the terms of the MIT license
+# <https://opensource.org/license/MIT>, and GPL version 2 or later
+# <http://www.gnu.org/licenses/gpl-2.0.html>. You must apply one of
+# these licenses when using or redistributing this software or any of
+# the files within it. See the URLs above, or the file `LICENSE`
+# included in the Bootstrap distribution for the full license texts.
+
+# Please report bugs or propose patches to:
+# <https://github.com/gnulib-modules/bootstrap/issues>
+
+# Set a version string for this script.
+scriptversion=2019-02-19.15; # UTC
+
+
+## ------ ##
+## Usage. ##
+## ------ ##
+
+# This file is a library for parsing options in your shell scripts along
+# with assorted other useful supporting features that you can make use
+# of too.
+#
+# For the simplest scripts you might need only:
+#
+# #!/bin/sh
+# . relative/path/to/funclib.sh
+# . relative/path/to/options-parser
+# scriptversion=1.0
+# func_options ${1+"$@"}
+# eval set dummy "$func_options_result"; shift
+# ...rest of your script...
+#
+# In order for the '--version' option to work, you will need to have a
+# suitably formatted comment like the one at the top of this file
+# starting with '# Written by ' and ending with '# Copyright'.
+#
+# For '-h' and '--help' to work, you will also need a one line
+# description of your script's purpose in a comment directly above the
+# '# Written by ' line, like the one at the top of this file.
+#
+# The default options also support '--debug', which will turn on shell
+# execution tracing (see the comment above debug_cmd below for another
+# use), and '--verbose' and the func_verbose function to allow your script
+# to display verbose messages only when your user has specified
+# '--verbose'.
+#
+# After sourcing this file, you can plug in processing for additional
+# options by amending the variables from the 'Configuration' section
+# below, and following the instructions in the 'Option parsing'
+# section further down.
+
+## -------------- ##
+## Configuration. ##
+## -------------- ##
+
+# You should override these variables in your script after sourcing this
+# file so that they reflect the customisations you have added to the
+# option parser.
+
+# The usage line for option parsing errors and the start of '-h' and
+# '--help' output messages. You can embed shell variables for delayed
+# expansion at the time the message is displayed, but you will need to
+# quote other shell meta-characters carefully to prevent them being
+# expanded when the contents are evaled.
+usage='$progpath [OPTION]...'
+
+# Short help message in response to '-h' and '--help'. Add to this or
+# override it after sourcing this library to reflect the full set of
+# options your script accepts.
+usage_message="\
+ --debug enable verbose shell tracing
+ -W, --warnings=CATEGORY
+ report the warnings falling in CATEGORY [all]
+ -v, --verbose verbosely report processing
+ --version print version information and exit
+ -h, --help print short or long help message and exit
+"
+
+# Additional text appended to 'usage_message' in response to '--help'.
+long_help_message="
+Warning categories include:
+ 'all' show all warnings
+ 'none' turn off all the warnings
+ 'error' warnings are treated as fatal errors"
+
+# Help message printed before fatal option parsing errors.
+fatal_help="Try '\$progname --help' for more information."
+
+
+
+## ------------------------- ##
+## Hook function management. ##
+## ------------------------- ##
+
+# This section contains functions for adding, removing, and running hooks
+# in the main code. A hook is just a list of function names that can be
+# run in order later on.
+
+# func_hookable FUNC_NAME
+# -----------------------
+# Declare that FUNC_NAME will run hooks added with
+# 'func_add_hook FUNC_NAME ...'.
+func_hookable ()
+{
+ $debug_cmd
+
+ func_append hookable_fns " $1"
+}
+
+
+# func_add_hook FUNC_NAME HOOK_FUNC
+# ---------------------------------
+# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must
+# first have been declared "hookable" by a call to 'func_hookable'.
+func_add_hook ()
+{
+ $debug_cmd
+
+ case " $hookable_fns " in
+ *" $1 "*) ;;
+ *) func_fatal_error "'$1' does not accept hook functions." ;;
+ esac
+
+ eval func_append ${1}_hooks '" $2"'
+}
+
+
+# func_remove_hook FUNC_NAME HOOK_FUNC
+# ------------------------------------
+# Remove HOOK_FUNC from the list of hook functions to be called by
+# FUNC_NAME.
+func_remove_hook ()
+{
+ $debug_cmd
+
+ eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`'
+}
+
+
+# func_propagate_result FUNC_NAME_A FUNC_NAME_B
+# ---------------------------------------------
+# If the *_result variable of FUNC_NAME_A _is set_, assign its value to
+# *_result variable of FUNC_NAME_B.
+func_propagate_result ()
+{
+ $debug_cmd
+
+ func_propagate_result_result=:
+ if eval "test \"\${${1}_result+set}\" = set"
+ then
+ eval "${2}_result=\$${1}_result"
+ else
+ func_propagate_result_result=false
+ fi
+}
+
+
+# func_run_hooks FUNC_NAME [ARG]...
+# ---------------------------------
+# Run all hook functions registered to FUNC_NAME.
+# It's assumed that the list of hook functions contains nothing more
+# than a whitespace-delimited list of legal shell function names, and
+# no effort is wasted trying to catch shell meta-characters or preserve
+# whitespace.
+func_run_hooks ()
+{
+ $debug_cmd
+
+ _G_rc_run_hooks=false
+
+ case " $hookable_fns " in
+ *" $1 "*) ;;
+ *) func_fatal_error "'$1' does not support hook functions." ;;
+ esac
+
+ eval _G_hook_fns=\$$1_hooks; shift
+
+ for _G_hook in $_G_hook_fns; do
+ func_unset "${_G_hook}_result"
+ eval $_G_hook '${1+"$@"}'
+ func_propagate_result $_G_hook func_run_hooks
+ if $func_propagate_result_result; then
+ eval set dummy "$func_run_hooks_result"; shift
+ fi
+ done
+}
+
+
+
+## --------------- ##
+## Option parsing. ##
+## --------------- ##
+
+# In order to add your own option parsing hooks, you must accept the
+# full positional parameter list from your hook function. You may remove
+# or edit any options that you action, and then pass back the remaining
+# unprocessed options in '<hooked_function_name>_result', escaped
+# suitably for 'eval'.
+#
+# The '<hooked_function_name>_result' variable is automatically unset
+# before your hook gets called; for best performance, only set the
+# *_result variable when necessary (i.e. don't call the 'func_quote'
+# function unnecessarily because it can be an expensive operation on some
+# machines).
+#
+# Like this:
+#
+# my_options_prep ()
+# {
+# $debug_cmd
+#
+# # Extend the existing usage message.
+# usage_message=$usage_message'
+# -s, --silent don'\''t print informational messages
+# '
+# # No change in '$@' (ignored completely by this hook). Leave
+# # my_options_prep_result variable intact.
+# }
+# func_add_hook func_options_prep my_options_prep
+#
+#
+# my_silent_option ()
+# {
+# $debug_cmd
+#
+# args_changed=false
+#
+# # Note that, for efficiency, we parse as many options as we can
+# # recognise in a loop before passing the remainder back to the
+# # caller on the first unrecognised argument we encounter.
+# while test $# -gt 0; do
+# opt=$1; shift
+# case $opt in
+# --silent|-s) opt_silent=:
+# args_changed=:
+# ;;
+# # Separate non-argument short options:
+# -s*) func_split_short_opt "$_G_opt"
+# set dummy "$func_split_short_opt_name" \
+# "-$func_split_short_opt_arg" ${1+"$@"}
+# shift
+# args_changed=:
+# ;;
+# *) # Make sure the first unrecognised option "$_G_opt"
+# # is added back to "$@" in case we need it later,
+# # if $args_changed was set to 'true'.
+# set dummy "$_G_opt" ${1+"$@"}; shift; break ;;
+# esac
+# done
+#
+# # Only call 'func_quote' here if we processed at least one argument.
+# if $args_changed; then
+# func_quote eval ${1+"$@"}
+# my_silent_option_result=$func_quote_result
+# fi
+# }
+# func_add_hook func_parse_options my_silent_option
+#
+#
+# my_option_validation ()
+# {
+# $debug_cmd
+#
+# $opt_silent && $opt_verbose && func_fatal_help "\
+# '--silent' and '--verbose' options are mutually exclusive."
+# }
+# func_add_hook func_validate_options my_option_validation
+#
+# You'll also need to manually amend $usage_message to reflect the extra
+# options you parse. It's preferable to append if you can, so that
+# multiple option parsing hooks can be added safely.
+
+
+# func_options_finish [ARG]...
+# ----------------------------
+# Finishing the option parse loop (call 'func_options' hooks ATM).
+func_options_finish ()
+{
+ $debug_cmd
+
+ func_run_hooks func_options ${1+"$@"}
+ func_propagate_result func_run_hooks func_options_finish
+}
+
+
+# func_options [ARG]...
+# ---------------------
+# All the functions called inside func_options are hookable. See the
+# individual implementations for details.
+func_hookable func_options
+func_options ()
+{
+ $debug_cmd
+
+ _G_options_quoted=false
+
+ for my_func in options_prep parse_options validate_options options_finish
+ do
+ func_unset func_${my_func}_result
+ func_unset func_run_hooks_result
+ eval func_$my_func '${1+"$@"}'
+ func_propagate_result func_$my_func func_options
+ if $func_propagate_result_result; then
+ eval set dummy "$func_options_result"; shift
+ _G_options_quoted=:
+ fi
+ done
+
+ $_G_options_quoted || {
+ # As we (func_options) are top-level options-parser function and
+ # nobody quoted "$@" for us yet, we need to do it explicitly for
+ # caller.
+ func_quote eval ${1+"$@"}
+ func_options_result=$func_quote_result
+ }
+}
+
+
+# func_options_prep [ARG]...
+# --------------------------
+# All initialisations required before starting the option parse loop.
+# Note that when calling hook functions, we pass through the list of
+# positional parameters. If a hook function modifies that list, and
+# needs to propagate that back to rest of this script, then the complete
+# modified list must be put in 'func_run_hooks_result' before returning.
+func_hookable func_options_prep
+func_options_prep ()
+{
+ $debug_cmd
+
+ # Option defaults:
+ opt_verbose=false
+ opt_warning_types=
+
+ func_run_hooks func_options_prep ${1+"$@"}
+ func_propagate_result func_run_hooks func_options_prep
+}
+
+
+# func_parse_options [ARG]...
+# ---------------------------
+# The main option parsing loop.
+func_hookable func_parse_options
+func_parse_options ()
+{
+ $debug_cmd
+
+ _G_parse_options_requote=false
+ # this just eases exit handling
+ while test $# -gt 0; do
+ # Defer to hook functions for initial option parsing, so they
+ # get priority in the event of reusing an option name.
+ func_run_hooks func_parse_options ${1+"$@"}
+ func_propagate_result func_run_hooks func_parse_options
+ if $func_propagate_result_result; then
+ eval set dummy "$func_parse_options_result"; shift
+ # Even though we may have changed "$@", we passed the "$@" array
+ # down into the hook and it quoted it for us (because we are in
+ # this if-branch). No need to quote it again.
+ _G_parse_options_requote=false
+ fi
+
+ # Break out of the loop if we already parsed every option.
+ test $# -gt 0 || break
+
+ # We expect that one of the options parsed in this function matches
+ # and thus we remove _G_opt from "$@" and need to re-quote.
+ _G_match_parse_options=:
+ _G_opt=$1
+ shift
+ case $_G_opt in
+ --debug|-x) debug_cmd='set -x'
+ func_echo "enabling shell trace mode" >&2
+ $debug_cmd
+ ;;
+
+ --no-warnings|--no-warning|--no-warn)
+ set dummy --warnings none ${1+"$@"}
+ shift
+ ;;
+
+ --warnings|--warning|-W)
+ if test $# = 0 && func_missing_arg $_G_opt; then
+ _G_parse_options_requote=:
+ break
+ fi
+ case " $warning_categories $1" in
+ *" $1 "*)
+ # trailing space prevents matching last $1 above
+ func_append_uniq opt_warning_types " $1"
+ ;;
+ *all)
+ opt_warning_types=$warning_categories
+ ;;
+ *none)
+ opt_warning_types=none
+ warning_func=:
+ ;;
+ *error)
+ opt_warning_types=$warning_categories
+ warning_func=func_fatal_error
+ ;;
+ *)
+ func_fatal_error \
+ "unsupported warning category: '$1'"
+ ;;
+ esac
+ shift
+ ;;
+
+ --verbose|-v) opt_verbose=: ;;
+ --version) func_version ;;
+ -\?|-h) func_usage ;;
+ --help) func_help ;;
+
+ # Separate optargs to long options (plugins may need this):
+ --*=*) func_split_equals "$_G_opt"
+ set dummy "$func_split_equals_lhs" \
+ "$func_split_equals_rhs" ${1+"$@"}
+ shift
+ ;;
+
+ # Separate optargs to short options:
+ -W*)
+ func_split_short_opt "$_G_opt"
+ set dummy "$func_split_short_opt_name" \
+ "$func_split_short_opt_arg" ${1+"$@"}
+ shift
+ ;;
+
+ # Separate non-argument short options:
+ -\?*|-h*|-v*|-x*)
+ func_split_short_opt "$_G_opt"
+ set dummy "$func_split_short_opt_name" \
+ "-$func_split_short_opt_arg" ${1+"$@"}
+ shift
+ ;;
+
+ --) _G_parse_options_requote=: ; break ;;
+ -*) func_fatal_help "unrecognised option: '$_G_opt'" ;;
+ *) set dummy "$_G_opt" ${1+"$@"}; shift
+ _G_match_parse_options=false
+ break
+ ;;
+ esac
+
+ if $_G_match_parse_options; then
+ _G_parse_options_requote=:
+ fi
+ done
+
+ if $_G_parse_options_requote; then
+ # save modified positional parameters for caller
+ func_quote eval ${1+"$@"}
+ func_parse_options_result=$func_quote_result
+ fi
+}
+
+
+# func_validate_options [ARG]...
+# ------------------------------
+# Perform any sanity checks on option settings and/or unconsumed
+# arguments.
+func_hookable func_validate_options
+func_validate_options ()
+{
+ $debug_cmd
+
+ # Display all warnings if -W was not given.
+ test -n "$opt_warning_types" || opt_warning_types=" $warning_categories"
+
+ func_run_hooks func_validate_options ${1+"$@"}
+ func_propagate_result func_run_hooks func_validate_options
+
+ # Bail if the options were screwed!
+ $exit_cmd $EXIT_FAILURE
+}
+
+
+
+## ----------------- ##
+## Helper functions. ##
+## ----------------- ##
+
+# This section contains the helper functions used by the rest of the
+# hookable option parser framework in ascii-betical order.
+
+
+# func_fatal_help ARG...
+# ----------------------
+# Echo program name prefixed message to standard error, followed by
+# a help hint, and exit.
+func_fatal_help ()
+{
+ $debug_cmd
+
+ eval \$ECHO \""Usage: $usage"\"
+ eval \$ECHO \""$fatal_help"\"
+ func_error ${1+"$@"}
+ exit $EXIT_FAILURE
+}
+
+
+# func_help
+# ---------
+# Echo long help message to standard output and exit.
+func_help ()
+{
+ $debug_cmd
+
+ func_usage_message
+ $ECHO "$long_help_message"
+ exit 0
+}
+
+
+# func_missing_arg ARGNAME
+# ------------------------
+# Echo program name prefixed message to standard error and set global
+# exit_cmd.
+func_missing_arg ()
+{
+ $debug_cmd
+
+ func_error "Missing argument for '$1'."
+ exit_cmd=exit
+}
+
+
+# func_split_equals STRING
+# ------------------------
+# Set func_split_equals_lhs and func_split_equals_rhs shell variables
+# after splitting STRING at the '=' sign.
+test -z "$_G_HAVE_XSI_OPS" \
+ && (eval 'x=a/b/c;
+ test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \
+ && _G_HAVE_XSI_OPS=yes
+
+if test yes = "$_G_HAVE_XSI_OPS"
+then
+ # This is an XSI compatible shell, allowing a faster implementation...
+ eval 'func_split_equals ()
+ {
+ $debug_cmd
+
+ func_split_equals_lhs=${1%%=*}
+ func_split_equals_rhs=${1#*=}
+ if test "x$func_split_equals_lhs" = "x$1"; then
+ func_split_equals_rhs=
+ fi
+ }'
+else
+ # ...otherwise fall back to using expr, which is often a shell builtin.
+ func_split_equals ()
+ {
+ $debug_cmd
+
+ func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'`
+ func_split_equals_rhs=
+ test "x$func_split_equals_lhs=" = "x$1" \
+ || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'`
+ }
+fi #func_split_equals
+
+
+# func_split_short_opt SHORTOPT
+# -----------------------------
+# Set func_split_short_opt_name and func_split_short_opt_arg shell
+# variables after splitting SHORTOPT after the 2nd character.
+if test yes = "$_G_HAVE_XSI_OPS"
+then
+ # This is an XSI compatible shell, allowing a faster implementation...
+ eval 'func_split_short_opt ()
+ {
+ $debug_cmd
+
+ func_split_short_opt_arg=${1#??}
+ func_split_short_opt_name=${1%"$func_split_short_opt_arg"}
+ }'
+else
+ # ...otherwise fall back to using expr, which is often a shell builtin.
+ func_split_short_opt ()
+ {
+ $debug_cmd
+
+ func_split_short_opt_name=`expr "x$1" : 'x\(-.\)'`
+ func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'`
+ }
+fi #func_split_short_opt
+
+
+# func_usage
+# ----------
+# Echo short help message to standard output and exit.
+func_usage ()
+{
+ $debug_cmd
+
+ func_usage_message
+ $ECHO "Run '$progname --help |${PAGER-more}' for full usage"
+ exit 0
+}
+
+
+# func_usage_message
+# ------------------
+# Echo short help message to standard output.
+func_usage_message ()
+{
+ $debug_cmd
+
+ eval \$ECHO \""Usage: $usage"\"
+ echo
+ $SED -n 's|^# ||
+ /^Written by/{
+ x;p;x
+ }
+ h
+ /^Written by/q' < "$progpath"
+ echo
+ eval \$ECHO \""$usage_message"\"
+}
+
+
+# func_version
+# ------------
+# Echo version message to standard output and exit.
+# The version message is extracted from the calling file's header
+# comments, with leading '# ' stripped:
+# 1. First display the progname and version
+# 2. Followed by the header comment line matching /^# Written by /
+# 3. Then a blank line followed by the first following line matching
+# /^# Copyright /
+# 4. Immediately followed by any lines between the previous matches,
+# except lines preceding the intervening completely blank line.
+# For example, see the header comments of this file.
+func_version ()
+{
+ $debug_cmd
+
+ printf '%s\n' "$progname $scriptversion"
+ $SED -n '
+ /^# Written by /!b
+ s|^# ||; p; n
+
+ :fwd2blnk
+ /./ {
+ n
+ b fwd2blnk
+ }
+ p; n
+
+ :holdwrnt
+ s|^# ||
+ s|^# *$||
+ /^Copyright /!{
+ /./H
+ n
+ b holdwrnt
+ }
+
+ s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2|
+ G
+ s|\(\n\)\n*|\1|g
+ p; q' < "$progpath"
+
+ exit $?
+}
+
+
+# Local variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-pattern: "30/scriptversion=%:y-%02m-%02d.%02H; # UTC"
+# time-stamp-time-zone: "UTC"
+# End:
+
+# Set a version string.
+scriptversion='(GNU libtool) 2.4.7'
+
+
+# func_echo ARG...
+# ----------------
+# Libtool also displays the current mode in messages, so override
+# funclib.sh func_echo with this custom definition.
+func_echo ()
+{
+ $debug_cmd
+
+ _G_message=$*
+
+ func_echo_IFS=$IFS
+ IFS=$nl
+ for _G_line in $_G_message; do
+ IFS=$func_echo_IFS
+ $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line"
+ done
+ IFS=$func_echo_IFS
+}
+
+
+# func_warning ARG...
+# -------------------
+# Libtool warnings are not categorized, so override funclib.sh
+# func_warning with this simpler definition.
+func_warning ()
+{
+ $debug_cmd
+
+ $warning_func ${1+"$@"}
+}
+
+
+## ---------------- ##
+## Options parsing. ##
+## ---------------- ##
+
+# Hook in the functions to make sure our own options are parsed during
+# the option parsing loop.
+
+usage='$progpath [OPTION]... [MODE-ARG]...'
+
+# Short help message in response to '-h'.
+usage_message="Options:
+ --config show all configuration variables
+ --debug enable verbose shell tracing
+ -n, --dry-run display commands without modifying any files
+ --features display basic configuration information and exit
+ --mode=MODE use operation mode MODE
+ --no-warnings equivalent to '-Wnone'
+ --preserve-dup-deps don't remove duplicate dependency libraries
+ --quiet, --silent don't print informational messages
+ --tag=TAG use configuration variables from tag TAG
+ -v, --verbose print more informational messages than default
+ --version print version information
+ -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all]
+ -h, --help, --help-all print short, long, or detailed help message
+"
+
+# Additional text appended to 'usage_message' in response to '--help'.
+func_help ()
+{
+ $debug_cmd
+
+ func_usage_message
+ $ECHO "$long_help_message
+
+MODE must be one of the following:
+
+ clean remove files from the build directory
+ compile compile a source file into a libtool object
+ execute automatically set library path, then run a program
+ finish complete the installation of libtool libraries
+ install install libraries or executables
+ link create a library or an executable
+ uninstall remove libraries from an installed directory
+
+MODE-ARGS vary depending on the MODE. When passed as first option,
+'--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that.
+Try '$progname --help --mode=MODE' for a more detailed description of MODE.
+
+When reporting a bug, please describe a test case to reproduce it and
+include the following information:
+
+ host-triplet: $host
+ shell: $SHELL
+ compiler: $LTCC
+ compiler flags: $LTCFLAGS
+ linker: $LD (gnu? $with_gnu_ld)
+ version: $progname $scriptversion Debian-2.4.7-4
+ automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q`
+ autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q`
+
+Report bugs to <bug-libtool@gnu.org>.
+GNU libtool home page: <http://www.gnu.org/s/libtool/>.
+General help using GNU software: <http://www.gnu.org/gethelp/>."
+ exit 0
+}
+
+
+# func_lo2o OBJECT-NAME
+# ---------------------
+# Transform OBJECT-NAME from a '.lo' suffix to the platform specific
+# object suffix.
+
+lo2o=s/\\.lo\$/.$objext/
+o2lo=s/\\.$objext\$/.lo/
+
+if test yes = "$_G_HAVE_XSI_OPS"; then
+ eval 'func_lo2o ()
+ {
+ case $1 in
+ *.lo) func_lo2o_result=${1%.lo}.$objext ;;
+ * ) func_lo2o_result=$1 ;;
+ esac
+ }'
+
+ # func_xform LIBOBJ-OR-SOURCE
+ # ---------------------------
+ # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise)
+ # suffix to a '.lo' libtool-object suffix.
+ eval 'func_xform ()
+ {
+ func_xform_result=${1%.*}.lo
+ }'
+else
+ # ...otherwise fall back to using sed.
+ func_lo2o ()
+ {
+ func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"`
+ }
+
+ func_xform ()
+ {
+ func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'`
+ }
+fi
+
+
+# func_fatal_configuration ARG...
+# -------------------------------
+# Echo program name prefixed message to standard error, followed by
+# a configuration failure hint, and exit.
+func_fatal_configuration ()
+{
+ func_fatal_error ${1+"$@"} \
+ "See the $PACKAGE documentation for more information." \
+ "Fatal configuration error."
+}
+
+
+# func_config
+# -----------
+# Display the configuration for all the tags in this script.
+func_config ()
+{
+ re_begincf='^# ### BEGIN LIBTOOL'
+ re_endcf='^# ### END LIBTOOL'
+
+ # Default configuration.
+ $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath"
+
+ # Now print the configurations for the tags.
+ for tagname in $taglist; do
+ $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath"
+ done
+
+ exit $?
+}
+
+
+# func_features
+# -------------
+# Display the features supported by this script.
+func_features ()
+{
+ echo "host: $host"
+ if test yes = "$build_libtool_libs"; then
+ echo "enable shared libraries"
+ else
+ echo "disable shared libraries"
+ fi
+ if test yes = "$build_old_libs"; then
+ echo "enable static libraries"
+ else
+ echo "disable static libraries"
+ fi
+
+ exit $?
+}
+
+
+# func_enable_tag TAGNAME
+# -----------------------
+# Verify that TAGNAME is valid, and either flag an error and exit, or
+# enable the TAGNAME tag. We also add TAGNAME to the global $taglist
+# variable here.
+func_enable_tag ()
+{
+ # Global variable:
+ tagname=$1
+
+ re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$"
+ re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$"
+ sed_extractcf=/$re_begincf/,/$re_endcf/p
+
+ # Validate tagname.
+ case $tagname in
+ *[!-_A-Za-z0-9,/]*)
+ func_fatal_error "invalid tag name: $tagname"
+ ;;
+ esac
+
+ # Don't test for the "default" C tag, as we know it's
+ # there but not specially marked.
+ case $tagname in
+ CC) ;;
+ *)
+ if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then
+ taglist="$taglist $tagname"
+
+ # Evaluate the configuration. Be careful to quote the path
+ # and the sed script, to avoid splitting on whitespace, but
+ # also don't use non-portable quotes within backquotes within
+ # quotes we have to do it in 2 steps:
+ extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"`
+ eval "$extractedcf"
+ else
+ func_error "ignoring unknown tag $tagname"
+ fi
+ ;;
+ esac
+}
+
+
+# func_check_version_match
+# ------------------------
+# Ensure that we are using m4 macros, and libtool script from the same
+# release of libtool.
+func_check_version_match ()
+{
+ if test "$package_revision" != "$macro_revision"; then
+ if test "$VERSION" != "$macro_version"; then
+ if test -z "$macro_version"; then
+ cat >&2 <<_LT_EOF
+$progname: Version mismatch error. This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from an older release.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+ else
+ cat >&2 <<_LT_EOF
+$progname: Version mismatch error. This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from $PACKAGE $macro_version.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+ fi
+ else
+ cat >&2 <<_LT_EOF
+$progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision,
+$progname: but the definition of this LT_INIT comes from revision $macro_revision.
+$progname: You should recreate aclocal.m4 with macros from revision $package_revision
+$progname: of $PACKAGE $VERSION and run autoconf again.
+_LT_EOF
+ fi
+
+ exit $EXIT_MISMATCH
+ fi
+}
+
+
+# libtool_options_prep [ARG]...
+# -----------------------------
+# Preparation for options parsed by libtool.
+libtool_options_prep ()
+{
+ $debug_mode
+
+ # Option defaults:
+ opt_config=false
+ opt_dlopen=
+ opt_dry_run=false
+ opt_help=false
+ opt_mode=
+ opt_preserve_dup_deps=false
+ opt_quiet=false
+
+ nonopt=
+ preserve_args=
+
+ _G_rc_lt_options_prep=:
+
+ _G_rc_lt_options_prep=:
+
+ # Shorthand for --mode=foo, only valid as the first argument
+ case $1 in
+ clean|clea|cle|cl)
+ shift; set dummy --mode clean ${1+"$@"}; shift
+ ;;
+ compile|compil|compi|comp|com|co|c)
+ shift; set dummy --mode compile ${1+"$@"}; shift
+ ;;
+ execute|execut|execu|exec|exe|ex|e)
+ shift; set dummy --mode execute ${1+"$@"}; shift
+ ;;
+ finish|finis|fini|fin|fi|f)
+ shift; set dummy --mode finish ${1+"$@"}; shift
+ ;;
+ install|instal|insta|inst|ins|in|i)
+ shift; set dummy --mode install ${1+"$@"}; shift
+ ;;
+ link|lin|li|l)
+ shift; set dummy --mode link ${1+"$@"}; shift
+ ;;
+ uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u)
+ shift; set dummy --mode uninstall ${1+"$@"}; shift
+ ;;
+ *)
+ _G_rc_lt_options_prep=false
+ ;;
+ esac
+
+ if $_G_rc_lt_options_prep; then
+ # Pass back the list of options.
+ func_quote eval ${1+"$@"}
+ libtool_options_prep_result=$func_quote_result
+ fi
+}
+func_add_hook func_options_prep libtool_options_prep
+
+
+# libtool_parse_options [ARG]...
+# ---------------------------------
+# Provide handling for libtool specific options.
+libtool_parse_options ()
+{
+ $debug_cmd
+
+ _G_rc_lt_parse_options=false
+
+ # Perform our own loop to consume as many options as possible in
+ # each iteration.
+ while test $# -gt 0; do
+ _G_match_lt_parse_options=:
+ _G_opt=$1
+ shift
+ case $_G_opt in
+ --dry-run|--dryrun|-n)
+ opt_dry_run=:
+ ;;
+
+ --config) func_config ;;
+
+ --dlopen|-dlopen)
+ opt_dlopen="${opt_dlopen+$opt_dlopen
+}$1"
+ shift
+ ;;
+
+ --preserve-dup-deps)
+ opt_preserve_dup_deps=: ;;
+
+ --features) func_features ;;
+
+ --finish) set dummy --mode finish ${1+"$@"}; shift ;;
+
+ --help) opt_help=: ;;
+
+ --help-all) opt_help=': help-all' ;;
+
+ --mode) test $# = 0 && func_missing_arg $_G_opt && break
+ opt_mode=$1
+ case $1 in
+ # Valid mode arguments:
+ clean|compile|execute|finish|install|link|relink|uninstall) ;;
+
+ # Catch anything else as an error
+ *) func_error "invalid argument for $_G_opt"
+ exit_cmd=exit
+ break
+ ;;
+ esac
+ shift
+ ;;
+
+ --no-silent|--no-quiet)
+ opt_quiet=false
+ func_append preserve_args " $_G_opt"
+ ;;
+
+ --no-warnings|--no-warning|--no-warn)
+ opt_warning=false
+ func_append preserve_args " $_G_opt"
+ ;;
+
+ --no-verbose)
+ opt_verbose=false
+ func_append preserve_args " $_G_opt"
+ ;;
+
+ --silent|--quiet)
+ opt_quiet=:
+ opt_verbose=false
+ func_append preserve_args " $_G_opt"
+ ;;
+
+ --tag) test $# = 0 && func_missing_arg $_G_opt && break
+ opt_tag=$1
+ func_append preserve_args " $_G_opt $1"
+ func_enable_tag "$1"
+ shift
+ ;;
+
+ --verbose|-v) opt_quiet=false
+ opt_verbose=:
+ func_append preserve_args " $_G_opt"
+ ;;
+
+ # An option not handled by this hook function:
+ *) set dummy "$_G_opt" ${1+"$@"} ; shift
+ _G_match_lt_parse_options=false
+ break
+ ;;
+ esac
+ $_G_match_lt_parse_options && _G_rc_lt_parse_options=:
+ done
+
+ if $_G_rc_lt_parse_options; then
+ # save modified positional parameters for caller
+ func_quote eval ${1+"$@"}
+ libtool_parse_options_result=$func_quote_result
+ fi
+}
+func_add_hook func_parse_options libtool_parse_options
+
+
+
+# libtool_validate_options [ARG]...
+# ---------------------------------
+# Perform any sanity checks on option settings and/or unconsumed
+# arguments.
+libtool_validate_options ()
+{
+ # save first non-option argument
+ if test 0 -lt $#; then
+ nonopt=$1
+ shift
+ fi
+
+ # preserve --debug
+ test : = "$debug_cmd" || func_append preserve_args " --debug"
+
+ case $host in
+ # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452
+ # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788
+ *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*)
+ # don't eliminate duplications in $postdeps and $predeps
+ opt_duplicate_compiler_generated_deps=:
+ ;;
+ *)
+ opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps
+ ;;
+ esac
+
+ $opt_help || {
+ # Sanity checks first:
+ func_check_version_match
+
+ test yes != "$build_libtool_libs" \
+ && test yes != "$build_old_libs" \
+ && func_fatal_configuration "not configured to build any kind of library"
+
+ # Darwin sucks
+ eval std_shrext=\"$shrext_cmds\"
+
+ # Only execute mode is allowed to have -dlopen flags.
+ if test -n "$opt_dlopen" && test execute != "$opt_mode"; then
+ func_error "unrecognized option '-dlopen'"
+ $ECHO "$help" 1>&2
+ exit $EXIT_FAILURE
+ fi
+
+ # Change the help message to a mode-specific one.
+ generic_help=$help
+ help="Try '$progname --help --mode=$opt_mode' for more information."
+ }
+
+ # Pass back the unparsed argument list
+ func_quote eval ${1+"$@"}
+ libtool_validate_options_result=$func_quote_result
+}
+func_add_hook func_validate_options libtool_validate_options
+
+
+# Process options as early as possible so that --help and --version
+# can return quickly.
+func_options ${1+"$@"}
+eval set dummy "$func_options_result"; shift
+
+
+
+## ----------- ##
+## Main. ##
+## ----------- ##
+
+magic='%%%MAGIC variable%%%'
+magic_exe='%%%MAGIC EXE variable%%%'
+
+# Global variables.
+extracted_archives=
+extracted_serial=0
+
+# If this variable is set in any of the actions, the command in it
+# will be execed at the end. This prevents here-documents from being
+# left over by shells.
+exec_cmd=
+
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+
+# func_generated_by_libtool
+# True iff stdin has been generated by Libtool. This function is only
+# a basic sanity check; it will hardly flush out determined imposters.
+func_generated_by_libtool_p ()
+{
+ $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1
+}
+
+# func_lalib_p file
+# True iff FILE is a libtool '.la' library or '.lo' object file.
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_lalib_p ()
+{
+ test -f "$1" &&
+ $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p
+}
+
+# func_lalib_unsafe_p file
+# True iff FILE is a libtool '.la' library or '.lo' object file.
+# This function implements the same check as func_lalib_p without
+# resorting to external programs. To this end, it redirects stdin and
+# closes it afterwards, without saving the original file descriptor.
+# As a safety measure, use it only where a negative result would be
+# fatal anyway. Works if 'file' does not exist.
+func_lalib_unsafe_p ()
+{
+ lalib_p=no
+ if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then
+ for lalib_p_l in 1 2 3 4
+ do
+ read lalib_p_line
+ case $lalib_p_line in
+ \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;;
+ esac
+ done
+ exec 0<&5 5<&-
+ fi
+ test yes = "$lalib_p"
+}
+
+# func_ltwrapper_script_p file
+# True iff FILE is a libtool wrapper script
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_script_p ()
+{
+ test -f "$1" &&
+ $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p
+}
+
+# func_ltwrapper_executable_p file
+# True iff FILE is a libtool wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_executable_p ()
+{
+ func_ltwrapper_exec_suffix=
+ case $1 in
+ *.exe) ;;
+ *) func_ltwrapper_exec_suffix=.exe ;;
+ esac
+ $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1
+}
+
+# func_ltwrapper_scriptname file
+# Assumes file is an ltwrapper_executable
+# uses $file to determine the appropriate filename for a
+# temporary ltwrapper_script.
+func_ltwrapper_scriptname ()
+{
+ func_dirname_and_basename "$1" "" "."
+ func_stripname '' '.exe' "$func_basename_result"
+ func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper
+}
+
+# func_ltwrapper_p file
+# True iff FILE is a libtool wrapper script or wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_p ()
+{
+ func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1"
+}
+
+
+# func_execute_cmds commands fail_cmd
+# Execute tilde-delimited COMMANDS.
+# If FAIL_CMD is given, eval that upon failure.
+# FAIL_CMD may read-access the current command in variable CMD!
+func_execute_cmds ()
+{
+ $debug_cmd
+
+ save_ifs=$IFS; IFS='~'
+ for cmd in $1; do
+ IFS=$sp$nl
+ eval cmd=\"$cmd\"
+ IFS=$save_ifs
+ func_show_eval "$cmd" "${2-:}"
+ done
+ IFS=$save_ifs
+}
+
+
+# func_source file
+# Source FILE, adding directory component if necessary.
+# Note that it is not necessary on cygwin/mingw to append a dot to
+# FILE even if both FILE and FILE.exe exist: automatic-append-.exe
+# behavior happens only for exec(3), not for open(2)! Also, sourcing
+# 'FILE.' does not work on cygwin managed mounts.
+func_source ()
+{
+ $debug_cmd
+
+ case $1 in
+ */* | *\\*) . "$1" ;;
+ *) . "./$1" ;;
+ esac
+}
+
+
+# func_resolve_sysroot PATH
+# Replace a leading = in PATH with a sysroot. Store the result into
+# func_resolve_sysroot_result
+func_resolve_sysroot ()
+{
+ func_resolve_sysroot_result=$1
+ case $func_resolve_sysroot_result in
+ =*)
+ func_stripname '=' '' "$func_resolve_sysroot_result"
+ func_resolve_sysroot_result=$lt_sysroot$func_stripname_result
+ ;;
+ esac
+}
+
+# func_replace_sysroot PATH
+# If PATH begins with the sysroot, replace it with = and
+# store the result into func_replace_sysroot_result.
+func_replace_sysroot ()
+{
+ case $lt_sysroot:$1 in
+ ?*:"$lt_sysroot"*)
+ func_stripname "$lt_sysroot" '' "$1"
+ func_replace_sysroot_result='='$func_stripname_result
+ ;;
+ *)
+ # Including no sysroot.
+ func_replace_sysroot_result=$1
+ ;;
+ esac
+}
+
+# func_infer_tag arg
+# Infer tagged configuration to use if any are available and
+# if one wasn't chosen via the "--tag" command line option.
+# Only attempt this if the compiler in the base compile
+# command doesn't match the default compiler.
+# arg is usually of the form 'gcc ...'
+func_infer_tag ()
+{
+ $debug_cmd
+
+ if test -n "$available_tags" && test -z "$tagname"; then
+ CC_quoted=
+ for arg in $CC; do
+ func_append_quoted CC_quoted "$arg"
+ done
+ CC_expanded=`func_echo_all $CC`
+ CC_quoted_expanded=`func_echo_all $CC_quoted`
+ case $@ in
+ # Blanks in the command may have been stripped by the calling shell,
+ # but not from the CC environment variable when configure was run.
+ " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;;
+ # Blanks at the start of $base_compile will cause this to fail
+ # if we don't check for them as well.
+ *)
+ for z in $available_tags; do
+ if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then
+ # Evaluate the configuration.
+ eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`"
+ CC_quoted=
+ for arg in $CC; do
+ # Double-quote args containing other shell metacharacters.
+ func_append_quoted CC_quoted "$arg"
+ done
+ CC_expanded=`func_echo_all $CC`
+ CC_quoted_expanded=`func_echo_all $CC_quoted`
+ case "$@ " in
+ " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+ " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*)
+ # The compiler in the base compile command matches
+ # the one in the tagged configuration.
+ # Assume this is the tagged configuration we want.
+ tagname=$z
+ break
+ ;;
+ esac
+ fi
+ done
+ # If $tagname still isn't set, then no tagged configuration
+ # was found and let the user know that the "--tag" command
+ # line option must be used.
+ if test -z "$tagname"; then
+ func_echo "unable to infer tagged configuration"
+ func_fatal_error "specify a tag with '--tag'"
+# else
+# func_verbose "using $tagname tagged configuration"
+ fi
+ ;;
+ esac
+ fi
+}
+
+
+
+# func_write_libtool_object output_name pic_name nonpic_name
+# Create a libtool object file (analogous to a ".la" file),
+# but don't create it if we're doing a dry run.
+func_write_libtool_object ()
+{
+ write_libobj=$1
+ if test yes = "$build_libtool_libs"; then
+ write_lobj=\'$2\'
+ else
+ write_lobj=none
+ fi
+
+ if test yes = "$build_old_libs"; then
+ write_oldobj=\'$3\'
+ else
+ write_oldobj=none
+ fi
+
+ $opt_dry_run || {
+ cat >${write_libobj}T <<EOF
+# $write_libobj - a libtool object file
+# Generated by $PROGRAM (GNU $PACKAGE) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
+pic_object=$write_lobj
+
+# Name of the non-PIC object
+non_pic_object=$write_oldobj
+
+EOF
+ $MV "${write_libobj}T" "$write_libobj"
+ }
+}
+
+
+##################################################
+# FILE NAME AND PATH CONVERSION HELPER FUNCTIONS #
+##################################################
+
+# func_convert_core_file_wine_to_w32 ARG
+# Helper function used by file name conversion functions when $build is *nix,
+# and $host is mingw, cygwin, or some other w32 environment. Relies on a
+# correctly configured wine environment available, with the winepath program
+# in $build's $PATH.
+#
+# ARG is the $build file name to be converted to w32 format.
+# Result is available in $func_convert_core_file_wine_to_w32_result, and will
+# be empty on error (or when ARG is empty)
+func_convert_core_file_wine_to_w32 ()
+{
+ $debug_cmd
+
+ func_convert_core_file_wine_to_w32_result=$1
+ if test -n "$1"; then
+ # Unfortunately, winepath does not exit with a non-zero error code, so we
+ # are forced to check the contents of stdout. On the other hand, if the
+ # command is not found, the shell will set an exit code of 127 and print
+ # *an error message* to stdout. So we must check for both error code of
+ # zero AND non-empty stdout, which explains the odd construction:
+ func_convert_core_file_wine_to_w32_tmp=`winepath -w "$1" 2>/dev/null`
+ if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then
+ func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" |
+ $SED -e "$sed_naive_backslashify"`
+ else
+ func_convert_core_file_wine_to_w32_result=
+ fi
+ fi
+}
+# end: func_convert_core_file_wine_to_w32
+
+
+# func_convert_core_path_wine_to_w32 ARG
+# Helper function used by path conversion functions when $build is *nix, and
+# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly
+# configured wine environment available, with the winepath program in $build's
+# $PATH. Assumes ARG has no leading or trailing path separator characters.
+#
+# ARG is path to be converted from $build format to win32.
+# Result is available in $func_convert_core_path_wine_to_w32_result.
+# Unconvertible file (directory) names in ARG are skipped; if no directory names
+# are convertible, then the result may be empty.
+func_convert_core_path_wine_to_w32 ()
+{
+ $debug_cmd
+
+ # unfortunately, winepath doesn't convert paths, only file names
+ func_convert_core_path_wine_to_w32_result=
+ if test -n "$1"; then
+ oldIFS=$IFS
+ IFS=:
+ for func_convert_core_path_wine_to_w32_f in $1; do
+ IFS=$oldIFS
+ func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f"
+ if test -n "$func_convert_core_file_wine_to_w32_result"; then
+ if test -z "$func_convert_core_path_wine_to_w32_result"; then
+ func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result
+ else
+ func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result"
+ fi
+ fi
+ done
+ IFS=$oldIFS
+ fi
+}
+# end: func_convert_core_path_wine_to_w32
+
+
+# func_cygpath ARGS...
+# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when
+# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2)
+# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or
+# (2), returns the Cygwin file name or path in func_cygpath_result (input
+# file name or path is assumed to be in w32 format, as previously converted
+# from $build's *nix or MSYS format). In case (3), returns the w32 file name
+# or path in func_cygpath_result (input file name or path is assumed to be in
+# Cygwin format). Returns an empty string on error.
+#
+# ARGS are passed to cygpath, with the last one being the file name or path to
+# be converted.
+#
+# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH
+# environment variable; do not put it in $PATH.
+func_cygpath ()
+{
+ $debug_cmd
+
+ if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then
+ func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null`
+ if test "$?" -ne 0; then
+ # on failure, ensure result is empty
+ func_cygpath_result=
+ fi
+ else
+ func_cygpath_result=
+ func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'"
+ fi
+}
+#end: func_cygpath
+
+
+# func_convert_core_msys_to_w32 ARG
+# Convert file name or path ARG from MSYS format to w32 format. Return
+# result in func_convert_core_msys_to_w32_result.
+func_convert_core_msys_to_w32 ()
+{
+ $debug_cmd
+
+ # awkward: cmd appends spaces to result
+ func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null |
+ $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"`
+}
+#end: func_convert_core_msys_to_w32
+
+
+# func_convert_file_check ARG1 ARG2
+# Verify that ARG1 (a file name in $build format) was converted to $host
+# format in ARG2. Otherwise, emit an error message, but continue (resetting
+# func_to_host_file_result to ARG1).
+func_convert_file_check ()
+{
+ $debug_cmd
+
+ if test -z "$2" && test -n "$1"; then
+ func_error "Could not determine host file name corresponding to"
+ func_error " '$1'"
+ func_error "Continuing, but uninstalled executables may not work."
+ # Fallback:
+ func_to_host_file_result=$1
+ fi
+}
+# end func_convert_file_check
+
+
+# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH
+# Verify that FROM_PATH (a path in $build format) was converted to $host
+# format in TO_PATH. Otherwise, emit an error message, but continue, resetting
+# func_to_host_file_result to a simplistic fallback value (see below).
+func_convert_path_check ()
+{
+ $debug_cmd
+
+ if test -z "$4" && test -n "$3"; then
+ func_error "Could not determine the host path corresponding to"
+ func_error " '$3'"
+ func_error "Continuing, but uninstalled executables may not work."
+ # Fallback. This is a deliberately simplistic "conversion" and
+ # should not be "improved". See libtool.info.
+ if test "x$1" != "x$2"; then
+ lt_replace_pathsep_chars="s|$1|$2|g"
+ func_to_host_path_result=`echo "$3" |
+ $SED -e "$lt_replace_pathsep_chars"`
+ else
+ func_to_host_path_result=$3
+ fi
+ fi
+}
+# end func_convert_path_check
+
+
+# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG
+# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT
+# and appending REPL if ORIG matches BACKPAT.
+func_convert_path_front_back_pathsep ()
+{
+ $debug_cmd
+
+ case $4 in
+ $1 ) func_to_host_path_result=$3$func_to_host_path_result
+ ;;
+ esac
+ case $4 in
+ $2 ) func_append func_to_host_path_result "$3"
+ ;;
+ esac
+}
+# end func_convert_path_front_back_pathsep
+
+
+##################################################
+# $build to $host FILE NAME CONVERSION FUNCTIONS #
+##################################################
+# invoked via '$to_host_file_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# Result will be available in $func_to_host_file_result.
+
+
+# func_to_host_file ARG
+# Converts the file name ARG from $build format to $host format. Return result
+# in func_to_host_file_result.
+func_to_host_file ()
+{
+ $debug_cmd
+
+ $to_host_file_cmd "$1"
+}
+# end func_to_host_file
+
+
+# func_to_tool_file ARG LAZY
+# converts the file name ARG from $build format to toolchain format. Return
+# result in func_to_tool_file_result. If the conversion in use is listed
+# in (the comma separated) LAZY, no conversion takes place.
+func_to_tool_file ()
+{
+ $debug_cmd
+
+ case ,$2, in
+ *,"$to_tool_file_cmd",*)
+ func_to_tool_file_result=$1
+ ;;
+ *)
+ $to_tool_file_cmd "$1"
+ func_to_tool_file_result=$func_to_host_file_result
+ ;;
+ esac
+}
+# end func_to_tool_file
+
+
+# func_convert_file_noop ARG
+# Copy ARG to func_to_host_file_result.
+func_convert_file_noop ()
+{
+ func_to_host_file_result=$1
+}
+# end func_convert_file_noop
+
+
+# func_convert_file_msys_to_w32 ARG
+# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper. Returns result in
+# func_to_host_file_result.
+func_convert_file_msys_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_file_result=$1
+ if test -n "$1"; then
+ func_convert_core_msys_to_w32 "$1"
+ func_to_host_file_result=$func_convert_core_msys_to_w32_result
+ fi
+ func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_w32
+
+
+# func_convert_file_cygwin_to_w32 ARG
+# Convert file name ARG from Cygwin to w32 format. Returns result in
+# func_to_host_file_result.
+func_convert_file_cygwin_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_file_result=$1
+ if test -n "$1"; then
+ # because $build is cygwin, we call "the" cygpath in $PATH; no need to use
+ # LT_CYGPATH in this case.
+ func_to_host_file_result=`cygpath -m "$1"`
+ fi
+ func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_cygwin_to_w32
+
+
+# func_convert_file_nix_to_w32 ARG
+# Convert file name ARG from *nix to w32 format. Requires a wine environment
+# and a working winepath. Returns result in func_to_host_file_result.
+func_convert_file_nix_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_file_result=$1
+ if test -n "$1"; then
+ func_convert_core_file_wine_to_w32 "$1"
+ func_to_host_file_result=$func_convert_core_file_wine_to_w32_result
+ fi
+ func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_w32
+
+
+# func_convert_file_msys_to_cygwin ARG
+# Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_file_msys_to_cygwin ()
+{
+ $debug_cmd
+
+ func_to_host_file_result=$1
+ if test -n "$1"; then
+ func_convert_core_msys_to_w32 "$1"
+ func_cygpath -u "$func_convert_core_msys_to_w32_result"
+ func_to_host_file_result=$func_cygpath_result
+ fi
+ func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_cygwin
+
+
+# func_convert_file_nix_to_cygwin ARG
+# Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed
+# in a wine environment, working winepath, and LT_CYGPATH set. Returns result
+# in func_to_host_file_result.
+func_convert_file_nix_to_cygwin ()
+{
+ $debug_cmd
+
+ func_to_host_file_result=$1
+ if test -n "$1"; then
+ # convert from *nix to w32, then use cygpath to convert from w32 to cygwin.
+ func_convert_core_file_wine_to_w32 "$1"
+ func_cygpath -u "$func_convert_core_file_wine_to_w32_result"
+ func_to_host_file_result=$func_cygpath_result
+ fi
+ func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_cygwin
+
+
+#############################################
+# $build to $host PATH CONVERSION FUNCTIONS #
+#############################################
+# invoked via '$to_host_path_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# The result will be available in $func_to_host_path_result.
+#
+# Path separators are also converted from $build format to $host format. If
+# ARG begins or ends with a path separator character, it is preserved (but
+# converted to $host format) on output.
+#
+# All path conversion functions are named using the following convention:
+# file name conversion function : func_convert_file_X_to_Y ()
+# path conversion function : func_convert_path_X_to_Y ()
+# where, for any given $build/$host combination the 'X_to_Y' value is the
+# same. If conversion functions are added for new $build/$host combinations,
+# the two new functions must follow this pattern, or func_init_to_host_path_cmd
+# will break.
+
+
+# func_init_to_host_path_cmd
+# Ensures that function "pointer" variable $to_host_path_cmd is set to the
+# appropriate value, based on the value of $to_host_file_cmd.
+to_host_path_cmd=
+func_init_to_host_path_cmd ()
+{
+ $debug_cmd
+
+ if test -z "$to_host_path_cmd"; then
+ func_stripname 'func_convert_file_' '' "$to_host_file_cmd"
+ to_host_path_cmd=func_convert_path_$func_stripname_result
+ fi
+}
+
+
+# func_to_host_path ARG
+# Converts the path ARG from $build format to $host format. Return result
+# in func_to_host_path_result.
+func_to_host_path ()
+{
+ $debug_cmd
+
+ func_init_to_host_path_cmd
+ $to_host_path_cmd "$1"
+}
+# end func_to_host_path
+
+
+# func_convert_path_noop ARG
+# Copy ARG to func_to_host_path_result.
+func_convert_path_noop ()
+{
+ func_to_host_path_result=$1
+}
+# end func_convert_path_noop
+
+
+# func_convert_path_msys_to_w32 ARG
+# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper. Returns result in
+# func_to_host_path_result.
+func_convert_path_msys_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_path_result=$1
+ if test -n "$1"; then
+ # Remove leading and trailing path separator characters from ARG. MSYS
+ # behavior is inconsistent here; cygpath turns them into '.;' and ';.';
+ # and winepath ignores them completely.
+ func_stripname : : "$1"
+ func_to_host_path_tmp1=$func_stripname_result
+ func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+ func_to_host_path_result=$func_convert_core_msys_to_w32_result
+ func_convert_path_check : ";" \
+ "$func_to_host_path_tmp1" "$func_to_host_path_result"
+ func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+ fi
+}
+# end func_convert_path_msys_to_w32
+
+
+# func_convert_path_cygwin_to_w32 ARG
+# Convert path ARG from Cygwin to w32 format. Returns result in
+# func_to_host_file_result.
+func_convert_path_cygwin_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_path_result=$1
+ if test -n "$1"; then
+ # See func_convert_path_msys_to_w32:
+ func_stripname : : "$1"
+ func_to_host_path_tmp1=$func_stripname_result
+ func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"`
+ func_convert_path_check : ";" \
+ "$func_to_host_path_tmp1" "$func_to_host_path_result"
+ func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+ fi
+}
+# end func_convert_path_cygwin_to_w32
+
+
+# func_convert_path_nix_to_w32 ARG
+# Convert path ARG from *nix to w32 format. Requires a wine environment and
+# a working winepath. Returns result in func_to_host_file_result.
+func_convert_path_nix_to_w32 ()
+{
+ $debug_cmd
+
+ func_to_host_path_result=$1
+ if test -n "$1"; then
+ # See func_convert_path_msys_to_w32:
+ func_stripname : : "$1"
+ func_to_host_path_tmp1=$func_stripname_result
+ func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+ func_to_host_path_result=$func_convert_core_path_wine_to_w32_result
+ func_convert_path_check : ";" \
+ "$func_to_host_path_tmp1" "$func_to_host_path_result"
+ func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+ fi
+}
+# end func_convert_path_nix_to_w32
+
+
+# func_convert_path_msys_to_cygwin ARG
+# Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_path_msys_to_cygwin ()
+{
+ $debug_cmd
+
+ func_to_host_path_result=$1
+ if test -n "$1"; then
+ # See func_convert_path_msys_to_w32:
+ func_stripname : : "$1"
+ func_to_host_path_tmp1=$func_stripname_result
+ func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+ func_cygpath -u -p "$func_convert_core_msys_to_w32_result"
+ func_to_host_path_result=$func_cygpath_result
+ func_convert_path_check : : \
+ "$func_to_host_path_tmp1" "$func_to_host_path_result"
+ func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+ fi
+}
+# end func_convert_path_msys_to_cygwin
+
+
+# func_convert_path_nix_to_cygwin ARG
+# Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a
+# a wine environment, working winepath, and LT_CYGPATH set. Returns result in
+# func_to_host_file_result.
+func_convert_path_nix_to_cygwin ()
+{
+ $debug_cmd
+
+ func_to_host_path_result=$1
+ if test -n "$1"; then
+ # Remove leading and trailing path separator characters from
+ # ARG. msys behavior is inconsistent here, cygpath turns them
+ # into '.;' and ';.', and winepath ignores them completely.
+ func_stripname : : "$1"
+ func_to_host_path_tmp1=$func_stripname_result
+ func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+ func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result"
+ func_to_host_path_result=$func_cygpath_result
+ func_convert_path_check : : \
+ "$func_to_host_path_tmp1" "$func_to_host_path_result"
+ func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+ fi
+}
+# end func_convert_path_nix_to_cygwin
+
+
+# func_dll_def_p FILE
+# True iff FILE is a Windows DLL '.def' file.
+# Keep in sync with _LT_DLL_DEF_P in libtool.m4
+func_dll_def_p ()
+{
+ $debug_cmd
+
+ func_dll_def_p_tmp=`$SED -n \
+ -e 's/^[ ]*//' \
+ -e '/^\(;.*\)*$/d' \
+ -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \
+ -e q \
+ "$1"`
+ test DEF = "$func_dll_def_p_tmp"
+}
+
+
+# func_mode_compile arg...
+func_mode_compile ()
+{
+ $debug_cmd
+
+ # Get the compilation command and the source file.
+ base_compile=
+ srcfile=$nonopt # always keep a non-empty value in "srcfile"
+ suppress_opt=yes
+ suppress_output=
+ arg_mode=normal
+ libobj=
+ later=
+ pie_flag=
+
+ for arg
+ do
+ case $arg_mode in
+ arg )
+ # do not "continue". Instead, add this to base_compile
+ lastarg=$arg
+ arg_mode=normal
+ ;;
+
+ target )
+ libobj=$arg
+ arg_mode=normal
+ continue
+ ;;
+
+ normal )
+ # Accept any command-line options.
+ case $arg in
+ -o)
+ test -n "$libobj" && \
+ func_fatal_error "you cannot specify '-o' more than once"
+ arg_mode=target
+ continue
+ ;;
+
+ -pie | -fpie | -fPIE)
+ func_append pie_flag " $arg"
+ continue
+ ;;
+
+ -shared | -static | -prefer-pic | -prefer-non-pic)
+ func_append later " $arg"
+ continue
+ ;;
+
+ -no-suppress)
+ suppress_opt=no
+ continue
+ ;;
+
+ -Xcompiler)
+ arg_mode=arg # the next one goes into the "base_compile" arg list
+ continue # The current "srcfile" will either be retained or
+ ;; # replaced later. I would guess that would be a bug.
+
+ -Wc,*)
+ func_stripname '-Wc,' '' "$arg"
+ args=$func_stripname_result
+ lastarg=
+ save_ifs=$IFS; IFS=,
+ for arg in $args; do
+ IFS=$save_ifs
+ func_append_quoted lastarg "$arg"
+ done
+ IFS=$save_ifs
+ func_stripname ' ' '' "$lastarg"
+ lastarg=$func_stripname_result
+
+ # Add the arguments to base_compile.
+ func_append base_compile " $lastarg"
+ continue
+ ;;
+
+ *)
+ # Accept the current argument as the source file.
+ # The previous "srcfile" becomes the current argument.
+ #
+ lastarg=$srcfile
+ srcfile=$arg
+ ;;
+ esac # case $arg
+ ;;
+ esac # case $arg_mode
+
+ # Aesthetically quote the previous argument.
+ func_append_quoted base_compile "$lastarg"
+ done # for arg
+
+ case $arg_mode in
+ arg)
+ func_fatal_error "you must specify an argument for -Xcompile"
+ ;;
+ target)
+ func_fatal_error "you must specify a target with '-o'"
+ ;;
+ *)
+ # Get the name of the library object.
+ test -z "$libobj" && {
+ func_basename "$srcfile"
+ libobj=$func_basename_result
+ }
+ ;;
+ esac
+
+ # Recognize several different file suffixes.
+ # If the user specifies -o file.o, it is replaced with file.lo
+ case $libobj in
+ *.[cCFSifmso] | \
+ *.ada | *.adb | *.ads | *.asm | \
+ *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \
+ *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup)
+ func_xform "$libobj"
+ libobj=$func_xform_result
+ ;;
+ esac
+
+ case $libobj in
+ *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;;
+ *)
+ func_fatal_error "cannot determine name of library object from '$libobj'"
+ ;;
+ esac
+
+ func_infer_tag $base_compile
+
+ for arg in $later; do
+ case $arg in
+ -shared)
+ test yes = "$build_libtool_libs" \
+ || func_fatal_configuration "cannot build a shared library"
+ build_old_libs=no
+ continue
+ ;;
+
+ -static)
+ build_libtool_libs=no
+ build_old_libs=yes
+ continue
+ ;;
+
+ -prefer-pic)
+ pic_mode=yes
+ continue
+ ;;
+
+ -prefer-non-pic)
+ pic_mode=no
+ continue
+ ;;
+ esac
+ done
+
+ func_quote_arg pretty "$libobj"
+ test "X$libobj" != "X$func_quote_arg_result" \
+ && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \
+ && func_warning "libobj name '$libobj' may not contain shell special characters."
+ func_dirname_and_basename "$obj" "/" ""
+ objname=$func_basename_result
+ xdir=$func_dirname_result
+ lobj=$xdir$objdir/$objname
+
+ test -z "$base_compile" && \
+ func_fatal_help "you must specify a compilation command"
+
+ # Delete any leftover library objects.
+ if test yes = "$build_old_libs"; then
+ removelist="$obj $lobj $libobj ${libobj}T"
+ else
+ removelist="$lobj $libobj ${libobj}T"
+ fi
+
+ # On Cygwin there's no "real" PIC flag so we must build both object types
+ case $host_os in
+ cygwin* | mingw* | pw32* | os2* | cegcc*)
+ pic_mode=default
+ ;;
+ esac
+ if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then
+ # non-PIC code in shared libraries is not supported
+ pic_mode=default
+ fi
+
+ # Calculate the filename of the output object if compiler does
+ # not support -o with -c
+ if test no = "$compiler_c_o"; then
+ output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext
+ lockfile=$output_obj.lock
+ else
+ output_obj=
+ need_locks=no
+ lockfile=
+ fi
+
+ # Lock this critical section if it is needed
+ # We use this script file to make the link, it avoids creating a new file
+ if test yes = "$need_locks"; then
+ until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+ func_echo "Waiting for $lockfile to be removed"
+ sleep 2
+ done
+ elif test warn = "$need_locks"; then
+ if test -f "$lockfile"; then
+ $ECHO "\
+*** ERROR, $lockfile exists and contains:
+`cat $lockfile 2>/dev/null`
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support '-c' and '-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $opt_dry_run || $RM $removelist
+ exit $EXIT_FAILURE
+ fi
+ func_append removelist " $output_obj"
+ $ECHO "$srcfile" > "$lockfile"
+ fi
+
+ $opt_dry_run || $RM $removelist
+ func_append removelist " $lockfile"
+ trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15
+
+ func_to_tool_file "$srcfile" func_convert_file_msys_to_w32
+ srcfile=$func_to_tool_file_result
+ func_quote_arg pretty "$srcfile"
+ qsrcfile=$func_quote_arg_result
+
+ # Only build a PIC object if we are building libtool libraries.
+ if test yes = "$build_libtool_libs"; then
+ # Without this assignment, base_compile gets emptied.
+ fbsd_hideous_sh_bug=$base_compile
+
+ if test no != "$pic_mode"; then
+ command="$base_compile $qsrcfile $pic_flag"
+ else
+ # Don't build PIC code
+ command="$base_compile $qsrcfile"
+ fi
+
+ func_mkdir_p "$xdir$objdir"
+
+ if test -z "$output_obj"; then
+ # Place PIC objects in $objdir
+ func_append command " -o $lobj"
+ fi
+
+ func_show_eval_locale "$command" \
+ 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE'
+
+ if test warn = "$need_locks" &&
+ test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+ $ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support '-c' and '-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $opt_dry_run || $RM $removelist
+ exit $EXIT_FAILURE
+ fi
+
+ # Just move the object if needed, then go on to compile the next one
+ if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then
+ func_show_eval '$MV "$output_obj" "$lobj"' \
+ 'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+ fi
+
+ # Allow error messages only from the first compilation.
+ if test yes = "$suppress_opt"; then
+ suppress_output=' >/dev/null 2>&1'
+ fi
+ fi
+
+ # Only build a position-dependent object if we build old libraries.
+ if test yes = "$build_old_libs"; then
+ if test yes != "$pic_mode"; then
+ # Don't build PIC code
+ command="$base_compile $qsrcfile$pie_flag"
+ else
+ command="$base_compile $qsrcfile $pic_flag"
+ fi
+ if test yes = "$compiler_c_o"; then
+ func_append command " -o $obj"
+ fi
+
+ # Suppress compiler output if we already did a PIC compilation.
+ func_append command "$suppress_output"
+ func_show_eval_locale "$command" \
+ '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE'
+
+ if test warn = "$need_locks" &&
+ test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+ $ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support '-c' and '-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $opt_dry_run || $RM $removelist
+ exit $EXIT_FAILURE
+ fi
+
+ # Just move the object if needed
+ if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then
+ func_show_eval '$MV "$output_obj" "$obj"' \
+ 'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+ fi
+ fi
+
+ $opt_dry_run || {
+ func_write_libtool_object "$libobj" "$objdir/$objname" "$objname"
+
+ # Unlock the critical section if it was locked
+ if test no != "$need_locks"; then
+ removelist=$lockfile
+ $RM "$lockfile"
+ fi
+ }
+
+ exit $EXIT_SUCCESS
+}
+
+$opt_help || {
+ test compile = "$opt_mode" && func_mode_compile ${1+"$@"}
+}
+
+func_mode_help ()
+{
+ # We need to display help for each of the modes.
+ case $opt_mode in
+ "")
+ # Generic help is extracted from the usage comments
+ # at the start of this file.
+ func_help
+ ;;
+
+ clean)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE...
+
+Remove files from the build directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed
+to RM.
+
+If FILE is a libtool library, object or program, all the files associated
+with it are deleted. Otherwise, only FILE itself is deleted using RM."
+ ;;
+
+ compile)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE
+
+Compile a source file into a libtool library object.
+
+This mode accepts the following additional options:
+
+ -o OUTPUT-FILE set the output file name to OUTPUT-FILE
+ -no-suppress do not suppress compiler output for multiple passes
+ -prefer-pic try to build PIC objects only
+ -prefer-non-pic try to build non-PIC objects only
+ -shared do not build a '.o' file suitable for static linking
+ -static only build a '.o' file suitable for static linking
+ -Wc,FLAG
+ -Xcompiler FLAG pass FLAG directly to the compiler
+
+COMPILE-COMMAND is a command to be used in creating a 'standard' object file
+from the given SOURCEFILE.
+
+The output file name is determined by removing the directory component from
+SOURCEFILE, then substituting the C source code suffix '.c' with the
+library object suffix, '.lo'."
+ ;;
+
+ execute)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]...
+
+Automatically set library path, then run a program.
+
+This mode accepts the following additional options:
+
+ -dlopen FILE add the directory containing FILE to the library path
+
+This mode sets the library path environment variable according to '-dlopen'
+flags.
+
+If any of the ARGS are libtool executable wrappers, then they are translated
+into their corresponding uninstalled binary, and any of their required library
+directories are added to the library path.
+
+Then, COMMAND is executed, with ARGS as arguments."
+ ;;
+
+ finish)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=finish [LIBDIR]...
+
+Complete the installation of libtool libraries.
+
+Each LIBDIR is a directory that contains libtool libraries.
+
+The commands that this mode executes may require superuser privileges. Use
+the '--dry-run' option if you just want to see what would be executed."
+ ;;
+
+ install)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND...
+
+Install executables or libraries.
+
+INSTALL-COMMAND is the installation command. The first component should be
+either the 'install' or 'cp' program.
+
+The following components of INSTALL-COMMAND are treated specially:
+
+ -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation
+
+The rest of the components are interpreted as arguments to that command (only
+BSD-compatible install options are recognized)."
+ ;;
+
+ link)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=link LINK-COMMAND...
+
+Link object files or libraries together to form another library, or to
+create an executable program.
+
+LINK-COMMAND is a command using the C compiler that you would use to create
+a program from several object files.
+
+The following components of LINK-COMMAND are treated specially:
+
+ -all-static do not do any dynamic linking at all
+ -avoid-version do not add a version suffix if possible
+ -bindir BINDIR specify path to binaries directory (for systems where
+ libraries must be found in the PATH setting at runtime)
+ -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime
+ -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols
+ -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3)
+ -export-symbols SYMFILE
+ try to export only the symbols listed in SYMFILE
+ -export-symbols-regex REGEX
+ try to export only the symbols matching REGEX
+ -LLIBDIR search LIBDIR for required installed libraries
+ -lNAME OUTPUT-FILE requires the installed library libNAME
+ -module build a library that can dlopened
+ -no-fast-install disable the fast-install mode
+ -no-install link a not-installable executable
+ -no-undefined declare that a library does not refer to external symbols
+ -o OUTPUT-FILE create OUTPUT-FILE from the specified objects
+ -objectlist FILE use a list of object files found in FILE to specify objects
+ -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes)
+ -precious-files-regex REGEX
+ don't remove output files matching REGEX
+ -release RELEASE specify package release information
+ -rpath LIBDIR the created library will eventually be installed in LIBDIR
+ -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries
+ -shared only do dynamic linking of libtool libraries
+ -shrext SUFFIX override the standard shared library file extension
+ -static do not do any dynamic linking of uninstalled libtool libraries
+ -static-libtool-libs
+ do not do any dynamic linking of libtool libraries
+ -version-info CURRENT[:REVISION[:AGE]]
+ specify library version info [each variable defaults to 0]
+ -weak LIBNAME declare that the target provides the LIBNAME interface
+ -Wc,FLAG
+ -Xcompiler FLAG pass linker-specific FLAG directly to the compiler
+ -Wa,FLAG
+ -Xassembler FLAG pass linker-specific FLAG directly to the assembler
+ -Wl,FLAG
+ -Xlinker FLAG pass linker-specific FLAG directly to the linker
+ -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC)
+
+All other options (arguments beginning with '-') are ignored.
+
+Every other argument is treated as a filename. Files ending in '.la' are
+treated as uninstalled libtool libraries, other files are standard or library
+object files.
+
+If the OUTPUT-FILE ends in '.la', then a libtool library is created,
+only library objects ('.lo' files) may be specified, and '-rpath' is
+required, except when creating a convenience library.
+
+If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created
+using 'ar' and 'ranlib', or on Windows using 'lib'.
+
+If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file
+is created, otherwise an executable program is created."
+ ;;
+
+ uninstall)
+ $ECHO \
+"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE...
+
+Remove libraries from an installation directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed
+to RM.
+
+If FILE is a libtool library, all the files associated with it are deleted.
+Otherwise, only FILE itself is deleted using RM."
+ ;;
+
+ *)
+ func_fatal_help "invalid operation mode '$opt_mode'"
+ ;;
+ esac
+
+ echo
+ $ECHO "Try '$progname --help' for more information about other modes."
+}
+
+# Now that we've collected a possible --mode arg, show help if necessary
+if $opt_help; then
+ if test : = "$opt_help"; then
+ func_mode_help
+ else
+ {
+ func_help noexit
+ for opt_mode in compile link execute install finish uninstall clean; do
+ func_mode_help
+ done
+ } | $SED -n '1p; 2,$s/^Usage:/ or: /p'
+ {
+ func_help noexit
+ for opt_mode in compile link execute install finish uninstall clean; do
+ echo
+ func_mode_help
+ done
+ } |
+ $SED '1d
+ /^When reporting/,/^Report/{
+ H
+ d
+ }
+ $x
+ /information about other modes/d
+ /more detailed .*MODE/d
+ s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/'
+ fi
+ exit $?
+fi
+
+
+# func_mode_execute arg...
+func_mode_execute ()
+{
+ $debug_cmd
+
+ # The first argument is the command name.
+ cmd=$nonopt
+ test -z "$cmd" && \
+ func_fatal_help "you must specify a COMMAND"
+
+ # Handle -dlopen flags immediately.
+ for file in $opt_dlopen; do
+ test -f "$file" \
+ || func_fatal_help "'$file' is not a file"
+
+ dir=
+ case $file in
+ *.la)
+ func_resolve_sysroot "$file"
+ file=$func_resolve_sysroot_result
+
+ # Check to see that this really is a libtool archive.
+ func_lalib_unsafe_p "$file" \
+ || func_fatal_help "'$lib' is not a valid libtool archive"
+
+ # Read the libtool library.
+ dlname=
+ library_names=
+ func_source "$file"
+
+ # Skip this library if it cannot be dlopened.
+ if test -z "$dlname"; then
+ # Warn if it was a shared library.
+ test -n "$library_names" && \
+ func_warning "'$file' was not linked with '-export-dynamic'"
+ continue
+ fi
+
+ func_dirname "$file" "" "."
+ dir=$func_dirname_result
+
+ if test -f "$dir/$objdir/$dlname"; then
+ func_append dir "/$objdir"
+ else
+ if test ! -f "$dir/$dlname"; then
+ func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'"
+ fi
+ fi
+ ;;
+
+ *.lo)
+ # Just add the directory containing the .lo file.
+ func_dirname "$file" "" "."
+ dir=$func_dirname_result
+ ;;
+
+ *)
+ func_warning "'-dlopen' is ignored for non-libtool libraries and objects"
+ continue
+ ;;
+ esac
+
+ # Get the absolute pathname.
+ absdir=`cd "$dir" && pwd`
+ test -n "$absdir" && dir=$absdir
+
+ # Now add the directory to shlibpath_var.
+ if eval "test -z \"\$$shlibpath_var\""; then
+ eval "$shlibpath_var=\"\$dir\""
+ else
+ eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\""
+ fi
+ done
+
+ # This variable tells wrapper scripts just to set shlibpath_var
+ # rather than running their programs.
+ libtool_execute_magic=$magic
+
+ # Check if any of the arguments is a wrapper script.
+ args=
+ for file
+ do
+ case $file in
+ -* | *.la | *.lo ) ;;
+ *)
+ # Do a test to see if this is really a libtool program.
+ if func_ltwrapper_script_p "$file"; then
+ func_source "$file"
+ # Transform arg to wrapped name.
+ file=$progdir/$program
+ elif func_ltwrapper_executable_p "$file"; then
+ func_ltwrapper_scriptname "$file"
+ func_source "$func_ltwrapper_scriptname_result"
+ # Transform arg to wrapped name.
+ file=$progdir/$program
+ fi
+ ;;
+ esac
+ # Quote arguments (to preserve shell metacharacters).
+ func_append_quoted args "$file"
+ done
+
+ if $opt_dry_run; then
+ # Display what would be done.
+ if test -n "$shlibpath_var"; then
+ eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\""
+ echo "export $shlibpath_var"
+ fi
+ $ECHO "$cmd$args"
+ exit $EXIT_SUCCESS
+ else
+ if test -n "$shlibpath_var"; then
+ # Export the shlibpath_var.
+ eval "export $shlibpath_var"
+ fi
+
+ # Restore saved environment variables
+ for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+ do
+ eval "if test \"\${save_$lt_var+set}\" = set; then
+ $lt_var=\$save_$lt_var; export $lt_var
+ else
+ $lt_unset $lt_var
+ fi"
+ done
+
+ # Now prepare to actually exec the command.
+ exec_cmd=\$cmd$args
+ fi
+}
+
+test execute = "$opt_mode" && func_mode_execute ${1+"$@"}
+
+
+# func_mode_finish arg...
+func_mode_finish ()
+{
+ $debug_cmd
+
+ libs=
+ libdirs=
+ admincmds=
+
+ for opt in "$nonopt" ${1+"$@"}
+ do
+ if test -d "$opt"; then
+ func_append libdirs " $opt"
+
+ elif test -f "$opt"; then
+ if func_lalib_unsafe_p "$opt"; then
+ func_append libs " $opt"
+ else
+ func_warning "'$opt' is not a valid libtool archive"
+ fi
+
+ else
+ func_fatal_error "invalid argument '$opt'"
+ fi
+ done
+
+ if test -n "$libs"; then
+ if test -n "$lt_sysroot"; then
+ sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"`
+ sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;"
+ else
+ sysroot_cmd=
+ fi
+
+ # Remove sysroot references
+ if $opt_dry_run; then
+ for lib in $libs; do
+ echo "removing references to $lt_sysroot and '=' prefixes from $lib"
+ done
+ else
+ tmpdir=`func_mktempdir`
+ for lib in $libs; do
+ $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \
+ > $tmpdir/tmp-la
+ mv -f $tmpdir/tmp-la $lib
+ done
+ ${RM}r "$tmpdir"
+ fi
+ fi
+
+ if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+ for libdir in $libdirs; do
+ if test -n "$finish_cmds"; then
+ # Do each command in the finish commands.
+ func_execute_cmds "$finish_cmds" 'admincmds="$admincmds
+'"$cmd"'"'
+ fi
+ if test -n "$finish_eval"; then
+ # Do the single finish_eval.
+ eval cmds=\"$finish_eval\"
+ $opt_dry_run || eval "$cmds" || func_append admincmds "
+ $cmds"
+ fi
+ done
+ fi
+
+ # Exit here if they wanted silent mode.
+ $opt_quiet && exit $EXIT_SUCCESS
+
+ if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+ echo "----------------------------------------------------------------------"
+ echo "Libraries have been installed in:"
+ for libdir in $libdirs; do
+ $ECHO " $libdir"
+ done
+ echo
+ echo "If you ever happen to want to link against installed libraries"
+ echo "in a given directory, LIBDIR, you must either use libtool, and"
+ echo "specify the full pathname of the library, or use the '-LLIBDIR'"
+ echo "flag during linking and do at least one of the following:"
+ if test -n "$shlibpath_var"; then
+ echo " - add LIBDIR to the '$shlibpath_var' environment variable"
+ echo " during execution"
+ fi
+ if test -n "$runpath_var"; then
+ echo " - add LIBDIR to the '$runpath_var' environment variable"
+ echo " during linking"
+ fi
+ if test -n "$hardcode_libdir_flag_spec"; then
+ libdir=LIBDIR
+ eval flag=\"$hardcode_libdir_flag_spec\"
+
+ $ECHO " - use the '$flag' linker flag"
+ fi
+ if test -n "$admincmds"; then
+ $ECHO " - have your system administrator run these commands:$admincmds"
+ fi
+ if test -f /etc/ld.so.conf; then
+ echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'"
+ fi
+ echo
+
+ echo "See any operating system documentation about shared libraries for"
+ case $host in
+ solaris2.[6789]|solaris2.1[0-9])
+ echo "more information, such as the ld(1), crle(1) and ld.so(8) manual"
+ echo "pages."
+ ;;
+ *)
+ echo "more information, such as the ld(1) and ld.so(8) manual pages."
+ ;;
+ esac
+ echo "----------------------------------------------------------------------"
+ fi
+ exit $EXIT_SUCCESS
+}
+
+test finish = "$opt_mode" && func_mode_finish ${1+"$@"}
+
+
+# func_mode_install arg...
+func_mode_install ()
+{
+ $debug_cmd
+
+ # There may be an optional sh(1) argument at the beginning of
+ # install_prog (especially on Windows NT).
+ if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" ||
+ # Allow the use of GNU shtool's install command.
+ case $nonopt in *shtool*) :;; *) false;; esac
+ then
+ # Aesthetically quote it.
+ func_quote_arg pretty "$nonopt"
+ install_prog="$func_quote_arg_result "
+ arg=$1
+ shift
+ else
+ install_prog=
+ arg=$nonopt
+ fi
+
+ # The real first argument should be the name of the installation program.
+ # Aesthetically quote it.
+ func_quote_arg pretty "$arg"
+ func_append install_prog "$func_quote_arg_result"
+ install_shared_prog=$install_prog
+ case " $install_prog " in
+ *[\\\ /]cp\ *) install_cp=: ;;
+ *) install_cp=false ;;
+ esac
+
+ # We need to accept at least all the BSD install flags.
+ dest=
+ files=
+ opts=
+ prev=
+ install_type=
+ isdir=false
+ stripme=
+ no_mode=:
+ for arg
+ do
+ arg2=
+ if test -n "$dest"; then
+ func_append files " $dest"
+ dest=$arg
+ continue
+ fi
+
+ case $arg in
+ -d) isdir=: ;;
+ -f)
+ if $install_cp; then :; else
+ prev=$arg
+ fi
+ ;;
+ -g | -m | -o)
+ prev=$arg
+ ;;
+ -s)
+ stripme=" -s"
+ continue
+ ;;
+ -*)
+ ;;
+ *)
+ # If the previous option needed an argument, then skip it.
+ if test -n "$prev"; then
+ if test X-m = "X$prev" && test -n "$install_override_mode"; then
+ arg2=$install_override_mode
+ no_mode=false
+ fi
+ prev=
+ else
+ dest=$arg
+ continue
+ fi
+ ;;
+ esac
+
+ # Aesthetically quote the argument.
+ func_quote_arg pretty "$arg"
+ func_append install_prog " $func_quote_arg_result"
+ if test -n "$arg2"; then
+ func_quote_arg pretty "$arg2"
+ fi
+ func_append install_shared_prog " $func_quote_arg_result"
+ done
+
+ test -z "$install_prog" && \
+ func_fatal_help "you must specify an install program"
+
+ test -n "$prev" && \
+ func_fatal_help "the '$prev' option requires an argument"
+
+ if test -n "$install_override_mode" && $no_mode; then
+ if $install_cp; then :; else
+ func_quote_arg pretty "$install_override_mode"
+ func_append install_shared_prog " -m $func_quote_arg_result"
+ fi
+ fi
+
+ if test -z "$files"; then
+ if test -z "$dest"; then
+ func_fatal_help "no file or destination specified"
+ else
+ func_fatal_help "you must specify a destination"
+ fi
+ fi
+
+ # Strip any trailing slash from the destination.
+ func_stripname '' '/' "$dest"
+ dest=$func_stripname_result
+
+ # Check to see that the destination is a directory.
+ test -d "$dest" && isdir=:
+ if $isdir; then
+ destdir=$dest
+ destname=
+ else
+ func_dirname_and_basename "$dest" "" "."
+ destdir=$func_dirname_result
+ destname=$func_basename_result
+
+ # Not a directory, so check to see that there is only one file specified.
+ set dummy $files; shift
+ test "$#" -gt 1 && \
+ func_fatal_help "'$dest' is not a directory"
+ fi
+ case $destdir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ for file in $files; do
+ case $file in
+ *.lo) ;;
+ *)
+ func_fatal_help "'$destdir' must be an absolute directory name"
+ ;;
+ esac
+ done
+ ;;
+ esac
+
+ # This variable tells wrapper scripts just to set variables rather
+ # than running their programs.
+ libtool_install_magic=$magic
+
+ staticlibs=
+ future_libdirs=
+ current_libdirs=
+ for file in $files; do
+
+ # Do each installation.
+ case $file in
+ *.$libext)
+ # Do the static libraries later.
+ func_append staticlibs " $file"
+ ;;
+
+ *.la)
+ func_resolve_sysroot "$file"
+ file=$func_resolve_sysroot_result
+
+ # Check to see that this really is a libtool archive.
+ func_lalib_unsafe_p "$file" \
+ || func_fatal_help "'$file' is not a valid libtool archive"
+
+ library_names=
+ old_library=
+ relink_command=
+ func_source "$file"
+
+ # Add the libdir to current_libdirs if it is the destination.
+ if test "X$destdir" = "X$libdir"; then
+ case "$current_libdirs " in
+ *" $libdir "*) ;;
+ *) func_append current_libdirs " $libdir" ;;
+ esac
+ else
+ # Note the libdir as a future libdir.
+ case "$future_libdirs " in
+ *" $libdir "*) ;;
+ *) func_append future_libdirs " $libdir" ;;
+ esac
+ fi
+
+ func_dirname "$file" "/" ""
+ dir=$func_dirname_result
+ func_append dir "$objdir"
+
+ if test -n "$relink_command"; then
+ # Determine the prefix the user has applied to our future dir.
+ inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"`
+
+ # Don't allow the user to place us outside of our expected
+ # location b/c this prevents finding dependent libraries that
+ # are installed to the same prefix.
+ # At present, this check doesn't affect windows .dll's that
+ # are installed into $libdir/../bin (currently, that works fine)
+ # but it's something to keep an eye on.
+ test "$inst_prefix_dir" = "$destdir" && \
+ func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir"
+
+ if test -n "$inst_prefix_dir"; then
+ # Stick the inst_prefix_dir data into the link command.
+ relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"`
+ else
+ relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"`
+ fi
+
+ func_warning "relinking '$file'"
+ func_show_eval "$relink_command" \
+ 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"'
+ fi
+
+ # See the names of the shared library.
+ set dummy $library_names; shift
+ if test -n "$1"; then
+ realname=$1
+ shift
+
+ srcname=$realname
+ test -n "$relink_command" && srcname=${realname}T
+
+ # Install the shared library and build the symlinks.
+ func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \
+ 'exit $?'
+ tstripme=$stripme
+ case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ case $realname in
+ *.dll.a)
+ tstripme=
+ ;;
+ esac
+ ;;
+ os2*)
+ case $realname in
+ *_dll.a)
+ tstripme=
+ ;;
+ esac
+ ;;
+ esac
+ if test -n "$tstripme" && test -n "$striplib"; then
+ func_show_eval "$striplib $destdir/$realname" 'exit $?'
+ fi
+
+ if test "$#" -gt 0; then
+ # Delete the old symlinks, and create new ones.
+ # Try 'ln -sf' first, because the 'ln' binary might depend on
+ # the symlink we replace! Solaris /bin/ln does not understand -f,
+ # so we also need to try rm && ln -s.
+ for linkname
+ do
+ test "$linkname" != "$realname" \
+ && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })"
+ done
+ fi
+
+ # Do each command in the postinstall commands.
+ lib=$destdir/$realname
+ func_execute_cmds "$postinstall_cmds" 'exit $?'
+ fi
+
+ # Install the pseudo-library for information purposes.
+ func_basename "$file"
+ name=$func_basename_result
+ instname=$dir/${name}i
+ func_show_eval "$install_prog $instname $destdir/$name" 'exit $?'
+
+ # Maybe install the static library, too.
+ test -n "$old_library" && func_append staticlibs " $dir/$old_library"
+ ;;
+
+ *.lo)
+ # Install (i.e. copy) a libtool object.
+
+ # Figure out destination file name, if it wasn't already specified.
+ if test -n "$destname"; then
+ destfile=$destdir/$destname
+ else
+ func_basename "$file"
+ destfile=$func_basename_result
+ destfile=$destdir/$destfile
+ fi
+
+ # Deduce the name of the destination old-style object file.
+ case $destfile in
+ *.lo)
+ func_lo2o "$destfile"
+ staticdest=$func_lo2o_result
+ ;;
+ *.$objext)
+ staticdest=$destfile
+ destfile=
+ ;;
+ *)
+ func_fatal_help "cannot copy a libtool object to '$destfile'"
+ ;;
+ esac
+
+ # Install the libtool object if requested.
+ test -n "$destfile" && \
+ func_show_eval "$install_prog $file $destfile" 'exit $?'
+
+ # Install the old object if enabled.
+ if test yes = "$build_old_libs"; then
+ # Deduce the name of the old-style object file.
+ func_lo2o "$file"
+ staticobj=$func_lo2o_result
+ func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?'
+ fi
+ exit $EXIT_SUCCESS
+ ;;
+
+ *)
+ # Figure out destination file name, if it wasn't already specified.
+ if test -n "$destname"; then
+ destfile=$destdir/$destname
+ else
+ func_basename "$file"
+ destfile=$func_basename_result
+ destfile=$destdir/$destfile
+ fi
+
+ # If the file is missing, and there is a .exe on the end, strip it
+ # because it is most likely a libtool script we actually want to
+ # install
+ stripped_ext=
+ case $file in
+ *.exe)
+ if test ! -f "$file"; then
+ func_stripname '' '.exe' "$file"
+ file=$func_stripname_result
+ stripped_ext=.exe
+ fi
+ ;;
+ esac
+
+ # Do a test to see if this is really a libtool program.
+ case $host in
+ *cygwin* | *mingw*)
+ if func_ltwrapper_executable_p "$file"; then
+ func_ltwrapper_scriptname "$file"
+ wrapper=$func_ltwrapper_scriptname_result
+ else
+ func_stripname '' '.exe' "$file"
+ wrapper=$func_stripname_result
+ fi
+ ;;
+ *)
+ wrapper=$file
+ ;;
+ esac
+ if func_ltwrapper_script_p "$wrapper"; then
+ notinst_deplibs=
+ relink_command=
+
+ func_source "$wrapper"
+
+ # Check the variables that should have been set.
+ test -z "$generated_by_libtool_version" && \
+ func_fatal_error "invalid libtool wrapper script '$wrapper'"
+
+ finalize=:
+ for lib in $notinst_deplibs; do
+ # Check to see that each library is installed.
+ libdir=
+ if test -f "$lib"; then
+ func_source "$lib"
+ fi
+ libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'`
+ if test -n "$libdir" && test ! -f "$libfile"; then
+ func_warning "'$lib' has not been installed in '$libdir'"
+ finalize=false
+ fi
+ done
+
+ relink_command=
+ func_source "$wrapper"
+
+ outputname=
+ if test no = "$fast_install" && test -n "$relink_command"; then
+ $opt_dry_run || {
+ if $finalize; then
+ tmpdir=`func_mktempdir`
+ func_basename "$file$stripped_ext"
+ file=$func_basename_result
+ outputname=$tmpdir/$file
+ # Replace the output file specification.
+ relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'`
+
+ $opt_quiet || {
+ func_quote_arg expand,pretty "$relink_command"
+ eval "func_echo $func_quote_arg_result"
+ }
+ if eval "$relink_command"; then :
+ else
+ func_error "error: relink '$file' with the above command before installing it"
+ $opt_dry_run || ${RM}r "$tmpdir"
+ continue
+ fi
+ file=$outputname
+ else
+ func_warning "cannot relink '$file'"
+ fi
+ }
+ else
+ # Install the binary that we compiled earlier.
+ file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"`
+ fi
+ fi
+
+ # remove .exe since cygwin /usr/bin/install will append another
+ # one anyway
+ case $install_prog,$host in
+ */usr/bin/install*,*cygwin*)
+ case $file:$destfile in
+ *.exe:*.exe)
+ # this is ok
+ ;;
+ *.exe:*)
+ destfile=$destfile.exe
+ ;;
+ *:*.exe)
+ func_stripname '' '.exe' "$destfile"
+ destfile=$func_stripname_result
+ ;;
+ esac
+ ;;
+ esac
+ func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?'
+ $opt_dry_run || if test -n "$outputname"; then
+ ${RM}r "$tmpdir"
+ fi
+ ;;
+ esac
+ done
+
+ for file in $staticlibs; do
+ func_basename "$file"
+ name=$func_basename_result
+
+ # Set up the ranlib parameters.
+ oldlib=$destdir/$name
+ func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+ tool_oldlib=$func_to_tool_file_result
+
+ func_show_eval "$install_prog \$file \$oldlib" 'exit $?'
+
+ if test -n "$stripme" && test -n "$old_striplib"; then
+ func_show_eval "$old_striplib $tool_oldlib" 'exit $?'
+ fi
+
+ # Do each command in the postinstall commands.
+ func_execute_cmds "$old_postinstall_cmds" 'exit $?'
+ done
+
+ test -n "$future_libdirs" && \
+ func_warning "remember to run '$progname --finish$future_libdirs'"
+
+ if test -n "$current_libdirs"; then
+ # Maybe just do a dry run.
+ $opt_dry_run && current_libdirs=" -n$current_libdirs"
+ exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs'
+ else
+ exit $EXIT_SUCCESS
+ fi
+}
+
+test install = "$opt_mode" && func_mode_install ${1+"$@"}
+
+
+# func_generate_dlsyms outputname originator pic_p
+# Extract symbols from dlprefiles and create ${outputname}S.o with
+# a dlpreopen symbol table.
+func_generate_dlsyms ()
+{
+ $debug_cmd
+
+ my_outputname=$1
+ my_originator=$2
+ my_pic_p=${3-false}
+ my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'`
+ my_dlsyms=
+
+ if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then
+ if test -n "$NM" && test -n "$global_symbol_pipe"; then
+ my_dlsyms=${my_outputname}S.c
+ else
+ func_error "not configured to extract global symbols from dlpreopened files"
+ fi
+ fi
+
+ if test -n "$my_dlsyms"; then
+ case $my_dlsyms in
+ "") ;;
+ *.c)
+ # Discover the nlist of each of the dlfiles.
+ nlist=$output_objdir/$my_outputname.nm
+
+ func_show_eval "$RM $nlist ${nlist}S ${nlist}T"
+
+ # Parse the name list into a source file.
+ func_verbose "creating $output_objdir/$my_dlsyms"
+
+ $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\
+/* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */
+/* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif
+
+#if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4))
+#pragma GCC diagnostic ignored \"-Wstrict-prototypes\"
+#endif
+
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */
+#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE
+/* DATA imports from DLLs on WIN32 can't be const, because runtime
+ relocations are performed -- see ld's documentation on pseudo-relocs. */
+# define LT_DLSYM_CONST
+#elif defined __osf__
+/* This system does not cope well with relocations in const data. */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0)
+
+/* External symbol declarations for the compiler. */\
+"
+
+ if test yes = "$dlself"; then
+ func_verbose "generating symbol list for '$output'"
+
+ $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist"
+
+ # Add our own program objects to the symbol list.
+ progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+ for progfile in $progfiles; do
+ func_to_tool_file "$progfile" func_convert_file_msys_to_w32
+ func_verbose "extracting global C symbols from '$func_to_tool_file_result'"
+ $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'"
+ done
+
+ if test -n "$exclude_expsyms"; then
+ $opt_dry_run || {
+ eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T'
+ eval '$MV "$nlist"T "$nlist"'
+ }
+ fi
+
+ if test -n "$export_symbols_regex"; then
+ $opt_dry_run || {
+ eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T'
+ eval '$MV "$nlist"T "$nlist"'
+ }
+ fi
+
+ # Prepare the list of exported symbols
+ if test -z "$export_symbols"; then
+ export_symbols=$output_objdir/$outputname.exp
+ $opt_dry_run || {
+ $RM $export_symbols
+ eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"'
+ case $host in
+ *cygwin* | *mingw* | *cegcc* )
+ eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+ eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"'
+ ;;
+ esac
+ }
+ else
+ $opt_dry_run || {
+ eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"'
+ eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T'
+ eval '$MV "$nlist"T "$nlist"'
+ case $host in
+ *cygwin* | *mingw* | *cegcc* )
+ eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+ eval 'cat "$nlist" >> "$output_objdir/$outputname.def"'
+ ;;
+ esac
+ }
+ fi
+ fi
+
+ for dlprefile in $dlprefiles; do
+ func_verbose "extracting global C symbols from '$dlprefile'"
+ func_basename "$dlprefile"
+ name=$func_basename_result
+ case $host in
+ *cygwin* | *mingw* | *cegcc* )
+ # if an import library, we need to obtain dlname
+ if func_win32_import_lib_p "$dlprefile"; then
+ func_tr_sh "$dlprefile"
+ eval "curr_lafile=\$libfile_$func_tr_sh_result"
+ dlprefile_dlbasename=
+ if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then
+ # Use subshell, to avoid clobbering current variable values
+ dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"`
+ if test -n "$dlprefile_dlname"; then
+ func_basename "$dlprefile_dlname"
+ dlprefile_dlbasename=$func_basename_result
+ else
+ # no lafile. user explicitly requested -dlpreopen <import library>.
+ $sharedlib_from_linklib_cmd "$dlprefile"
+ dlprefile_dlbasename=$sharedlib_from_linklib_result
+ fi
+ fi
+ $opt_dry_run || {
+ if test -n "$dlprefile_dlbasename"; then
+ eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"'
+ else
+ func_warning "Could not compute DLL name from $name"
+ eval '$ECHO ": $name " >> "$nlist"'
+ fi
+ func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+ eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe |
+ $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'"
+ }
+ else # not an import lib
+ $opt_dry_run || {
+ eval '$ECHO ": $name " >> "$nlist"'
+ func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+ eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+ }
+ fi
+ ;;
+ *)
+ $opt_dry_run || {
+ eval '$ECHO ": $name " >> "$nlist"'
+ func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+ eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+ }
+ ;;
+ esac
+ done
+
+ $opt_dry_run || {
+ # Make sure we have at least an empty file.
+ test -f "$nlist" || : > "$nlist"
+
+ if test -n "$exclude_expsyms"; then
+ $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T
+ $MV "$nlist"T "$nlist"
+ fi
+
+ # Try sorting and uniquifying the output.
+ if $GREP -v "^: " < "$nlist" |
+ if sort -k 3 </dev/null >/dev/null 2>&1; then
+ sort -k 3
+ else
+ sort +2
+ fi |
+ uniq > "$nlist"S; then
+ :
+ else
+ $GREP -v "^: " < "$nlist" > "$nlist"S
+ fi
+
+ if test -f "$nlist"S; then
+ eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"'
+ else
+ echo '/* NONE */' >> "$output_objdir/$my_dlsyms"
+ fi
+
+ func_show_eval '$RM "${nlist}I"'
+ if test -n "$global_symbol_to_import"; then
+ eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I'
+ fi
+
+ echo >> "$output_objdir/$my_dlsyms" "\
+
+/* The mapping between symbol names and symbols. */
+typedef struct {
+ const char *name;
+ void *address;
+} lt_dlsymlist;
+extern LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[];\
+"
+
+ if test -s "$nlist"I; then
+ echo >> "$output_objdir/$my_dlsyms" "\
+static void lt_syminit(void)
+{
+ LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols;
+ for (; symbol->name; ++symbol)
+ {"
+ $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms"
+ echo >> "$output_objdir/$my_dlsyms" "\
+ }
+}"
+ fi
+ echo >> "$output_objdir/$my_dlsyms" "\
+LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[] =
+{ {\"$my_originator\", (void *) 0},"
+
+ if test -s "$nlist"I; then
+ echo >> "$output_objdir/$my_dlsyms" "\
+ {\"@INIT@\", (void *) &lt_syminit},"
+ fi
+
+ case $need_lib_prefix in
+ no)
+ eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms"
+ ;;
+ *)
+ eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms"
+ ;;
+ esac
+ echo >> "$output_objdir/$my_dlsyms" "\
+ {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+ return lt_${my_prefix}_LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif\
+"
+ } # !$opt_dry_run
+
+ pic_flag_for_symtable=
+ case "$compile_command " in
+ *" -static "*) ;;
+ *)
+ case $host in
+ # compiling the symbol table file with pic_flag works around
+ # a FreeBSD bug that causes programs to crash when -lm is
+ # linked before any other PIC object. But we must not use
+ # pic_flag when linking with -static. The problem exists in
+ # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1.
+ *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*)
+ pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;;
+ *-*-hpux*)
+ pic_flag_for_symtable=" $pic_flag" ;;
+ *)
+ $my_pic_p && pic_flag_for_symtable=" $pic_flag"
+ ;;
+ esac
+ ;;
+ esac
+ symtab_cflags=
+ for arg in $LTCFLAGS; do
+ case $arg in
+ -pie | -fpie | -fPIE) ;;
+ *) func_append symtab_cflags " $arg" ;;
+ esac
+ done
+
+ # Now compile the dynamic symbol file.
+ func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?'
+
+ # Clean up the generated files.
+ func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"'
+
+ # Transform the symbol file into the correct name.
+ symfileobj=$output_objdir/${my_outputname}S.$objext
+ case $host in
+ *cygwin* | *mingw* | *cegcc* )
+ if test -f "$output_objdir/$my_outputname.def"; then
+ compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+ finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+ else
+ compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+ finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+ fi
+ ;;
+ *)
+ compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+ finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+ ;;
+ esac
+ ;;
+ *)
+ func_fatal_error "unknown suffix for '$my_dlsyms'"
+ ;;
+ esac
+ else
+ # We keep going just in case the user didn't refer to
+ # lt_preloaded_symbols. The linker will fail if global_symbol_pipe
+ # really was required.
+
+ # Nullify the symbol file.
+ compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"`
+ finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"`
+ fi
+}
+
+# func_cygming_gnu_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is a GNU/binutils-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_gnu_implib_p ()
+{
+ $debug_cmd
+
+ func_to_tool_file "$1" func_convert_file_msys_to_w32
+ func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'`
+ test -n "$func_cygming_gnu_implib_tmp"
+}
+
+# func_cygming_ms_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is an MS-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_ms_implib_p ()
+{
+ $debug_cmd
+
+ func_to_tool_file "$1" func_convert_file_msys_to_w32
+ func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'`
+ test -n "$func_cygming_ms_implib_tmp"
+}
+
+# func_win32_libid arg
+# return the library type of file 'arg'
+#
+# Need a lot of goo to handle *both* DLLs and import libs
+# Has to be a shell function in order to 'eat' the argument
+# that is supplied when $file_magic_command is called.
+# Despite the name, also deal with 64 bit binaries.
+func_win32_libid ()
+{
+ $debug_cmd
+
+ win32_libid_type=unknown
+ win32_fileres=`file -L $1 2>/dev/null`
+ case $win32_fileres in
+ *ar\ archive\ import\ library*) # definitely import
+ win32_libid_type="x86 archive import"
+ ;;
+ *ar\ archive*) # could be an import, or static
+ # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD.
+ if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null |
+ $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then
+ case $nm_interface in
+ "MS dumpbin")
+ if func_cygming_ms_implib_p "$1" ||
+ func_cygming_gnu_implib_p "$1"
+ then
+ win32_nmres=import
+ else
+ win32_nmres=
+ fi
+ ;;
+ *)
+ func_to_tool_file "$1" func_convert_file_msys_to_w32
+ win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" |
+ $SED -n -e '
+ 1,100{
+ / I /{
+ s|.*|import|
+ p
+ q
+ }
+ }'`
+ ;;
+ esac
+ case $win32_nmres in
+ import*) win32_libid_type="x86 archive import";;
+ *) win32_libid_type="x86 archive static";;
+ esac
+ fi
+ ;;
+ *DLL*)
+ win32_libid_type="x86 DLL"
+ ;;
+ *executable*) # but shell scripts are "executable" too...
+ case $win32_fileres in
+ *MS\ Windows\ PE\ Intel*)
+ win32_libid_type="x86 DLL"
+ ;;
+ esac
+ ;;
+ esac
+ $ECHO "$win32_libid_type"
+}
+
+# func_cygming_dll_for_implib ARG
+#
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+# Invoked by eval'ing the libtool variable
+# $sharedlib_from_linklib_cmd
+# Result is available in the variable
+# $sharedlib_from_linklib_result
+func_cygming_dll_for_implib ()
+{
+ $debug_cmd
+
+ sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"`
+}
+
+# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs
+#
+# The is the core of a fallback implementation of a
+# platform-specific function to extract the name of the
+# DLL associated with the specified import library LIBNAME.
+#
+# SECTION_NAME is either .idata$6 or .idata$7, depending
+# on the platform and compiler that created the implib.
+#
+# Echos the name of the DLL associated with the
+# specified import library.
+func_cygming_dll_for_implib_fallback_core ()
+{
+ $debug_cmd
+
+ match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"`
+ $OBJDUMP -s --section "$1" "$2" 2>/dev/null |
+ $SED '/^Contents of section '"$match_literal"':/{
+ # Place marker at beginning of archive member dllname section
+ s/.*/====MARK====/
+ p
+ d
+ }
+ # These lines can sometimes be longer than 43 characters, but
+ # are always uninteresting
+ /:[ ]*file format pe[i]\{,1\}-/d
+ /^In archive [^:]*:/d
+ # Ensure marker is printed
+ /^====MARK====/p
+ # Remove all lines with less than 43 characters
+ /^.\{43\}/!d
+ # From remaining lines, remove first 43 characters
+ s/^.\{43\}//' |
+ $SED -n '
+ # Join marker and all lines until next marker into a single line
+ /^====MARK====/ b para
+ H
+ $ b para
+ b
+ :para
+ x
+ s/\n//g
+ # Remove the marker
+ s/^====MARK====//
+ # Remove trailing dots and whitespace
+ s/[\. \t]*$//
+ # Print
+ /./p' |
+ # we now have a list, one entry per line, of the stringified
+ # contents of the appropriate section of all members of the
+ # archive that possess that section. Heuristic: eliminate
+ # all those that have a first or second character that is
+ # a '.' (that is, objdump's representation of an unprintable
+ # character.) This should work for all archives with less than
+ # 0x302f exports -- but will fail for DLLs whose name actually
+ # begins with a literal '.' or a single character followed by
+ # a '.'.
+ #
+ # Of those that remain, print the first one.
+ $SED -e '/^\./d;/^.\./d;q'
+}
+
+# func_cygming_dll_for_implib_fallback ARG
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+#
+# This fallback implementation is for use when $DLLTOOL
+# does not support the --identify-strict option.
+# Invoked by eval'ing the libtool variable
+# $sharedlib_from_linklib_cmd
+# Result is available in the variable
+# $sharedlib_from_linklib_result
+func_cygming_dll_for_implib_fallback ()
+{
+ $debug_cmd
+
+ if func_cygming_gnu_implib_p "$1"; then
+ # binutils import library
+ sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"`
+ elif func_cygming_ms_implib_p "$1"; then
+ # ms-generated import library
+ sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"`
+ else
+ # unknown
+ sharedlib_from_linklib_result=
+ fi
+}
+
+
+# func_extract_an_archive dir oldlib
+func_extract_an_archive ()
+{
+ $debug_cmd
+
+ f_ex_an_ar_dir=$1; shift
+ f_ex_an_ar_oldlib=$1
+ if test yes = "$lock_old_archive_extraction"; then
+ lockfile=$f_ex_an_ar_oldlib.lock
+ until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+ func_echo "Waiting for $lockfile to be removed"
+ sleep 2
+ done
+ fi
+ func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \
+ 'stat=$?; rm -f "$lockfile"; exit $stat'
+ if test yes = "$lock_old_archive_extraction"; then
+ $opt_dry_run || rm -f "$lockfile"
+ fi
+ if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then
+ :
+ else
+ func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib"
+ fi
+}
+
+
+# func_extract_archives gentop oldlib ...
+func_extract_archives ()
+{
+ $debug_cmd
+
+ my_gentop=$1; shift
+ my_oldlibs=${1+"$@"}
+ my_oldobjs=
+ my_xlib=
+ my_xabs=
+ my_xdir=
+
+ for my_xlib in $my_oldlibs; do
+ # Extract the objects.
+ case $my_xlib in
+ [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;;
+ *) my_xabs=`pwd`"/$my_xlib" ;;
+ esac
+ func_basename "$my_xlib"
+ my_xlib=$func_basename_result
+ my_xlib_u=$my_xlib
+ while :; do
+ case " $extracted_archives " in
+ *" $my_xlib_u "*)
+ func_arith $extracted_serial + 1
+ extracted_serial=$func_arith_result
+ my_xlib_u=lt$extracted_serial-$my_xlib ;;
+ *) break ;;
+ esac
+ done
+ extracted_archives="$extracted_archives $my_xlib_u"
+ my_xdir=$my_gentop/$my_xlib_u
+
+ func_mkdir_p "$my_xdir"
+
+ case $host in
+ *-darwin*)
+ func_verbose "Extracting $my_xabs"
+ # Do not bother doing anything if just a dry run
+ $opt_dry_run || {
+ darwin_orig_dir=`pwd`
+ cd $my_xdir || exit $?
+ darwin_archive=$my_xabs
+ darwin_curdir=`pwd`
+ func_basename "$darwin_archive"
+ darwin_base_archive=$func_basename_result
+ darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true`
+ if test -n "$darwin_arches"; then
+ darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'`
+ darwin_arch=
+ func_verbose "$darwin_base_archive has multiple architectures $darwin_arches"
+ for darwin_arch in $darwin_arches; do
+ func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch"
+ $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive"
+ cd "unfat-$$/$darwin_base_archive-$darwin_arch"
+ func_extract_an_archive "`pwd`" "$darwin_base_archive"
+ cd "$darwin_curdir"
+ $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive"
+ done # $darwin_arches
+ ## Okay now we've a bunch of thin objects, gotta fatten them up :)
+ darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u`
+ darwin_file=
+ darwin_files=
+ for darwin_file in $darwin_filelist; do
+ darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP`
+ $LIPO -create -output "$darwin_file" $darwin_files
+ done # $darwin_filelist
+ $RM -rf unfat-$$
+ cd "$darwin_orig_dir"
+ else
+ cd $darwin_orig_dir
+ func_extract_an_archive "$my_xdir" "$my_xabs"
+ fi # $darwin_arches
+ } # !$opt_dry_run
+ ;;
+ *)
+ func_extract_an_archive "$my_xdir" "$my_xabs"
+ ;;
+ esac
+ my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP`
+ done
+
+ func_extract_archives_result=$my_oldobjs
+}
+
+
+# func_emit_wrapper [arg=no]
+#
+# Emit a libtool wrapper script on stdout.
+# Don't directly open a file because we may want to
+# incorporate the script contents within a cygwin/mingw
+# wrapper executable. Must ONLY be called from within
+# func_mode_link because it depends on a number of variables
+# set therein.
+#
+# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR
+# variable will take. If 'yes', then the emitted script
+# will assume that the directory where it is stored is
+# the $objdir directory. This is a cygwin/mingw-specific
+# behavior.
+func_emit_wrapper ()
+{
+ func_emit_wrapper_arg1=${1-no}
+
+ $ECHO "\
+#! $SHELL
+
+# $output - temporary wrapper script for $objdir/$outputname
+# Generated by $PROGRAM (GNU $PACKAGE) $VERSION
+#
+# The $output program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='$sed_quote_subst'
+
+# Be Bourne compatible
+if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '\${1+\"\$@\"}'='\"\$@\"'
+ setopt NO_GLOB_SUBST
+else
+ case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=\"$relink_command\"
+
+# This environment variable determines our operation mode.
+if test \"\$libtool_install_magic\" = \"$magic\"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='$macro_version'
+ notinst_deplibs='$notinst_deplibs'
+else
+ # When we are sourced in execute mode, \$file and \$ECHO are already set.
+ if test \"\$libtool_execute_magic\" != \"$magic\"; then
+ file=\"\$0\""
+
+ func_quote_arg pretty "$ECHO"
+ qECHO=$func_quote_arg_result
+ $ECHO "\
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+ ECHO=$qECHO
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string "--lt-"
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's $0 value, followed by "$@".
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=\$0
+ shift
+ for lt_opt
+ do
+ case \"\$lt_opt\" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\`
+ test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=.
+ lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\`
+ cat \"\$lt_dump_D/\$lt_dump_F\"
+ exit 0
+ ;;
+ --lt-*)
+ \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n \"\$lt_option_debug\"; then
+ echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\"
+ lt_dump_args_N=\`expr \$lt_dump_args_N + 1\`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+"
+ case $host in
+ # Backslashes separate directories on plain windows
+ *-*-mingw | *-*-os2* | *-cegcc*)
+ $ECHO "\
+ if test -n \"\$lt_option_debug\"; then
+ \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2
+ func_lt_dump_args \${1+\"\$@\"} 1>&2
+ fi
+ exec \"\$progdir\\\\\$program\" \${1+\"\$@\"}
+"
+ ;;
+
+ *)
+ $ECHO "\
+ if test -n \"\$lt_option_debug\"; then
+ \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2
+ func_lt_dump_args \${1+\"\$@\"} 1>&2
+ fi
+ exec \"\$progdir/\$program\" \${1+\"\$@\"}
+"
+ ;;
+ esac
+ $ECHO "\
+ \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from \$@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case \" \$* \" in
+ *\\ --lt-*)
+ for lt_wr_arg
+ do
+ case \$lt_wr_arg in
+ --lt-*) ;;
+ *) set x \"\$@\" \"\$lt_wr_arg\"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core \${1+\"\$@\"}
+}
+
+ # Parse options
+ func_parse_lt_options \"\$0\" \${1+\"\$@\"}
+
+ # Find the directory that this script lives in.
+ thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\`
+ test \"x\$thisdir\" = \"x\$file\" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\`
+ while test -n \"\$file\"; do
+ destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\`
+
+ # If there was a directory component, then change thisdir.
+ if test \"x\$destdir\" != \"x\$file\"; then
+ case \"\$destdir\" in
+ [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;;
+ *) thisdir=\"\$thisdir/\$destdir\" ;;
+ esac
+ fi
+
+ file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\`
+ file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1
+ if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then
+ # special case for '.'
+ if test \"\$thisdir\" = \".\"; then
+ thisdir=\`pwd\`
+ fi
+ # remove .libs from thisdir
+ case \"\$thisdir\" in
+ *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;;
+ $objdir ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=\`cd \"\$thisdir\" && pwd\`
+ test -n \"\$absdir\" && thisdir=\"\$absdir\"
+"
+
+ if test yes = "$fast_install"; then
+ $ECHO "\
+ program=lt-'$outputname'$exeext
+ progdir=\"\$thisdir/$objdir\"
+
+ if test ! -f \"\$progdir/\$program\" ||
+ { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\
+ test \"X\$file\" != \"X\$progdir/\$program\"; }; then
+
+ file=\"\$\$-\$program\"
+
+ if test ! -d \"\$progdir\"; then
+ $MKDIR \"\$progdir\"
+ else
+ $RM \"\$progdir/\$file\"
+ fi"
+
+ $ECHO "\
+
+ # relink executable if necessary
+ if test -n \"\$relink_command\"; then
+ if relink_command_output=\`eval \$relink_command 2>&1\`; then :
+ else
+ \$ECHO \"\$relink_command_output\" >&2
+ $RM \"\$progdir/\$file\"
+ exit 1
+ fi
+ fi
+
+ $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null ||
+ { $RM \"\$progdir/\$program\";
+ $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; }
+ $RM \"\$progdir/\$file\"
+ fi"
+ else
+ $ECHO "\
+ program='$outputname'
+ progdir=\"\$thisdir/$objdir\"
+"
+ fi
+
+ $ECHO "\
+
+ if test -f \"\$progdir/\$program\"; then"
+
+ # fixup the dll searchpath if we need to.
+ #
+ # Fix the DLL searchpath if we need to. Do this before prepending
+ # to shlibpath, because on Windows, both are PATH and uninstalled
+ # libraries must come first.
+ if test -n "$dllsearchpath"; then
+ $ECHO "\
+ # Add the dll search path components to the executable PATH
+ PATH=$dllsearchpath:\$PATH
+"
+ fi
+
+ # Export our shlibpath_var if we have one.
+ if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+ $ECHO "\
+ # Add our own library path to $shlibpath_var
+ $shlibpath_var=\"$temp_rpath\$$shlibpath_var\"
+
+ # Some systems cannot cope with colon-terminated $shlibpath_var
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\`
+
+ export $shlibpath_var
+"
+ fi
+
+ $ECHO "\
+ if test \"\$libtool_execute_magic\" != \"$magic\"; then
+ # Run the actual program with our arguments.
+ func_exec_program \${1+\"\$@\"}
+ fi
+ else
+ # The program doesn't exist.
+ \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2
+ \$ECHO \"This script is just a wrapper for \$program.\" 1>&2
+ \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2
+ exit 1
+ fi
+fi\
+"
+}
+
+
+# func_emit_cwrapperexe_src
+# emit the source code for a wrapper executable on stdout
+# Must ONLY be called from within func_mode_link because
+# it depends on a number of variable set therein.
+func_emit_cwrapperexe_src ()
+{
+ cat <<EOF
+
+/* $cwrappersource - temporary wrapper executable for $objdir/$outputname
+ Generated by $PROGRAM (GNU $PACKAGE) $VERSION
+
+ The $output program cannot be directly executed until all the libtool
+ libraries that it depends on are installed.
+
+ This wrapper executable should never be moved out of the build directory.
+ If it is, it will not operate correctly.
+*/
+EOF
+ cat <<"EOF"
+#ifdef _MSC_VER
+# define _CRT_SECURE_NO_DEPRECATE 1
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <process.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <stdint.h>
+# ifdef __CYGWIN__
+# include <io.h>
+# endif
+#endif
+#include <malloc.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0)
+
+/* declarations of non-ANSI functions */
+#if defined __MINGW32__
+# ifdef __STRICT_ANSI__
+int _putenv (const char *);
+# endif
+#elif defined __CYGWIN__
+# ifdef __STRICT_ANSI__
+char *realpath (const char *, char *);
+int putenv (char *);
+int setenv (const char *, const char *, int);
+# endif
+/* #elif defined other_platform || defined ... */
+#endif
+
+/* portability defines, excluding path handling macros */
+#if defined _MSC_VER
+# define setmode _setmode
+# define stat _stat
+# define chmod _chmod
+# define getcwd _getcwd
+# define putenv _putenv
+# define S_IXUSR _S_IEXEC
+#elif defined __MINGW32__
+# define setmode _setmode
+# define stat _stat
+# define chmod _chmod
+# define getcwd _getcwd
+# define putenv _putenv
+#elif defined __CYGWIN__
+# define HAVE_SETENV
+# define FOPEN_WB "wb"
+/* #elif defined other platforms ... */
+#endif
+
+#if defined PATH_MAX
+# define LT_PATHMAX PATH_MAX
+#elif defined MAXPATHLEN
+# define LT_PATHMAX MAXPATHLEN
+#else
+# define LT_PATHMAX 1024
+#endif
+
+#ifndef S_IXOTH
+# define S_IXOTH 0
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 0
+#endif
+
+/* path handling portability macros */
+#ifndef DIR_SEPARATOR
+# define DIR_SEPARATOR '/'
+# define PATH_SEPARATOR ':'
+#endif
+
+#if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \
+ defined __OS2__
+# define HAVE_DOS_BASED_FILE_SYSTEM
+# define FOPEN_WB "wb"
+# ifndef DIR_SEPARATOR_2
+# define DIR_SEPARATOR_2 '\\'
+# endif
+# ifndef PATH_SEPARATOR_2
+# define PATH_SEPARATOR_2 ';'
+# endif
+#endif
+
+#ifndef DIR_SEPARATOR_2
+# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
+#else /* DIR_SEPARATOR_2 */
+# define IS_DIR_SEPARATOR(ch) \
+ (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
+#endif /* DIR_SEPARATOR_2 */
+
+#ifndef PATH_SEPARATOR_2
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR)
+#else /* PATH_SEPARATOR_2 */
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2)
+#endif /* PATH_SEPARATOR_2 */
+
+#ifndef FOPEN_WB
+# define FOPEN_WB "w"
+#endif
+#ifndef _O_BINARY
+# define _O_BINARY 0
+#endif
+
+#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type)))
+#define XFREE(stale) do { \
+ if (stale) { free (stale); stale = 0; } \
+} while (0)
+
+#if defined LT_DEBUGWRAPPER
+static int lt_debug = 1;
+#else
+static int lt_debug = 0;
+#endif
+
+const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */
+
+void *xmalloc (size_t num);
+char *xstrdup (const char *string);
+const char *base_name (const char *name);
+char *find_executable (const char *wrapper);
+char *chase_symlinks (const char *pathspec);
+int make_executable (const char *path);
+int check_executable (const char *path);
+char *strendzap (char *str, const char *pat);
+void lt_debugprintf (const char *file, int line, const char *fmt, ...);
+void lt_fatal (const char *file, int line, const char *message, ...);
+static const char *nonnull (const char *s);
+static const char *nonempty (const char *s);
+void lt_setenv (const char *name, const char *value);
+char *lt_extend_str (const char *orig_value, const char *add, int to_end);
+void lt_update_exe_path (const char *name, const char *value);
+void lt_update_lib_path (const char *name, const char *value);
+char **prepare_spawn (char **argv);
+void lt_dump_script (FILE *f);
+EOF
+
+ cat <<EOF
+#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 5)
+# define externally_visible volatile
+#else
+# define externally_visible __attribute__((externally_visible)) volatile
+#endif
+externally_visible const char * MAGIC_EXE = "$magic_exe";
+const char * LIB_PATH_VARNAME = "$shlibpath_var";
+EOF
+
+ if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+ func_to_host_path "$temp_rpath"
+ cat <<EOF
+const char * LIB_PATH_VALUE = "$func_to_host_path_result";
+EOF
+ else
+ cat <<"EOF"
+const char * LIB_PATH_VALUE = "";
+EOF
+ fi
+
+ if test -n "$dllsearchpath"; then
+ func_to_host_path "$dllsearchpath:"
+ cat <<EOF
+const char * EXE_PATH_VARNAME = "PATH";
+const char * EXE_PATH_VALUE = "$func_to_host_path_result";
+EOF
+ else
+ cat <<"EOF"
+const char * EXE_PATH_VARNAME = "";
+const char * EXE_PATH_VALUE = "";
+EOF
+ fi
+
+ if test yes = "$fast_install"; then
+ cat <<EOF
+const char * TARGET_PROGRAM_NAME = "lt-$outputname"; /* hopefully, no .exe */
+EOF
+ else
+ cat <<EOF
+const char * TARGET_PROGRAM_NAME = "$outputname"; /* hopefully, no .exe */
+EOF
+ fi
+
+
+ cat <<"EOF"
+
+#define LTWRAPPER_OPTION_PREFIX "--lt-"
+
+static const char *ltwrapper_option_prefix = LTWRAPPER_OPTION_PREFIX;
+static const char *dumpscript_opt = LTWRAPPER_OPTION_PREFIX "dump-script";
+static const char *debug_opt = LTWRAPPER_OPTION_PREFIX "debug";
+
+int
+main (int argc, char *argv[])
+{
+ char **newargz;
+ int newargc;
+ char *tmp_pathspec;
+ char *actual_cwrapper_path;
+ char *actual_cwrapper_name;
+ char *target_name;
+ char *lt_argv_zero;
+ int rval = 127;
+
+ int i;
+
+ program_name = (char *) xstrdup (base_name (argv[0]));
+ newargz = XMALLOC (char *, (size_t) argc + 1);
+
+ /* very simple arg parsing; don't want to rely on getopt
+ * also, copy all non cwrapper options to newargz, except
+ * argz[0], which is handled differently
+ */
+ newargc=0;
+ for (i = 1; i < argc; i++)
+ {
+ if (STREQ (argv[i], dumpscript_opt))
+ {
+EOF
+ case $host in
+ *mingw* | *cygwin* )
+ # make stdout use "unix" line endings
+ echo " setmode(1,_O_BINARY);"
+ ;;
+ esac
+
+ cat <<"EOF"
+ lt_dump_script (stdout);
+ return 0;
+ }
+ if (STREQ (argv[i], debug_opt))
+ {
+ lt_debug = 1;
+ continue;
+ }
+ if (STREQ (argv[i], ltwrapper_option_prefix))
+ {
+ /* however, if there is an option in the LTWRAPPER_OPTION_PREFIX
+ namespace, but it is not one of the ones we know about and
+ have already dealt with, above (inluding dump-script), then
+ report an error. Otherwise, targets might begin to believe
+ they are allowed to use options in the LTWRAPPER_OPTION_PREFIX
+ namespace. The first time any user complains about this, we'll
+ need to make LTWRAPPER_OPTION_PREFIX a configure-time option
+ or a configure.ac-settable value.
+ */
+ lt_fatal (__FILE__, __LINE__,
+ "unrecognized %s option: '%s'",
+ ltwrapper_option_prefix, argv[i]);
+ }
+ /* otherwise ... */
+ newargz[++newargc] = xstrdup (argv[i]);
+ }
+ newargz[++newargc] = NULL;
+
+EOF
+ cat <<EOF
+ /* The GNU banner must be the first non-error debug message */
+ lt_debugprintf (__FILE__, __LINE__, "libtool wrapper (GNU $PACKAGE) $VERSION\n");
+EOF
+ cat <<"EOF"
+ lt_debugprintf (__FILE__, __LINE__, "(main) argv[0]: %s\n", argv[0]);
+ lt_debugprintf (__FILE__, __LINE__, "(main) program_name: %s\n", program_name);
+
+ tmp_pathspec = find_executable (argv[0]);
+ if (tmp_pathspec == NULL)
+ lt_fatal (__FILE__, __LINE__, "couldn't find %s", argv[0]);
+ lt_debugprintf (__FILE__, __LINE__,
+ "(main) found exe (before symlink chase) at: %s\n",
+ tmp_pathspec);
+
+ actual_cwrapper_path = chase_symlinks (tmp_pathspec);
+ lt_debugprintf (__FILE__, __LINE__,
+ "(main) found exe (after symlink chase) at: %s\n",
+ actual_cwrapper_path);
+ XFREE (tmp_pathspec);
+
+ actual_cwrapper_name = xstrdup (base_name (actual_cwrapper_path));
+ strendzap (actual_cwrapper_path, actual_cwrapper_name);
+
+ /* wrapper name transforms */
+ strendzap (actual_cwrapper_name, ".exe");
+ tmp_pathspec = lt_extend_str (actual_cwrapper_name, ".exe", 1);
+ XFREE (actual_cwrapper_name);
+ actual_cwrapper_name = tmp_pathspec;
+ tmp_pathspec = 0;
+
+ /* target_name transforms -- use actual target program name; might have lt- prefix */
+ target_name = xstrdup (base_name (TARGET_PROGRAM_NAME));
+ strendzap (target_name, ".exe");
+ tmp_pathspec = lt_extend_str (target_name, ".exe", 1);
+ XFREE (target_name);
+ target_name = tmp_pathspec;
+ tmp_pathspec = 0;
+
+ lt_debugprintf (__FILE__, __LINE__,
+ "(main) libtool target name: %s\n",
+ target_name);
+EOF
+
+ cat <<EOF
+ newargz[0] =
+ XMALLOC (char, (strlen (actual_cwrapper_path) +
+ strlen ("$objdir") + 1 + strlen (actual_cwrapper_name) + 1));
+ strcpy (newargz[0], actual_cwrapper_path);
+ strcat (newargz[0], "$objdir");
+ strcat (newargz[0], "/");
+EOF
+
+ cat <<"EOF"
+ /* stop here, and copy so we don't have to do this twice */
+ tmp_pathspec = xstrdup (newargz[0]);
+
+ /* do NOT want the lt- prefix here, so use actual_cwrapper_name */
+ strcat (newargz[0], actual_cwrapper_name);
+
+ /* DO want the lt- prefix here if it exists, so use target_name */
+ lt_argv_zero = lt_extend_str (tmp_pathspec, target_name, 1);
+ XFREE (tmp_pathspec);
+ tmp_pathspec = NULL;
+EOF
+
+ case $host_os in
+ mingw*)
+ cat <<"EOF"
+ {
+ char* p;
+ while ((p = strchr (newargz[0], '\\')) != NULL)
+ {
+ *p = '/';
+ }
+ while ((p = strchr (lt_argv_zero, '\\')) != NULL)
+ {
+ *p = '/';
+ }
+ }
+EOF
+ ;;
+ esac
+
+ cat <<"EOF"
+ XFREE (target_name);
+ XFREE (actual_cwrapper_path);
+ XFREE (actual_cwrapper_name);
+
+ lt_setenv ("BIN_SH", "xpg4"); /* for Tru64 */
+ lt_setenv ("DUALCASE", "1"); /* for MSK sh */
+ /* Update the DLL searchpath. EXE_PATH_VALUE ($dllsearchpath) must
+ be prepended before (that is, appear after) LIB_PATH_VALUE ($temp_rpath)
+ because on Windows, both *_VARNAMEs are PATH but uninstalled
+ libraries must come first. */
+ lt_update_exe_path (EXE_PATH_VARNAME, EXE_PATH_VALUE);
+ lt_update_lib_path (LIB_PATH_VARNAME, LIB_PATH_VALUE);
+
+ lt_debugprintf (__FILE__, __LINE__, "(main) lt_argv_zero: %s\n",
+ nonnull (lt_argv_zero));
+ for (i = 0; i < newargc; i++)
+ {
+ lt_debugprintf (__FILE__, __LINE__, "(main) newargz[%d]: %s\n",
+ i, nonnull (newargz[i]));
+ }
+
+EOF
+
+ case $host_os in
+ mingw*)
+ cat <<"EOF"
+ /* execv doesn't actually work on mingw as expected on unix */
+ newargz = prepare_spawn (newargz);
+ rval = (int) _spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz);
+ if (rval == -1)
+ {
+ /* failed to start process */
+ lt_debugprintf (__FILE__, __LINE__,
+ "(main) failed to launch target \"%s\": %s\n",
+ lt_argv_zero, nonnull (strerror (errno)));
+ return 127;
+ }
+ return rval;
+EOF
+ ;;
+ *)
+ cat <<"EOF"
+ execv (lt_argv_zero, newargz);
+ return rval; /* =127, but avoids unused variable warning */
+EOF
+ ;;
+ esac
+
+ cat <<"EOF"
+}
+
+void *
+xmalloc (size_t num)
+{
+ void *p = (void *) malloc (num);
+ if (!p)
+ lt_fatal (__FILE__, __LINE__, "memory exhausted");
+
+ return p;
+}
+
+char *
+xstrdup (const char *string)
+{
+ return string ? strcpy ((char *) xmalloc (strlen (string) + 1),
+ string) : NULL;
+}
+
+const char *
+base_name (const char *name)
+{
+ const char *base;
+
+#if defined HAVE_DOS_BASED_FILE_SYSTEM
+ /* Skip over the disk name in MSDOS pathnames. */
+ if (isalpha ((unsigned char) name[0]) && name[1] == ':')
+ name += 2;
+#endif
+
+ for (base = name; *name; name++)
+ if (IS_DIR_SEPARATOR (*name))
+ base = name + 1;
+ return base;
+}
+
+int
+check_executable (const char *path)
+{
+ struct stat st;
+
+ lt_debugprintf (__FILE__, __LINE__, "(check_executable): %s\n",
+ nonempty (path));
+ if ((!path) || (!*path))
+ return 0;
+
+ if ((stat (path, &st) >= 0)
+ && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+ return 1;
+ else
+ return 0;
+}
+
+int
+make_executable (const char *path)
+{
+ int rval = 0;
+ struct stat st;
+
+ lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n",
+ nonempty (path));
+ if ((!path) || (!*path))
+ return 0;
+
+ if (stat (path, &st) >= 0)
+ {
+ rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR);
+ }
+ return rval;
+}
+
+/* Searches for the full path of the wrapper. Returns
+ newly allocated full path name if found, NULL otherwise
+ Does not chase symlinks, even on platforms that support them.
+*/
+char *
+find_executable (const char *wrapper)
+{
+ int has_slash = 0;
+ const char *p;
+ const char *p_next;
+ /* static buffer for getcwd */
+ char tmp[LT_PATHMAX + 1];
+ size_t tmp_len;
+ char *concat_name;
+
+ lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n",
+ nonempty (wrapper));
+
+ if ((wrapper == NULL) || (*wrapper == '\0'))
+ return NULL;
+
+ /* Absolute path? */
+#if defined HAVE_DOS_BASED_FILE_SYSTEM
+ if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':')
+ {
+ concat_name = xstrdup (wrapper);
+ if (check_executable (concat_name))
+ return concat_name;
+ XFREE (concat_name);
+ }
+ else
+ {
+#endif
+ if (IS_DIR_SEPARATOR (wrapper[0]))
+ {
+ concat_name = xstrdup (wrapper);
+ if (check_executable (concat_name))
+ return concat_name;
+ XFREE (concat_name);
+ }
+#if defined HAVE_DOS_BASED_FILE_SYSTEM
+ }
+#endif
+
+ for (p = wrapper; *p; p++)
+ if (*p == '/')
+ {
+ has_slash = 1;
+ break;
+ }
+ if (!has_slash)
+ {
+ /* no slashes; search PATH */
+ const char *path = getenv ("PATH");
+ if (path != NULL)
+ {
+ for (p = path; *p; p = p_next)
+ {
+ const char *q;
+ size_t p_len;
+ for (q = p; *q; q++)
+ if (IS_PATH_SEPARATOR (*q))
+ break;
+ p_len = (size_t) (q - p);
+ p_next = (*q == '\0' ? q : q + 1);
+ if (p_len == 0)
+ {
+ /* empty path: current directory */
+ if (getcwd (tmp, LT_PATHMAX) == NULL)
+ lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+ nonnull (strerror (errno)));
+ tmp_len = strlen (tmp);
+ concat_name =
+ XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+ memcpy (concat_name, tmp, tmp_len);
+ concat_name[tmp_len] = '/';
+ strcpy (concat_name + tmp_len + 1, wrapper);
+ }
+ else
+ {
+ concat_name =
+ XMALLOC (char, p_len + 1 + strlen (wrapper) + 1);
+ memcpy (concat_name, p, p_len);
+ concat_name[p_len] = '/';
+ strcpy (concat_name + p_len + 1, wrapper);
+ }
+ if (check_executable (concat_name))
+ return concat_name;
+ XFREE (concat_name);
+ }
+ }
+ /* not found in PATH; assume curdir */
+ }
+ /* Relative path | not found in path: prepend cwd */
+ if (getcwd (tmp, LT_PATHMAX) == NULL)
+ lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+ nonnull (strerror (errno)));
+ tmp_len = strlen (tmp);
+ concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+ memcpy (concat_name, tmp, tmp_len);
+ concat_name[tmp_len] = '/';
+ strcpy (concat_name + tmp_len + 1, wrapper);
+
+ if (check_executable (concat_name))
+ return concat_name;
+ XFREE (concat_name);
+ return NULL;
+}
+
+char *
+chase_symlinks (const char *pathspec)
+{
+#ifndef S_ISLNK
+ return xstrdup (pathspec);
+#else
+ char buf[LT_PATHMAX];
+ struct stat s;
+ char *tmp_pathspec = xstrdup (pathspec);
+ char *p;
+ int has_symlinks = 0;
+ while (strlen (tmp_pathspec) && !has_symlinks)
+ {
+ lt_debugprintf (__FILE__, __LINE__,
+ "checking path component for symlinks: %s\n",
+ tmp_pathspec);
+ if (lstat (tmp_pathspec, &s) == 0)
+ {
+ if (S_ISLNK (s.st_mode) != 0)
+ {
+ has_symlinks = 1;
+ break;
+ }
+
+ /* search backwards for last DIR_SEPARATOR */
+ p = tmp_pathspec + strlen (tmp_pathspec) - 1;
+ while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+ p--;
+ if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+ {
+ /* no more DIR_SEPARATORS left */
+ break;
+ }
+ *p = '\0';
+ }
+ else
+ {
+ lt_fatal (__FILE__, __LINE__,
+ "error accessing file \"%s\": %s",
+ tmp_pathspec, nonnull (strerror (errno)));
+ }
+ }
+ XFREE (tmp_pathspec);
+
+ if (!has_symlinks)
+ {
+ return xstrdup (pathspec);
+ }
+
+ tmp_pathspec = realpath (pathspec, buf);
+ if (tmp_pathspec == 0)
+ {
+ lt_fatal (__FILE__, __LINE__,
+ "could not follow symlinks for %s", pathspec);
+ }
+ return xstrdup (tmp_pathspec);
+#endif
+}
+
+char *
+strendzap (char *str, const char *pat)
+{
+ size_t len, patlen;
+
+ assert (str != NULL);
+ assert (pat != NULL);
+
+ len = strlen (str);
+ patlen = strlen (pat);
+
+ if (patlen <= len)
+ {
+ str += len - patlen;
+ if (STREQ (str, pat))
+ *str = '\0';
+ }
+ return str;
+}
+
+void
+lt_debugprintf (const char *file, int line, const char *fmt, ...)
+{
+ va_list args;
+ if (lt_debug)
+ {
+ (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line);
+ va_start (args, fmt);
+ (void) vfprintf (stderr, fmt, args);
+ va_end (args);
+ }
+}
+
+static void
+lt_error_core (int exit_status, const char *file,
+ int line, const char *mode,
+ const char *message, va_list ap)
+{
+ fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode);
+ vfprintf (stderr, message, ap);
+ fprintf (stderr, ".\n");
+
+ if (exit_status >= 0)
+ exit (exit_status);
+}
+
+void
+lt_fatal (const char *file, int line, const char *message, ...)
+{
+ va_list ap;
+ va_start (ap, message);
+ lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap);
+ va_end (ap);
+}
+
+static const char *
+nonnull (const char *s)
+{
+ return s ? s : "(null)";
+}
+
+static const char *
+nonempty (const char *s)
+{
+ return (s && !*s) ? "(empty)" : nonnull (s);
+}
+
+void
+lt_setenv (const char *name, const char *value)
+{
+ lt_debugprintf (__FILE__, __LINE__,
+ "(lt_setenv) setting '%s' to '%s'\n",
+ nonnull (name), nonnull (value));
+ {
+#ifdef HAVE_SETENV
+ /* always make a copy, for consistency with !HAVE_SETENV */
+ char *str = xstrdup (value);
+ setenv (name, str, 1);
+#else
+ size_t len = strlen (name) + 1 + strlen (value) + 1;
+ char *str = XMALLOC (char, len);
+ sprintf (str, "%s=%s", name, value);
+ if (putenv (str) != EXIT_SUCCESS)
+ {
+ XFREE (str);
+ }
+#endif
+ }
+}
+
+char *
+lt_extend_str (const char *orig_value, const char *add, int to_end)
+{
+ char *new_value;
+ if (orig_value && *orig_value)
+ {
+ size_t orig_value_len = strlen (orig_value);
+ size_t add_len = strlen (add);
+ new_value = XMALLOC (char, add_len + orig_value_len + 1);
+ if (to_end)
+ {
+ strcpy (new_value, orig_value);
+ strcpy (new_value + orig_value_len, add);
+ }
+ else
+ {
+ strcpy (new_value, add);
+ strcpy (new_value + add_len, orig_value);
+ }
+ }
+ else
+ {
+ new_value = xstrdup (add);
+ }
+ return new_value;
+}
+
+void
+lt_update_exe_path (const char *name, const char *value)
+{
+ lt_debugprintf (__FILE__, __LINE__,
+ "(lt_update_exe_path) modifying '%s' by prepending '%s'\n",
+ nonnull (name), nonnull (value));
+
+ if (name && *name && value && *value)
+ {
+ char *new_value = lt_extend_str (getenv (name), value, 0);
+ /* some systems can't cope with a ':'-terminated path #' */
+ size_t len = strlen (new_value);
+ while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1]))
+ {
+ new_value[--len] = '\0';
+ }
+ lt_setenv (name, new_value);
+ XFREE (new_value);
+ }
+}
+
+void
+lt_update_lib_path (const char *name, const char *value)
+{
+ lt_debugprintf (__FILE__, __LINE__,
+ "(lt_update_lib_path) modifying '%s' by prepending '%s'\n",
+ nonnull (name), nonnull (value));
+
+ if (name && *name && value && *value)
+ {
+ char *new_value = lt_extend_str (getenv (name), value, 0);
+ lt_setenv (name, new_value);
+ XFREE (new_value);
+ }
+}
+
+EOF
+ case $host_os in
+ mingw*)
+ cat <<"EOF"
+
+/* Prepares an argument vector before calling spawn().
+ Note that spawn() does not by itself call the command interpreter
+ (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
+ ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&v);
+ v.dwPlatformId == VER_PLATFORM_WIN32_NT;
+ }) ? "cmd.exe" : "command.com").
+ Instead it simply concatenates the arguments, separated by ' ', and calls
+ CreateProcess(). We must quote the arguments since Win32 CreateProcess()
+ interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a
+ special way:
+ - Space and tab are interpreted as delimiters. They are not treated as
+ delimiters if they are surrounded by double quotes: "...".
+ - Unescaped double quotes are removed from the input. Their only effect is
+ that within double quotes, space and tab are treated like normal
+ characters.
+ - Backslashes not followed by double quotes are not special.
+ - But 2*n+1 backslashes followed by a double quote become
+ n backslashes followed by a double quote (n >= 0):
+ \" -> "
+ \\\" -> \"
+ \\\\\" -> \\"
+ */
+#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+char **
+prepare_spawn (char **argv)
+{
+ size_t argc;
+ char **new_argv;
+ size_t i;
+
+ /* Count number of arguments. */
+ for (argc = 0; argv[argc] != NULL; argc++)
+ ;
+
+ /* Allocate new argument vector. */
+ new_argv = XMALLOC (char *, argc + 1);
+
+ /* Put quoted arguments into the new argument vector. */
+ for (i = 0; i < argc; i++)
+ {
+ const char *string = argv[i];
+
+ if (string[0] == '\0')
+ new_argv[i] = xstrdup ("\"\"");
+ else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
+ {
+ int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
+ size_t length;
+ unsigned int backslashes;
+ const char *s;
+ char *quoted_string;
+ char *p;
+
+ length = 0;
+ backslashes = 0;
+ if (quote_around)
+ length++;
+ for (s = string; *s != '\0'; s++)
+ {
+ char c = *s;
+ if (c == '"')
+ length += backslashes + 1;
+ length++;
+ if (c == '\\')
+ backslashes++;
+ else
+ backslashes = 0;
+ }
+ if (quote_around)
+ length += backslashes + 1;
+
+ quoted_string = XMALLOC (char, length + 1);
+
+ p = quoted_string;
+ backslashes = 0;
+ if (quote_around)
+ *p++ = '"';
+ for (s = string; *s != '\0'; s++)
+ {
+ char c = *s;
+ if (c == '"')
+ {
+ unsigned int j;
+ for (j = backslashes + 1; j > 0; j--)
+ *p++ = '\\';
+ }
+ *p++ = c;
+ if (c == '\\')
+ backslashes++;
+ else
+ backslashes = 0;
+ }
+ if (quote_around)
+ {
+ unsigned int j;
+ for (j = backslashes; j > 0; j--)
+ *p++ = '\\';
+ *p++ = '"';
+ }
+ *p = '\0';
+
+ new_argv[i] = quoted_string;
+ }
+ else
+ new_argv[i] = (char *) string;
+ }
+ new_argv[argc] = NULL;
+
+ return new_argv;
+}
+EOF
+ ;;
+ esac
+
+ cat <<"EOF"
+void lt_dump_script (FILE* f)
+{
+EOF
+ func_emit_wrapper yes |
+ $SED -n -e '
+s/^\(.\{79\}\)\(..*\)/\1\
+\2/
+h
+s/\([\\"]\)/\\\1/g
+s/$/\\n/
+s/\([^\n]*\).*/ fputs ("\1", f);/p
+g
+D'
+ cat <<"EOF"
+}
+EOF
+}
+# end: func_emit_cwrapperexe_src
+
+# func_win32_import_lib_p ARG
+# True if ARG is an import lib, as indicated by $file_magic_cmd
+func_win32_import_lib_p ()
+{
+ $debug_cmd
+
+ case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in
+ *import*) : ;;
+ *) false ;;
+ esac
+}
+
+# func_suncc_cstd_abi
+# !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!!
+# Several compiler flags select an ABI that is incompatible with the
+# Cstd library. Avoid specifying it if any are in CXXFLAGS.
+func_suncc_cstd_abi ()
+{
+ $debug_cmd
+
+ case " $compile_command " in
+ *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*)
+ suncc_use_cstd_abi=no
+ ;;
+ *)
+ suncc_use_cstd_abi=yes
+ ;;
+ esac
+}
+
+# func_mode_link arg...
+func_mode_link ()
+{
+ $debug_cmd
+
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+ # It is impossible to link a dll without this setting, and
+ # we shouldn't force the makefile maintainer to figure out
+ # what system we are compiling for in order to pass an extra
+ # flag for every libtool invocation.
+ # allow_undefined=no
+
+ # FIXME: Unfortunately, there are problems with the above when trying
+ # to make a dll that has undefined symbols, in which case not
+ # even a static library is built. For now, we need to specify
+ # -no-undefined on the libtool link line when we can be certain
+ # that all symbols are satisfied, otherwise we get a static library.
+ allow_undefined=yes
+ ;;
+ *)
+ allow_undefined=yes
+ ;;
+ esac
+ libtool_args=$nonopt
+ base_compile="$nonopt $@"
+ compile_command=$nonopt
+ finalize_command=$nonopt
+
+ compile_rpath=
+ finalize_rpath=
+ compile_shlibpath=
+ finalize_shlibpath=
+ convenience=
+ old_convenience=
+ deplibs=
+ old_deplibs=
+ compiler_flags=
+ linker_flags=
+ dllsearchpath=
+ lib_search_path=`pwd`
+ inst_prefix_dir=
+ new_inherited_linker_flags=
+
+ avoid_version=no
+ bindir=
+ dlfiles=
+ dlprefiles=
+ dlself=no
+ export_dynamic=no
+ export_symbols=
+ export_symbols_regex=
+ generated=
+ libobjs=
+ ltlibs=
+ module=no
+ no_install=no
+ objs=
+ os2dllname=
+ non_pic_objects=
+ precious_files_regex=
+ prefer_static_libs=no
+ preload=false
+ prev=
+ prevarg=
+ release=
+ rpath=
+ xrpath=
+ perm_rpath=
+ temp_rpath=
+ thread_safe=no
+ vinfo=
+ vinfo_number=no
+ weak_libs=
+ single_module=$wl-single_module
+ func_infer_tag $base_compile
+
+ # We need to know -static, to get the right output filenames.
+ for arg
+ do
+ case $arg in
+ -shared)
+ test yes != "$build_libtool_libs" \
+ && func_fatal_configuration "cannot build a shared library"
+ build_old_libs=no
+ break
+ ;;
+ -all-static | -static | -static-libtool-libs)
+ case $arg in
+ -all-static)
+ if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then
+ func_warning "complete static linking is impossible in this configuration"
+ fi
+ if test -n "$link_static_flag"; then
+ dlopen_self=$dlopen_self_static
+ fi
+ prefer_static_libs=yes
+ ;;
+ -static)
+ if test -z "$pic_flag" && test -n "$link_static_flag"; then
+ dlopen_self=$dlopen_self_static
+ fi
+ prefer_static_libs=built
+ ;;
+ -static-libtool-libs)
+ if test -z "$pic_flag" && test -n "$link_static_flag"; then
+ dlopen_self=$dlopen_self_static
+ fi
+ prefer_static_libs=yes
+ ;;
+ esac
+ build_libtool_libs=no
+ build_old_libs=yes
+ break
+ ;;
+ esac
+ done
+
+ # See if our shared archives depend on static archives.
+ test -n "$old_archive_from_new_cmds" && build_old_libs=yes
+
+ # Go through the arguments, transforming them on the way.
+ while test "$#" -gt 0; do
+ arg=$1
+ shift
+ func_quote_arg pretty,unquoted "$arg"
+ qarg=$func_quote_arg_unquoted_result
+ func_append libtool_args " $func_quote_arg_result"
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$prev"; then
+ case $prev in
+ output)
+ func_append compile_command " @OUTPUT@"
+ func_append finalize_command " @OUTPUT@"
+ ;;
+ esac
+
+ case $prev in
+ bindir)
+ bindir=$arg
+ prev=
+ continue
+ ;;
+ dlfiles|dlprefiles)
+ $preload || {
+ # Add the symbol object into the linking commands.
+ func_append compile_command " @SYMFILE@"
+ func_append finalize_command " @SYMFILE@"
+ preload=:
+ }
+ case $arg in
+ *.la | *.lo) ;; # We handle these cases below.
+ force)
+ if test no = "$dlself"; then
+ dlself=needless
+ export_dynamic=yes
+ fi
+ prev=
+ continue
+ ;;
+ self)
+ if test dlprefiles = "$prev"; then
+ dlself=yes
+ elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then
+ dlself=yes
+ else
+ dlself=needless
+ export_dynamic=yes
+ fi
+ prev=
+ continue
+ ;;
+ *)
+ if test dlfiles = "$prev"; then
+ func_append dlfiles " $arg"
+ else
+ func_append dlprefiles " $arg"
+ fi
+ prev=
+ continue
+ ;;
+ esac
+ ;;
+ expsyms)
+ export_symbols=$arg
+ test -f "$arg" \
+ || func_fatal_error "symbol file '$arg' does not exist"
+ prev=
+ continue
+ ;;
+ expsyms_regex)
+ export_symbols_regex=$arg
+ prev=
+ continue
+ ;;
+ framework)
+ case $host in
+ *-*-darwin*)
+ case "$deplibs " in
+ *" $qarg.ltframework "*) ;;
+ *) func_append deplibs " $qarg.ltframework" # this is fixed later
+ ;;
+ esac
+ ;;
+ esac
+ prev=
+ continue
+ ;;
+ inst_prefix)
+ inst_prefix_dir=$arg
+ prev=
+ continue
+ ;;
+ mllvm)
+ # Clang does not use LLVM to link, so we can simply discard any
+ # '-mllvm $arg' options when doing the link step.
+ prev=
+ continue
+ ;;
+ objectlist)
+ if test -f "$arg"; then
+ save_arg=$arg
+ moreargs=
+ for fil in `cat "$save_arg"`
+ do
+# func_append moreargs " $fil"
+ arg=$fil
+ # A libtool-controlled object.
+
+ # Check to see that this really is a libtool object.
+ if func_lalib_unsafe_p "$arg"; then
+ pic_object=
+ non_pic_object=
+
+ # Read the .lo file
+ func_source "$arg"
+
+ if test -z "$pic_object" ||
+ test -z "$non_pic_object" ||
+ test none = "$pic_object" &&
+ test none = "$non_pic_object"; then
+ func_fatal_error "cannot find name of object for '$arg'"
+ fi
+
+ # Extract subdirectory from the argument.
+ func_dirname "$arg" "/" ""
+ xdir=$func_dirname_result
+
+ if test none != "$pic_object"; then
+ # Prepend the subdirectory the object is found in.
+ pic_object=$xdir$pic_object
+
+ if test dlfiles = "$prev"; then
+ if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then
+ func_append dlfiles " $pic_object"
+ prev=
+ continue
+ else
+ # If libtool objects are unsupported, then we need to preload.
+ prev=dlprefiles
+ fi
+ fi
+
+ # CHECK ME: I think I busted this. -Ossama
+ if test dlprefiles = "$prev"; then
+ # Preload the old-style object.
+ func_append dlprefiles " $pic_object"
+ prev=
+ fi
+
+ # A PIC object.
+ func_append libobjs " $pic_object"
+ arg=$pic_object
+ fi
+
+ # Non-PIC object.
+ if test none != "$non_pic_object"; then
+ # Prepend the subdirectory the object is found in.
+ non_pic_object=$xdir$non_pic_object
+
+ # A standard non-PIC object
+ func_append non_pic_objects " $non_pic_object"
+ if test -z "$pic_object" || test none = "$pic_object"; then
+ arg=$non_pic_object
+ fi
+ else
+ # If the PIC object exists, use it instead.
+ # $xdir was prepended to $pic_object above.
+ non_pic_object=$pic_object
+ func_append non_pic_objects " $non_pic_object"
+ fi
+ else
+ # Only an error if not doing a dry-run.
+ if $opt_dry_run; then
+ # Extract subdirectory from the argument.
+ func_dirname "$arg" "/" ""
+ xdir=$func_dirname_result
+
+ func_lo2o "$arg"
+ pic_object=$xdir$objdir/$func_lo2o_result
+ non_pic_object=$xdir$func_lo2o_result
+ func_append libobjs " $pic_object"
+ func_append non_pic_objects " $non_pic_object"
+ else
+ func_fatal_error "'$arg' is not a valid libtool object"
+ fi
+ fi
+ done
+ else
+ func_fatal_error "link input file '$arg' does not exist"
+ fi
+ arg=$save_arg
+ prev=
+ continue
+ ;;
+ os2dllname)
+ os2dllname=$arg
+ prev=
+ continue
+ ;;
+ precious_regex)
+ precious_files_regex=$arg
+ prev=
+ continue
+ ;;
+ release)
+ release=-$arg
+ prev=
+ continue
+ ;;
+ rpath | xrpath)
+ # We need an absolute path.
+ case $arg in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ func_fatal_error "only absolute run-paths are allowed"
+ ;;
+ esac
+ if test rpath = "$prev"; then
+ case "$rpath " in
+ *" $arg "*) ;;
+ *) func_append rpath " $arg" ;;
+ esac
+ else
+ case "$xrpath " in
+ *" $arg "*) ;;
+ *) func_append xrpath " $arg" ;;
+ esac
+ fi
+ prev=
+ continue
+ ;;
+ shrext)
+ shrext_cmds=$arg
+ prev=
+ continue
+ ;;
+ weak)
+ func_append weak_libs " $arg"
+ prev=
+ continue
+ ;;
+ xassembler)
+ func_append compiler_flags " -Xassembler $qarg"
+ prev=
+ func_append compile_command " -Xassembler $qarg"
+ func_append finalize_command " -Xassembler $qarg"
+ continue
+ ;;
+ xcclinker)
+ func_append linker_flags " $qarg"
+ func_append compiler_flags " $qarg"
+ prev=
+ func_append compile_command " $qarg"
+ func_append finalize_command " $qarg"
+ continue
+ ;;
+ xcompiler)
+ func_append compiler_flags " $qarg"
+ prev=
+ func_append compile_command " $qarg"
+ func_append finalize_command " $qarg"
+ continue
+ ;;
+ xlinker)
+ func_append linker_flags " $qarg"
+ func_append compiler_flags " $wl$qarg"
+ prev=
+ func_append compile_command " $wl$qarg"
+ func_append finalize_command " $wl$qarg"
+ continue
+ ;;
+ *)
+ eval "$prev=\"\$arg\""
+ prev=
+ continue
+ ;;
+ esac
+ fi # test -n "$prev"
+
+ prevarg=$arg
+
+ case $arg in
+ -all-static)
+ if test -n "$link_static_flag"; then
+ # See comment for -static flag below, for more details.
+ func_append compile_command " $link_static_flag"
+ func_append finalize_command " $link_static_flag"
+ fi
+ continue
+ ;;
+
+ -allow-undefined)
+ # FIXME: remove this flag sometime in the future.
+ func_fatal_error "'-allow-undefined' must not be used because it is the default"
+ ;;
+
+ -avoid-version)
+ avoid_version=yes
+ continue
+ ;;
+
+ -bindir)
+ prev=bindir
+ continue
+ ;;
+
+ -dlopen)
+ prev=dlfiles
+ continue
+ ;;
+
+ -dlpreopen)
+ prev=dlprefiles
+ continue
+ ;;
+
+ -export-dynamic)
+ export_dynamic=yes
+ continue
+ ;;
+
+ -export-symbols | -export-symbols-regex)
+ if test -n "$export_symbols" || test -n "$export_symbols_regex"; then
+ func_fatal_error "more than one -exported-symbols argument is not allowed"
+ fi
+ if test X-export-symbols = "X$arg"; then
+ prev=expsyms
+ else
+ prev=expsyms_regex
+ fi
+ continue
+ ;;
+
+ -framework)
+ prev=framework
+ continue
+ ;;
+
+ -inst-prefix-dir)
+ prev=inst_prefix
+ continue
+ ;;
+
+ # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:*
+ # so, if we see these flags be careful not to treat them like -L
+ -L[A-Z][A-Z]*:*)
+ case $with_gcc/$host in
+ no/*-*-irix* | /*-*-irix*)
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ ;;
+ esac
+ continue
+ ;;
+
+ -L*)
+ func_stripname "-L" '' "$arg"
+ if test -z "$func_stripname_result"; then
+ if test "$#" -gt 0; then
+ func_fatal_error "require no space between '-L' and '$1'"
+ else
+ func_fatal_error "need path for '-L' option"
+ fi
+ fi
+ func_resolve_sysroot "$func_stripname_result"
+ dir=$func_resolve_sysroot_result
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ absdir=`cd "$dir" && pwd`
+ test -z "$absdir" && \
+ func_fatal_error "cannot determine absolute directory name of '$dir'"
+ dir=$absdir
+ ;;
+ esac
+ case "$deplibs " in
+ *" -L$dir "* | *" $arg "*)
+ # Will only happen for absolute or sysroot arguments
+ ;;
+ *)
+ # Preserve sysroot, but never include relative directories
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;;
+ *) func_append deplibs " -L$dir" ;;
+ esac
+ func_append lib_search_path " $dir"
+ ;;
+ esac
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+ testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'`
+ case :$dllsearchpath: in
+ *":$dir:"*) ;;
+ ::) dllsearchpath=$dir;;
+ *) func_append dllsearchpath ":$dir";;
+ esac
+ case :$dllsearchpath: in
+ *":$testbindir:"*) ;;
+ ::) dllsearchpath=$testbindir;;
+ *) func_append dllsearchpath ":$testbindir";;
+ esac
+ ;;
+ esac
+ continue
+ ;;
+
+ -l*)
+ if test X-lc = "X$arg" || test X-lm = "X$arg"; then
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*)
+ # These systems don't actually have a C or math library (as such)
+ continue
+ ;;
+ *-*-os2*)
+ # These systems don't actually have a C library (as such)
+ test X-lc = "X$arg" && continue
+ ;;
+ *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig* | *-*-midnightbsd*)
+ # Do not include libc due to us having libc/libc_r.
+ test X-lc = "X$arg" && continue
+ ;;
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # Rhapsody C and math libraries are in the System framework
+ func_append deplibs " System.ltframework"
+ continue
+ ;;
+ *-*-sco3.2v5* | *-*-sco5v6*)
+ # Causes problems with __ctype
+ test X-lc = "X$arg" && continue
+ ;;
+ *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+ # Compiler inserts libc in the correct place for threads to work
+ test X-lc = "X$arg" && continue
+ ;;
+ esac
+ elif test X-lc_r = "X$arg"; then
+ case $host in
+ *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig* | *-*-midnightbsd*)
+ # Do not include libc_r directly, use -pthread flag.
+ continue
+ ;;
+ esac
+ fi
+ func_append deplibs " $arg"
+ continue
+ ;;
+
+ -mllvm)
+ prev=mllvm
+ continue
+ ;;
+
+ -module)
+ module=yes
+ continue
+ ;;
+
+ # Tru64 UNIX uses -model [arg] to determine the layout of C++
+ # classes, name mangling, and exception handling.
+ # Darwin uses the -arch flag to determine output architecture.
+ -model|-arch|-isysroot|--sysroot)
+ func_append compiler_flags " $arg"
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ prev=xcompiler
+ continue
+ ;;
+ # Solaris ld rejects as of 11.4. Refer to Oracle bug 22985199.
+ -pthread)
+ case $host in
+ *solaris2*) ;;
+ *)
+ case "$new_inherited_linker_flags " in
+ *" $arg "*) ;;
+ * ) func_append new_inherited_linker_flags " $arg" ;;
+ esac
+ ;;
+ esac
+ continue
+ ;;
+ -mt|-mthreads|-kthread|-Kthread|-pthreads|--thread-safe \
+ |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+ func_append compiler_flags " $arg"
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ case "$new_inherited_linker_flags " in
+ *" $arg "*) ;;
+ * ) func_append new_inherited_linker_flags " $arg" ;;
+ esac
+ continue
+ ;;
+
+ -multi_module)
+ single_module=$wl-multi_module
+ continue
+ ;;
+
+ -no-fast-install)
+ fast_install=no
+ continue
+ ;;
+
+ -no-install)
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*)
+ # The PATH hackery in wrapper scripts is required on Windows
+ # and Darwin in order for the loader to find any dlls it needs.
+ func_warning "'-no-install' is ignored for $host"
+ func_warning "assuming '-no-fast-install' instead"
+ fast_install=no
+ ;;
+ *) no_install=yes ;;
+ esac
+ continue
+ ;;
+
+ -no-undefined)
+ allow_undefined=no
+ continue
+ ;;
+
+ -objectlist)
+ prev=objectlist
+ continue
+ ;;
+
+ -os2dllname)
+ prev=os2dllname
+ continue
+ ;;
+
+ -o) prev=output ;;
+
+ -precious-files-regex)
+ prev=precious_regex
+ continue
+ ;;
+
+ -release)
+ prev=release
+ continue
+ ;;
+
+ -rpath)
+ prev=rpath
+ continue
+ ;;
+
+ -R)
+ prev=xrpath
+ continue
+ ;;
+
+ -R*)
+ func_stripname '-R' '' "$arg"
+ dir=$func_stripname_result
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ =*)
+ func_stripname '=' '' "$dir"
+ dir=$lt_sysroot$func_stripname_result
+ ;;
+ *)
+ func_fatal_error "only absolute run-paths are allowed"
+ ;;
+ esac
+ case "$xrpath " in
+ *" $dir "*) ;;
+ *) func_append xrpath " $dir" ;;
+ esac
+ continue
+ ;;
+
+ -shared)
+ # The effects of -shared are defined in a previous loop.
+ continue
+ ;;
+
+ -shrext)
+ prev=shrext
+ continue
+ ;;
+
+ -static | -static-libtool-libs)
+ # The effects of -static are defined in a previous loop.
+ # We used to do the same as -all-static on platforms that
+ # didn't have a PIC flag, but the assumption that the effects
+ # would be equivalent was wrong. It would break on at least
+ # Digital Unix and AIX.
+ continue
+ ;;
+
+ -thread-safe)
+ thread_safe=yes
+ continue
+ ;;
+
+ -version-info)
+ prev=vinfo
+ continue
+ ;;
+
+ -version-number)
+ prev=vinfo
+ vinfo_number=yes
+ continue
+ ;;
+
+ -weak)
+ prev=weak
+ continue
+ ;;
+
+ -Wc,*)
+ func_stripname '-Wc,' '' "$arg"
+ args=$func_stripname_result
+ arg=
+ save_ifs=$IFS; IFS=,
+ for flag in $args; do
+ IFS=$save_ifs
+ func_quote_arg pretty "$flag"
+ func_append arg " $func_quote_arg_result"
+ func_append compiler_flags " $func_quote_arg_result"
+ done
+ IFS=$save_ifs
+ func_stripname ' ' '' "$arg"
+ arg=$func_stripname_result
+ ;;
+
+ -Wl,*)
+ func_stripname '-Wl,' '' "$arg"
+ args=$func_stripname_result
+ arg=
+ save_ifs=$IFS; IFS=,
+ for flag in $args; do
+ IFS=$save_ifs
+ func_quote_arg pretty "$flag"
+ func_append arg " $wl$func_quote_arg_result"
+ func_append compiler_flags " $wl$func_quote_arg_result"
+ func_append linker_flags " $func_quote_arg_result"
+ done
+ IFS=$save_ifs
+ func_stripname ' ' '' "$arg"
+ arg=$func_stripname_result
+ ;;
+
+ -Xassembler)
+ prev=xassembler
+ continue
+ ;;
+
+ -Xcompiler)
+ prev=xcompiler
+ continue
+ ;;
+
+ -Xlinker)
+ prev=xlinker
+ continue
+ ;;
+
+ -XCClinker)
+ prev=xcclinker
+ continue
+ ;;
+
+ # -msg_* for osf cc
+ -msg_*)
+ func_quote_arg pretty "$arg"
+ arg=$func_quote_arg_result
+ ;;
+
+ # Flags to be passed through unchanged, with rationale:
+ # -64, -mips[0-9] enable 64-bit mode for the SGI compiler
+ # -r[0-9][0-9]* specify processor for the SGI compiler
+ # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler
+ # +DA*, +DD* enable 64-bit mode for the HP compiler
+ # -q* compiler args for the IBM compiler
+ # -m*, -t[45]*, -txscale* architecture-specific flags for GCC
+ # -F/path path to uninstalled frameworks, gcc on darwin
+ # -p, -pg, --coverage, -fprofile-* profiling flags for GCC
+ # -fstack-protector* stack protector flags for GCC
+ # @file GCC response files
+ # -tp=* Portland pgcc target processor selection
+ # --sysroot=* for sysroot support
+ # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization
+ # -specs=* GCC specs files
+ # -stdlib=* select c++ std lib with clang
+ # -fsanitize=* Clang/GCC memory and address sanitizer
+ # -fuse-ld=* Linker select flags for GCC
+ # -static-* direct GCC to link specific libraries statically
+ # -fcilkplus Cilk Plus language extension features for C/C++
+ # -Wa,* Pass flags directly to the assembler
+ -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \
+ -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \
+ -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \
+ -specs=*|-fsanitize=*|-fuse-ld=*|-static-*|-fcilkplus|-Wa,*)
+ func_quote_arg pretty "$arg"
+ arg=$func_quote_arg_result
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ func_append compiler_flags " $arg"
+ continue
+ ;;
+
+ -Z*)
+ if test os2 = "`expr $host : '.*\(os2\)'`"; then
+ # OS/2 uses -Zxxx to specify OS/2-specific options
+ compiler_flags="$compiler_flags $arg"
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ case $arg in
+ -Zlinker | -Zstack)
+ prev=xcompiler
+ ;;
+ esac
+ continue
+ else
+ # Otherwise treat like 'Some other compiler flag' below
+ func_quote_arg pretty "$arg"
+ arg=$func_quote_arg_result
+ fi
+ ;;
+
+ # Some other compiler flag.
+ -* | +*)
+ func_quote_arg pretty "$arg"
+ arg=$func_quote_arg_result
+ ;;
+
+ *.$objext)
+ # A standard object.
+ func_append objs " $arg"
+ ;;
+
+ *.lo)
+ # A libtool-controlled object.
+
+ # Check to see that this really is a libtool object.
+ if func_lalib_unsafe_p "$arg"; then
+ pic_object=
+ non_pic_object=
+
+ # Read the .lo file
+ func_source "$arg"
+
+ if test -z "$pic_object" ||
+ test -z "$non_pic_object" ||
+ test none = "$pic_object" &&
+ test none = "$non_pic_object"; then
+ func_fatal_error "cannot find name of object for '$arg'"
+ fi
+
+ # Extract subdirectory from the argument.
+ func_dirname "$arg" "/" ""
+ xdir=$func_dirname_result
+
+ test none = "$pic_object" || {
+ # Prepend the subdirectory the object is found in.
+ pic_object=$xdir$pic_object
+
+ if test dlfiles = "$prev"; then
+ if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then
+ func_append dlfiles " $pic_object"
+ prev=
+ continue
+ else
+ # If libtool objects are unsupported, then we need to preload.
+ prev=dlprefiles
+ fi
+ fi
+
+ # CHECK ME: I think I busted this. -Ossama
+ if test dlprefiles = "$prev"; then
+ # Preload the old-style object.
+ func_append dlprefiles " $pic_object"
+ prev=
+ fi
+
+ # A PIC object.
+ func_append libobjs " $pic_object"
+ arg=$pic_object
+ }
+
+ # Non-PIC object.
+ if test none != "$non_pic_object"; then
+ # Prepend the subdirectory the object is found in.
+ non_pic_object=$xdir$non_pic_object
+
+ # A standard non-PIC object
+ func_append non_pic_objects " $non_pic_object"
+ if test -z "$pic_object" || test none = "$pic_object"; then
+ arg=$non_pic_object
+ fi
+ else
+ # If the PIC object exists, use it instead.
+ # $xdir was prepended to $pic_object above.
+ non_pic_object=$pic_object
+ func_append non_pic_objects " $non_pic_object"
+ fi
+ else
+ # Only an error if not doing a dry-run.
+ if $opt_dry_run; then
+ # Extract subdirectory from the argument.
+ func_dirname "$arg" "/" ""
+ xdir=$func_dirname_result
+
+ func_lo2o "$arg"
+ pic_object=$xdir$objdir/$func_lo2o_result
+ non_pic_object=$xdir$func_lo2o_result
+ func_append libobjs " $pic_object"
+ func_append non_pic_objects " $non_pic_object"
+ else
+ func_fatal_error "'$arg' is not a valid libtool object"
+ fi
+ fi
+ ;;
+
+ *.$libext)
+ # An archive.
+ func_append deplibs " $arg"
+ func_append old_deplibs " $arg"
+ continue
+ ;;
+
+ *.la)
+ # A libtool-controlled library.
+
+ func_resolve_sysroot "$arg"
+ if test dlfiles = "$prev"; then
+ # This library was specified with -dlopen.
+ func_append dlfiles " $func_resolve_sysroot_result"
+ prev=
+ elif test dlprefiles = "$prev"; then
+ # The library was specified with -dlpreopen.
+ func_append dlprefiles " $func_resolve_sysroot_result"
+ prev=
+ else
+ func_append deplibs " $func_resolve_sysroot_result"
+ fi
+ continue
+ ;;
+
+ # Some other compiler argument.
+ *)
+ # Unknown arguments in both finalize_command and compile_command need
+ # to be aesthetically quoted because they are evaled later.
+ func_quote_arg pretty "$arg"
+ arg=$func_quote_arg_result
+ ;;
+ esac # arg
+
+ # Now actually substitute the argument into the commands.
+ if test -n "$arg"; then
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ fi
+ done # argument parsing loop
+
+ test -n "$prev" && \
+ func_fatal_help "the '$prevarg' option requires an argument"
+
+ if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then
+ eval arg=\"$export_dynamic_flag_spec\"
+ func_append compile_command " $arg"
+ func_append finalize_command " $arg"
+ fi
+
+ oldlibs=
+ # calculate the name of the file, without its directory
+ func_basename "$output"
+ outputname=$func_basename_result
+ libobjs_save=$libobjs
+
+ if test -n "$shlibpath_var"; then
+ # get the directories listed in $shlibpath_var
+ eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\`
+ else
+ shlib_search_path=
+ fi
+ eval sys_lib_search_path=\"$sys_lib_search_path_spec\"
+ eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\"
+
+ # Definition is injected by LT_CONFIG during libtool generation.
+ func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH"
+
+ func_dirname "$output" "/" ""
+ output_objdir=$func_dirname_result$objdir
+ func_to_tool_file "$output_objdir/"
+ tool_output_objdir=$func_to_tool_file_result
+ # Create the object directory.
+ func_mkdir_p "$output_objdir"
+
+ # Determine the type of output
+ case $output in
+ "")
+ func_fatal_help "you must specify an output file"
+ ;;
+ *.$libext) linkmode=oldlib ;;
+ *.lo | *.$objext) linkmode=obj ;;
+ *.la) linkmode=lib ;;
+ *) linkmode=prog ;; # Anything else should be a program.
+ esac
+
+ specialdeplibs=
+
+ libs=
+ # Find all interdependent deplibs by searching for libraries
+ # that are linked more than once (e.g. -la -lb -la)
+ for deplib in $deplibs; do
+ if $opt_preserve_dup_deps; then
+ case "$libs " in
+ *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+ esac
+ fi
+ func_append libs " $deplib"
+ done
+
+ if test lib = "$linkmode"; then
+ libs="$predeps $libs $compiler_lib_search_path $postdeps"
+
+ # Compute libraries that are listed more than once in $predeps
+ # $postdeps and mark them as special (i.e., whose duplicates are
+ # not to be eliminated).
+ pre_post_deps=
+ if $opt_duplicate_compiler_generated_deps; then
+ for pre_post_dep in $predeps $postdeps; do
+ case "$pre_post_deps " in
+ *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;;
+ esac
+ func_append pre_post_deps " $pre_post_dep"
+ done
+ fi
+ pre_post_deps=
+ fi
+
+ deplibs=
+ newdependency_libs=
+ newlib_search_path=
+ need_relink=no # whether we're linking any uninstalled libtool libraries
+ notinst_deplibs= # not-installed libtool libraries
+ notinst_path= # paths that contain not-installed libtool libraries
+
+ case $linkmode in
+ lib)
+ passes="conv dlpreopen link"
+ for file in $dlfiles $dlprefiles; do
+ case $file in
+ *.la) ;;
+ *)
+ func_fatal_help "libraries can '-dlopen' only libtool libraries: $file"
+ ;;
+ esac
+ done
+ ;;
+ prog)
+ compile_deplibs=
+ finalize_deplibs=
+ alldeplibs=false
+ newdlfiles=
+ newdlprefiles=
+ passes="conv scan dlopen dlpreopen link"
+ ;;
+ *) passes="conv"
+ ;;
+ esac
+
+ for pass in $passes; do
+ # The preopen pass in lib mode reverses $deplibs; put it back here
+ # so that -L comes before libs that need it for instance...
+ if test lib,link = "$linkmode,$pass"; then
+ ## FIXME: Find the place where the list is rebuilt in the wrong
+ ## order, and fix it there properly
+ tmp_deplibs=
+ for deplib in $deplibs; do
+ tmp_deplibs="$deplib $tmp_deplibs"
+ done
+ deplibs=$tmp_deplibs
+ fi
+
+ if test lib,link = "$linkmode,$pass" ||
+ test prog,scan = "$linkmode,$pass"; then
+ libs=$deplibs
+ deplibs=
+ fi
+ if test prog = "$linkmode"; then
+ case $pass in
+ dlopen) libs=$dlfiles ;;
+ dlpreopen) libs=$dlprefiles ;;
+ link)
+ libs="$deplibs %DEPLIBS%"
+ test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs"
+ ;;
+ esac
+ fi
+ if test lib,dlpreopen = "$linkmode,$pass"; then
+ # Collect and forward deplibs of preopened libtool libs
+ for lib in $dlprefiles; do
+ # Ignore non-libtool-libs
+ dependency_libs=
+ func_resolve_sysroot "$lib"
+ case $lib in
+ *.la) func_source "$func_resolve_sysroot_result" ;;
+ esac
+
+ # Collect preopened libtool deplibs, except any this library
+ # has declared as weak libs
+ for deplib in $dependency_libs; do
+ func_basename "$deplib"
+ deplib_base=$func_basename_result
+ case " $weak_libs " in
+ *" $deplib_base "*) ;;
+ *) func_append deplibs " $deplib" ;;
+ esac
+ done
+ done
+ libs=$dlprefiles
+ fi
+ if test dlopen = "$pass"; then
+ # Collect dlpreopened libraries
+ save_deplibs=$deplibs
+ deplibs=
+ fi
+
+ for deplib in $libs; do
+ lib=
+ found=false
+ case $deplib in
+ -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \
+ |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+ if test prog,link = "$linkmode,$pass"; then
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ func_append compiler_flags " $deplib"
+ if test lib = "$linkmode"; then
+ case "$new_inherited_linker_flags " in
+ *" $deplib "*) ;;
+ * ) func_append new_inherited_linker_flags " $deplib" ;;
+ esac
+ fi
+ fi
+ continue
+ ;;
+ -l*)
+ if test lib != "$linkmode" && test prog != "$linkmode"; then
+ func_warning "'-l' is ignored for archives/objects"
+ continue
+ fi
+ func_stripname '-l' '' "$deplib"
+ name=$func_stripname_result
+ if test lib = "$linkmode"; then
+ searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path"
+ else
+ searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path"
+ fi
+ for searchdir in $searchdirs; do
+ for search_ext in .la $std_shrext .so .a; do
+ # Search the libtool library
+ lib=$searchdir/lib$name$search_ext
+ if test -f "$lib"; then
+ if test .la = "$search_ext"; then
+ found=:
+ else
+ found=false
+ fi
+ break 2
+ fi
+ done
+ done
+ if $found; then
+ # deplib is a libtool library
+ # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib,
+ # We need to do some special things here, and not later.
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ case " $predeps $postdeps " in
+ *" $deplib "*)
+ if func_lalib_p "$lib"; then
+ library_names=
+ old_library=
+ func_source "$lib"
+ for l in $old_library $library_names; do
+ ll=$l
+ done
+ if test "X$ll" = "X$old_library"; then # only static version available
+ found=false
+ func_dirname "$lib" "" "."
+ ladir=$func_dirname_result
+ lib=$ladir/$old_library
+ if test prog,link = "$linkmode,$pass"; then
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ deplibs="$deplib $deplibs"
+ test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs"
+ fi
+ continue
+ fi
+ fi
+ ;;
+ *) ;;
+ esac
+ fi
+ else
+ # deplib doesn't seem to be a libtool library
+ if test prog,link = "$linkmode,$pass"; then
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ deplibs="$deplib $deplibs"
+ test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs"
+ fi
+ continue
+ fi
+ ;; # -l
+ *.ltframework)
+ if test prog,link = "$linkmode,$pass"; then
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ deplibs="$deplib $deplibs"
+ if test lib = "$linkmode"; then
+ case "$new_inherited_linker_flags " in
+ *" $deplib "*) ;;
+ * ) func_append new_inherited_linker_flags " $deplib" ;;
+ esac
+ fi
+ fi
+ continue
+ ;;
+ -L*)
+ case $linkmode in
+ lib)
+ deplibs="$deplib $deplibs"
+ test conv = "$pass" && continue
+ newdependency_libs="$deplib $newdependency_libs"
+ func_stripname '-L' '' "$deplib"
+ func_resolve_sysroot "$func_stripname_result"
+ func_append newlib_search_path " $func_resolve_sysroot_result"
+ ;;
+ prog)
+ if test conv = "$pass"; then
+ deplibs="$deplib $deplibs"
+ continue
+ fi
+ if test scan = "$pass"; then
+ deplibs="$deplib $deplibs"
+ else
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ fi
+ func_stripname '-L' '' "$deplib"
+ func_resolve_sysroot "$func_stripname_result"
+ func_append newlib_search_path " $func_resolve_sysroot_result"
+ ;;
+ *)
+ func_warning "'-L' is ignored for archives/objects"
+ ;;
+ esac # linkmode
+ continue
+ ;; # -L
+ -R*)
+ if test link = "$pass"; then
+ func_stripname '-R' '' "$deplib"
+ func_resolve_sysroot "$func_stripname_result"
+ dir=$func_resolve_sysroot_result
+ # Make sure the xrpath contains only unique directories.
+ case "$xrpath " in
+ *" $dir "*) ;;
+ *) func_append xrpath " $dir" ;;
+ esac
+ fi
+ deplibs="$deplib $deplibs"
+ continue
+ ;;
+ *.la)
+ func_resolve_sysroot "$deplib"
+ lib=$func_resolve_sysroot_result
+ ;;
+ *.$libext)
+ if test conv = "$pass"; then
+ deplibs="$deplib $deplibs"
+ continue
+ fi
+ case $linkmode in
+ lib)
+ # Linking convenience modules into shared libraries is allowed,
+ # but linking other static libraries is non-portable.
+ case " $dlpreconveniencelibs " in
+ *" $deplib "*) ;;
+ *)
+ valid_a_lib=false
+ case $deplibs_check_method in
+ match_pattern*)
+ set dummy $deplibs_check_method; shift
+ match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+ if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \
+ | $EGREP "$match_pattern_regex" > /dev/null; then
+ valid_a_lib=:
+ fi
+ ;;
+ pass_all)
+ valid_a_lib=:
+ ;;
+ esac
+ if $valid_a_lib; then
+ echo
+ $ECHO "*** Warning: Linking the shared library $output against the"
+ $ECHO "*** static library $deplib is not portable!"
+ deplibs="$deplib $deplibs"
+ else
+ echo
+ $ECHO "*** Warning: Trying to link with static lib archive $deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have"
+ echo "*** because the file extensions .$libext of this argument makes me believe"
+ echo "*** that it is just a static archive that I should not use here."
+ fi
+ ;;
+ esac
+ continue
+ ;;
+ prog)
+ if test link != "$pass"; then
+ deplibs="$deplib $deplibs"
+ else
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ fi
+ continue
+ ;;
+ esac # linkmode
+ ;; # *.$libext
+ *.lo | *.$objext)
+ if test conv = "$pass"; then
+ deplibs="$deplib $deplibs"
+ elif test prog = "$linkmode"; then
+ if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then
+ # If there is no dlopen support or we're linking statically,
+ # we need to preload.
+ func_append newdlprefiles " $deplib"
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ func_append newdlfiles " $deplib"
+ fi
+ fi
+ continue
+ ;;
+ %DEPLIBS%)
+ alldeplibs=:
+ continue
+ ;;
+ esac # case $deplib
+
+ $found || test -f "$lib" \
+ || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'"
+
+ # Check to see that this really is a libtool archive.
+ func_lalib_unsafe_p "$lib" \
+ || func_fatal_error "'$lib' is not a valid libtool archive"
+
+ func_dirname "$lib" "" "."
+ ladir=$func_dirname_result
+
+ dlname=
+ dlopen=
+ dlpreopen=
+ libdir=
+ library_names=
+ old_library=
+ inherited_linker_flags=
+ # If the library was installed with an old release of libtool,
+ # it will not redefine variables installed, or shouldnotlink
+ installed=yes
+ shouldnotlink=no
+ avoidtemprpath=
+
+
+ # Read the .la file
+ func_source "$lib"
+
+ # Convert "-framework foo" to "foo.ltframework"
+ if test -n "$inherited_linker_flags"; then
+ tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'`
+ for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do
+ case " $new_inherited_linker_flags " in
+ *" $tmp_inherited_linker_flag "*) ;;
+ *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";;
+ esac
+ done
+ fi
+ dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ if test lib,link = "$linkmode,$pass" ||
+ test prog,scan = "$linkmode,$pass" ||
+ { test prog != "$linkmode" && test lib != "$linkmode"; }; then
+ test -n "$dlopen" && func_append dlfiles " $dlopen"
+ test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen"
+ fi
+
+ if test conv = "$pass"; then
+ # Only check for convenience libraries
+ deplibs="$lib $deplibs"
+ if test -z "$libdir"; then
+ if test -z "$old_library"; then
+ func_fatal_error "cannot find name of link library for '$lib'"
+ fi
+ # It is a libtool convenience library, so add in its objects.
+ func_append convenience " $ladir/$objdir/$old_library"
+ func_append old_convenience " $ladir/$objdir/$old_library"
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ deplibs="$deplib $deplibs"
+ if $opt_preserve_dup_deps; then
+ case "$tmp_libs " in
+ *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+ esac
+ fi
+ func_append tmp_libs " $deplib"
+ done
+ elif test prog != "$linkmode" && test lib != "$linkmode"; then
+ func_fatal_error "'$lib' is not a convenience library"
+ fi
+ continue
+ fi # $pass = conv
+
+
+ # Get the name of the library we link against.
+ linklib=
+ if test -n "$old_library" &&
+ { test yes = "$prefer_static_libs" ||
+ test built,no = "$prefer_static_libs,$installed"; }; then
+ linklib=$old_library
+ else
+ for l in $old_library $library_names; do
+ linklib=$l
+ done
+ fi
+ if test -z "$linklib"; then
+ func_fatal_error "cannot find name of link library for '$lib'"
+ fi
+
+ # This library was specified with -dlopen.
+ if test dlopen = "$pass"; then
+ test -z "$libdir" \
+ && func_fatal_error "cannot -dlopen a convenience library: '$lib'"
+ if test -z "$dlname" ||
+ test yes != "$dlopen_support" ||
+ test no = "$build_libtool_libs"
+ then
+ # If there is no dlname, no dlopen support or we're linking
+ # statically, we need to preload. We also need to preload any
+ # dependent libraries so libltdl's deplib preloader doesn't
+ # bomb out in the load deplibs phase.
+ func_append dlprefiles " $lib $dependency_libs"
+ else
+ func_append newdlfiles " $lib"
+ fi
+ continue
+ fi # $pass = dlopen
+
+ # We need an absolute path.
+ case $ladir in
+ [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;;
+ *)
+ abs_ladir=`cd "$ladir" && pwd`
+ if test -z "$abs_ladir"; then
+ func_warning "cannot determine absolute directory name of '$ladir'"
+ func_warning "passing it literally to the linker, although it might fail"
+ abs_ladir=$ladir
+ fi
+ ;;
+ esac
+ func_basename "$lib"
+ laname=$func_basename_result
+
+ # Find the relevant object directory and library name.
+ if test yes = "$installed"; then
+ if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+ func_warning "library '$lib' was moved."
+ dir=$ladir
+ absdir=$abs_ladir
+ libdir=$abs_ladir
+ else
+ dir=$lt_sysroot$libdir
+ absdir=$lt_sysroot$libdir
+ fi
+ test yes = "$hardcode_automatic" && avoidtemprpath=yes
+ else
+ if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+ dir=$ladir
+ absdir=$abs_ladir
+ # Remove this search path later
+ func_append notinst_path " $abs_ladir"
+ else
+ dir=$ladir/$objdir
+ absdir=$abs_ladir/$objdir
+ # Remove this search path later
+ func_append notinst_path " $abs_ladir"
+ fi
+ fi # $installed = yes
+ func_stripname 'lib' '.la' "$laname"
+ name=$func_stripname_result
+
+ # This library was specified with -dlpreopen.
+ if test dlpreopen = "$pass"; then
+ if test -z "$libdir" && test prog = "$linkmode"; then
+ func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'"
+ fi
+ case $host in
+ # special handling for platforms with PE-DLLs.
+ *cygwin* | *mingw* | *cegcc* )
+ # Linker will automatically link against shared library if both
+ # static and shared are present. Therefore, ensure we extract
+ # symbols from the import library if a shared library is present
+ # (otherwise, the dlopen module name will be incorrect). We do
+ # this by putting the import library name into $newdlprefiles.
+ # We recover the dlopen module name by 'saving' the la file
+ # name in a special purpose variable, and (later) extracting the
+ # dlname from the la file.
+ if test -n "$dlname"; then
+ func_tr_sh "$dir/$linklib"
+ eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname"
+ func_append newdlprefiles " $dir/$linklib"
+ else
+ func_append newdlprefiles " $dir/$old_library"
+ # Keep a list of preopened convenience libraries to check
+ # that they are being used correctly in the link pass.
+ test -z "$libdir" && \
+ func_append dlpreconveniencelibs " $dir/$old_library"
+ fi
+ ;;
+ * )
+ # Prefer using a static library (so that no silly _DYNAMIC symbols
+ # are required to link).
+ if test -n "$old_library"; then
+ func_append newdlprefiles " $dir/$old_library"
+ # Keep a list of preopened convenience libraries to check
+ # that they are being used correctly in the link pass.
+ test -z "$libdir" && \
+ func_append dlpreconveniencelibs " $dir/$old_library"
+ # Otherwise, use the dlname, so that lt_dlopen finds it.
+ elif test -n "$dlname"; then
+ func_append newdlprefiles " $dir/$dlname"
+ else
+ func_append newdlprefiles " $dir/$linklib"
+ fi
+ ;;
+ esac
+ fi # $pass = dlpreopen
+
+ if test -z "$libdir"; then
+ # Link the convenience library
+ if test lib = "$linkmode"; then
+ deplibs="$dir/$old_library $deplibs"
+ elif test prog,link = "$linkmode,$pass"; then
+ compile_deplibs="$dir/$old_library $compile_deplibs"
+ finalize_deplibs="$dir/$old_library $finalize_deplibs"
+ else
+ deplibs="$lib $deplibs" # used for prog,scan pass
+ fi
+ continue
+ fi
+
+
+ if test prog = "$linkmode" && test link != "$pass"; then
+ func_append newlib_search_path " $ladir"
+ deplibs="$lib $deplibs"
+
+ linkalldeplibs=false
+ if test no != "$link_all_deplibs" || test -z "$library_names" ||
+ test no = "$build_libtool_libs"; then
+ linkalldeplibs=:
+ fi
+
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ case $deplib in
+ -L*) func_stripname '-L' '' "$deplib"
+ func_resolve_sysroot "$func_stripname_result"
+ func_append newlib_search_path " $func_resolve_sysroot_result"
+ ;;
+ esac
+ # Need to link against all dependency_libs?
+ if $linkalldeplibs; then
+ deplibs="$deplib $deplibs"
+ else
+ # Need to hardcode shared library paths
+ # or/and link against static libraries
+ newdependency_libs="$deplib $newdependency_libs"
+ fi
+ if $opt_preserve_dup_deps; then
+ case "$tmp_libs " in
+ *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+ esac
+ fi
+ func_append tmp_libs " $deplib"
+ done # for deplib
+ continue
+ fi # $linkmode = prog...
+
+ if test prog,link = "$linkmode,$pass"; then
+ if test -n "$library_names" &&
+ { { test no = "$prefer_static_libs" ||
+ test built,yes = "$prefer_static_libs,$installed"; } ||
+ test -z "$old_library"; }; then
+ # We need to hardcode the library path
+ if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then
+ # Make sure the rpath contains only unique directories.
+ case $temp_rpath: in
+ *"$absdir:"*) ;;
+ *) func_append temp_rpath "$absdir:" ;;
+ esac
+ fi
+
+ # Hardcode the library path.
+ # Skip directories that are in the system default run-time
+ # search path.
+ case " $sys_lib_dlsearch_path " in
+ *" $absdir "*) ;;
+ *)
+ case "$compile_rpath " in
+ *" $absdir "*) ;;
+ *) func_append compile_rpath " $absdir" ;;
+ esac
+ ;;
+ esac
+ case " $sys_lib_dlsearch_path " in
+ *" $libdir "*) ;;
+ *)
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) func_append finalize_rpath " $libdir" ;;
+ esac
+ ;;
+ esac
+ fi # $linkmode,$pass = prog,link...
+
+ if $alldeplibs &&
+ { test pass_all = "$deplibs_check_method" ||
+ { test yes = "$build_libtool_libs" &&
+ test -n "$library_names"; }; }; then
+ # We only need to search for static libraries
+ continue
+ fi
+ fi
+
+ link_static=no # Whether the deplib will be linked statically
+ use_static_libs=$prefer_static_libs
+ if test built = "$use_static_libs" && test yes = "$installed"; then
+ use_static_libs=no
+ fi
+ if test -n "$library_names" &&
+ { test no = "$use_static_libs" || test -z "$old_library"; }; then
+ case $host in
+ *cygwin* | *mingw* | *cegcc* | *os2*)
+ # No point in relinking DLLs because paths are not encoded
+ func_append notinst_deplibs " $lib"
+ need_relink=no
+ ;;
+ *)
+ if test no = "$installed"; then
+ func_append notinst_deplibs " $lib"
+ need_relink=yes
+ fi
+ ;;
+ esac
+ # This is a shared library
+
+ # Warn about portability, can't link against -module's on some
+ # systems (darwin). Don't bleat about dlopened modules though!
+ dlopenmodule=
+ for dlpremoduletest in $dlprefiles; do
+ if test "X$dlpremoduletest" = "X$lib"; then
+ dlopenmodule=$dlpremoduletest
+ break
+ fi
+ done
+ if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then
+ echo
+ if test prog = "$linkmode"; then
+ $ECHO "*** Warning: Linking the executable $output against the loadable module"
+ else
+ $ECHO "*** Warning: Linking the shared library $output against the loadable module"
+ fi
+ $ECHO "*** $linklib is not portable!"
+ fi
+ if test lib = "$linkmode" &&
+ test yes = "$hardcode_into_libs"; then
+ # Hardcode the library path.
+ # Skip directories that are in the system default run-time
+ # search path.
+ case " $sys_lib_dlsearch_path " in
+ *" $absdir "*) ;;
+ *)
+ case "$compile_rpath " in
+ *" $absdir "*) ;;
+ *) func_append compile_rpath " $absdir" ;;
+ esac
+ ;;
+ esac
+ case " $sys_lib_dlsearch_path " in
+ *" $libdir "*) ;;
+ *)
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) func_append finalize_rpath " $libdir" ;;
+ esac
+ ;;
+ esac
+ fi
+
+ if test -n "$old_archive_from_expsyms_cmds"; then
+ # figure out the soname
+ set dummy $library_names
+ shift
+ realname=$1
+ shift
+ libname=`eval "\\$ECHO \"$libname_spec\""`
+ # use dlname if we got it. it's perfectly good, no?
+ if test -n "$dlname"; then
+ soname=$dlname
+ elif test -n "$soname_spec"; then
+ # bleh windows
+ case $host in
+ *cygwin* | mingw* | *cegcc* | *os2*)
+ func_arith $current - $age
+ major=$func_arith_result
+ versuffix=-$major
+ ;;
+ esac
+ eval soname=\"$soname_spec\"
+ else
+ soname=$realname
+ fi
+
+ # Make a new name for the extract_expsyms_cmds to use
+ soroot=$soname
+ func_basename "$soroot"
+ soname=$func_basename_result
+ func_stripname 'lib' '.dll' "$soname"
+ newlib=libimp-$func_stripname_result.a
+
+ # If the library has no export list, then create one now
+ if test -f "$output_objdir/$soname-def"; then :
+ else
+ func_verbose "extracting exported symbol list from '$soname'"
+ func_execute_cmds "$extract_expsyms_cmds" 'exit $?'
+ fi
+
+ # Create $newlib
+ if test -f "$output_objdir/$newlib"; then :; else
+ func_verbose "generating import library for '$soname'"
+ func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?'
+ fi
+ # make sure the library variables are pointing to the new library
+ dir=$output_objdir
+ linklib=$newlib
+ fi # test -n "$old_archive_from_expsyms_cmds"
+
+ if test prog = "$linkmode" || test relink != "$opt_mode"; then
+ add_shlibpath=
+ add_dir=
+ add=
+ lib_linked=yes
+ case $hardcode_action in
+ immediate | unsupported)
+ if test no = "$hardcode_direct"; then
+ add=$dir/$linklib
+ case $host in
+ *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;;
+ *-*-sysv4*uw2*) add_dir=-L$dir ;;
+ *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \
+ *-*-unixware7*) add_dir=-L$dir ;;
+ *-*-darwin* )
+ # if the lib is a (non-dlopened) module then we cannot
+ # link against it, someone is ignoring the earlier warnings
+ if /usr/bin/file -L $add 2> /dev/null |
+ $GREP ": [^:]* bundle" >/dev/null; then
+ if test "X$dlopenmodule" != "X$lib"; then
+ $ECHO "*** Warning: lib $linklib is a module, not a shared library"
+ if test -z "$old_library"; then
+ echo
+ echo "*** And there doesn't seem to be a static archive available"
+ echo "*** The link will probably fail, sorry"
+ else
+ add=$dir/$old_library
+ fi
+ elif test -n "$old_library"; then
+ add=$dir/$old_library
+ fi
+ fi
+ esac
+ elif test no = "$hardcode_minus_L"; then
+ case $host in
+ *-*-sunos*) add_shlibpath=$dir ;;
+ esac
+ add_dir=-L$dir
+ add=-l$name
+ elif test no = "$hardcode_shlibpath_var"; then
+ add_shlibpath=$dir
+ add=-l$name
+ else
+ lib_linked=no
+ fi
+ ;;
+ relink)
+ if test yes = "$hardcode_direct" &&
+ test no = "$hardcode_direct_absolute"; then
+ add=$dir/$linklib
+ elif test yes = "$hardcode_minus_L"; then
+ add_dir=-L$absdir
+ # Try looking first in the location we're being installed to.
+ if test -n "$inst_prefix_dir"; then
+ case $libdir in
+ [\\/]*)
+ func_append add_dir " -L$inst_prefix_dir$libdir"
+ ;;
+ esac
+ fi
+ add=-l$name
+ elif test yes = "$hardcode_shlibpath_var"; then
+ add_shlibpath=$dir
+ add=-l$name
+ else
+ lib_linked=no
+ fi
+ ;;
+ *) lib_linked=no ;;
+ esac
+
+ if test yes != "$lib_linked"; then
+ func_fatal_configuration "unsupported hardcode properties"
+ fi
+
+ if test -n "$add_shlibpath"; then
+ case :$compile_shlibpath: in
+ *":$add_shlibpath:"*) ;;
+ *) func_append compile_shlibpath "$add_shlibpath:" ;;
+ esac
+ fi
+ if test prog = "$linkmode"; then
+ test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs"
+ test -n "$add" && compile_deplibs="$add $compile_deplibs"
+ else
+ test -n "$add_dir" && deplibs="$add_dir $deplibs"
+ test -n "$add" && deplibs="$add $deplibs"
+ if test yes != "$hardcode_direct" &&
+ test yes != "$hardcode_minus_L" &&
+ test yes = "$hardcode_shlibpath_var"; then
+ case :$finalize_shlibpath: in
+ *":$libdir:"*) ;;
+ *) func_append finalize_shlibpath "$libdir:" ;;
+ esac
+ fi
+ fi
+ fi
+
+ if test prog = "$linkmode" || test relink = "$opt_mode"; then
+ add_shlibpath=
+ add_dir=
+ add=
+ # Finalize command for both is simple: just hardcode it.
+ if test yes = "$hardcode_direct" &&
+ test no = "$hardcode_direct_absolute"; then
+ add=$libdir/$linklib
+ elif test yes = "$hardcode_minus_L"; then
+ add_dir=-L$libdir
+ add=-l$name
+ elif test yes = "$hardcode_shlibpath_var"; then
+ case :$finalize_shlibpath: in
+ *":$libdir:"*) ;;
+ *) func_append finalize_shlibpath "$libdir:" ;;
+ esac
+ add=-l$name
+ elif test yes = "$hardcode_automatic"; then
+ if test -n "$inst_prefix_dir" &&
+ test -f "$inst_prefix_dir$libdir/$linklib"; then
+ add=$inst_prefix_dir$libdir/$linklib
+ else
+ add=$libdir/$linklib
+ fi
+ else
+ # We cannot seem to hardcode it, guess we'll fake it.
+ add_dir=-L$libdir
+ # Try looking first in the location we're being installed to.
+ if test -n "$inst_prefix_dir"; then
+ case $libdir in
+ [\\/]*)
+ func_append add_dir " -L$inst_prefix_dir$libdir"
+ ;;
+ esac
+ fi
+ add=-l$name
+ fi
+
+ if test prog = "$linkmode"; then
+ test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs"
+ test -n "$add" && finalize_deplibs="$add $finalize_deplibs"
+ else
+ test -n "$add_dir" && deplibs="$add_dir $deplibs"
+ test -n "$add" && deplibs="$add $deplibs"
+ fi
+ fi
+ elif test prog = "$linkmode"; then
+ # Here we assume that one of hardcode_direct or hardcode_minus_L
+ # is not unsupported. This is valid on all known static and
+ # shared platforms.
+ if test unsupported != "$hardcode_direct"; then
+ test -n "$old_library" && linklib=$old_library
+ compile_deplibs="$dir/$linklib $compile_deplibs"
+ finalize_deplibs="$dir/$linklib $finalize_deplibs"
+ else
+ compile_deplibs="-l$name -L$dir $compile_deplibs"
+ finalize_deplibs="-l$name -L$dir $finalize_deplibs"
+ fi
+ elif test yes = "$build_libtool_libs"; then
+ # Not a shared library
+ if test pass_all != "$deplibs_check_method"; then
+ # We're trying link a shared library against a static one
+ # but the system doesn't support it.
+
+ # Just print a warning and add the library to dependency_libs so
+ # that the program can be linked against the static library.
+ echo
+ $ECHO "*** Warning: This system cannot link to static lib archive $lib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ if test yes = "$module"; then
+ echo "*** But as you try to build a module library, libtool will still create "
+ echo "*** a static module, that should work as long as the dlopening application"
+ echo "*** is linked with the -dlopen flag to resolve symbols at runtime."
+ if test -z "$global_symbol_pipe"; then
+ echo
+ echo "*** However, this would only work if libtool was able to extract symbol"
+ echo "*** lists from a program, using 'nm' or equivalent, but libtool could"
+ echo "*** not find such a program. So, this module is probably useless."
+ echo "*** 'nm' from GNU binutils and a full rebuild may help."
+ fi
+ if test no = "$build_old_libs"; then
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ fi
+ else
+ deplibs="$dir/$old_library $deplibs"
+ link_static=yes
+ fi
+ fi # link shared/static library?
+
+ if test lib = "$linkmode"; then
+ if test -n "$dependency_libs" &&
+ { test yes != "$hardcode_into_libs" ||
+ test yes = "$build_old_libs" ||
+ test yes = "$link_static"; }; then
+ # Extract -R from dependency_libs
+ temp_deplibs=
+ for libdir in $dependency_libs; do
+ case $libdir in
+ -R*) func_stripname '-R' '' "$libdir"
+ temp_xrpath=$func_stripname_result
+ case " $xrpath " in
+ *" $temp_xrpath "*) ;;
+ *) func_append xrpath " $temp_xrpath";;
+ esac;;
+ *) func_append temp_deplibs " $libdir";;
+ esac
+ done
+ dependency_libs=$temp_deplibs
+ fi
+
+ func_append newlib_search_path " $absdir"
+ # Link against this library
+ test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs"
+ # ... and its dependency_libs
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ newdependency_libs="$deplib $newdependency_libs"
+ case $deplib in
+ -L*) func_stripname '-L' '' "$deplib"
+ func_resolve_sysroot "$func_stripname_result";;
+ *) func_resolve_sysroot "$deplib" ;;
+ esac
+ if $opt_preserve_dup_deps; then
+ case "$tmp_libs " in
+ *" $func_resolve_sysroot_result "*)
+ func_append specialdeplibs " $func_resolve_sysroot_result" ;;
+ esac
+ fi
+ func_append tmp_libs " $func_resolve_sysroot_result"
+ done
+
+ if test no != "$link_all_deplibs"; then
+ # Add the search paths of all dependency libraries
+ for deplib in $dependency_libs; do
+ path=
+ case $deplib in
+ -L*) path=$deplib ;;
+ *.la)
+ func_resolve_sysroot "$deplib"
+ deplib=$func_resolve_sysroot_result
+ func_dirname "$deplib" "" "."
+ dir=$func_dirname_result
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;;
+ *)
+ absdir=`cd "$dir" && pwd`
+ if test -z "$absdir"; then
+ func_warning "cannot determine absolute directory name of '$dir'"
+ absdir=$dir
+ fi
+ ;;
+ esac
+ if $GREP "^installed=no" $deplib > /dev/null; then
+ case $host in
+ *-*-darwin*)
+ depdepl=
+ eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib`
+ if test -n "$deplibrary_names"; then
+ for tmp in $deplibrary_names; do
+ depdepl=$tmp
+ done
+ if test -f "$absdir/$objdir/$depdepl"; then
+ depdepl=$absdir/$objdir/$depdepl
+ darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'`
+ if test -z "$darwin_install_name"; then
+ darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'`
+ fi
+ func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl"
+ func_append linker_flags " -dylib_file $darwin_install_name:$depdepl"
+ path=
+ fi
+ fi
+ ;;
+ *)
+ path=-L$absdir/$objdir
+ ;;
+ esac
+ else
+ eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
+ test -z "$libdir" && \
+ func_fatal_error "'$deplib' is not a valid libtool archive"
+ test "$absdir" != "$libdir" && \
+ func_warning "'$deplib' seems to be moved"
+
+ path=-L$absdir
+ fi
+ ;;
+ esac
+ case " $deplibs " in
+ *" $path "*) ;;
+ *) deplibs="$path $deplibs" ;;
+ esac
+ done
+ fi # link_all_deplibs != no
+ fi # linkmode = lib
+ done # for deplib in $libs
+ if test link = "$pass"; then
+ if test prog = "$linkmode"; then
+ compile_deplibs="$new_inherited_linker_flags $compile_deplibs"
+ finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs"
+ else
+ compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ fi
+ fi
+ dependency_libs=$newdependency_libs
+ if test dlpreopen = "$pass"; then
+ # Link the dlpreopened libraries before other libraries
+ for deplib in $save_deplibs; do
+ deplibs="$deplib $deplibs"
+ done
+ fi
+ if test dlopen != "$pass"; then
+ test conv = "$pass" || {
+ # Make sure lib_search_path contains only unique directories.
+ lib_search_path=
+ for dir in $newlib_search_path; do
+ case "$lib_search_path " in
+ *" $dir "*) ;;
+ *) func_append lib_search_path " $dir" ;;
+ esac
+ done
+ newlib_search_path=
+ }
+
+ if test prog,link = "$linkmode,$pass"; then
+ vars="compile_deplibs finalize_deplibs"
+ else
+ vars=deplibs
+ fi
+ for var in $vars dependency_libs; do
+ # Add libraries to $var in reverse order
+ eval tmp_libs=\"\$$var\"
+ new_libs=
+ for deplib in $tmp_libs; do
+ # FIXME: Pedantically, this is the right thing to do, so
+ # that some nasty dependency loop isn't accidentally
+ # broken:
+ #new_libs="$deplib $new_libs"
+ # Pragmatically, this seems to cause very few problems in
+ # practice:
+ case $deplib in
+ -L*) new_libs="$deplib $new_libs" ;;
+ -R*) ;;
+ *)
+ # And here is the reason: when a library appears more
+ # than once as an explicit dependence of a library, or
+ # is implicitly linked in more than once by the
+ # compiler, it is considered special, and multiple
+ # occurrences thereof are not removed. Compare this
+ # with having the same library being listed as a
+ # dependency of multiple other libraries: in this case,
+ # we know (pedantically, we assume) the library does not
+ # need to be listed more than once, so we keep only the
+ # last copy. This is not always right, but it is rare
+ # enough that we require users that really mean to play
+ # such unportable linking tricks to link the library
+ # using -Wl,-lname, so that libtool does not consider it
+ # for duplicate removal.
+ case " $specialdeplibs " in
+ *" $deplib "*) new_libs="$deplib $new_libs" ;;
+ *)
+ case " $new_libs " in
+ *" $deplib "*) ;;
+ *) new_libs="$deplib $new_libs" ;;
+ esac
+ ;;
+ esac
+ ;;
+ esac
+ done
+ tmp_libs=
+ for deplib in $new_libs; do
+ case $deplib in
+ -L*)
+ case " $tmp_libs " in
+ *" $deplib "*) ;;
+ *) func_append tmp_libs " $deplib" ;;
+ esac
+ ;;
+ *) func_append tmp_libs " $deplib" ;;
+ esac
+ done
+ eval $var=\"$tmp_libs\"
+ done # for var
+ fi
+
+ # Add Sun CC postdeps if required:
+ test CXX = "$tagname" && {
+ case $host_os in
+ linux*)
+ case `$CC -V 2>&1 | $SED 5q` in
+ *Sun\ C*) # Sun C++ 5.9
+ func_suncc_cstd_abi
+
+ if test no != "$suncc_use_cstd_abi"; then
+ func_append postdeps ' -library=Cstd -library=Crun'
+ fi
+ ;;
+ esac
+ ;;
+
+ solaris*)
+ func_cc_basename "$CC"
+ case $func_cc_basename_result in
+ CC* | sunCC*)
+ func_suncc_cstd_abi
+
+ if test no != "$suncc_use_cstd_abi"; then
+ func_append postdeps ' -library=Cstd -library=Crun'
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ }
+
+ # Last step: remove runtime libs from dependency_libs
+ # (they stay in deplibs)
+ tmp_libs=
+ for i in $dependency_libs; do
+ case " $predeps $postdeps $compiler_lib_search_path " in
+ *" $i "*)
+ i=
+ ;;
+ esac
+ if test -n "$i"; then
+ func_append tmp_libs " $i"
+ fi
+ done
+ dependency_libs=$tmp_libs
+ done # for pass
+ if test prog = "$linkmode"; then
+ dlfiles=$newdlfiles
+ fi
+ if test prog = "$linkmode" || test lib = "$linkmode"; then
+ dlprefiles=$newdlprefiles
+ fi
+
+ case $linkmode in
+ oldlib)
+ if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then
+ func_warning "'-dlopen' is ignored for archives"
+ fi
+
+ case " $deplibs" in
+ *\ -l* | *\ -L*)
+ func_warning "'-l' and '-L' are ignored for archives" ;;
+ esac
+
+ test -n "$rpath" && \
+ func_warning "'-rpath' is ignored for archives"
+
+ test -n "$xrpath" && \
+ func_warning "'-R' is ignored for archives"
+
+ test -n "$vinfo" && \
+ func_warning "'-version-info/-version-number' is ignored for archives"
+
+ test -n "$release" && \
+ func_warning "'-release' is ignored for archives"
+
+ test -n "$export_symbols$export_symbols_regex" && \
+ func_warning "'-export-symbols' is ignored for archives"
+
+ # Now set the variables for building old libraries.
+ build_libtool_libs=no
+ oldlibs=$output
+ func_append objs "$old_deplibs"
+ ;;
+
+ lib)
+ # Make sure we only generate libraries of the form 'libNAME.la'.
+ case $outputname in
+ lib*)
+ func_stripname 'lib' '.la' "$outputname"
+ name=$func_stripname_result
+ eval shared_ext=\"$shrext_cmds\"
+ eval libname=\"$libname_spec\"
+ ;;
+ *)
+ test no = "$module" \
+ && func_fatal_help "libtool library '$output' must begin with 'lib'"
+
+ if test no != "$need_lib_prefix"; then
+ # Add the "lib" prefix for modules if required
+ func_stripname '' '.la' "$outputname"
+ name=$func_stripname_result
+ eval shared_ext=\"$shrext_cmds\"
+ eval libname=\"$libname_spec\"
+ else
+ func_stripname '' '.la' "$outputname"
+ libname=$func_stripname_result
+ fi
+ ;;
+ esac
+
+ if test -n "$objs"; then
+ if test pass_all != "$deplibs_check_method"; then
+ func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs"
+ else
+ echo
+ $ECHO "*** Warning: Linking the shared library $output against the non-libtool"
+ $ECHO "*** objects $objs is not portable!"
+ func_append libobjs " $objs"
+ fi
+ fi
+
+ test no = "$dlself" \
+ || func_warning "'-dlopen self' is ignored for libtool libraries"
+
+ set dummy $rpath
+ shift
+ test 1 -lt "$#" \
+ && func_warning "ignoring multiple '-rpath's for a libtool library"
+
+ install_libdir=$1
+
+ oldlibs=
+ if test -z "$rpath"; then
+ if test yes = "$build_libtool_libs"; then
+ # Building a libtool convenience library.
+ # Some compilers have problems with a '.al' extension so
+ # convenience libraries should have the same extension an
+ # archive normally would.
+ oldlibs="$output_objdir/$libname.$libext $oldlibs"
+ build_libtool_libs=convenience
+ build_old_libs=yes
+ fi
+
+ test -n "$vinfo" && \
+ func_warning "'-version-info/-version-number' is ignored for convenience libraries"
+
+ test -n "$release" && \
+ func_warning "'-release' is ignored for convenience libraries"
+ else
+
+ # Parse the version information argument.
+ save_ifs=$IFS; IFS=:
+ set dummy $vinfo 0 0 0
+ shift
+ IFS=$save_ifs
+
+ test -n "$7" && \
+ func_fatal_help "too many parameters to '-version-info'"
+
+ # convert absolute version numbers to libtool ages
+ # this retains compatibility with .la files and attempts
+ # to make the code below a bit more comprehensible
+
+ case $vinfo_number in
+ yes)
+ number_major=$1
+ number_minor=$2
+ number_revision=$3
+ #
+ # There are really only two kinds -- those that
+ # use the current revision as the major version
+ # and those that subtract age and use age as
+ # a minor version. But, then there is irix
+ # that has an extra 1 added just for fun
+ #
+ case $version_type in
+ # correct linux to gnu/linux during the next big refactor
+ darwin|freebsd-elf|linux|midnightbsd-elf|osf|windows|none)
+ func_arith $number_major + $number_minor
+ current=$func_arith_result
+ age=$number_minor
+ revision=$number_revision
+ ;;
+ freebsd-aout|qnx|sunos)
+ current=$number_major
+ revision=$number_minor
+ age=0
+ ;;
+ irix|nonstopux)
+ func_arith $number_major + $number_minor
+ current=$func_arith_result
+ age=$number_minor
+ revision=$number_minor
+ lt_irix_increment=no
+ ;;
+ *)
+ func_fatal_configuration "$modename: unknown library version type '$version_type'"
+ ;;
+ esac
+ ;;
+ no)
+ current=$1
+ revision=$2
+ age=$3
+ ;;
+ esac
+
+ # Check that each of the things are valid numbers.
+ case $current in
+ 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+ *)
+ func_error "CURRENT '$current' must be a nonnegative integer"
+ func_fatal_error "'$vinfo' is not valid version information"
+ ;;
+ esac
+
+ case $revision in
+ 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+ *)
+ func_error "REVISION '$revision' must be a nonnegative integer"
+ func_fatal_error "'$vinfo' is not valid version information"
+ ;;
+ esac
+
+ case $age in
+ 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+ *)
+ func_error "AGE '$age' must be a nonnegative integer"
+ func_fatal_error "'$vinfo' is not valid version information"
+ ;;
+ esac
+
+ if test "$age" -gt "$current"; then
+ func_error "AGE '$age' is greater than the current interface number '$current'"
+ func_fatal_error "'$vinfo' is not valid version information"
+ fi
+
+ # Calculate the version variables.
+ major=
+ versuffix=
+ verstring=
+ case $version_type in
+ none) ;;
+
+ darwin)
+ # Like Linux, but with the current version available in
+ # verstring for coding it into the library header
+ func_arith $current - $age
+ major=.$func_arith_result
+ versuffix=$major.$age.$revision
+ # Darwin ld doesn't like 0 for these options...
+ func_arith $current + 1
+ minor_current=$func_arith_result
+ xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision"
+ verstring="-compatibility_version $minor_current -current_version $minor_current.$revision"
+ # On Darwin other compilers
+ case $CC in
+ nagfor*)
+ verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision"
+ ;;
+ *)
+ verstring="-compatibility_version $minor_current -current_version $minor_current.$revision"
+ ;;
+ esac
+ ;;
+
+ freebsd-aout)
+ major=.$current
+ versuffix=.$current.$revision
+ ;;
+
+ freebsd-elf | midnightbsd-elf)
+ func_arith $current - $age
+ major=.$func_arith_result
+ versuffix=$major.$age.$revision
+ ;;
+
+ irix | nonstopux)
+ if test no = "$lt_irix_increment"; then
+ func_arith $current - $age
+ else
+ func_arith $current - $age + 1
+ fi
+ major=$func_arith_result
+
+ case $version_type in
+ nonstopux) verstring_prefix=nonstopux ;;
+ *) verstring_prefix=sgi ;;
+ esac
+ verstring=$verstring_prefix$major.$revision
+
+ # Add in all the interfaces that we are compatible with.
+ loop=$revision
+ while test 0 -ne "$loop"; do
+ func_arith $revision - $loop
+ iface=$func_arith_result
+ func_arith $loop - 1
+ loop=$func_arith_result
+ verstring=$verstring_prefix$major.$iface:$verstring
+ done
+
+ # Before this point, $major must not contain '.'.
+ major=.$major
+ versuffix=$major.$revision
+ ;;
+
+ linux) # correct to gnu/linux during the next big refactor
+ func_arith $current - $age
+ major=.$func_arith_result
+ versuffix=$major.$age.$revision
+ ;;
+
+ osf)
+ func_arith $current - $age
+ major=.$func_arith_result
+ versuffix=.$current.$age.$revision
+ verstring=$current.$age.$revision
+
+ # Add in all the interfaces that we are compatible with.
+ loop=$age
+ while test 0 -ne "$loop"; do
+ func_arith $current - $loop
+ iface=$func_arith_result
+ func_arith $loop - 1
+ loop=$func_arith_result
+ verstring=$verstring:$iface.0
+ done
+
+ # Make executables depend on our current version.
+ func_append verstring ":$current.0"
+ ;;
+
+ qnx)
+ major=.$current
+ versuffix=.$current
+ ;;
+
+ sco)
+ major=.$current
+ versuffix=.$current
+ ;;
+
+ sunos)
+ major=.$current
+ versuffix=.$current.$revision
+ ;;
+
+ windows)
+ # Use '-' rather than '.', since we only want one
+ # extension on DOS 8.3 file systems.
+ func_arith $current - $age
+ major=$func_arith_result
+ versuffix=-$major
+ ;;
+
+ *)
+ func_fatal_configuration "unknown library version type '$version_type'"
+ ;;
+ esac
+
+ # Clear the version info if we defaulted, and they specified a release.
+ if test -z "$vinfo" && test -n "$release"; then
+ major=
+ case $version_type in
+ darwin)
+ # we can't check for "0.0" in archive_cmds due to quoting
+ # problems, so we reset it completely
+ verstring=
+ ;;
+ *)
+ verstring=0.0
+ ;;
+ esac
+ if test no = "$need_version"; then
+ versuffix=
+ else
+ versuffix=.0.0
+ fi
+ fi
+
+ # Remove version info from name if versioning should be avoided
+ if test yes,no = "$avoid_version,$need_version"; then
+ major=
+ versuffix=
+ verstring=
+ fi
+
+ # Check to see if the archive will have undefined symbols.
+ if test yes = "$allow_undefined"; then
+ if test unsupported = "$allow_undefined_flag"; then
+ if test yes = "$build_old_libs"; then
+ func_warning "undefined symbols not allowed in $host shared libraries; building static only"
+ build_libtool_libs=no
+ else
+ func_fatal_error "can't build $host shared library unless -no-undefined is specified"
+ fi
+ fi
+ else
+ # Don't allow undefined symbols.
+ allow_undefined_flag=$no_undefined_flag
+ fi
+
+ fi
+
+ func_generate_dlsyms "$libname" "$libname" :
+ func_append libobjs " $symfileobj"
+ test " " = "$libobjs" && libobjs=
+
+ if test relink != "$opt_mode"; then
+ # Remove our outputs, but don't remove object files since they
+ # may have been created when compiling PIC objects.
+ removelist=
+ tempremovelist=`$ECHO "$output_objdir/*"`
+ for p in $tempremovelist; do
+ case $p in
+ *.$objext | *.gcno)
+ ;;
+ $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*)
+ if test -n "$precious_files_regex"; then
+ if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1
+ then
+ continue
+ fi
+ fi
+ func_append removelist " $p"
+ ;;
+ *) ;;
+ esac
+ done
+ test -n "$removelist" && \
+ func_show_eval "${RM}r \$removelist"
+ fi
+
+ # Now set the variables for building old libraries.
+ if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then
+ func_append oldlibs " $output_objdir/$libname.$libext"
+
+ # Transform .lo files to .o files.
+ oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP`
+ fi
+
+ # Eliminate all temporary directories.
+ #for path in $notinst_path; do
+ # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"`
+ # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"`
+ # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"`
+ #done
+
+ if test -n "$xrpath"; then
+ # If the user specified any rpath flags, then add them.
+ temp_xrpath=
+ for libdir in $xrpath; do
+ func_replace_sysroot "$libdir"
+ func_append temp_xrpath " -R$func_replace_sysroot_result"
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) func_append finalize_rpath " $libdir" ;;
+ esac
+ done
+ if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then
+ dependency_libs="$temp_xrpath $dependency_libs"
+ fi
+ fi
+
+ # Make sure dlfiles contains only unique files that won't be dlpreopened
+ old_dlfiles=$dlfiles
+ dlfiles=
+ for lib in $old_dlfiles; do
+ case " $dlprefiles $dlfiles " in
+ *" $lib "*) ;;
+ *) func_append dlfiles " $lib" ;;
+ esac
+ done
+
+ # Make sure dlprefiles contains only unique files
+ old_dlprefiles=$dlprefiles
+ dlprefiles=
+ for lib in $old_dlprefiles; do
+ case "$dlprefiles " in
+ *" $lib "*) ;;
+ *) func_append dlprefiles " $lib" ;;
+ esac
+ done
+
+ if test yes = "$build_libtool_libs"; then
+ if test -n "$rpath"; then
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*)
+ # these systems don't actually have a c library (as such)!
+ ;;
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # Rhapsody C library is in the System framework
+ func_append deplibs " System.ltframework"
+ ;;
+ *-*-netbsd*)
+ # Don't link with libc until the a.out ld.so is fixed.
+ ;;
+ *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-midnightbsd*)
+ # Do not include libc due to us having libc/libc_r.
+ ;;
+ *-*-sco3.2v5* | *-*-sco5v6*)
+ # Causes problems with __ctype
+ ;;
+ *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+ # Compiler inserts libc in the correct place for threads to work
+ ;;
+ *)
+ # Add libc to deplibs on all other systems if necessary.
+ if test yes = "$build_libtool_need_lc"; then
+ func_append deplibs " -lc"
+ fi
+ ;;
+ esac
+ fi
+
+ # Transform deplibs into only deplibs that can be linked in shared.
+ name_save=$name
+ libname_save=$libname
+ release_save=$release
+ versuffix_save=$versuffix
+ major_save=$major
+ # I'm not sure if I'm treating the release correctly. I think
+ # release should show up in the -l (ie -lgmp5) so we don't want to
+ # add it in twice. Is that correct?
+ release=
+ versuffix=
+ major=
+ newdeplibs=
+ droppeddeps=no
+ case $deplibs_check_method in
+ pass_all)
+ # Don't check for shared/static. Everything works.
+ # This might be a little naive. We might want to check
+ # whether the library exists or not. But this is on
+ # osf3 & osf4 and I'm not really sure... Just
+ # implementing what was already the behavior.
+ newdeplibs=$deplibs
+ ;;
+ test_compile)
+ # This code stresses the "libraries are programs" paradigm to its
+ # limits. Maybe even breaks it. We compile a program, linking it
+ # against the deplibs as a proxy for the library. Then we can check
+ # whether they linked in statically or dynamically with ldd.
+ $opt_dry_run || $RM conftest.c
+ cat > conftest.c <<EOF
+ int main() { return 0; }
+EOF
+ $opt_dry_run || $RM conftest
+ if $LTCC $LTCFLAGS -o conftest conftest.c $deplibs; then
+ ldd_output=`ldd conftest`
+ for i in $deplibs; do
+ case $i in
+ -l*)
+ func_stripname -l '' "$i"
+ name=$func_stripname_result
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ case " $predeps $postdeps " in
+ *" $i "*)
+ func_append newdeplibs " $i"
+ i=
+ ;;
+ esac
+ fi
+ if test -n "$i"; then
+ libname=`eval "\\$ECHO \"$libname_spec\""`
+ deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+ set dummy $deplib_matches; shift
+ deplib_match=$1
+ if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0; then
+ func_append newdeplibs " $i"
+ else
+ droppeddeps=yes
+ echo
+ $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which I believe you do not have"
+ echo "*** because a test_compile did reveal that the linker did not use it for"
+ echo "*** its dynamic dependency list that programs get resolved with at runtime."
+ fi
+ fi
+ ;;
+ *)
+ func_append newdeplibs " $i"
+ ;;
+ esac
+ done
+ else
+ # Error occurred in the first compile. Let's try to salvage
+ # the situation: Compile a separate program for each library.
+ for i in $deplibs; do
+ case $i in
+ -l*)
+ func_stripname -l '' "$i"
+ name=$func_stripname_result
+ $opt_dry_run || $RM conftest
+ if $LTCC $LTCFLAGS -o conftest conftest.c $i; then
+ ldd_output=`ldd conftest`
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ case " $predeps $postdeps " in
+ *" $i "*)
+ func_append newdeplibs " $i"
+ i=
+ ;;
+ esac
+ fi
+ if test -n "$i"; then
+ libname=`eval "\\$ECHO \"$libname_spec\""`
+ deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+ set dummy $deplib_matches; shift
+ deplib_match=$1
+ if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0; then
+ func_append newdeplibs " $i"
+ else
+ droppeddeps=yes
+ echo
+ $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have"
+ echo "*** because a test_compile did reveal that the linker did not use this one"
+ echo "*** as a dynamic dependency that programs can get resolved with at runtime."
+ fi
+ fi
+ else
+ droppeddeps=yes
+ echo
+ $ECHO "*** Warning! Library $i is needed by this library but I was not able to"
+ echo "*** make it link in! You will probably need to install it or some"
+ echo "*** library that it depends on before this library will be fully"
+ echo "*** functional. Installing it before continuing would be even better."
+ fi
+ ;;
+ *)
+ func_append newdeplibs " $i"
+ ;;
+ esac
+ done
+ fi
+ ;;
+ file_magic*)
+ set dummy $deplibs_check_method; shift
+ file_magic_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+ for a_deplib in $deplibs; do
+ case $a_deplib in
+ -l*)
+ func_stripname -l '' "$a_deplib"
+ name=$func_stripname_result
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ case " $predeps $postdeps " in
+ *" $a_deplib "*)
+ func_append newdeplibs " $a_deplib"
+ a_deplib=
+ ;;
+ esac
+ fi
+ if test -n "$a_deplib"; then
+ libname=`eval "\\$ECHO \"$libname_spec\""`
+ if test -n "$file_magic_glob"; then
+ libnameglob=`func_echo_all "$libname" | $SED -e $file_magic_glob`
+ else
+ libnameglob=$libname
+ fi
+ test yes = "$want_nocaseglob" && nocaseglob=`shopt -p nocaseglob`
+ for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+ if test yes = "$want_nocaseglob"; then
+ shopt -s nocaseglob
+ potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+ $nocaseglob
+ else
+ potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+ fi
+ for potent_lib in $potential_libs; do
+ # Follow soft links.
+ if ls -lLd "$potent_lib" 2>/dev/null |
+ $GREP " -> " >/dev/null; then
+ continue
+ fi
+ # The statement above tries to avoid entering an
+ # endless loop below, in case of cyclic links.
+ # We might still enter an endless loop, since a link
+ # loop can be closed while we follow links,
+ # but so what?
+ potlib=$potent_lib
+ while test -h "$potlib" 2>/dev/null; do
+ potliblink=`ls -ld $potlib | $SED 's/.* -> //'`
+ case $potliblink in
+ [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;;
+ *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";;
+ esac
+ done
+ if eval $file_magic_cmd \"\$potlib\" 2>/dev/null |
+ $SED -e 10q |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ func_append newdeplibs " $a_deplib"
+ a_deplib=
+ break 2
+ fi
+ done
+ done
+ fi
+ if test -n "$a_deplib"; then
+ droppeddeps=yes
+ echo
+ $ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have"
+ echo "*** because I did check the linker path looking for a file starting"
+ if test -z "$potlib"; then
+ $ECHO "*** with $libname but no candidates were found. (...for file magic test)"
+ else
+ $ECHO "*** with $libname and none of the candidates passed a file format test"
+ $ECHO "*** using a file magic. Last file checked: $potlib"
+ fi
+ fi
+ ;;
+ *)
+ # Add a -L argument.
+ func_append newdeplibs " $a_deplib"
+ ;;
+ esac
+ done # Gone through all deplibs.
+ ;;
+ match_pattern*)
+ set dummy $deplibs_check_method; shift
+ match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+ for a_deplib in $deplibs; do
+ case $a_deplib in
+ -l*)
+ func_stripname -l '' "$a_deplib"
+ name=$func_stripname_result
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ case " $predeps $postdeps " in
+ *" $a_deplib "*)
+ func_append newdeplibs " $a_deplib"
+ a_deplib=
+ ;;
+ esac
+ fi
+ if test -n "$a_deplib"; then
+ libname=`eval "\\$ECHO \"$libname_spec\""`
+ for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+ potential_libs=`ls $i/$libname[.-]* 2>/dev/null`
+ for potent_lib in $potential_libs; do
+ potlib=$potent_lib # see symlink-check above in file_magic test
+ if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \
+ $EGREP "$match_pattern_regex" > /dev/null; then
+ func_append newdeplibs " $a_deplib"
+ a_deplib=
+ break 2
+ fi
+ done
+ done
+ fi
+ if test -n "$a_deplib"; then
+ droppeddeps=yes
+ echo
+ $ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have"
+ echo "*** because I did check the linker path looking for a file starting"
+ if test -z "$potlib"; then
+ $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)"
+ else
+ $ECHO "*** with $libname and none of the candidates passed a file format test"
+ $ECHO "*** using a regex pattern. Last file checked: $potlib"
+ fi
+ fi
+ ;;
+ *)
+ # Add a -L argument.
+ func_append newdeplibs " $a_deplib"
+ ;;
+ esac
+ done # Gone through all deplibs.
+ ;;
+ none | unknown | *)
+ newdeplibs=
+ tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'`
+ if test yes = "$allow_libtool_libs_with_static_runtimes"; then
+ for i in $predeps $postdeps; do
+ # can't use Xsed below, because $i might contain '/'
+ tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"`
+ done
+ fi
+ case $tmp_deplibs in
+ *[!\ \ ]*)
+ echo
+ if test none = "$deplibs_check_method"; then
+ echo "*** Warning: inter-library dependencies are not supported in this platform."
+ else
+ echo "*** Warning: inter-library dependencies are not known to be supported."
+ fi
+ echo "*** All declared inter-library dependencies are being dropped."
+ droppeddeps=yes
+ ;;
+ esac
+ ;;
+ esac
+ versuffix=$versuffix_save
+ major=$major_save
+ release=$release_save
+ libname=$libname_save
+ name=$name_save
+
+ case $host in
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # On Rhapsody replace the C library with the System framework
+ newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'`
+ ;;
+ esac
+
+ if test yes = "$droppeddeps"; then
+ if test yes = "$module"; then
+ echo
+ echo "*** Warning: libtool could not satisfy all declared inter-library"
+ $ECHO "*** dependencies of module $libname. Therefore, libtool will create"
+ echo "*** a static module, that should work as long as the dlopening"
+ echo "*** application is linked with the -dlopen flag."
+ if test -z "$global_symbol_pipe"; then
+ echo
+ echo "*** However, this would only work if libtool was able to extract symbol"
+ echo "*** lists from a program, using 'nm' or equivalent, but libtool could"
+ echo "*** not find such a program. So, this module is probably useless."
+ echo "*** 'nm' from GNU binutils and a full rebuild may help."
+ fi
+ if test no = "$build_old_libs"; then
+ oldlibs=$output_objdir/$libname.$libext
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ else
+ echo "*** The inter-library dependencies that have been dropped here will be"
+ echo "*** automatically added whenever a program is linked with this library"
+ echo "*** or is declared to -dlopen it."
+
+ if test no = "$allow_undefined"; then
+ echo
+ echo "*** Since this library must not contain undefined symbols,"
+ echo "*** because either the platform does not support them or"
+ echo "*** it was explicitly requested with -no-undefined,"
+ echo "*** libtool will only create a static version of it."
+ if test no = "$build_old_libs"; then
+ oldlibs=$output_objdir/$libname.$libext
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ fi
+ fi
+ fi
+ # Done checking deplibs!
+ deplibs=$newdeplibs
+ fi
+ # Time to change all our "foo.ltframework" stuff back to "-framework foo"
+ case $host in
+ *-*-darwin*)
+ newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ ;;
+ esac
+
+ # move library search paths that coincide with paths to not yet
+ # installed libraries to the beginning of the library search list
+ new_libs=
+ for path in $notinst_path; do
+ case " $new_libs " in
+ *" -L$path/$objdir "*) ;;
+ *)
+ case " $deplibs " in
+ *" -L$path/$objdir "*)
+ func_append new_libs " -L$path/$objdir" ;;
+ esac
+ ;;
+ esac
+ done
+ for deplib in $deplibs; do
+ case $deplib in
+ -L*)
+ case " $new_libs " in
+ *" $deplib "*) ;;
+ *) func_append new_libs " $deplib" ;;
+ esac
+ ;;
+ *) func_append new_libs " $deplib" ;;
+ esac
+ done
+ deplibs=$new_libs
+
+ # All the library-specific variables (install_libdir is set above).
+ library_names=
+ old_library=
+ dlname=
+
+ # Test again, we may have decided not to build it any more
+ if test yes = "$build_libtool_libs"; then
+ # Remove $wl instances when linking with ld.
+ # FIXME: should test the right _cmds variable.
+ case $archive_cmds in
+ *\$LD\ *) wl= ;;
+ esac
+ if test yes = "$hardcode_into_libs"; then
+ # Hardcode the library paths
+ hardcode_libdirs=
+ dep_rpath=
+ rpath=$finalize_rpath
+ test relink = "$opt_mode" || rpath=$compile_rpath$rpath
+ for libdir in $rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ func_replace_sysroot "$libdir"
+ libdir=$func_replace_sysroot_result
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs=$libdir
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ func_append dep_rpath " $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$perm_rpath " in
+ *" $libdir "*) ;;
+ *) func_append perm_rpath " $libdir" ;;
+ esac
+ fi
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir=$hardcode_libdirs
+ eval "dep_rpath=\"$hardcode_libdir_flag_spec\""
+ fi
+ if test -n "$runpath_var" && test -n "$perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $perm_rpath; do
+ func_append rpath "$dir:"
+ done
+ eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var"
+ fi
+ test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs"
+ fi
+
+ shlibpath=$finalize_shlibpath
+ test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath
+ if test -n "$shlibpath"; then
+ eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var"
+ fi
+
+ # Get the real and link names of the library.
+ eval shared_ext=\"$shrext_cmds\"
+ eval library_names=\"$library_names_spec\"
+ set dummy $library_names
+ shift
+ realname=$1
+ shift
+
+ if test -n "$soname_spec"; then
+ eval soname=\"$soname_spec\"
+ else
+ soname=$realname
+ fi
+ if test -z "$dlname"; then
+ dlname=$soname
+ fi
+
+ lib=$output_objdir/$realname
+ linknames=
+ for link
+ do
+ func_append linknames " $link"
+ done
+
+ # Use standard objects if they are pic
+ test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+ test "X$libobjs" = "X " && libobjs=
+
+ delfiles=
+ if test -n "$export_symbols" && test -n "$include_expsyms"; then
+ $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp"
+ export_symbols=$output_objdir/$libname.uexp
+ func_append delfiles " $export_symbols"
+ fi
+
+ orig_export_symbols=
+ case $host_os in
+ cygwin* | mingw* | cegcc*)
+ if test -n "$export_symbols" && test -z "$export_symbols_regex"; then
+ # exporting using user supplied symfile
+ func_dll_def_p "$export_symbols" || {
+ # and it's NOT already a .def file. Must figure out
+ # which of the given symbols are data symbols and tag
+ # them as such. So, trigger use of export_symbols_cmds.
+ # export_symbols gets reassigned inside the "prepare
+ # the list of exported symbols" if statement, so the
+ # include_expsyms logic still works.
+ orig_export_symbols=$export_symbols
+ export_symbols=
+ always_export_symbols=yes
+ }
+ fi
+ ;;
+ esac
+
+ # Prepare the list of exported symbols
+ if test -z "$export_symbols"; then
+ if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then
+ func_verbose "generating symbol list for '$libname.la'"
+ export_symbols=$output_objdir/$libname.exp
+ $opt_dry_run || $RM $export_symbols
+ cmds=$export_symbols_cmds
+ save_ifs=$IFS; IFS='~'
+ for cmd1 in $cmds; do
+ IFS=$save_ifs
+ # Take the normal branch if the nm_file_list_spec branch
+ # doesn't work or if tool conversion is not needed.
+ case $nm_file_list_spec~$to_tool_file_cmd in
+ *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*)
+ try_normal_branch=yes
+ eval cmd=\"$cmd1\"
+ func_len " $cmd"
+ len=$func_len_result
+ ;;
+ *)
+ try_normal_branch=no
+ ;;
+ esac
+ if test yes = "$try_normal_branch" \
+ && { test "$len" -lt "$max_cmd_len" \
+ || test "$max_cmd_len" -le -1; }
+ then
+ func_show_eval "$cmd" 'exit $?'
+ skipped_export=false
+ elif test -n "$nm_file_list_spec"; then
+ func_basename "$output"
+ output_la=$func_basename_result
+ save_libobjs=$libobjs
+ save_output=$output
+ output=$output_objdir/$output_la.nm
+ func_to_tool_file "$output"
+ libobjs=$nm_file_list_spec$func_to_tool_file_result
+ func_append delfiles " $output"
+ func_verbose "creating $NM input file list: $output"
+ for obj in $save_libobjs; do
+ func_to_tool_file "$obj"
+ $ECHO "$func_to_tool_file_result"
+ done > "$output"
+ eval cmd=\"$cmd1\"
+ func_show_eval "$cmd" 'exit $?'
+ output=$save_output
+ libobjs=$save_libobjs
+ skipped_export=false
+ else
+ # The command line is too long to execute in one step.
+ func_verbose "using reloadable object file for export list..."
+ skipped_export=:
+ # Break out early, otherwise skipped_export may be
+ # set to false by a later but shorter cmd.
+ break
+ fi
+ done
+ IFS=$save_ifs
+ if test -n "$export_symbols_regex" && test : != "$skipped_export"; then
+ func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+ func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+ fi
+ fi
+ fi
+
+ if test -n "$export_symbols" && test -n "$include_expsyms"; then
+ tmp_export_symbols=$export_symbols
+ test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols
+ $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+ fi
+
+ if test : != "$skipped_export" && test -n "$orig_export_symbols"; then
+ # The given exports_symbols file has to be filtered, so filter it.
+ func_verbose "filter symbol list for '$libname.la' to tag DATA exports"
+ # FIXME: $output_objdir/$libname.filter potentially contains lots of
+ # 's' commands, which not all seds can handle. GNU sed should be fine
+ # though. Also, the filter scales superlinearly with the number of
+ # global variables. join(1) would be nice here, but unfortunately
+ # isn't a blessed tool.
+ $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+ func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+ export_symbols=$output_objdir/$libname.def
+ $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+ fi
+
+ tmp_deplibs=
+ for test_deplib in $deplibs; do
+ case " $convenience " in
+ *" $test_deplib "*) ;;
+ *)
+ func_append tmp_deplibs " $test_deplib"
+ ;;
+ esac
+ done
+ deplibs=$tmp_deplibs
+
+ if test -n "$convenience"; then
+ if test -n "$whole_archive_flag_spec" &&
+ test yes = "$compiler_needs_object" &&
+ test -z "$libobjs"; then
+ # extract the archives, so we have objects to list.
+ # TODO: could optimize this to just extract one archive.
+ whole_archive_flag_spec=
+ fi
+ if test -n "$whole_archive_flag_spec"; then
+ save_libobjs=$libobjs
+ eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+ test "X$libobjs" = "X " && libobjs=
+ else
+ gentop=$output_objdir/${outputname}x
+ func_append generated " $gentop"
+
+ func_extract_archives $gentop $convenience
+ func_append libobjs " $func_extract_archives_result"
+ test "X$libobjs" = "X " && libobjs=
+ fi
+ fi
+
+ if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then
+ eval flag=\"$thread_safe_flag_spec\"
+ func_append linker_flags " $flag"
+ fi
+
+ # Make a backup of the uninstalled library when relinking
+ if test relink = "$opt_mode"; then
+ $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $?
+ fi
+
+ # Do each of the archive commands.
+ if test yes = "$module" && test -n "$module_cmds"; then
+ if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+ eval test_cmds=\"$module_expsym_cmds\"
+ cmds=$module_expsym_cmds
+ else
+ eval test_cmds=\"$module_cmds\"
+ cmds=$module_cmds
+ fi
+ else
+ if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+ eval test_cmds=\"$archive_expsym_cmds\"
+ cmds=$archive_expsym_cmds
+ else
+ eval test_cmds=\"$archive_cmds\"
+ cmds=$archive_cmds
+ fi
+ fi
+
+ if test : != "$skipped_export" &&
+ func_len " $test_cmds" &&
+ len=$func_len_result &&
+ test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+ :
+ else
+ # The command line is too long to link in one step, link piecewise
+ # or, if using GNU ld and skipped_export is not :, use a linker
+ # script.
+
+ # Save the value of $output and $libobjs because we want to
+ # use them later. If we have whole_archive_flag_spec, we
+ # want to use save_libobjs as it was before
+ # whole_archive_flag_spec was expanded, because we can't
+ # assume the linker understands whole_archive_flag_spec.
+ # This may have to be revisited, in case too many
+ # convenience libraries get linked in and end up exceeding
+ # the spec.
+ if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then
+ save_libobjs=$libobjs
+ fi
+ save_output=$output
+ func_basename "$output"
+ output_la=$func_basename_result
+
+ # Clear the reloadable object creation command queue and
+ # initialize k to one.
+ test_cmds=
+ concat_cmds=
+ objlist=
+ last_robj=
+ k=1
+
+ if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then
+ output=$output_objdir/$output_la.lnkscript
+ func_verbose "creating GNU ld script: $output"
+ echo 'INPUT (' > $output
+ for obj in $save_libobjs
+ do
+ func_to_tool_file "$obj"
+ $ECHO "$func_to_tool_file_result" >> $output
+ done
+ echo ')' >> $output
+ func_append delfiles " $output"
+ func_to_tool_file "$output"
+ output=$func_to_tool_file_result
+ elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then
+ output=$output_objdir/$output_la.lnk
+ func_verbose "creating linker input file list: $output"
+ : > $output
+ set x $save_libobjs
+ shift
+ firstobj=
+ if test yes = "$compiler_needs_object"; then
+ firstobj="$1 "
+ shift
+ fi
+ for obj
+ do
+ func_to_tool_file "$obj"
+ $ECHO "$func_to_tool_file_result" >> $output
+ done
+ func_append delfiles " $output"
+ func_to_tool_file "$output"
+ output=$firstobj\"$file_list_spec$func_to_tool_file_result\"
+ else
+ if test -n "$save_libobjs"; then
+ func_verbose "creating reloadable object files..."
+ output=$output_objdir/$output_la-$k.$objext
+ eval test_cmds=\"$reload_cmds\"
+ func_len " $test_cmds"
+ len0=$func_len_result
+ len=$len0
+
+ # Loop over the list of objects to be linked.
+ for obj in $save_libobjs
+ do
+ func_len " $obj"
+ func_arith $len + $func_len_result
+ len=$func_arith_result
+ if test -z "$objlist" ||
+ test "$len" -lt "$max_cmd_len"; then
+ func_append objlist " $obj"
+ else
+ # The command $test_cmds is almost too long, add a
+ # command to the queue.
+ if test 1 -eq "$k"; then
+ # The first file doesn't have a previous command to add.
+ reload_objs=$objlist
+ eval concat_cmds=\"$reload_cmds\"
+ else
+ # All subsequent reloadable object files will link in
+ # the last one created.
+ reload_objs="$objlist $last_robj"
+ eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\"
+ fi
+ last_robj=$output_objdir/$output_la-$k.$objext
+ func_arith $k + 1
+ k=$func_arith_result
+ output=$output_objdir/$output_la-$k.$objext
+ objlist=" $obj"
+ func_len " $last_robj"
+ func_arith $len0 + $func_len_result
+ len=$func_arith_result
+ fi
+ done
+ # Handle the remaining objects by creating one last
+ # reloadable object file. All subsequent reloadable object
+ # files will link in the last one created.
+ test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+ reload_objs="$objlist $last_robj"
+ eval concat_cmds=\"\$concat_cmds$reload_cmds\"
+ if test -n "$last_robj"; then
+ eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\"
+ fi
+ func_append delfiles " $output"
+
+ else
+ output=
+ fi
+
+ ${skipped_export-false} && {
+ func_verbose "generating symbol list for '$libname.la'"
+ export_symbols=$output_objdir/$libname.exp
+ $opt_dry_run || $RM $export_symbols
+ libobjs=$output
+ # Append the command to create the export file.
+ test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+ eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\"
+ if test -n "$last_robj"; then
+ eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\"
+ fi
+ }
+
+ test -n "$save_libobjs" &&
+ func_verbose "creating a temporary reloadable object file: $output"
+
+ # Loop through the commands generated above and execute them.
+ save_ifs=$IFS; IFS='~'
+ for cmd in $concat_cmds; do
+ IFS=$save_ifs
+ $opt_quiet || {
+ func_quote_arg expand,pretty "$cmd"
+ eval "func_echo $func_quote_arg_result"
+ }
+ $opt_dry_run || eval "$cmd" || {
+ lt_exit=$?
+
+ # Restore the uninstalled library and exit
+ if test relink = "$opt_mode"; then
+ ( cd "$output_objdir" && \
+ $RM "${realname}T" && \
+ $MV "${realname}U" "$realname" )
+ fi
+
+ exit $lt_exit
+ }
+ done
+ IFS=$save_ifs
+
+ if test -n "$export_symbols_regex" && ${skipped_export-false}; then
+ func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+ func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+ fi
+ fi
+
+ ${skipped_export-false} && {
+ if test -n "$export_symbols" && test -n "$include_expsyms"; then
+ tmp_export_symbols=$export_symbols
+ test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols
+ $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+ fi
+
+ if test -n "$orig_export_symbols"; then
+ # The given exports_symbols file has to be filtered, so filter it.
+ func_verbose "filter symbol list for '$libname.la' to tag DATA exports"
+ # FIXME: $output_objdir/$libname.filter potentially contains lots of
+ # 's' commands, which not all seds can handle. GNU sed should be fine
+ # though. Also, the filter scales superlinearly with the number of
+ # global variables. join(1) would be nice here, but unfortunately
+ # isn't a blessed tool.
+ $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+ func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+ export_symbols=$output_objdir/$libname.def
+ $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+ fi
+ }
+
+ libobjs=$output
+ # Restore the value of output.
+ output=$save_output
+
+ if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then
+ eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+ test "X$libobjs" = "X " && libobjs=
+ fi
+ # Expand the library linking commands again to reset the
+ # value of $libobjs for piecewise linking.
+
+ # Do each of the archive commands.
+ if test yes = "$module" && test -n "$module_cmds"; then
+ if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+ cmds=$module_expsym_cmds
+ else
+ cmds=$module_cmds
+ fi
+ else
+ if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+ cmds=$archive_expsym_cmds
+ else
+ cmds=$archive_cmds
+ fi
+ fi
+ fi
+
+ if test -n "$delfiles"; then
+ # Append the command to remove temporary files to $cmds.
+ eval cmds=\"\$cmds~\$RM $delfiles\"
+ fi
+
+ # Add any objects from preloaded convenience libraries
+ if test -n "$dlprefiles"; then
+ gentop=$output_objdir/${outputname}x
+ func_append generated " $gentop"
+
+ func_extract_archives $gentop $dlprefiles
+ func_append libobjs " $func_extract_archives_result"
+ test "X$libobjs" = "X " && libobjs=
+ fi
+
+ save_ifs=$IFS; IFS='~'
+ for cmd in $cmds; do
+ IFS=$sp$nl
+ eval cmd=\"$cmd\"
+ IFS=$save_ifs
+ $opt_quiet || {
+ func_quote_arg expand,pretty "$cmd"
+ eval "func_echo $func_quote_arg_result"
+ }
+ $opt_dry_run || eval "$cmd" || {
+ lt_exit=$?
+
+ # Restore the uninstalled library and exit
+ if test relink = "$opt_mode"; then
+ ( cd "$output_objdir" && \
+ $RM "${realname}T" && \
+ $MV "${realname}U" "$realname" )
+ fi
+
+ exit $lt_exit
+ }
+ done
+ IFS=$save_ifs
+
+ # Restore the uninstalled library and exit
+ if test relink = "$opt_mode"; then
+ $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $?
+
+ if test -n "$convenience"; then
+ if test -z "$whole_archive_flag_spec"; then
+ func_show_eval '${RM}r "$gentop"'
+ fi
+ fi
+
+ exit $EXIT_SUCCESS
+ fi
+
+ # Create links to the real library.
+ for linkname in $linknames; do
+ if test "$realname" != "$linkname"; then
+ func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?'
+ fi
+ done
+
+ # If -module or -export-dynamic was specified, set the dlname.
+ if test yes = "$module" || test yes = "$export_dynamic"; then
+ # On all known operating systems, these are identical.
+ dlname=$soname
+ fi
+ fi
+ ;;
+
+ obj)
+ if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then
+ func_warning "'-dlopen' is ignored for objects"
+ fi
+
+ case " $deplibs" in
+ *\ -l* | *\ -L*)
+ func_warning "'-l' and '-L' are ignored for objects" ;;
+ esac
+
+ test -n "$rpath" && \
+ func_warning "'-rpath' is ignored for objects"
+
+ test -n "$xrpath" && \
+ func_warning "'-R' is ignored for objects"
+
+ test -n "$vinfo" && \
+ func_warning "'-version-info' is ignored for objects"
+
+ test -n "$release" && \
+ func_warning "'-release' is ignored for objects"
+
+ case $output in
+ *.lo)
+ test -n "$objs$old_deplibs" && \
+ func_fatal_error "cannot build library object '$output' from non-libtool objects"
+
+ libobj=$output
+ func_lo2o "$libobj"
+ obj=$func_lo2o_result
+ ;;
+ *)
+ libobj=
+ obj=$output
+ ;;
+ esac
+
+ # Delete the old objects.
+ $opt_dry_run || $RM $obj $libobj
+
+ # Objects from convenience libraries. This assumes
+ # single-version convenience libraries. Whenever we create
+ # different ones for PIC/non-PIC, this we'll have to duplicate
+ # the extraction.
+ reload_conv_objs=
+ gentop=
+ # if reload_cmds runs $LD directly, get rid of -Wl from
+ # whole_archive_flag_spec and hope we can get by with turning comma
+ # into space.
+ case $reload_cmds in
+ *\$LD[\ \$]*) wl= ;;
+ esac
+ if test -n "$convenience"; then
+ if test -n "$whole_archive_flag_spec"; then
+ eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\"
+ test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'`
+ reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags
+ else
+ gentop=$output_objdir/${obj}x
+ func_append generated " $gentop"
+
+ func_extract_archives $gentop $convenience
+ reload_conv_objs="$reload_objs $func_extract_archives_result"
+ fi
+ fi
+
+ # If we're not building shared, we need to use non_pic_objs
+ test yes = "$build_libtool_libs" || libobjs=$non_pic_objects
+
+ # Create the old-style object.
+ reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs
+
+ output=$obj
+ func_execute_cmds "$reload_cmds" 'exit $?'
+
+ # Exit if we aren't doing a library object file.
+ if test -z "$libobj"; then
+ if test -n "$gentop"; then
+ func_show_eval '${RM}r "$gentop"'
+ fi
+
+ exit $EXIT_SUCCESS
+ fi
+
+ test yes = "$build_libtool_libs" || {
+ if test -n "$gentop"; then
+ func_show_eval '${RM}r "$gentop"'
+ fi
+
+ # Create an invalid libtool object if no PIC, so that we don't
+ # accidentally link it into a program.
+ # $show "echo timestamp > $libobj"
+ # $opt_dry_run || eval "echo timestamp > $libobj" || exit $?
+ exit $EXIT_SUCCESS
+ }
+
+ if test -n "$pic_flag" || test default != "$pic_mode"; then
+ # Only do commands if we really have different PIC objects.
+ reload_objs="$libobjs $reload_conv_objs"
+ output=$libobj
+ func_execute_cmds "$reload_cmds" 'exit $?'
+ fi
+
+ if test -n "$gentop"; then
+ func_show_eval '${RM}r "$gentop"'
+ fi
+
+ exit $EXIT_SUCCESS
+ ;;
+
+ prog)
+ case $host in
+ *cygwin*) func_stripname '' '.exe' "$output"
+ output=$func_stripname_result.exe;;
+ esac
+ test -n "$vinfo" && \
+ func_warning "'-version-info' is ignored for programs"
+
+ test -n "$release" && \
+ func_warning "'-release' is ignored for programs"
+
+ $preload \
+ && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \
+ && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support."
+
+ case $host in
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # On Rhapsody replace the C library is the System framework
+ compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'`
+ finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'`
+ ;;
+ esac
+
+ case $host in
+ *-*-darwin*)
+ # Don't allow lazy linking, it breaks C++ global constructors
+ # But is supposedly fixed on 10.4 or later (yay!).
+ if test CXX = "$tagname"; then
+ case ${MACOSX_DEPLOYMENT_TARGET-10.0} in
+ 10.[0123])
+ func_append compile_command " $wl-bind_at_load"
+ func_append finalize_command " $wl-bind_at_load"
+ ;;
+ esac
+ fi
+ # Time to change all our "foo.ltframework" stuff back to "-framework foo"
+ compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+ ;;
+ esac
+
+
+ # move library search paths that coincide with paths to not yet
+ # installed libraries to the beginning of the library search list
+ new_libs=
+ for path in $notinst_path; do
+ case " $new_libs " in
+ *" -L$path/$objdir "*) ;;
+ *)
+ case " $compile_deplibs " in
+ *" -L$path/$objdir "*)
+ func_append new_libs " -L$path/$objdir" ;;
+ esac
+ ;;
+ esac
+ done
+ for deplib in $compile_deplibs; do
+ case $deplib in
+ -L*)
+ case " $new_libs " in
+ *" $deplib "*) ;;
+ *) func_append new_libs " $deplib" ;;
+ esac
+ ;;
+ *) func_append new_libs " $deplib" ;;
+ esac
+ done
+ compile_deplibs=$new_libs
+
+
+ func_append compile_command " $compile_deplibs"
+ func_append finalize_command " $finalize_deplibs"
+
+ if test -n "$rpath$xrpath"; then
+ # If the user specified any rpath flags, then add them.
+ for libdir in $rpath $xrpath; do
+ # This is the magic to use -rpath.
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) func_append finalize_rpath " $libdir" ;;
+ esac
+ done
+ fi
+
+ # Now hardcode the library paths
+ rpath=
+ hardcode_libdirs=
+ for libdir in $compile_rpath $finalize_rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs=$libdir
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ func_append rpath " $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$perm_rpath " in
+ *" $libdir "*) ;;
+ *) func_append perm_rpath " $libdir" ;;
+ esac
+ fi
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+ testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'`
+ case :$dllsearchpath: in
+ *":$libdir:"*) ;;
+ ::) dllsearchpath=$libdir;;
+ *) func_append dllsearchpath ":$libdir";;
+ esac
+ case :$dllsearchpath: in
+ *":$testbindir:"*) ;;
+ ::) dllsearchpath=$testbindir;;
+ *) func_append dllsearchpath ":$testbindir";;
+ esac
+ ;;
+ esac
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir=$hardcode_libdirs
+ eval rpath=\" $hardcode_libdir_flag_spec\"
+ fi
+ compile_rpath=$rpath
+
+ rpath=
+ hardcode_libdirs=
+ for libdir in $finalize_rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs=$libdir
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ func_append rpath " $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$finalize_perm_rpath " in
+ *" $libdir "*) ;;
+ *) func_append finalize_perm_rpath " $libdir" ;;
+ esac
+ fi
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir=$hardcode_libdirs
+ eval rpath=\" $hardcode_libdir_flag_spec\"
+ fi
+ finalize_rpath=$rpath
+
+ if test -n "$libobjs" && test yes = "$build_old_libs"; then
+ # Transform all the library objects into standard objects.
+ compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+ finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+ fi
+
+ func_generate_dlsyms "$outputname" "@PROGRAM@" false
+
+ # template prelinking step
+ if test -n "$prelink_cmds"; then
+ func_execute_cmds "$prelink_cmds" 'exit $?'
+ fi
+
+ wrappers_required=:
+ case $host in
+ *cegcc* | *mingw32ce*)
+ # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway.
+ wrappers_required=false
+ ;;
+ *cygwin* | *mingw* )
+ test yes = "$build_libtool_libs" || wrappers_required=false
+ ;;
+ *)
+ if test no = "$need_relink" || test yes != "$build_libtool_libs"; then
+ wrappers_required=false
+ fi
+ ;;
+ esac
+ $wrappers_required || {
+ # Replace the output file specification.
+ compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+ link_command=$compile_command$compile_rpath
+
+ # We have no uninstalled library dependencies, so finalize right now.
+ exit_status=0
+ func_show_eval "$link_command" 'exit_status=$?'
+
+ if test -n "$postlink_cmds"; then
+ func_to_tool_file "$output"
+ postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+ func_execute_cmds "$postlink_cmds" 'exit $?'
+ fi
+
+ # Delete the generated files.
+ if test -f "$output_objdir/${outputname}S.$objext"; then
+ func_show_eval '$RM "$output_objdir/${outputname}S.$objext"'
+ fi
+
+ exit $exit_status
+ }
+
+ if test -n "$compile_shlibpath$finalize_shlibpath"; then
+ compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command"
+ fi
+ if test -n "$finalize_shlibpath"; then
+ finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command"
+ fi
+
+ compile_var=
+ finalize_var=
+ if test -n "$runpath_var"; then
+ if test -n "$perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $perm_rpath; do
+ func_append rpath "$dir:"
+ done
+ compile_var="$runpath_var=\"$rpath\$$runpath_var\" "
+ fi
+ if test -n "$finalize_perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $finalize_perm_rpath; do
+ func_append rpath "$dir:"
+ done
+ finalize_var="$runpath_var=\"$rpath\$$runpath_var\" "
+ fi
+ fi
+
+ if test yes = "$no_install"; then
+ # We don't need to create a wrapper script.
+ link_command=$compile_var$compile_command$compile_rpath
+ # Replace the output file specification.
+ link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+ # Delete the old output file.
+ $opt_dry_run || $RM $output
+ # Link the executable and exit
+ func_show_eval "$link_command" 'exit $?'
+
+ if test -n "$postlink_cmds"; then
+ func_to_tool_file "$output"
+ postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+ func_execute_cmds "$postlink_cmds" 'exit $?'
+ fi
+
+ exit $EXIT_SUCCESS
+ fi
+
+ case $hardcode_action,$fast_install in
+ relink,*)
+ # Fast installation is not supported
+ link_command=$compile_var$compile_command$compile_rpath
+ relink_command=$finalize_var$finalize_command$finalize_rpath
+
+ func_warning "this platform does not like uninstalled shared libraries"
+ func_warning "'$output' will be relinked during installation"
+ ;;
+ *,yes)
+ link_command=$finalize_var$compile_command$finalize_rpath
+ relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'`
+ ;;
+ *,no)
+ link_command=$compile_var$compile_command$compile_rpath
+ relink_command=$finalize_var$finalize_command$finalize_rpath
+ ;;
+ *,needless)
+ link_command=$finalize_var$compile_command$finalize_rpath
+ relink_command=
+ ;;
+ esac
+
+ # Replace the output file specification.
+ link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'`
+
+ # Delete the old output files.
+ $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname
+
+ func_show_eval "$link_command" 'exit $?'
+
+ if test -n "$postlink_cmds"; then
+ func_to_tool_file "$output_objdir/$outputname"
+ postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+ func_execute_cmds "$postlink_cmds" 'exit $?'
+ fi
+
+ # Now create the wrapper script.
+ func_verbose "creating $output"
+
+ # Quote the relink command for shipping.
+ if test -n "$relink_command"; then
+ # Preserve any variables that may affect compiler behavior
+ for var in $variables_saved_for_relink; do
+ if eval test -z \"\${$var+set}\"; then
+ relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+ elif eval var_value=\$$var; test -z "$var_value"; then
+ relink_command="$var=; export $var; $relink_command"
+ else
+ func_quote_arg pretty "$var_value"
+ relink_command="$var=$func_quote_arg_result; export $var; $relink_command"
+ fi
+ done
+ func_quote eval cd "`pwd`"
+ func_quote_arg pretty,unquoted "($func_quote_result; $relink_command)"
+ relink_command=$func_quote_arg_unquoted_result
+ fi
+
+ # Only actually do things if not in dry run mode.
+ $opt_dry_run || {
+ # win32 will think the script is a binary if it has
+ # a .exe suffix, so we strip it off here.
+ case $output in
+ *.exe) func_stripname '' '.exe' "$output"
+ output=$func_stripname_result ;;
+ esac
+ # test for cygwin because mv fails w/o .exe extensions
+ case $host in
+ *cygwin*)
+ exeext=.exe
+ func_stripname '' '.exe' "$outputname"
+ outputname=$func_stripname_result ;;
+ *) exeext= ;;
+ esac
+ case $host in
+ *cygwin* | *mingw* )
+ func_dirname_and_basename "$output" "" "."
+ output_name=$func_basename_result
+ output_path=$func_dirname_result
+ cwrappersource=$output_path/$objdir/lt-$output_name.c
+ cwrapper=$output_path/$output_name.exe
+ $RM $cwrappersource $cwrapper
+ trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15
+
+ func_emit_cwrapperexe_src > $cwrappersource
+
+ # The wrapper executable is built using the $host compiler,
+ # because it contains $host paths and files. If cross-
+ # compiling, it, like the target executable, must be
+ # executed on the $host or under an emulation environment.
+ $opt_dry_run || {
+ $LTCC $LTCFLAGS -o $cwrapper $cwrappersource
+ $STRIP $cwrapper
+ }
+
+ # Now, create the wrapper script for func_source use:
+ func_ltwrapper_scriptname $cwrapper
+ $RM $func_ltwrapper_scriptname_result
+ trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15
+ $opt_dry_run || {
+ # note: this script will not be executed, so do not chmod.
+ if test "x$build" = "x$host"; then
+ $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result
+ else
+ func_emit_wrapper no > $func_ltwrapper_scriptname_result
+ fi
+ }
+ ;;
+ * )
+ $RM $output
+ trap "$RM $output; exit $EXIT_FAILURE" 1 2 15
+
+ func_emit_wrapper no > $output
+ chmod +x $output
+ ;;
+ esac
+ }
+ exit $EXIT_SUCCESS
+ ;;
+ esac
+
+ # See if we need to build an old-fashioned archive.
+ for oldlib in $oldlibs; do
+
+ case $build_libtool_libs in
+ convenience)
+ oldobjs="$libobjs_save $symfileobj"
+ addlibs=$convenience
+ build_libtool_libs=no
+ ;;
+ module)
+ oldobjs=$libobjs_save
+ addlibs=$old_convenience
+ build_libtool_libs=no
+ ;;
+ *)
+ oldobjs="$old_deplibs $non_pic_objects"
+ $preload && test -f "$symfileobj" \
+ && func_append oldobjs " $symfileobj"
+ addlibs=$old_convenience
+ ;;
+ esac
+
+ if test -n "$addlibs"; then
+ gentop=$output_objdir/${outputname}x
+ func_append generated " $gentop"
+
+ func_extract_archives $gentop $addlibs
+ func_append oldobjs " $func_extract_archives_result"
+ fi
+
+ # Do each command in the archive commands.
+ if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then
+ cmds=$old_archive_from_new_cmds
+ else
+
+ # Add any objects from preloaded convenience libraries
+ if test -n "$dlprefiles"; then
+ gentop=$output_objdir/${outputname}x
+ func_append generated " $gentop"
+
+ func_extract_archives $gentop $dlprefiles
+ func_append oldobjs " $func_extract_archives_result"
+ fi
+
+ # POSIX demands no paths to be encoded in archives. We have
+ # to avoid creating archives with duplicate basenames if we
+ # might have to extract them afterwards, e.g., when creating a
+ # static archive out of a convenience library, or when linking
+ # the entirety of a libtool archive into another (currently
+ # not supported by libtool).
+ if (for obj in $oldobjs
+ do
+ func_basename "$obj"
+ $ECHO "$func_basename_result"
+ done | sort | sort -uc >/dev/null 2>&1); then
+ :
+ else
+ echo "copying selected object files to avoid basename conflicts..."
+ gentop=$output_objdir/${outputname}x
+ func_append generated " $gentop"
+ func_mkdir_p "$gentop"
+ save_oldobjs=$oldobjs
+ oldobjs=
+ counter=1
+ for obj in $save_oldobjs
+ do
+ func_basename "$obj"
+ objbase=$func_basename_result
+ case " $oldobjs " in
+ " ") oldobjs=$obj ;;
+ *[\ /]"$objbase "*)
+ while :; do
+ # Make sure we don't pick an alternate name that also
+ # overlaps.
+ newobj=lt$counter-$objbase
+ func_arith $counter + 1
+ counter=$func_arith_result
+ case " $oldobjs " in
+ *[\ /]"$newobj "*) ;;
+ *) if test ! -f "$gentop/$newobj"; then break; fi ;;
+ esac
+ done
+ func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj"
+ func_append oldobjs " $gentop/$newobj"
+ ;;
+ *) func_append oldobjs " $obj" ;;
+ esac
+ done
+ fi
+ func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+ tool_oldlib=$func_to_tool_file_result
+ eval cmds=\"$old_archive_cmds\"
+
+ func_len " $cmds"
+ len=$func_len_result
+ if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+ cmds=$old_archive_cmds
+ elif test -n "$archiver_list_spec"; then
+ func_verbose "using command file archive linking..."
+ for obj in $oldobjs
+ do
+ func_to_tool_file "$obj"
+ $ECHO "$func_to_tool_file_result"
+ done > $output_objdir/$libname.libcmd
+ func_to_tool_file "$output_objdir/$libname.libcmd"
+ oldobjs=" $archiver_list_spec$func_to_tool_file_result"
+ cmds=$old_archive_cmds
+ else
+ # the command line is too long to link in one step, link in parts
+ func_verbose "using piecewise archive linking..."
+ save_RANLIB=$RANLIB
+ RANLIB=:
+ objlist=
+ concat_cmds=
+ save_oldobjs=$oldobjs
+ oldobjs=
+ # Is there a better way of finding the last object in the list?
+ for obj in $save_oldobjs
+ do
+ last_oldobj=$obj
+ done
+ eval test_cmds=\"$old_archive_cmds\"
+ func_len " $test_cmds"
+ len0=$func_len_result
+ len=$len0
+ for obj in $save_oldobjs
+ do
+ func_len " $obj"
+ func_arith $len + $func_len_result
+ len=$func_arith_result
+ func_append objlist " $obj"
+ if test "$len" -lt "$max_cmd_len"; then
+ :
+ else
+ # the above command should be used before it gets too long
+ oldobjs=$objlist
+ if test "$obj" = "$last_oldobj"; then
+ RANLIB=$save_RANLIB
+ fi
+ test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+ eval concat_cmds=\"\$concat_cmds$old_archive_cmds\"
+ objlist=
+ len=$len0
+ fi
+ done
+ RANLIB=$save_RANLIB
+ oldobjs=$objlist
+ if test -z "$oldobjs"; then
+ eval cmds=\"\$concat_cmds\"
+ else
+ eval cmds=\"\$concat_cmds~\$old_archive_cmds\"
+ fi
+ fi
+ fi
+ func_execute_cmds "$cmds" 'exit $?'
+ done
+
+ test -n "$generated" && \
+ func_show_eval "${RM}r$generated"
+
+ # Now create the libtool archive.
+ case $output in
+ *.la)
+ old_library=
+ test yes = "$build_old_libs" && old_library=$libname.$libext
+ func_verbose "creating $output"
+
+ # Preserve any variables that may affect compiler behavior
+ for var in $variables_saved_for_relink; do
+ if eval test -z \"\${$var+set}\"; then
+ relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+ elif eval var_value=\$$var; test -z "$var_value"; then
+ relink_command="$var=; export $var; $relink_command"
+ else
+ func_quote_arg pretty,unquoted "$var_value"
+ relink_command="$var=$func_quote_arg_unquoted_result; export $var; $relink_command"
+ fi
+ done
+ # Quote the link command for shipping.
+ func_quote eval cd "`pwd`"
+ relink_command="($func_quote_result; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)"
+ func_quote_arg pretty,unquoted "$relink_command"
+ relink_command=$func_quote_arg_unquoted_result
+ if test yes = "$hardcode_automatic"; then
+ relink_command=
+ fi
+
+ # Only create the output if not a dry run.
+ $opt_dry_run || {
+ for installed in no yes; do
+ if test yes = "$installed"; then
+ if test -z "$install_libdir"; then
+ break
+ fi
+ output=$output_objdir/${outputname}i
+ # Replace all uninstalled libtool libraries with the installed ones
+ newdependency_libs=
+ for deplib in $dependency_libs; do
+ case $deplib in
+ *.la)
+ func_basename "$deplib"
+ name=$func_basename_result
+ func_resolve_sysroot "$deplib"
+ eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result`
+ test -z "$libdir" && \
+ func_fatal_error "'$deplib' is not a valid libtool archive"
+ func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name"
+ ;;
+ -L*)
+ func_stripname -L '' "$deplib"
+ func_replace_sysroot "$func_stripname_result"
+ func_append newdependency_libs " -L$func_replace_sysroot_result"
+ ;;
+ -R*)
+ func_stripname -R '' "$deplib"
+ func_replace_sysroot "$func_stripname_result"
+ func_append newdependency_libs " -R$func_replace_sysroot_result"
+ ;;
+ *) func_append newdependency_libs " $deplib" ;;
+ esac
+ done
+ dependency_libs=$newdependency_libs
+ newdlfiles=
+
+ for lib in $dlfiles; do
+ case $lib in
+ *.la)
+ func_basename "$lib"
+ name=$func_basename_result
+ eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+ test -z "$libdir" && \
+ func_fatal_error "'$lib' is not a valid libtool archive"
+ func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name"
+ ;;
+ *) func_append newdlfiles " $lib" ;;
+ esac
+ done
+ dlfiles=$newdlfiles
+ newdlprefiles=
+ for lib in $dlprefiles; do
+ case $lib in
+ *.la)
+ # Only pass preopened files to the pseudo-archive (for
+ # eventual linking with the app. that links it) if we
+ # didn't already link the preopened objects directly into
+ # the library:
+ func_basename "$lib"
+ name=$func_basename_result
+ eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+ test -z "$libdir" && \
+ func_fatal_error "'$lib' is not a valid libtool archive"
+ func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name"
+ ;;
+ esac
+ done
+ dlprefiles=$newdlprefiles
+ else
+ newdlfiles=
+ for lib in $dlfiles; do
+ case $lib in
+ [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;;
+ *) abs=`pwd`"/$lib" ;;
+ esac
+ func_append newdlfiles " $abs"
+ done
+ dlfiles=$newdlfiles
+ newdlprefiles=
+ for lib in $dlprefiles; do
+ case $lib in
+ [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;;
+ *) abs=`pwd`"/$lib" ;;
+ esac
+ func_append newdlprefiles " $abs"
+ done
+ dlprefiles=$newdlprefiles
+ fi
+ $RM $output
+ # place dlname in correct position for cygwin
+ # In fact, it would be nice if we could use this code for all target
+ # systems that can't hard-code library paths into their executables
+ # and that have no shared library path variable independent of PATH,
+ # but it turns out we can't easily determine that from inspecting
+ # libtool variables, so we have to hard-code the OSs to which it
+ # applies here; at the moment, that means platforms that use the PE
+ # object format with DLL files. See the long comment at the top of
+ # tests/bindir.at for full details.
+ tdlname=$dlname
+ case $host,$output,$installed,$module,$dlname in
+ *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll)
+ # If a -bindir argument was supplied, place the dll there.
+ if test -n "$bindir"; then
+ func_relative_path "$install_libdir" "$bindir"
+ tdlname=$func_relative_path_result/$dlname
+ else
+ # Otherwise fall back on heuristic.
+ tdlname=../bin/$dlname
+ fi
+ ;;
+ esac
+ $ECHO > $output "\
+# $outputname - a libtool library file
+# Generated by $PROGRAM (GNU $PACKAGE) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='$tdlname'
+
+# Names of this library.
+library_names='$library_names'
+
+# The name of the static archive.
+old_library='$old_library'
+
+# Linker flags that cannot go in dependency_libs.
+inherited_linker_flags='$new_inherited_linker_flags'
+
+# Libraries that this one depends upon.
+dependency_libs='$dependency_libs'
+
+# Names of additional weak libraries provided by this library
+weak_library_names='$weak_libs'
+
+# Version information for $libname.
+current=$current
+age=$age
+revision=$revision
+
+# Is this an already installed library?
+installed=$installed
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=$module
+
+# Files to dlopen/dlpreopen
+dlopen='$dlfiles'
+dlpreopen='$dlprefiles'
+
+# Directory that this library needs to be installed in:
+libdir='$install_libdir'"
+ if test no,yes = "$installed,$need_relink"; then
+ $ECHO >> $output "\
+relink_command=\"$relink_command\""
+ fi
+ done
+ }
+
+ # Do a symbolic link so that the libtool archive can be found in
+ # LD_LIBRARY_PATH before the program is installed.
+ func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?'
+ ;;
+ esac
+ exit $EXIT_SUCCESS
+}
+
+if test link = "$opt_mode" || test relink = "$opt_mode"; then
+ func_mode_link ${1+"$@"}
+fi
+
+
+# func_mode_uninstall arg...
+func_mode_uninstall ()
+{
+ $debug_cmd
+
+ RM=$nonopt
+ files=
+ rmforce=false
+ exit_status=0
+
+ # This variable tells wrapper scripts just to set variables rather
+ # than running their programs.
+ libtool_install_magic=$magic
+
+ for arg
+ do
+ case $arg in
+ -f) func_append RM " $arg"; rmforce=: ;;
+ -*) func_append RM " $arg" ;;
+ *) func_append files " $arg" ;;
+ esac
+ done
+
+ test -z "$RM" && \
+ func_fatal_help "you must specify an RM program"
+
+ rmdirs=
+
+ for file in $files; do
+ func_dirname "$file" "" "."
+ dir=$func_dirname_result
+ if test . = "$dir"; then
+ odir=$objdir
+ else
+ odir=$dir/$objdir
+ fi
+ func_basename "$file"
+ name=$func_basename_result
+ test uninstall = "$opt_mode" && odir=$dir
+
+ # Remember odir for removal later, being careful to avoid duplicates
+ if test clean = "$opt_mode"; then
+ case " $rmdirs " in
+ *" $odir "*) ;;
+ *) func_append rmdirs " $odir" ;;
+ esac
+ fi
+
+ # Don't error if the file doesn't exist and rm -f was used.
+ if { test -L "$file"; } >/dev/null 2>&1 ||
+ { test -h "$file"; } >/dev/null 2>&1 ||
+ test -f "$file"; then
+ :
+ elif test -d "$file"; then
+ exit_status=1
+ continue
+ elif $rmforce; then
+ continue
+ fi
+
+ rmfiles=$file
+
+ case $name in
+ *.la)
+ # Possibly a libtool archive, so verify it.
+ if func_lalib_p "$file"; then
+ func_source $dir/$name
+
+ # Delete the libtool libraries and symlinks.
+ for n in $library_names; do
+ func_append rmfiles " $odir/$n"
+ done
+ test -n "$old_library" && func_append rmfiles " $odir/$old_library"
+
+ case $opt_mode in
+ clean)
+ case " $library_names " in
+ *" $dlname "*) ;;
+ *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;;
+ esac
+ test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i"
+ ;;
+ uninstall)
+ if test -n "$library_names"; then
+ # Do each command in the postuninstall commands.
+ func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1'
+ fi
+
+ if test -n "$old_library"; then
+ # Do each command in the old_postuninstall commands.
+ func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1'
+ fi
+ # FIXME: should reinstall the best remaining shared library.
+ ;;
+ esac
+ fi
+ ;;
+
+ *.lo)
+ # Possibly a libtool object, so verify it.
+ if func_lalib_p "$file"; then
+
+ # Read the .lo file
+ func_source $dir/$name
+
+ # Add PIC object to the list of files to remove.
+ if test -n "$pic_object" && test none != "$pic_object"; then
+ func_append rmfiles " $dir/$pic_object"
+ fi
+
+ # Add non-PIC object to the list of files to remove.
+ if test -n "$non_pic_object" && test none != "$non_pic_object"; then
+ func_append rmfiles " $dir/$non_pic_object"
+ fi
+ fi
+ ;;
+
+ *)
+ if test clean = "$opt_mode"; then
+ noexename=$name
+ case $file in
+ *.exe)
+ func_stripname '' '.exe' "$file"
+ file=$func_stripname_result
+ func_stripname '' '.exe' "$name"
+ noexename=$func_stripname_result
+ # $file with .exe has already been added to rmfiles,
+ # add $file without .exe
+ func_append rmfiles " $file"
+ ;;
+ esac
+ # Do a test to see if this is a libtool program.
+ if func_ltwrapper_p "$file"; then
+ if func_ltwrapper_executable_p "$file"; then
+ func_ltwrapper_scriptname "$file"
+ relink_command=
+ func_source $func_ltwrapper_scriptname_result
+ func_append rmfiles " $func_ltwrapper_scriptname_result"
+ else
+ relink_command=
+ func_source $dir/$noexename
+ fi
+
+ # note $name still contains .exe if it was in $file originally
+ # as does the version of $file that was added into $rmfiles
+ func_append rmfiles " $odir/$name $odir/${name}S.$objext"
+ if test yes = "$fast_install" && test -n "$relink_command"; then
+ func_append rmfiles " $odir/lt-$name"
+ fi
+ if test "X$noexename" != "X$name"; then
+ func_append rmfiles " $odir/lt-$noexename.c"
+ fi
+ fi
+ fi
+ ;;
+ esac
+ func_show_eval "$RM $rmfiles" 'exit_status=1'
+ done
+
+ # Try to remove the $objdir's in the directories where we deleted files
+ for dir in $rmdirs; do
+ if test -d "$dir"; then
+ func_show_eval "rmdir $dir >/dev/null 2>&1"
+ fi
+ done
+
+ exit $exit_status
+}
+
+if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then
+ func_mode_uninstall ${1+"$@"}
+fi
+
+test -z "$opt_mode" && {
+ help=$generic_help
+ func_fatal_help "you must specify a MODE"
+}
+
+test -z "$exec_cmd" && \
+ func_fatal_help "invalid operation mode '$opt_mode'"
+
+if test -n "$exec_cmd"; then
+ eval exec "$exec_cmd"
+ exit $EXIT_FAILURE
+fi
+
+exit $exit_status
+
+
+# The TAGs below are defined such that we never get into a situation
+# where we disable both kinds of libraries. Given conflicting
+# choices, we go for a static library, that is the most portable,
+# since we can't tell whether shared libraries were disabled because
+# the user asked for that or because the platform doesn't support
+# them. This is particularly important on AIX, because we don't
+# support having both static and shared libraries enabled at the same
+# time on that platform, so we default to a shared-only configuration.
+# If a disable-shared tag is given, we'll fallback to a static-only
+# configuration. But we'll never go from static-only to shared-only.
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-shared
+build_libtool_libs=no
+build_old_libs=yes
+# ### END LIBTOOL TAG CONFIG: disable-shared
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-static
+build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac`
+# ### END LIBTOOL TAG CONFIG: disable-static
+
+# Local Variables:
+# mode:shell-script
+# sh-indentation:2
+# End:
diff --git a/missing b/missing
new file mode 100755
index 00000000..1fe1611f
--- /dev/null
+++ b/missing
@@ -0,0 +1,215 @@
+#! /bin/sh
+# Common wrapper for a few potentially missing GNU programs.
+
+scriptversion=2018-03-07.03; # UTC
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# 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 distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+ echo 1>&2 "Try '$0 --help' for more information"
+ exit 1
+fi
+
+case $1 in
+
+ --is-lightweight)
+ # Used by our autoconf macros to check whether the available missing
+ # script is modern enough.
+ exit 0
+ ;;
+
+ --run)
+ # Back-compat with the calling convention used by older automake.
+ shift
+ ;;
+
+ -h|--h|--he|--hel|--help)
+ echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
+to PROGRAM being missing or too old.
+
+Options:
+ -h, --help display this help and exit
+ -v, --version output version information and exit
+
+Supported PROGRAM values:
+ aclocal autoconf autoheader autom4te automake makeinfo
+ bison yacc flex lex help2man
+
+Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
+'g' are ignored when checking the name.
+
+Send bug reports to <bug-automake@gnu.org>."
+ exit $?
+ ;;
+
+ -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+ echo "missing $scriptversion (GNU Automake)"
+ exit $?
+ ;;
+
+ -*)
+ echo 1>&2 "$0: unknown '$1' option"
+ echo 1>&2 "Try '$0 --help' for more information"
+ exit 1
+ ;;
+
+esac
+
+# Run the given program, remember its exit status.
+"$@"; st=$?
+
+# If it succeeded, we are done.
+test $st -eq 0 && exit 0
+
+# Also exit now if we it failed (or wasn't found), and '--version' was
+# passed; such an option is passed most likely to detect whether the
+# program is present and works.
+case $2 in --version|--help) exit $st;; esac
+
+# Exit code 63 means version mismatch. This often happens when the user
+# tries to use an ancient version of a tool on a file that requires a
+# minimum version.
+if test $st -eq 63; then
+ msg="probably too old"
+elif test $st -eq 127; then
+ # Program was missing.
+ msg="missing on your system"
+else
+ # Program was found and executed, but failed. Give up.
+ exit $st
+fi
+
+perl_URL=https://www.perl.org/
+flex_URL=https://github.com/westes/flex
+gnu_software_URL=https://www.gnu.org/software
+
+program_details ()
+{
+ case $1 in
+ aclocal|automake)
+ echo "The '$1' program is part of the GNU Automake package:"
+ echo "<$gnu_software_URL/automake>"
+ echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
+ echo "<$gnu_software_URL/autoconf>"
+ echo "<$gnu_software_URL/m4/>"
+ echo "<$perl_URL>"
+ ;;
+ autoconf|autom4te|autoheader)
+ echo "The '$1' program is part of the GNU Autoconf package:"
+ echo "<$gnu_software_URL/autoconf/>"
+ echo "It also requires GNU m4 and Perl in order to run:"
+ echo "<$gnu_software_URL/m4/>"
+ echo "<$perl_URL>"
+ ;;
+ esac
+}
+
+give_advice ()
+{
+ # Normalize program name to check for.
+ normalized_program=`echo "$1" | sed '
+ s/^gnu-//; t
+ s/^gnu//; t
+ s/^g//; t'`
+
+ printf '%s\n' "'$1' is $msg."
+
+ configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
+ case $normalized_program in
+ autoconf*)
+ echo "You should only need it if you modified 'configure.ac',"
+ echo "or m4 files included by it."
+ program_details 'autoconf'
+ ;;
+ autoheader*)
+ echo "You should only need it if you modified 'acconfig.h' or"
+ echo "$configure_deps."
+ program_details 'autoheader'
+ ;;
+ automake*)
+ echo "You should only need it if you modified 'Makefile.am' or"
+ echo "$configure_deps."
+ program_details 'automake'
+ ;;
+ aclocal*)
+ echo "You should only need it if you modified 'acinclude.m4' or"
+ echo "$configure_deps."
+ program_details 'aclocal'
+ ;;
+ autom4te*)
+ echo "You might have modified some maintainer files that require"
+ echo "the 'autom4te' program to be rebuilt."
+ program_details 'autom4te'
+ ;;
+ bison*|yacc*)
+ echo "You should only need it if you modified a '.y' file."
+ echo "You may want to install the GNU Bison package:"
+ echo "<$gnu_software_URL/bison/>"
+ ;;
+ lex*|flex*)
+ echo "You should only need it if you modified a '.l' file."
+ echo "You may want to install the Fast Lexical Analyzer package:"
+ echo "<$flex_URL>"
+ ;;
+ help2man*)
+ echo "You should only need it if you modified a dependency" \
+ "of a man page."
+ echo "You may want to install the GNU Help2man package:"
+ echo "<$gnu_software_URL/help2man/>"
+ ;;
+ makeinfo*)
+ echo "You should only need it if you modified a '.texi' file, or"
+ echo "any other file indirectly affecting the aspect of the manual."
+ echo "You might want to install the Texinfo package:"
+ echo "<$gnu_software_URL/texinfo/>"
+ echo "The spurious makeinfo call might also be the consequence of"
+ echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
+ echo "want to install GNU make:"
+ echo "<$gnu_software_URL/make/>"
+ ;;
+ *)
+ echo "You might have modified some files without having the proper"
+ echo "tools for further handling them. Check the 'README' file, it"
+ echo "often tells you about the needed prerequisites for installing"
+ echo "this package. You may also peek at any GNU archive site, in"
+ echo "case some other package contains this missing '$1' program."
+ ;;
+ esac
+}
+
+give_advice "$1" | sed -e '1s/^/WARNING: /' \
+ -e '2,$s/^/ /' >&2
+
+# Propagate the correct exit status (expected to be 127 for a program
+# not found, 63 for a program that failed due to version mismatch).
+exit $st
+
+# Local variables:
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/scripts/40-usb-blacklist.rules b/scripts/40-usb-blacklist.rules
new file mode 100644
index 00000000..66313880
--- /dev/null
+++ b/scripts/40-usb-blacklist.rules
@@ -0,0 +1,14 @@
+#
+# Blacklist specific USB devices
+#
+# don't inquire sn and di on broken devices (https://bugzilla.suse.com/show_bug.cgi?id=840054)
+
+ACTION!="add|change", GOTO="usb_blacklist_end"
+KERNEL!="sd*[!0-9]|sr*", GOTO="usb_blacklist_end"
+
+# unknown device
+ATTRS{idVendor}=="0aec", ATTRS{idProduct}=="3260", ENV{ID_SCSI_INQUIRY}="1"
+# Sony/JMicron port replicator
+ATTRS{idVendor}=="054c", ATTRS{idProduct}=="06a0", ENV{ID_SCSI_INQUIRY}="1"
+
+LABEL="usb_blacklist_end"
diff --git a/scripts/54-before-scsi-sg3_id.rules b/scripts/54-before-scsi-sg3_id.rules
new file mode 100644
index 00000000..bb36650a
--- /dev/null
+++ b/scripts/54-before-scsi-sg3_id.rules
@@ -0,0 +1,55 @@
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/disk/{by-id,by-path}
+# scheme based on "Linux persistent device names", 2004, Hannes Reinecke <hare@suse.de>
+
+# This file contains rules for setting udev environment variables based on
+# hardware properties (serial numbers etc), which can be obtained without
+# actually reading from the device.
+#
+# Hopefully this will be integrated into systemd/udev soon (as 54-storage-hardware.rules).
+# Until then, we ship it here in sg3-utils.
+# It's important that rules dealing with low-level hardware attributes run
+# before the generic SCSI rules in 55-scsi-sg3_utils.rules.
+
+ACTION=="remove", GOTO="storage_hardware_end"
+SUBSYSTEM!="block", GOTO="block_storage_end"
+KERNEL!="sd*|sr*|cciss*", GOTO="block_storage_end"
+
+# ignore partitions that span the entire disk
+TEST=="whole_disk", GOTO="block_storage_end"
+
+# for partitions import parent information
+ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}!="?*", IMPORT{parent}="ID_*"
+
+# ATA
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
+
+# ATAPI devices (SPC-3 or later)
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
+
+# Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures)
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $devnode"
+
+# Fall back usb_id for USB devices
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+
+# FireWire
+ENV{ID_IEEE1394}!="?*", KERNEL=="sd*|sr*", ATTRS{ieee1394_id}=="?*", ENV{ID_IEEE1394}="$attr{ieee1394_id}"
+
+# by-path
+ENV{ID_PATH}!="?*", ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id"
+
+LABEL="block_storage_end"
+
+# SCSI tape devices
+SUBSYSTEM!="scsi_tape", GOTO="storage_hardware_end"
+KERNEL!="st*[0-9]|nst*[0-9]", GOTO="storage_hardware_end"
+
+ENV{ID_SERIAL}!="?*", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
+ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", ATTRS{serial}=="?*", IMPORT{builtin}="usb_id"
+
+# by-path
+ENV{ID_PATH}!="?*", IMPORT{builtin}="path_id"
+
+LABEL="storage_hardware_end"
diff --git a/scripts/55-scsi-sg3_id.rules b/scripts/55-scsi-sg3_id.rules
new file mode 100644
index 00000000..453210bd
--- /dev/null
+++ b/scripts/55-scsi-sg3_id.rules
@@ -0,0 +1,109 @@
+# SCSI-ID mappings for sg3_utils
+
+ACTION=="remove", GOTO="sg3_utils_id_end"
+
+SUBSYSTEM=="block", GOTO="block_dev"
+
+# SCSI devices other than "block"
+# This code used to live in 60-persistent-storage-tape.rules.
+
+# type 8 devices are "Medium Changers"
+SUBSYSTEM=="scsi_generic", KERNEL=="sg*[0-9]", ATTRS{type}=="8", \
+ GOTO="scsi_inquiry"
+SUBSYSTEM=="scsi_changer", KERNEL=="sch*[0-9]", ATTRS{type}=="8", \
+ ENV{.INQUIRY_DEV}="$root/bsg/$id", GOTO="scsi_inquiry"
+
+# tapes need to be accessed through their bsg device
+KERNEL=="st*[0-9]|nst*[0-9]", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", \
+ ENV{.INQUIRY_DEV}="$root/bsg/$id", GOTO="scsi_inquiry"
+
+GOTO="sg3_utils_id_end"
+
+LABEL="block_dev"
+
+# Import values for partitions
+ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_SCSI", IMPORT{parent}="SCSI_*"
+ENV{DEVTYPE}=="partition", ENV{ID_SCSI}=="1", GOTO="compat"
+
+# Handle non-SCSI devices that implement SCSI inquiry
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", GOTO="sg_inquiry"
+
+# Ignore everything else except sd/sr
+KERNEL!="sd*[!0-9]|sr*", GOTO="sg3_utils_id_end"
+
+# SCSI INQUIRY values
+# If the 'inquiry' sysfs attribute is present the kernel will already
+# have scanned for VPD pages, so if the vpd page attribute is not
+# present it is not supported (or deemed unsafe to access).
+# Hence we can skip the call to sg_inq and avoid I/O altogether.
+# Set 'ID_SCSI_INQUIRY=0' in an earlier udev rule if the kernel
+# fails to scan VPD pages correctly; the rules will then fall
+# back to calling sg_vpd directly.
+LABEL="scsi_inquiry"
+ENV{ID_SCSI_INQUIRY}=="0", GOTO="sg_inquiry"
+
+# "inquiry" is an attribute of the scsi_device in sysfs,
+# we obtain it by using $id after an ATTRS match.
+SUBSYSTEMS=="scsi", ATTRS{inquiry}=="*", KERNELS=="[0-9]*:*[0-9]", \
+ ENV{.SYSFS_PATH}="$sys/class/scsi_device/$id/device"
+ENV{.SYSFS_PATH}=="", GOTO="sg_inquiry"
+
+IMPORT{program}="/usr/bin/sg_inq --export --inhex=$env{.SYSFS_PATH}/inquiry --raw", \
+ ENV{ID_SCSI}="1", ENV{ID_SCSI_INQUIRY}="1"
+# If inquiry sysfs attribute reading it failed, fallback to sg
+ENV{ID_SCSI}!="1", GOTO="sg_inquiry"
+# Read VPD pages 80 (sn) and 83 (di)
+IMPORT{program}="/usr/bin/sg_inq --export --inhex=$env{.SYSFS_PATH}/vpd_pg80 --raw"
+IMPORT{program}="/usr/bin/sg_inq --export --inhex=$env{.SYSFS_PATH}/vpd_pg83 --raw"
+GOTO="compat"
+
+LABEL="sg_inquiry"
+# Handle devices that have no inquiry attributes in sysfs
+ENV{.INQUIRY_DEV}=="", ENV{.INQUIRY_DEV}="$tempnode"
+
+IMPORT{program}="/usr/bin/sg_inq --export $env{.INQUIRY_DEV}", ENV{ID_SCSI}="1"
+# Give up if this fails, too
+ENV{ID_SCSI}!="1", GOTO="sg3_utils_id_end"
+IMPORT{program}="/usr/bin/sg_inq --export --page=sn $env{.INQUIRY_DEV}"
+IMPORT{program}="/usr/bin/sg_inq --export --page=di $env{.INQUIRY_DEV}"
+
+LABEL="compat"
+
+# scsi_id compat mappings
+ENV{ID_VENDOR}!="?*", ENV{SCSI_VENDOR}=="?*", ENV{ID_VENDOR}="$env{SCSI_VENDOR}"
+ENV{ID_VENDOR_ENC}!="?*", ENV{SCSI_VENDOR_ENC}=="?*", ENV{ID_VENDOR_ENC}="$env{SCSI_VENDOR_ENC}"
+ENV{ID_MODEL}!="?*", ENV{SCSI_MODEL}=="?*", ENV{ID_MODEL}="$env{SCSI_MODEL}"
+ENV{ID_MODEL_ENC}!="?*", ENV{SCSI_MODEL_ENC}=="?*", ENV{ID_MODEL_ENC}="$env{SCSI_MODEL_ENC}"
+ENV{ID_REVISION}!="?*", ENV{SCSI_REVISION}=="?*", ENV{ID_REVISION}="$env{SCSI_REVISION}"
+ENV{ID_TYPE}!="?*", ENV{SCSI_TYPE}=="?*", ENV{ID_TYPE}="$env{SCSI_TYPE}"
+ENV{ID_TARGET_PORT}!="?*", ENV{SCSI_IDENT_PORT_TARGET_PORT_GROUP}=="?*", \
+ PROGRAM="/bin/sh -c 'echo $env{SCSI_IDENT_PORT_TARGET_PORT_GROUP} | /bin/sed s/^0x//'", \
+ ENV{ID_TARGET_PORT}="$result"
+
+# ID_WWN compat mapping
+ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_REGEXT}"
+ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_REG}"
+ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_EXT}"
+ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_LOCAL}"
+# ID_WWN has max 16 characters
+ENV{ID_WWN_WITH_EXTENSION}=="?*", ENV{ID_WWN}!="?*", \
+ PROGRAM="/bin/sh -c 'echo $env{ID_WWN_WITH_EXTENSION} | /bin/sed s/^\\\(0x.\\\{1,16\\\}\\\).*/\\1/'", \
+ ENV{ID_WWN}="$result"
+
+# ata_id compatibility
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_ATA}=="?*", ENV{ID_BUS}="ata", ENV{ID_ATA}="1", ENV{ID_SERIAL}="$env{SCSI_IDENT_LUN_ATA}"
+ENV{ID_SERIAL_SHORT}!="?*", ENV{SCSI_VENDOR}=="ATA", ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_VENDOR}"
+# Compat ID_SERIAL setting
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_REGEXT}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_REGEXT}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_REG}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_REG}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_EXT}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_EXT}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="2$env{SCSI_IDENT_LUN_EUI64}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_EUI64}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="8$env{SCSI_IDENT_LUN_NAME}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAME}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="1$env{SCSI_IDENT_LUN_T10}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_T10}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_LOCAL}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_LOCAL}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_VENDOR}"
+ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_SERIAL}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_SERIAL}"
+
+# Compat ID_SCSI_SERIAL setting
+ENV{ID_SCSI_SERIAL}!="?*", ENV{SCSI_IDENT_SERIAL}=="?*", ENV{ID_SCSI_SERIAL}="$env{SCSI_IDENT_SERIAL}"
+LABEL="sg3_utils_id_end"
diff --git a/scripts/58-scsi-sg3_symlink.rules b/scripts/58-scsi-sg3_symlink.rules
new file mode 100644
index 00000000..fe6b0000
--- /dev/null
+++ b/scripts/58-scsi-sg3_symlink.rules
@@ -0,0 +1,38 @@
+# SCSI-ID symlinks for sg3_utils
+
+ACTION=="remove", GOTO="sg3_utils_symlink_end"
+
+SUBSYSTEM!="block", GOTO="sg3_utils_symlink_end"
+ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="sg3_utils_symlink_end"
+
+# Select which identifier to use per default
+# 0: vpd page 0x80 identifier
+ENV{SCSI_IDENT_SERIAL}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}"
+ENV{SCSI_IDENT_SERIAL}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}-part%n"
+# NAA identifier (prefix 3)
+# 1: IEEE Registered Extended first
+ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REGEXT}"
+ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REGEXT}-part%n"
+# 2: IEEE Registered
+ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REG}"
+ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REG}-part%n"
+# 3: IEEE Extended
+ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_EXT}"
+ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_EXT}-part%n"
+# 4: EUI-64 identifier (prefix 2)
+ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-2$env{SCSI_IDENT_LUN_EUI64}"
+ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-2$env{SCSI_IDENT_LUN_EUI64}-part%n"
+# 5: SCSI name identifier (prefix 8)
+ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-8$env{SCSI_IDENT_LUN_NAME}"
+ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-8$env{SCSI_IDENT_LUN_NAME}-part%n"
+# 6: T10 Vendor identifier (prefix 1)
+ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-1$env{SCSI_IDENT_LUN_T10}"
+ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-1$env{SCSI_IDENT_LUN_T10}-part%n"
+# 7: IEEE Locally assigned
+ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_LOCAL}"
+ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_LOCAL}-part%n"
+# 8: Vendor-specific identifier (prefix 0)
+ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}"
+ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}-part%n"
+
+LABEL="sg3_utils_symlink_end"
diff --git a/scripts/59-fc-wwpn-id.rules b/scripts/59-fc-wwpn-id.rules
new file mode 100644
index 00000000..5ad0a5c8
--- /dev/null
+++ b/scripts/59-fc-wwpn-id.rules
@@ -0,0 +1,17 @@
+#
+# FC WWPN-based by-path links
+#
+
+ACTION!="add|change", GOTO="fc_wwpn_end"
+KERNEL!="sd*", GOTO="fc_wwpn_end"
+
+ENV{DEVTYPE}=="disk", IMPORT{program}="fc_wwpn_id %p"
+ENV{DEVTYPE}=="partition", IMPORT{parent}="FC_*"
+ENV{FC_TARGET_WWPN}!="?*", GOTO="fc_wwpn_end"
+ENV{FC_INITIATOR_WWPN}!="?*", GOTO="fc_wwpn_end"
+ENV{FC_TARGET_LUN}!="?*", GOTO="fc_wwpn_end"
+
+ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-path/fc-$env{FC_INITIATOR_WWPN}-$env{FC_TARGET_WWPN}-lun-$env{FC_TARGET_LUN}"
+ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-path/fc-$env{FC_INITIATOR_WWPN}-$env{FC_TARGET_WWPN}-lun-$env{FC_TARGET_LUN}-part%n"
+
+LABEL="fc_wwpn_end"
diff --git a/scripts/59-scsi-cciss_id.rules b/scripts/59-scsi-cciss_id.rules
new file mode 100644
index 00000000..4eb4561e
--- /dev/null
+++ b/scripts/59-scsi-cciss_id.rules
@@ -0,0 +1,18 @@
+# cciss compat rules
+
+ACTION!="add|change", GOTO="cciss_compat_end"
+KERNEL!="sd*", GOTO="cciss_compat_end"
+ENV{ID_VENDOR}!="HP", ENV{ID_VENDOR}!="COMPAQ", GOTO="cciss_compat_end"
+ENV{ID_MODEL}!="LOGICAL_VOLUME", GOTO="cciss_compat_end"
+
+ENV{DEVTYPE}=="disk", DRIVERS=="hpsa", IMPORT{program}="cciss_id %p"
+ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
+ENV{ID_CCISS}!="?*", GOTO="cciss_compat_end"
+
+ENV{DEVTYPE}=="disk", SYMLINK+="cciss/$env{ID_CCISS}"
+ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/cciss-$env{ID_SERIAL}"
+
+ENV{DEVTYPE}=="partition", SYMLINK+="cciss/$env{ID_CCISS}p%n"
+ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/cciss-$env{ID_SERIAL}-part%n"
+
+LABEL="cciss_compat_end"
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
new file mode 100644
index 00000000..01d44362
--- /dev/null
+++ b/scripts/Makefile.am
@@ -0,0 +1,3 @@
+dist_bin_SCRIPTS = scsi_logging_level scsi_mandat scsi_readcap scsi_ready \
+ scsi_satl scsi_start scsi_stop scsi_temperature \
+ rescan-scsi-bus.sh
diff --git a/scripts/Makefile.in b/scripts/Makefile.in
new file mode 100644
index 00000000..21673ab2
--- /dev/null
+++ b/scripts/Makefile.in
@@ -0,0 +1,518 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = scripts
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(dist_bin_SCRIPTS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(bindir)"
+SCRIPTS = $(dist_bin_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+dist_bin_SCRIPTS = scsi_logging_level scsi_mandat scsi_readcap scsi_ready \
+ scsi_satl scsi_start scsi_stop scsi_temperature \
+ rescan-scsi-bus.sh
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign scripts/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign scripts/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-dist_binSCRIPTS: $(dist_bin_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-dist_binSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(SCRIPTS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-dist_binSCRIPTS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-dist_binSCRIPTS
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am \
+ install-dist_binSCRIPTS install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+ uninstall-am uninstall-dist_binSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 00000000..72cbdcf5
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,55 @@
+ README for sg3_utils/scripts
+ ============================
+Introduction
+============
+This directory contains bash shell scripts. Most of them call one or
+more utilities from the sg3_utils package. They assume the sg3_utils
+package utilities are on the PATH of the user.
+
+rescan-scsi-bus.sh is written by Kurt Garloff (formerly from Suse Labs)
+with patches from Hannes Reinecke (Suse) and Redhat.
+
+scsi_logging_level is written by Andreas Herrmann <aherrman at de dot ibm
+dot com>. It sets the logging level of the SCSI subsystem in the Linux
+2.6 series kernels. See that file for more information.
+
+The other scripts are written by the author. Some do testing while others
+do bulk tasks (e.g. stopping multiple disks).
+
+Details
+=======
+Each script supplies more information, typically by supplying a '-h'
+or '--help' option. The script source often contains explanatory
+information. Following is a usage summary with a one line description:
+ rescan-scsi-bus.sh [OPTIONS]
+ - see the output of 'rescan-scsi-bus.sh --help'
+ scsi_logging_level [OPTIONS]
+ - set Linux SCSI subsystem logging level
+ scsi_mandat [-h] [-L] [-q] <device>
+ - check for mandatory SCSI command support
+ scsi_readcap [-b] [-h] [-v] <device>+
+ - fetch capacity/size information for each <device>
+ scsi_ready [-h] [-v] <device>+
+ - check the media ready status on each <device>
+ scsi_satl [-h] [-L] [-q] [-v] <device>
+ - check <device> for SCSI to ATA Translation Layer (SATL)
+ scsi_start [-h] [-v] [-w] <device>+
+ - start media (i.e. spin up) in each <device>
+ scsi_stop [-h] [-v] [-w] <device>+
+ - stop media (i.e. spin down) in each <device>
+ scsi_temperature [-h] [-v] <device>+
+ - check temperature in each <device>
+
+These scripts assume that the main sg3_utils utilities are installed
+and are on the user's PATH.
+
+This directory, prior to sg3_utils-1.28, contained the sas_disk_blink
+script. Since it depends on the sdparm utility it has been moved to
+the sdparm package in its scripts directory.
+
+59-scsi-sg3_utils.rules is a Linux specific file for udev. These rules use
+'sg_inq --export' to help udev create identifying device nodes, for example
+/dev/disk/by-id/wwn-0x5001501234567890-part1.
+
+Douglas Gilbert
+4th October 2021
diff --git a/scripts/cciss_id b/scripts/cciss_id
new file mode 100755
index 00000000..8ac11d5f
--- /dev/null
+++ b/scripts/cciss_id
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# cciss_id
+#
+# Generates device node names according to the cciss naming rules
+#
+# Copyright (C) 2011 SUSE Linux Products GmbH
+# Author:
+# Hannes Reinecke <hare@suse.de>
+#
+#
+# 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 version 2 of the License.
+#
+# This script generates a device node name which is compatible
+# with the 'cciss' device naming rules.
+# It is intended to provide backward-compatible names for the
+# 'hpsa' driver.
+#
+
+cciss_enumerate()
+{
+ local last_pci_dev=${1##0000:}
+ local cur_pci_dev
+ local cciss_num=0
+
+ for cur_pci_dev in $(lspci -n | tac | sed -n 's/\(..:..\..\) .* 103c:\(3220\|3230\|3238\|323a\|323b\) .*/\1/p') ; do
+ if [ "$cur_pci_dev" == "$last_pci_dev" ] ; then
+ echo "$cciss_num"
+ return;
+ fi
+ cciss_num=$(($cciss_num + 1))
+ done
+ echo "$cciss_num"
+}
+
+hpsa_lun_offset()
+{
+ local scsi_host=$1
+
+ scsi_id=$(lsscsi 2>/dev/null | sed -n "s/.\(${scsi_host}:[0-9]*:[0-9]*:[0-9]*\)..*disk .*/\1/p" | head -1)
+ echo ${scsi_id##*:}
+}
+
+DEVPATH=$1
+SCSIPATH=$(cd -P /sys$DEVPATH/device; echo $PWD)
+SCSIID=${SCSIPATH##*/}
+HOSTID=${SCSIID%%:*}
+LUNID=${SCSIID##*:}
+PCIPATH=${SCSIPATH%%/host*}
+PCIDEV=${PCIPATH##*/}
+HOSTPATH=${PCIPATH}/host${HOSTID}/scsi_host/host${HOSTID}
+read controller 2>/dev/null <${HOSTPATH}/ctlr_num || controller=$(cciss_enumerate $PCIDEV)
+
+# hpsa lies about the LUN ...
+disk_offset=$(hpsa_lun_offset $HOSTID)
+if [ "$disk_offset" ] ; then
+ disk=$(( $LUNID - $disk_offset ))
+else
+ disk=$LUNID
+fi
+
+if [ "$controller" ] && [ "$disk" ] ; then
+ echo "ID_CCISS=c${controller}d${disk}"
+fi
diff --git a/scripts/fc_wwpn_id b/scripts/fc_wwpn_id
new file mode 100644
index 00000000..17c74fe8
--- /dev/null
+++ b/scripts/fc_wwpn_id
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# fc_wwpn_id
+#
+# Generates device node names links based on FC WWPN
+# Copyright (c) 2016-2021 Hannes Reinecke, SUSE Linux GmbH
+#
+# 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 version 2 of the License.
+#
+
+DEVPATH=$1
+SCSIPATH=$(cd -P "/sys$DEVPATH/device" || exit; echo "$PWD")
+
+d=$SCSIPATH
+[ -d "$d/scsi_disk" ] || exit 0
+target_lun=${d##*:}
+
+while [ -n "$d" ] ; do
+ d=${d%/*}
+ e=${d##*/}
+ case "$e" in
+ rport*)
+ rport=$e
+ rport_dir="/sys/class/fc_remote_ports/$rport"
+ if [ -d "$rport_dir" ] ; then
+ rport_wwpn=$(cat "$rport_dir/port_name")
+ fi
+ ;;
+ host*)
+ host=$e
+ host_dir="/sys/class/fc_host/$host"
+ if [ -d "$host_dir" ] ; then
+ host_wwpn=$(cat "$host_dir/port_name")
+ break;
+ fi
+ esac
+done
+
+if [ -n "$rport_wwpn" ] || [ -n "$host_wwpn" ] ; then
+ echo "FC_TARGET_LUN=$target_lun"
+fi
+
+if [ -n "$rport_wwpn" ] ; then
+ echo "FC_TARGET_WWPN=$rport_wwpn"
+fi
+
+if [ -n "$host_wwpn" ] ; then
+ echo "FC_INITIATOR_WWPN=$host_wwpn"
+fi
diff --git a/scripts/lunmask.service b/scripts/lunmask.service
new file mode 100644
index 00000000..03fdd96a
--- /dev/null
+++ b/scripts/lunmask.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Disable LUN masking and scan SCSI Hosts
+After=systemd-udev-trigger.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/usr/lib/systemd/scripts/scsi-enable-target-scan.sh
+
+[Install]
+WantedBy=multi-user.target
diff --git a/scripts/rescan-scsi-bus.sh b/scripts/rescan-scsi-bus.sh
new file mode 100755
index 00000000..1ebc0e91
--- /dev/null
+++ b/scripts/rescan-scsi-bus.sh
@@ -0,0 +1,1436 @@
+#!/bin/bash
+# Script to rescan SCSI bus, using the scsi add-single-device mechanism.
+# (c) 1998--2010 Kurt Garloff <kurt@garloff.de>, GNU GPL v2 or v3
+# (c) 2006--2022 Hannes Reinecke, GNU GPL v2 or later
+# $Id: rescan-scsi-bus.sh,v 1.57 2012/03/31 14:08:48 garloff Exp $
+
+VERSION="20220930"
+SCAN_WILD_CARD=4294967295
+
+TMPLUNINFOFILE="/tmp/rescan-scsi-mpath-info.txt"
+
+setcolor ()
+{
+ red="\e[0;31m"
+ green="\e[0;32m"
+ yellow="\e[0;33m"
+ bold="\e[0;1m"
+ norm="\e[0;0m"
+}
+
+unsetcolor ()
+{
+ red=""; green=""
+ yellow=""; norm=""
+}
+
+echo_debug()
+{
+ if [ "$debug" -eq 1 ] ; then
+ echo "$1"
+ fi
+}
+
+# Output some text and return cursor to previous position
+# (only works for simple strings)
+# Stores length of string in LN and returns it
+print_and_scroll_back ()
+{
+ STRG="$1"
+ LN=${#STRG}
+ BK=""
+ declare -i cntr=0
+ while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; let cntr+=1; done
+ echo -en "$STRG$BK"
+ return "$LN"
+}
+
+# Overwrite a text of length $LN with whitespace
+white_out ()
+{
+ BK=""; WH=""
+ declare -i cntr=0
+ while [ $cntr -lt "$LN" ] ; do BK="$BK\e[D"; WH="$WH "; let cntr+=1; done
+ echo -en "$WH$BK"
+}
+
+# Return hosts. sysfs must be mounted
+findhosts_26 ()
+{
+ hosts=
+ for hostdir in /sys/class/scsi_host/host* ; do
+ [ -e "$hostdir" ] || continue
+ hostno=${hostdir#/sys/class/scsi_host/host}
+ if [ -f "$hostdir/isp_name" ] ; then
+ hostname="qla2xxx"
+ elif [ -f "$hostdir/lpfc_drvr_version" ] ; then
+ hostname="lpfc"
+ else
+ hostname=$(cat "$hostdir/proc_name")
+ fi
+ hosts="$hosts $hostno"
+ echo_debug "Host adapter $hostno ($hostname) found."
+ done
+ if [ -z "$hosts" ] ; then
+ echo "No SCSI host adapters found in sysfs"
+ exit 1;
+ fi
+ # ensure numeric ordering. No quotes arount $hosts to skip leading space.
+ hosts=$(echo $hosts | tr ' ' '\n' | sort -n)
+}
+
+# Return hosts. /proc/scsi/HOSTADAPTER/? must exist
+findhosts ()
+{
+ hosts=
+ for driverdir in /proc/scsi/*; do
+ driver=${driverdir#/proc/scsi/}
+ if [ "$driver" = scsi ] || [ "$driver" = sg ] || [ "$driver" = dummy ] || [ "$driver" = device_info ] ; then continue; fi
+ for hostdir in $driverdir/*; do
+ name=${hostdir#/proc/scsi/*/}
+ if [ "$name" = add_map ] || [ "$name" = map ] || [ "$name" = mod_parm ] ; then continue; fi
+ num=$name
+ driverinfo=$driver
+ if [ -r "$hostdir/status" ] ; then
+ num=$(printf '%d\n' "$(sed -n 's/SCSI host number://p' "$hostdir/status")")
+ driverinfo="$driver:$name"
+ fi
+ hosts="$hosts $num"
+ echo "Host adapter $num ($driverinfo) found."
+ done
+ done
+}
+
+printtype ()
+{
+ local type=$1
+
+ case "$type" in
+ 0) echo "Direct-Access" ;;
+ 1) echo "Sequential-Access" ;;
+ 2) echo "Printer" ;;
+ 3) echo "Processor" ;;
+ 4) echo "WORM" ;;
+ 5) echo "CD-ROM" ;;
+ 6) echo "Scanner" ;;
+ 7) echo "Optical-Device" ;;
+ 8) echo "Medium-Changer" ;;
+ 9) echo "Communications" ;;
+ 10) echo "Unknown" ;;
+ 11) echo "Unknown" ;;
+ 12) echo "RAID" ;;
+ 13) echo "Enclosure" ;;
+ 14) echo "Direct-Access-RBC" ;;
+ *) echo "Unknown" ;;
+ esac
+}
+
+print02i()
+{
+ if [ "$1" = "*" ] ; then
+ echo "00"
+ else
+ printf "%02i" "$1"
+ fi
+}
+
+# Get /proc/scsi/scsi info for device $host:$channel:$id:$lun
+# Optional parameter: Number of lines after first (default = 2),
+# result in SCSISTR, return code 1 means empty.
+procscsiscsi ()
+{
+ if [ -z "$1" ] ; then
+ LN=2
+ else
+ LN=$1
+ fi
+ CHANNEL=$(print02i "$channel")
+ ID=$(print02i "$id")
+ LUN=$(print02i "$lun")
+ if [ -d /sys/class/scsi_device ]; then
+ SCSIPATH="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}"
+ if [ -d "$SCSIPATH" ] ; then
+ SCSISTR="Host: scsi${host} Channel: $CHANNEL Id: $ID Lun: $LUN"
+ if [ "$LN" -gt 0 ] ; then
+ IVEND=$(cat "${SCSIPATH}/device/vendor")
+ IPROD=$(cat "${SCSIPATH}/device/model")
+ IPREV=$(cat "${SCSIPATH}/device/rev")
+ SCSIDEV=$(printf ' Vendor: %-08s Model: %-16s Rev: %-4s' "$IVEND" "$IPROD" "$IPREV")
+ SCSISTR="$SCSISTR
+$SCSIDEV"
+ fi
+ if [ "$LN" -gt 1 ] ; then
+ ILVL=$(cat "${SCSIPATH}/device/scsi_level")
+ type=$(cat "${SCSIPATH}/device/type")
+ ITYPE=$(printtype "$type")
+ SCSITMP=$(printf ' Type: %-17s ANSI SCSI revision: %02d' "$ITYPE" "$((ILVL - 1))")
+ SCSISTR="$SCSISTR
+$SCSITMP"
+ fi
+ else
+ return 1
+ fi
+ else
+ grepstr="scsi$host Channel: $CHANNEL Id: $ID Lun: $LUN"
+ SCSISTR=$(grep -A "$LN" -e "$grepstr" /proc/scsi/scsi)
+ fi
+ if [ -z "$SCSISTR" ] ; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+# Find sg device with 2.6 sysfs support
+sgdevice26 ()
+{
+ local gendev
+
+ # if the scsi device has not been added, then there would not
+ # a related sgdev. So it's pointless to scan all sgs to find
+ # a related sg.
+ scsidev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}
+ if [ ! -e "$scsidev" ]; then
+ SGDEV=""
+ return
+ fi
+
+ gendev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/generic
+ if [ -e "$gendev" ] ; then
+ SGDEV=$(basename "$(readlink "$gendev")")
+ return
+ fi
+ SGDEV=""
+}
+
+# Find sg device with 2.4 report-devs extensions
+sgdevice24 ()
+{
+ if procscsiscsi 3; then
+ SGDEV=$(echo "$SCSISTR" | grep 'Attached drivers:' | sed 's/^ *Attached drivers: \(sg[0-9]*\).*/\1/')
+ fi
+}
+
+# Find sg device that belongs to SCSI device $host $channel $id $lun
+# and return in SGDEV
+sgdevice ()
+{
+ SGDEV=
+ if [ -d /sys/class/scsi_device ] ; then
+ sgdevice26
+ else
+ DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null)
+ repdevstat=$((1-$?))
+ if [ $repdevstat = 0 ]; then
+ echo "scsi report-devs 1" >/proc/scsi/scsi
+ DRV=$(grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null)
+ [ $? -eq 1 ] && return
+ fi
+ if ! echo "$DRV" | grep -q 'drivers: sg'; then
+ modprobe sg
+ fi
+ sgdevice24
+ if [ $repdevstat = 0 ]; then
+ echo "scsi report-devs 0" >/proc/scsi/scsi
+ fi
+ fi
+}
+
+# Whether or not the RMB (removable) bit has been set in the INQUIRY response.
+# Uses ${host}, ${channel}, ${id} and ${lun}. Assumes that sg_device() has
+# already been called. How to test this function: copy/paste this function
+# in a shell and run
+# (cd /sys/class/scsi_device && for d in *; do set ${d//:/ }; echo -n "$d $(</sys/class/scsi_device/${d}/device/block/*/removable) <> "; SGDEV=bsg/$d host=$1 channel=$2 id=$3 lun=$4 is_removable; done)
+is_removable ()
+{
+ local b p
+
+ p=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/inquiry
+ # Extract the second byte of the INQUIRY response and check bit 7 (mask 0x80).
+ b=$(hexdump -n1 -e '/1 "%02X"' "$p" 2>/dev/null)
+ if [ -n "$b" ]; then
+ echo $(((0x$b & 0x80) != 0))
+ else
+ sg_inq /dev/$SGDEV 2>/dev/null | sed -n 's/^.*RMB=\([0-9]*\).*$/\1/p'
+ fi
+}
+
+# Test if SCSI device is still responding to commands
+# Return values:
+# 0 device is present
+# 1 device has changed
+# 2 device has been removed
+testonline ()
+{
+ local ctr RC RMB
+
+ : testonline
+ ctr=0
+ RC=0
+ # Set default values
+ IPTYPE=31
+ IPQUAL=3
+ [ ! -x /usr/bin/sg_turs ] && return 0
+ sgdevice
+ [ -z "$SGDEV" ] && return 0
+ sg_turs /dev/$SGDEV >/dev/null 2>&1
+ RC=$?
+
+ # Handle in progress of becoming ready and unit attention
+ while [ $RC = 2 -o $RC = 6 ] && [ $ctr -lt $timeout ] ; do
+ if [ $RC = 2 ] && [ "$RMB" != "1" ] && sg_inq /dev/$SGDEV | grep -q -i "PQual=0" ; then
+ echo -n "."
+ let LN+=1
+ sleep 1
+ else
+ sleep 0.02
+ fi
+ let ctr+=1
+ sg_turs /dev/$SGDEV >/dev/null 2>&1
+ RC=$?
+ # Check for removable device; TEST UNIT READY obviously will
+ # fail for a removable device with no medium
+ RMB=$(is_removable)
+ print_and_scroll_back "$host:$channel:$id:$lun $SGDEV ($RMB) "
+ [ $RC = 2 ] && [ "$RMB" = "1" ] && break
+ done
+ if [ $ctr != 0 ] ; then
+ white_out
+ fi
+ # echo -e "\e[A\e[A\e[A${yellow}Test existence of $SGDEV = $RC ${norm} \n\n\n"
+ [ $RC = 1 ] && return $RC
+ # Reset RC (might be !=0 for passive paths)
+ RC=0
+ # OK, device online, compare INQUIRY string
+ INQ=$(sg_inq "$sg_len_arg" /dev/$SGDEV 2>/dev/null)
+ if [ -z "$INQ" ] ; then
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}INQUIRY failed${norm} \n\n\n"
+ return 2
+ fi
+ IVEND=$(echo "$INQ" | grep 'Vendor identification:' | sed 's/^[^:]*: \(.*\)$/\1/')
+ IPROD=$(echo "$INQ" | grep 'Product identification:' | sed 's/^[^:]*: \(.*\)$/\1/')
+ IPREV=$(echo "$INQ" | grep 'Product revision level:' | sed 's/^[^:]*: \(.*\)$/\1/')
+ STR=$(printf " Vendor: %-08s Model: %-16s Rev: %-4s" "$IVEND" "$IPROD" "$IPREV")
+ IPTYPE=$(echo "$INQ" | sed -n 's/.* Device_type=\([0-9]*\) .*/\1/p')
+ if [ -z "$IPTYPE" ]; then
+ IPTYPE=$(echo "$INQ" | sed -n 's/.* PDT=\([0-9]*\) .*/\1/p')
+ fi
+ IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) Device.*/\1/p')
+ if [ -z "$IPQUAL" ] ; then
+ IPQUAL=$(echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) PDT.*/\1/p')
+ fi
+ if [ "$IPQUAL" != 0 ] ; then
+ [ -z "$IPQUAL" ] && IPQUAL=3
+ [ -z "$IPTYPE" ] && IPTYPE=31
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}LU not available (PQual $IPQUAL)${norm} \n\n\n"
+ return 2
+ fi
+
+ TYPE=$(printtype $IPTYPE)
+ if ! procscsiscsi ; then
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV removed. ${norm}\n\n\n"
+ return 2
+ fi
+ TMPSTR=$(echo "$SCSISTR" | grep 'Vendor:')
+ if [ "$ignore_rev" -eq 0 ] ; then
+ if [ "$TMPSTR" != "$STR" ]; then
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n"
+ return 1
+ fi
+ else
+ # Ignore disk revision change
+ local old_str_no_rev=
+ local new_str_no_rev=
+
+ old_str_no_rev=${TMPSTR%Rev:*}
+ new_str_no_rev=${STR%Rev:*}
+ if [ "$old_str_no_rev" != "$new_str_no_rev" ]; then
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n"
+ return 1
+ fi
+ fi
+ TMPSTR=$(echo "$SCSISTR" | sed -n 's/.*Type: *\(.*\) *ANSI.*/\1/p' | sed 's/ *$//g')
+ if [ "$TMPSTR" != "$TYPE" ] ; then
+ echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${TMPSTR} \nto: $TYPE ${norm} \n\n\n"
+ return 1
+ fi
+ return $RC
+}
+
+# Test if SCSI device $host $channel $id $lun exists
+# Outputs description from /proc/scsi/scsi (unless arg passed)
+# Returns SCSISTR (empty if no dev)
+testexist ()
+{
+ : testexist
+ SCSISTR=
+ if procscsiscsi && [ -z "$1" ] ; then
+ echo "$SCSISTR" | head -n1
+ echo "$SCSISTR" | tail -n2 | pr -o4 -l1
+ fi
+}
+
+# Returns the list of existing channels per host
+chanlist ()
+{
+ local hcil
+ local cil
+ local chan
+ local tmpchan
+
+ for dev in /sys/class/scsi_device/${host}:* ; do
+ [ -d "$dev" ] || continue;
+ hcil=${dev##*/}
+ cil=${hcil#*:}
+ chan=${cil%%:*}
+ for tmpchan in $channelsearch ; do
+ if [ "$chan" -eq "$tmpchan" ] ; then
+ chan=
+ fi
+ done
+ if [ -n "$chan" ] ; then
+ channelsearch="$channelsearch $chan"
+ fi
+ done
+ if [ -z "$channelsearch" ] ; then
+ channelsearch="0"
+ fi
+}
+
+# Returns the list of existing targets per host
+idlist ()
+{
+ local tmpid
+ local newid
+ local oldid
+
+ oldlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n")
+ # Rescan LUN 0 to check if we found new targets
+ echo "${channel} - -" > "/sys/class/scsi_host/host${host}/scan"
+ newlist=$(find /sys/class/scsi_device -name "${host}:${channel}:*" -printf "%f\n")
+ for newid in $newlist ; do
+ oldid=$newid
+ for tmpid in $oldlist ; do
+ if [ "$newid" = "$tmpid" ] ; then
+ oldid=
+ break
+ fi
+ done
+ if [ -n "$oldid" ] ; then
+ if [ -d /sys/class/scsi_device/$oldid ] ; then
+ hcil=${oldid}
+ printf "\r${green}NEW: %s ${norm}"
+ testexist
+ if [ "$SCSISTR" ] ; then
+ incrfound "$hcil"
+ fi
+ fi
+ fi
+ done
+ idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :)
+}
+
+# Returns the list of existing LUNs from device $host $channel $id $lun
+# and returns list to stdout
+getluns()
+{
+ sgdevice
+ [ -z "$SGDEV" ] && return 1
+ if [ ! -x /usr/bin/sg_luns ] ; then
+ echo 0
+ return 1
+ fi
+ LLUN=$(sg_luns /dev/$SGDEV 2>/dev/null | sed -n 's/ \(.*\)/\1/p')
+ # Added -z $LLUN condition because $? gets the RC from sed, not sg_luns
+ if [ $? -ne 0 ] || [ -z "$LLUN" ] ; then
+ echo 0
+ return 1
+ fi
+ for lun in $LLUN ; do
+ # Swap LUN number
+ l0=0x$lun
+ l1=$(( (l0 >> 48) & 0xffff ))
+ l2=$(( (l0 >> 32) & 0xffff ))
+ l3=$(( (l0 >> 16) & 0xffff ))
+ l4=$(( l0 & 0xffff ))
+ l0=$(( ( ( (l4 * 0xffff) + l3 ) * 0xffff + l2 ) * 0xffff + l1 ))
+ printf "%u\n" $l0
+ done
+ return 0
+}
+
+# Wait for udev to settle (create device nodes etc.)
+udevadm_settle()
+{
+ local tmo=60
+ if [ -x /sbin/udevadm ] ; then
+ print_and_scroll_back " Calling udevadm settle (can take a while) "
+ # Loop for up to 60 seconds if sd devices still are settling..
+ # This allows us to continue if udev events are stuck on multipaths in recovery mode
+ while [ $tmo -gt 0 ] ; do
+ if ! /sbin/udevadm settle --timeout=1 | egrep -q sd[a-z]+ ; then
+ break;
+ fi
+ let tmo=$tmo-1
+ done
+ white_out
+ elif [ -x /sbin/udevsettle ] ; then
+ print_and_scroll_back " Calling udevsettle (can take a while) "
+ /sbin/udevsettle
+ white_out
+ else
+ sleep 0.02
+ fi
+}
+
+# Perform scan on a single lun $host $channel $id $lun
+dolunscan()
+{
+ local remappedlun0=
+ local devpath
+ SCSISTR=
+ devnr="$host $channel $id $lun"
+ echo -e " Scanning for device $devnr ... "
+ printf "${yellow}OLD: %s ${norm}"
+ testexist
+ # Device exists: Test whether it's still online
+ # (testonline returns 2 if it's gone and 1 if it has changed)
+ devpath="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device"
+ if [ "$SCSISTR" ] ; then
+ testonline
+ RC=$?
+ # Well known lun transition case. Only for Direct-Access devs (type 0)
+ # If block directory exists && and PQUAL != 0, we unmapped lun0 and just have a well-known lun
+ # If block directory doesn't exist && PQUAL == 0, we mapped a real lun0
+ if [ "$lun" -eq 0 ] && [ $IPTYPE -eq 0 ] ; then
+ if [ $RC = 2 ] ; then
+ if [ -e "$devpath" ] ; then
+ if [ -d "$devpath/block" ] ; then
+ remappedlun0=2 # Transition from real lun 0 to well-known
+ else
+ RC=0 # Set this so the system leaves the existing well known lun alone. This is a lun 0 with no block directory
+ fi
+ fi
+ elif [ $RC = 0 ] && [ $IPTYPE -eq 0 ] ; then
+ if [ -e "$devpath" ] ; then
+ if [ ! -d "$devpath/block" ] ; then
+ remappedlun0=1 # Transition from well-known to real lun 0
+ fi
+ fi
+ fi
+ fi
+ fi
+
+ # Special case: lun 0 just got added (for reportlunscan),
+ # so make sure we correctly treat it as new
+ if [ "$lun" = "0" ] && [ "$1" = "1" ] && [ -z "$remappedlun0" ] ; then
+ SCSISTR=""
+ printf "\r\e[A\e[A\e[A"
+ fi
+
+ : f "$remove" s $SCSISTR
+ if [ "$remove" ] && [ "$SCSISTR" -o "$remappedlun0" = "1" ] ; then
+ if [ $RC != 0 ] || [ ! -z "$forceremove" ] || [ -n "$remappedlun0" ] ; then
+ if [ "$remappedlun0" != "1" ] ; then
+ echo -en "\r\e[A\e[A\e[A${red}REM: "
+ echo "$SCSISTR" | head -n1
+ echo -e "${norm}\e[B\e[B"
+ fi
+ if [ -e "$devpath" ] ; then
+ # have to preemptively do this so we can figure out the mpath device
+ # Don't do this if we're deleting a well known lun to replace it
+ if [ "$remappedlun0" != "1" ] ; then
+ incrrmvd "$host:$channel:$id:$lun"
+ fi
+ echo 1 > "$devpath/delete"
+ sleep 0.02
+ else
+ echo "scsi remove-single-device $devnr" > /proc/scsi/scsi
+ if [ $RC -eq 1 ] || [ "$lun" -eq 0 ] ; then
+ # Try readding, should fail if device is gone
+ echo "scsi add-single-device $devnr" > /proc/scsi/scsi
+ fi
+ fi
+ fi
+ if [ $RC = 0 ] || [ "$forcerescan" ] ; then
+ if [ -e "$devpath" ] ; then
+ echo 1 > "$devpath/rescan"
+ fi
+ fi
+ printf "\r\e[A\e[A\e[A${yellow}OLD: %s ${norm}"
+ testexist
+ if [ -z "$SCSISTR" ] && [ $RC != 1 ] && [ "$remappedlun0" != "1" ] ; then
+ printf "\r${red}DEL: %s\r\n\n ${norm}"
+ # In the event we're replacing with a well known node, we need to let it continue, to create the replacement node
+ [ "$remappedlun0" != "2" ] && return 2
+ fi
+ fi
+ if [ -z "$SCSISTR" ] || [ -n "$remappedlun0" ] ; then
+ if [ "$remappedlun0" != "2" ] ; then
+ # Device does not exist, try to add
+ printf "\r${green}NEW: %s ${norm}"
+ fi
+ if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then
+ echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null
+ else
+ echo "scsi add-single-device $devnr" > /proc/scsi/scsi
+ fi
+ testexist
+ if [ -z "$SCSISTR" ] ; then
+ # Device not present
+ printf "\r\e[A";
+ # Optimization: if lun==0, stop here (only if in non-remove mode)
+ if [ "$lun" = 0 ] && [ -z "$remove" ] && [ "$optscan" = 1 ] ; then
+ return 1;
+ fi
+ else
+ if [ "$remappedlun0" != "2" ] ; then
+ incrfound "$host:$channel:$id:$lun"
+ fi
+ fi
+ fi
+ return 0;
+}
+
+# Perform report lun scan on $host $channel $id using REPORT_LUNS
+doreportlun()
+{
+ lun=0
+ SCSISTR=
+ devnr="$host $channel $id $lun"
+ echo -en " Scanning for device $devnr ...\r"
+ lun0added=
+ #printf "${yellow}OLD: %s ${norm}"
+ # Phase one: If LUN0 does not exist, try to add
+ testexist -q
+ if [ -z "$SCSISTR" ] ; then
+ # Device does not exist, try to add
+ #printf "\r${green}NEW: %s ${norm}"
+ if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then
+ echo "$channel $id $lun" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null
+ udevadm_settle
+ else
+ echo "scsi add-single-device $devnr" > /proc/scsi/scsi
+ fi
+ testexist -q
+ if [ -n "$SCSISTR" ] ; then
+ lun0added=1
+ #testonline
+ else
+ # Device not present
+ # return
+ # Find alternative LUN to send getluns to
+ for dev in /sys/class/scsi_device/${host}:${channel}:${id}:*; do
+ [ -d "$dev" ] || continue
+ lun=${dev##*:}
+ break
+ done
+ fi
+ fi
+ targetluns=$(getluns)
+ REPLUNSTAT=$?
+ lunremove=
+ #echo "getluns reports " $targetluns
+ olddev=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" 2>/dev/null | sort -t: -k4 -n)
+ oldtargets="$targetluns"
+ # OK -- if we don't have a LUN to send a REPORT_LUNS to, we could
+ # fall back to wildcard scanning. Same thing if the device does not
+ # support REPORT_LUNS
+ # TODO: We might be better off to ALWAYS use wildcard scanning if
+ # it works
+ if [ "$REPLUNSTAT" = "1" ] ; then
+ if [ -e "/sys/class/scsi_host/host${host}/scan" ] ; then
+ echo "$channel $id -" > "/sys/class/scsi_host/host${host}/scan" 2> /dev/null
+ udevadm_settle
+ else
+ echo "scsi add-single-device $host $channel $id $SCAN_WILD_CARD" > /proc/scsi/scsi
+ fi
+ targetluns=$(find /sys/class/scsi_device/ -name "$host:$channel:$id:*" -printf "%f\n" | cut -d : -f 4)
+ let found+=$(echo "$targetluns" | wc -l)
+ let found-=$(echo "$olddev" | wc -l)
+ fi
+ [ -z "$targetluns" ] && targetluns="$oldtargets"
+ # Check existing luns
+ for dev in $olddev; do
+ [ -d "$dev" ] || continue
+ lun=${dev##*:}
+ newsearch=
+ inlist=
+ # OK, is existing $lun (still) in reported list
+ for tmplun in $targetluns; do
+ if [ "$tmplun" = "$lun" ] ; then
+ inlist=1
+ dolunscan $lun0added
+ [ $? -eq 1 ] && break
+ else
+ newsearch="$newsearch $tmplun"
+ fi
+ done
+ # OK, we have now done a lunscan on $lun and
+ # $newsearch is the old $targetluns without $lun
+ if [ -z "$inlist" ]; then
+ # Stale lun
+ lunremove="$lunremove $lun"
+ fi
+ # $lun removed from $lunsearch
+ targetluns=${newsearch# }
+ done
+ # Add new ones and check stale ones
+ for lun in $targetluns $lunremove; do
+ dolunscan $lun0added
+ [ $? -eq 1 ] && break
+ done
+}
+
+# Perform search (scan $host)
+dosearch ()
+{
+ if [ -z "$channelsearch" ] ; then
+ chanlist
+ fi
+ for channel in $channelsearch; do
+ if [ -z "$idsearch" ] ; then
+ if [ -z "$lunsearch" ] ; then
+ idlist
+ else
+ idsearch=$(find /sys/bus/scsi/devices -name "target${host}:${channel}:*" -printf "%f\n" | cut -f 3 -d :)
+ fi
+ fi
+ for id in $idsearch; do
+ if [ -z "$lunsearch" ] ; then
+ doreportlun
+ else
+ for lun in $lunsearch; do
+ dolunscan
+ [ $? -eq 1 ] && break
+ done
+ fi
+ done
+ done
+}
+
+expandlist ()
+{
+ list=$1
+ result=""
+ first=${list%%,*}
+ rest=${list#*,}
+ while [ ! -z "$first" ] ; do
+ beg=${first%%-*};
+ if [ "$beg" = "$first" ] ; then
+ result="$result $beg";
+ else
+ end=${first#*-}
+ result="$result $(seq -s ' ' $beg $end)"
+ fi
+ [ "$rest" = "$first" ] && rest=""
+ first=${rest%%,*}
+ rest=${rest#*,}
+ done
+ echo "$result"
+}
+
+searchexisting()
+{
+ local tmpch;
+ local tmpid
+ local match=0
+ local targets=
+
+ targets=$(find /sys/bus/scsi/devices -name "target${host}:*" -printf "%f\n" | cut -d : -f 2-3)
+ # Nothing came back on this host, so we should skip it
+ [ -z "$targets" ] && return
+
+ local target=;
+ for target in $targets ; do
+ channel=${target%:*}
+ id=${target#*:}
+ if [ -n "$channelsearch" ] ; then
+ for tmpch in $channelsearch ; do
+ [ $tmpch -eq "$channel" ] && match=1
+ done
+ else
+ match=1
+ fi
+
+ [ $match -eq 0 ] && continue
+ match=0
+
+ if [ "$filter_ids" -eq 1 ] ; then
+ for tmpid in $idsearch ; do
+ if [ "$tmpid" = "$id" ] ; then
+ match=1
+ fi
+ done
+ else
+ match=1
+ fi
+
+ [ $match -eq 0 ] && continue
+
+ if [ -z "$lunsearch" ] ; then
+ doreportlun
+ else
+ for lun in $lunsearch ; do
+ dolunscan
+ [ $? -eq 1 ] && break
+ done
+ fi
+ done
+}
+
+getallmultipathinfo()
+{
+ local mp=
+ local uuid=
+ local dmtmp=
+ local maj_min=
+ local tmpfile=
+
+ truncate -s 0 $TMPLUNINFOFILE
+ for mp in $($DMSETUP ls --target=multipath | cut -f 1) ; do
+ [ "$mp" = "No" ] && break;
+ maj_min=$($DMSETUP status "$mp" | cut -d " " -f14)
+ if [ ! -L /dev/mapper/${mp} ]; then
+ echo "softlink /dev/mapper/${mp} not available."
+ continue
+ fi
+ local ret=$(readlink /dev/mapper/$mp 2>/dev/null)
+ if [[ $? -ne 0 || -z "$ret" ]]; then
+ echo "readlink /dev/mapper/$mp failed. check multipath status."
+ continue
+ fi
+ dmtmp=$(basename $ret)
+ uuid=$(cut -f2 -d- "/sys/block/$dmtmp/dm/uuid")
+ echo "$mp $maj_min $dmtmp $uuid" >> $TMPLUNINFOFILE
+ done
+}
+
+# Go through all of the existing devices and figure out any that have been remapped
+findremapped()
+{
+ local hctl=;
+ local devs=
+ local sddev=
+ local id_serial=
+ local id_serial_old=
+ local remapped=
+ mpaths=""
+ local tmpfile=
+
+ tmpfile=$(mktemp /tmp/rescan-scsi-bus.XXXXXXXX 2> /dev/null)
+ if [ -z "$tmpfile" ] ; then
+ tmpfile="/tmp/rescan-scsi-bus.$$"
+ rm -f $tmpfile
+ fi
+
+ # Get all of the ID_SERIAL attributes, after finding their sd node
+ devs=$(ls /sys/class/scsi_device/)
+ for hctl in $devs ; do
+ if [ -d "/sys/class/scsi_device/$hctl/device/block" ] ; then
+ sddev=$(ls "/sys/class/scsi_device/$hctl/device/block")
+ id_serial_old=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2)
+ [ -z "$id_serial_old" ] && id_serial_old="none"
+ echo "$hctl $sddev $id_serial_old" >> $tmpfile
+ fi
+ done
+
+ # Trigger udev to update the info
+ echo -n "Triggering udev to update device information... "
+ /sbin/udevadm trigger
+ udevadm_settle 2>&1 /dev/null
+ echo "Done"
+
+ getallmultipathinfo
+
+ # See what changed and reload the respective multipath device if applicable
+ while read -r hctl sddev id_serial_old ; do
+ remapped=0
+ id_serial=$(udevadm info -q all -n "$sddev" | grep "ID_SERIAL=" | cut -d"=" -f2)
+ [ -z "$id_serial" ] && id_serial="none"
+ if [ "$id_serial_old" != "$id_serial" ] ; then
+ remapped=1
+ fi
+ # If udev events updated the disks already, but the multipath device isn't update
+ # check for old devices to make sure we found remapped luns
+ if [ -n "$mp_enable" ] && [ $remapped -eq 0 ]; then
+ findmultipath "$sddev" $id_serial
+ if [ $? -eq 1 ] ; then
+ remapped=1
+ fi
+ fi
+
+ # if uuid is 1, it's unmapped, so we don't want to treat it as a remap
+ # if remapped flag is 0, just skip the rest of the logic
+ if [ "$id_serial" = "1" ] || [ $remapped -eq 0 ] ; then
+ continue
+ fi
+ printf "${yellow}REMAPPED: %s ${norm}"
+ host=$(echo "$hctl" | cut -d":" -f1)
+ channel=$(echo "$hctl" | cut -d":" -f2)
+ id=$(echo "$hctl" | cut -d":" -f3)
+ lun=$(echo "$hctl" | cut -d":" -f4)
+ procscsiscsi
+ echo "$SCSISTR"
+ incrchgd "$hctl"
+ done < $tmpfile
+ rm -f $tmpfile
+
+ if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then
+ echo "Updating multipath device mappings"
+ flushmpaths
+ $MULTIPATH | grep "create:" 2> /dev/null
+ fi
+}
+
+incrfound()
+{
+ local hctl="$1"
+ if [ -n "$hctl" ] ; then
+ let found+=1
+ FOUNDDEVS="$FOUNDDEVS\t[$hctl]\n"
+ else
+ return
+ fi
+}
+
+incrchgd()
+{
+ local hctl="$1"
+ if [ -n "$hctl" ] ; then
+ if ! echo "$CHGDEVS" | grep -q "\[$hctl\]"; then
+ let updated+=1
+ CHGDEVS="$CHGDEVS\t[$hctl]\n"
+ fi
+ else
+ return
+ fi
+
+ if [ -n "$mp_enable" ] ; then
+ local sdev
+
+ sdev=$(findsddev "$hctl")
+ if [ -n "$sdev" ] ; then
+ findmultipath "$sdev"
+ fi
+ fi
+}
+
+incrrmvd()
+{
+ local hctl="$1"
+ if [ -n "$hctl" ] ; then
+ let rmvd+=1;
+ RMVDDEVS="$RMVDDEVS\t[$hctl]\n"
+ else
+ return
+ fi
+
+ if [ -n "$mp_enable" ] ; then
+ local sdev
+
+ sdev=$(findsddev "$hctl")
+ if [ -n "$sdev" ] ; then
+ findmultipath "$sdev"
+ fi
+ fi
+}
+
+findsddev()
+{
+ local hctl="$1"
+ local sddev=
+ local blkpath
+
+ blkpath="/sys/class/scsi_device/$hctl/device/block"
+ if [ -e "$blkpath" ] ; then
+ sddev=$(ls "$blkpath")
+ echo "$sddev"
+ fi
+}
+
+addmpathtolist()
+{
+ local mp="$1"
+ local mp2=
+
+ for mp2 in $mpaths ; do
+ # The multipath device is already in the list
+ if [ "$mp2" = "$mp" ] ; then
+ return
+ fi
+ done
+ mpaths="$mpaths $mp"
+}
+
+findmultipath()
+{
+ local dev="$1"
+ local find_mismatch="$2"
+ local mp=
+ local found_dup=0
+ local maj_min=
+
+ # Need a sdev, and executable multipath and dmsetup command here
+ if [ -z "$dev" ] || [ ! -x "$DMSETUP" ] || [ ! -x "$MULTIPATH" ] ; then
+ return 1
+ fi
+
+ maj_min=$(cat "/sys/block/$dev/dev")
+ mp=$(cat $TMPLUNINFOFILE | grep -w "$maj_min" | cut -d " " -f1)
+ if [ -n "$mp" ]; then
+ if [ -n "$find_mismatch" ] ; then
+ uuid=$(cat $TMPLUNINFOFILE | grep -w "$maj_min" | cut -d " " -f4)
+ if [ "$find_mismatch" != "$uuid" ] ; then
+ addmpathtolist "$mp"
+ found_dup=1
+ fi
+ else
+ # Normal mode: Find the first multipath with the sdev
+ # and add it to the list
+ addmpathtolist "$mp"
+ return
+ fi
+ fi
+
+ # Return 1 to signal that a duplicate was found to the calling function
+ if [ $found_dup -eq 1 ] ; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+reloadmpaths()
+{
+ local mpath
+ if [ ! -x "$MULTIPATH" ] ; then
+ echo "no -x multipath"
+ return
+ fi
+
+ # Pass 1 as the argument to reload all mpaths
+ if [ "$1" = "1" ] ; then
+ echo "Reloading all multipath devices"
+ $MULTIPATH -r > /dev/null 2>&1
+ return
+ fi
+
+ # Reload the multipath devices
+ for mpath in $mpaths ; do
+ echo -n "Reloading multipath device $mpath... "
+ if $MULTIPATH -r "$mpath" > /dev/null 2>&1 ; then
+ echo "Done"
+ else
+ echo "Fail"
+ fi
+ done
+}
+
+resizempaths()
+{
+ local mpath
+
+ for mpath in $mpaths ; do
+ echo -n "Resizing multipath map $mpath ..."
+ multipathd -k"resize map $mpath"
+ let updated+=1
+ done
+}
+
+flushmpaths()
+{
+ local mpath
+ local remove=""
+ local i
+ local flush_retries=5
+
+ if [ -n "$1" ] ; then
+ for mpath in $($DMSETUP ls --target=multipath | cut -f 1) ; do
+ [ "$mpath" = "No" ] && break
+ num=$($DMSETUP status "$mpath" | awk 'BEGIN{RS=" ";active=0}/[0-9]+:[0-9]+/{dev=1}/A/{if (dev == 1) active++; dev=0} END{ print active }')
+ if [ "$num" -eq 0 ] ; then
+ remove="$remove $mpath"
+ fi
+ done
+ else
+ remove="$mpaths"
+ fi
+
+ for mpath in $remove ; do
+ i=0
+ echo -n "Flushing multipath device $mpath... "
+ while [ $i -lt $flush_retries ] ; do
+ $DMSETUP message "$mpath" 0 fail_if_no_path > /dev/null 2>&1
+ if $MULTIPATH -f "$mpath" > /dev/null 2>&1 ; then
+ echo "Done ($i retries)"
+ break
+ elif [ $i -eq $flush_retries ] ; then
+ echo "Fail"
+ fi
+ sleep 0.02
+ let i=$i+1
+ done
+ done
+}
+
+
+# Find resized luns
+findresized()
+{
+ local devs=
+ local size=
+ local new_size=
+ local sysfs_path=
+ local sddev=
+ local i=
+ local m=
+ local mpathsize=
+ declare -a mpathsizes
+
+ if [ -z "$lunsearch" ] ; then
+ devs=$(ls /sys/class/scsi_device/)
+ else
+ for lun in $lunsearch ; do
+ devs="$devs $(cd /sys/class/scsi_device/ && ls -d *:${lun})"
+ done
+ fi
+
+ for hctl in $devs ; do
+ sysfs_path="/sys/class/scsi_device/$hctl/device"
+ if [ -d "$sysfs_path/block" ] ; then
+ sddev=$(ls "$sysfs_path/block")
+ size=$(cat "$sysfs_path/block/$sddev/size")
+
+ echo 1 > "$sysfs_path/rescan"
+ new_size=$(cat "$sysfs_path/block/$sddev/size")
+
+ if [ "$size" != "$new_size" ] && [ "$size" != "0" ] && [ "$new_size" != "0" ] ; then
+ printf "${yellow}RESIZED: %s ${norm}"
+ host=$(echo "$hctl" | cut -d":" -f1)
+ channel=$(echo "$hctl" | cut -d":" -f2)
+ id=$(echo "$hctl" | cut -d":" -f3)
+ lun=$(echo "$hctl" | cut -d":" -f4)
+
+ procscsiscsi
+ echo "$SCSISTR"
+ incrchgd "$hctl"
+ fi
+ fi
+ done
+
+ if [ -n "$mp_enable" ] && [ -n "$mpaths" ] ; then
+ i=0
+ for m in $mpaths ; do
+ mpathsizes[$i]="$($MULTIPATH -l "$m" | egrep -o [0-9]+.[0-9]+[KMGT])"
+ let i=$i+1
+ done
+ resizempaths
+ i=0
+ for m in $mpaths ; do
+ mpathsize="$($MULTIPATH -l "$m" | egrep -o [0-9\.]+[KMGT])"
+ echo "$m ${mpathsizes[$i]} => $mpathsize"
+ let i=$i+1
+ done
+ fi
+}
+
+FOUNDDEVS=""
+CHGDEVS=""
+RMVDDEVS=""
+
+# main
+if [ "@$1" = @--help ] || [ "@$1" = @-h ] || [ "@$1" = "@-?" ] ; then
+ echo "Usage: rescan-scsi-bus.sh [options] [host [host ...]]"
+ echo "Options:"
+ echo " -a scan all targets, not just currently existing [default: disabled]"
+ echo " -c enables scanning of channels 0 1 [default: 0 / all detected ones]"
+ echo " -d enable debug [default: 0]"
+ echo " -f flush failed multipath devices [default: disabled]"
+ echo " -h help: print this usage message then exit"
+ echo " -i issue a FibreChannel LIP reset [default: disabled]"
+ echo " -I SECS issue a FibreChannel LIP reset and wait for SECS seconds [default: disabled]"
+ echo " -l activates scanning for LUNs 0--7 [default: 0]"
+ echo " -L NUM activates scanning for LUNs 0--NUM [default: 0]"
+ echo " -m update multipath devices [default: disabled]"
+ echo " -r enables removing of devices [default: disabled]"
+ echo " -s look for resized disks and reload associated multipath devices, if applicable"
+ echo " -t SECS timeout for testing if device is online. Test is skipped if 0 [default: 30]"
+ echo " -u look for existing disks that have been remapped"
+ echo " -V print version date then exit"
+ echo " -w scan for target device IDs 0--15 [default: 0--7]"
+ echo "--alltargets: same as -a"
+ echo "--attachpq3: Tell kernel to attach sg to LUN 0 that reports PQ=3"
+ echo "--channels=LIST: Scan only channel(s) in LIST"
+ echo "--color: use coloured prefixes OLD/NEW/DEL"
+ echo "--flush: same as -f"
+ echo "--forceremove: Remove stale devices (DANGEROUS)"
+ echo "--forcerescan: Remove and readd existing devices (DANGEROUS)"
+ echo "--help: print this usage message then exit"
+ echo "--hosts=LIST: Scan only host(s) in LIST"
+ echo "--ids=LIST: Scan only target ID(s) in LIST"
+ echo "--ignore-rev: Ignore the revision change"
+ echo "--issue-lip: same as -i"
+ echo "--issue-lip-wait=SECS: same as -I"
+ echo "--largelun: Tell kernel to support LUNs > 7 even on SCSI2 devs"
+ echo "--luns=LIST: Scan only lun(s) in LIST"
+ echo "--multipath: same as -m"
+ echo "--no-lip-scan: don't scan FC Host with issue-lip"
+ echo "--nooptscan: don't stop looking for LUNs if 0 is not found"
+ echo "--remove: same as -r"
+ echo "--reportlun2: Tell kernel to try REPORT_LUN even on SCSI2 devices"
+ echo "--resize: same as -s"
+ echo "--sparselun: Tell kernel to support sparse LUN numbering"
+ echo "--sync/nosync: Issue a sync / no sync [default: sync if remove]"
+ echo "--timeout=SECS: same as -t"
+ echo "--update: same as -u"
+ echo "--version: same as -V"
+ echo "--wide: same as -w"
+ echo ""
+ echo "Host numbers may thus be specified either directly on cmd line (deprecated)"
+ echo "or with the --hosts=LIST parameter (recommended)."
+ echo "LIST: A[-B][,C[-D]]... is a comma separated list of single values and ranges"
+ echo "(No spaces allowed.)"
+ exit 0
+fi
+
+if [ "@$1" = @--version ] || [ "@$1" = @-V ] ; then
+ echo ${VERSION}
+ exit 0
+fi
+
+if [ ! -d /sys/class/scsi_host/ ] && [ ! -d /proc/scsi/ ] ; then
+ echo "Error: SCSI subsystem not active"
+ exit 1
+fi
+
+# Make sure sg is there
+modprobe sg >/dev/null 2>&1
+
+if [ -x /usr/bin/sg_inq ] ; then
+ sg_version=$(sg_inq -V 2>&1 | cut -d " " -f 3)
+ if [ -n "$sg_version" ] ; then
+ sg_ver_maj=${sg_version:0:1}
+ sg_version=${sg_version##?.}
+ let sg_version+=$((100 * sg_ver_maj))
+ fi
+ sg_version=${sg_version##0.}
+ #echo "\"$sg_version\""
+ if [ -z "$sg_version" ] || [ "$sg_version" -lt 70 ] ; then
+ sg_len_arg="-36"
+ else
+ sg_len_arg="--len=36"
+ fi
+else
+ echo "WARN: /usr/bin/sg_inq not present -- please install sg3_utils"
+ echo " or rescan-scsi-bus.sh might not fully work."
+fi
+
+# defaults
+unsetcolor
+debug=0
+lunsearch=
+opt_idsearch=$(seq -s ' ' 0 7)
+filter_ids=0
+opt_channelsearch=
+remove=
+updated=0
+update=0
+resize=0
+forceremove=
+optscan=1
+sync=1
+existing_targets=1
+mp_enable=
+lipreset=-1
+timeout=30
+declare -i scan_flags=0
+ignore_rev=0
+no_lip_scan=0
+
+# Scan options
+opt="$1"
+while [ ! -z "$opt" ] && [ -z "${opt##-*}" ] ; do
+ opt=${opt#-}
+ case "$opt" in
+ a) existing_targets=;; #Scan ALL targets when specified
+ c) opt_channelsearch="0 1" ;;
+ d) debug=1 ;;
+ f) flush=1 ;;
+ i) lipreset=0 ;;
+ I) shift; lipreset=$1 ;;
+ l) lunsearch=$(seq -s ' ' 0 7) ;;
+ L) lunsearch=$(seq -s ' ' 0 "$2"); shift ;;
+ m) mp_enable=1 ;;
+ r) remove=1 ;;
+ s) resize=1; mp_enable=1 ;;
+ t) timeout=$2; shift ;;
+ u) update=1 ;;
+ w) opt_idsearch=$(seq -s ' ' 0 15) ;;
+ -alltargets) existing_targets=;;
+ -attachpq3) scan_flags=$((scan_flags|0x1000000)) ;;
+ -channels=*) arg=${opt#-channels=};opt_channelsearch=$(expandlist "$arg") ;;
+ -color) setcolor ;;
+ -flush) flush=1 ;;
+ -forceremove) remove=1; forceremove=1 ;;
+ -forcerescan) remove=1; forcerescan=1 ;;
+ -hosts=*) arg=${opt#-hosts=}; hosts=$(expandlist "$arg") ;;
+ -ids=*) arg=${opt#-ids=}; opt_idsearch=$(expandlist "$arg") ; filter_ids=1;;
+ -ignore-rev) ignore_rev=1;;
+ -issue-lip) lipreset=0 ;;
+ -issue-lip-wait=*) lipreset=${opt#-issue-lip-wait=};;
+ -largelun) scan_flags=$((scan_flags|0x200)) ;;
+ -luns=*) arg=${opt#-luns=}; lunsearch=$(expandlist "$arg") ;;
+ -multipath) mp_enable=1 ;;
+ -no-lip-scan) no_lip_scan=1 ;;
+ -nooptscan) optscan=0 ;;
+ -nosync) sync=0 ;;
+ -remove) remove=1 ;;
+ -reportlun2) scan_flags=$((scan_flags|0x20000)) ;;
+ -resize) resize=1;;
+ -timeout=*) timeout=${opt#-timeout=};;
+ -sparselun) scan_flags=$((scan_flags|0x40)) ;;
+ -sync) sync=2 ;;
+ -update) update=1;;
+ -wide) opt_idsearch=$(seq -s ' ' 0 15) ;;
+ *) echo "Unknown option -$opt !" ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ -z "$hosts" ] ; then
+ if [ -d /sys/class/scsi_host ] ; then
+ findhosts_26
+ else
+ findhosts
+ fi
+fi
+
+if [ -d /sys/class/scsi_host ] && [ ! -w /sys/class/scsi_host ]; then
+ echo "You need to run scsi-rescan-bus.sh as root"
+ exit 2
+fi
+[ "$sync" = 1 ] && [ "$remove" = 1 ] && sync=2
+if [ "$sync" = 2 ] ; then
+ echo "Syncing file systems"
+ sync
+fi
+if [ -w /sys/module/scsi_mod/parameters/default_dev_flags ] && [ $scan_flags != 0 ] ; then
+ OLD_SCANFLAGS=$(cat /sys/module/scsi_mod/parameters/default_dev_flags)
+ NEW_SCANFLAGS=$((OLD_SCANFLAGS|scan_flags))
+ if [ "$OLD_SCANFLAGS" != "$NEW_SCANFLAGS" ] ; then
+ echo -n "Temporarily setting kernel scanning flags from "
+ printf "0x%08x to 0x%08x\n" "$OLD_SCANFLAGS" "$NEW_SCANFLAGS"
+ echo $NEW_SCANFLAGS > /sys/module/scsi_mod/parameters/default_dev_flags
+ else
+ unset OLD_SCANFLAGS
+ fi
+fi
+DMSETUP=$(which dmsetup)
+[ -z "$DMSETUP" ] && flush= && mp_enable=
+MULTIPATH=$(which multipath)
+[ -z "$MULTIPATH" ] && flush= && mp_enable=
+
+echo -n "Scanning SCSI subsystem for new devices"
+[ -z "$flush" ] || echo -n ", flush failed multipath devices,"
+[ -z "$remove" ] || echo -n " and remove devices that have disappeared"
+echo
+declare -i found=0
+declare -i updated=0
+declare -i rmvd=0
+
+if [ -n "$flush" ] ; then
+ if [ -x "$MULTIPATH" ] ; then
+ flushmpaths 1
+ fi
+fi
+
+# Update existing mappings
+if [ $update -eq 1 ] ; then
+ echo "Searching for remapped LUNs"
+ findremapped
+ # If you've changed the mapping, there's a chance it's a different size
+ mpaths=""
+ findresized
+# Search for resized LUNs
+elif [ $resize -eq 1 ] ; then
+ echo "Searching for resized LUNs"
+ findresized
+# Normal rescan mode
+else
+ for host in $hosts; do
+ echo -n "Scanning host $host "
+ if [ $no_lip_scan -eq 0 ] && [ -e "/sys/class/fc_host/host$host" ] ; then
+ # It's pointless to do a target scan on FC
+ issue_lip=/sys/class/fc_host/host$host/issue_lip
+ if [ -e "$issue_lip" ] && [ "$lipreset" -ge 0 ] ; then
+ echo 1 > "$issue_lip" 2> /dev/null;
+ udevadm_settle
+ [ "$lipreset" -gt 0 ] && sleep "$lipreset"
+ fi
+ channelsearch=
+ idsearch=
+ else
+ channelsearch=$opt_channelsearch
+ idsearch=$opt_idsearch
+ fi
+ [ -n "$channelsearch" ] && echo -n "channels $channelsearch "
+ echo -n "for "
+ if [ -n "$idsearch" ] ; then
+ echo -n " SCSI target IDs $idsearch"
+ else
+ echo -n " all SCSI target IDs"
+ fi
+ if [ -n "$lunsearch" ] ; then
+ echo ", LUNs $lunsearch"
+ else
+ echo ", all LUNs"
+ fi
+
+ if [ -n "$existing_targets" ] ; then
+ searchexisting
+ else
+ dosearch
+ fi
+ done
+ if [ -n "$OLD_SCANFLAGS" ] ; then
+ echo "$OLD_SCANFLAGS" > /sys/module/scsi_mod/parameters/default_dev_flags
+ fi
+fi
+
+let rmvd_found=$rmvd+$found
+if [ -n "$mp_enable" ] && [ $rmvd_found -gt 0 ] ; then
+ echo "Attempting to update multipath devices..."
+ if [ $rmvd -gt 0 ] ; then
+ udevadm_settle
+ echo "Removing multipath mappings for removed devices if all paths are now failed... "
+ flushmpaths 1
+ fi
+ if [ $found -gt 0 ] ; then
+ /sbin/udevadm trigger --sysname-match=sd*
+ udevadm_settle
+ if [ -x "$MULTIPATH" ] ; then
+ echo "Trying to discover new multipath mappings for newly discovered devices... "
+ $MULTIPATH | grep "create:" 2> /dev/null
+ fi
+ fi
+fi
+
+echo "$found new or changed device(s) found. "
+if [ ! -z "$FOUNDDEVS" ] ; then
+ echo -e "$FOUNDDEVS"
+fi
+echo "$updated remapped or resized device(s) found."
+if [ ! -z "$CHGDEVS" ] ; then
+ echo -e "$CHGDEVS"
+fi
+echo "$rmvd device(s) removed. "
+if [ ! -z "$RMVDDEVS" ] ; then
+ echo -e "$RMVDDEVS"
+fi
+
+# Local Variables:
+# sh-basic-offset: 2
+# End:
+
diff --git a/scripts/scsi-enable-target-scan.sh b/scripts/scsi-enable-target-scan.sh
new file mode 100755
index 00000000..63bb9afb
--- /dev/null
+++ b/scripts/scsi-enable-target-scan.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+
+MODPARM=/sys/module/scsi_mod/parameters
+if [ -w "$MODPARM/scan" ] ; then
+ scan_type=$(cat $MODPARM/scan)
+ if [ "$scan_type" = "manual" ] ; then
+ echo sync > $MODPARM/scan
+
+ for shost in /sys/class/scsi_host/host* ; do
+ echo '- - -' > ${shost}/scan
+ done
+ fi
+fi
diff --git a/scripts/scsi_logging_level b/scripts/scsi_logging_level
new file mode 100755
index 00000000..2fba2b7f
--- /dev/null
+++ b/scripts/scsi_logging_level
@@ -0,0 +1,268 @@
+#! /bin/bash
+###############################################################################
+# Conveniently create and set scsi logging level, show SCSI_LOG fields in human
+# readable form.
+#
+# (C) Copyright IBM Corp. 2006
+#
+# Modified by D. Gilbert to replace the use of sysctl [20080218]
+# Lat change: D. Gilbert 20150219
+###############################################################################
+
+
+REVISION="1.0"
+SCRIPTNAME="scsi_logging_level"
+
+declare -i LOG_ERROR=0
+declare -i LOG_TIMEOUT=0
+declare -i LOG_SCAN=0
+declare -i LOG_MLQUEUE=0
+declare -i LOG_MLCOMPLETE=0
+declare -i LOG_LLQUEUE=0
+declare -i LOG_LLCOMPLETE=0
+declare -i LOG_HLQUEUE=0
+declare -i LOG_HLCOMPLETE=0
+declare -i LOG_IOCTL=0
+
+declare -i LEVEL=0
+
+SET=0
+GET=0
+CREATE=0
+
+OPTS=$(getopt -o hvcgsa:E:T:S:I:M:L:H: --long \
+help,version,create,get,set,all:,error:,timeout:,scan:,ioctl:,\
+midlevel:,mlqueue:,mlcomplete:,lowlevel:,llqueue:,llcomplete:,\
+highlevel:,hlqueue:,hlcomplete: -n \'$SCRIPTNAME\' -- "$@")
+eval set -- "$OPTS"
+
+# print version info
+printversion()
+{
+ cat <<EOF
+%S390_TOOLS_VERSION% ($SCRIPTNAME $REVISION)
+(C) Copyright IBM Corp. 2006
+EOF
+}
+
+# print usage and help
+printhelp()
+{
+ cat <<EOF
+Usage: $SCRIPTNAME [OPTIONS]
+
+Create, get or set scsi logging level.
+
+Options:
+
+ -h, --help print this help
+ -v, --version print version information
+ -s, --set create and set logging level as specified on
+ command line
+ -g, --get get current logging level and display it
+ -c, --create create logging level as specified on command line
+ -a, --all specify value for all SCSI_LOG fields
+ -E, --error specify SCSI_LOG_ERROR
+ -T, --timeout specify SCSI_LOG_TIMEOUT
+ -S, --scan specify SCSI_LOG_SCAN
+ -M, --midlevel specify SCSI_LOG_MLQUEUE and SCSI_LOG_MLCOMPLETE
+ --mlqueue specify SCSI_LOG_MLQUEUE
+ --mlcomplete specify SCSI_LOG_MLCOMPLETE
+ -L, --lowlevel specify SCSI_LOG_LLQUEUE and SCSI_LOG_LLCOMPLETE
+ --llqueue specify SCSI_LOG_LLQUEUE
+ --llcomplete specify SCSI_LOG_LLCOMPLETE
+ -H, --highlevel specify SCSI_LOG_HLQUEUE and SCSI_LOG_HLCOMPLETE
+ --hlqueue specify SCSI_LOG_HLQUEUE
+ --hlcomplete specify SCSI_LOG_HLCOMPLETE
+ -I, --ioctl specify SCSI_LOG_IOCTL
+
+Exactly one of the options "-c", "-g" and "-s" has to be specified.
+Valid values for SCSI_LOG fields are integers from 0 to 7.
+
+Note: Several SCSI_LOG fields can be specified using several options.
+When multiple options specify same SCSI_LOG field the most specific
+option has precedence.
+
+Example: "scsi_logging_level --hlqueue 3 --highlevel 2 --all 1 -s" sets
+SCSI_LOG_HLQUEUE=3, SCSI_LOG_HLCOMPLETE=2 and assigns all other SCSI_LOG
+fields the value 1.
+EOF
+}
+
+check_level()
+{
+ num=$(($1))
+ if [ $num != "$1" ] ; then
+ invalid_cmdline "log level '$1' not a number"
+ elif [ $num -lt 0 ] || [ $num -gt 7 ] ; then
+ invalid_cmdline "log level '$1' out of range, expect '0' to '7'"
+ fi
+}
+
+# check cmd line arguments
+check_cmdline()
+{
+ while true ; do
+ case "$1" in
+ -a|--all) _ALL="$2"; check_level "$2"
+ shift 2;;
+ -c|--create) CREATE=1;
+ shift 1;;
+ -g|--get) GET=1
+ shift 1;;
+ -h|--help) printhelp
+ exit 0;;
+ -s|--set) SET=1
+ shift 1;;
+ -v|--version) printversion
+ exit 0;;
+ -E|--error) _ERROR="$2"; check_level "$2"
+ shift 2;;
+ -T|--timeout) _TIMEOUT="$2"; check_level "$2"
+ shift 2;;
+ -S|--scan) _SCAN="$2"; check_level "$2"
+ shift 2;;
+ -M|--midlevel) _ML="$2"; check_level "$2"
+ shift 2;;
+ --mlqueue) _MLQUEUE="$2"; check_level "$2"
+ shift 2;;
+ --mlcomplete) _MLCOMPLETE="$2"; check_level "$2"
+ shift 2;;
+ -L|--lowlevel) _LL="$2"; check_level "$2"
+ shift 2;;
+ --llqueue) _LLQUEUE="$2"; check_level "$2"
+ shift 2;;
+ --llcomplete) _LLCOMPLETE="$2"; check_level "$2"
+ shift 2;;
+ -H|--highlevel) _HL="$2"; check_level "$2"
+ shift 2;;
+ --hlqueue) _HLQUEUE="$2"; check_level "$2"
+ shift 2;;
+ --hlcomplete) _HLCOMPLETE="$2"; check_level "$2"
+ shift 2;;
+ -I|--ioctl) _IOCTL="$2"; check_level "$2"
+ shift 2;;
+ --) shift; break;;
+ *) echo "Internal error!" ; exit 1;;
+ esac
+ done
+
+ if [ -n "$*" ]
+ then
+ invalid_cmdline invalid parameter "$@"
+ fi
+
+ if [ $GET = "1" -a $SET = "1" ]
+ then
+ invalid_cmdline options \'-c\', \'-g\' and \'-s\' are mutual exclusive
+ elif [ $GET = "1" -a $CREATE = "1" ]
+ then
+ invalid_cmdline options \'-c\', \'-g\' and \'-s\' are mutual exclusive
+ elif [ $SET = "1" -a $CREATE = "1" ]
+ then
+ invalid_cmdline options \'-c\', \'-g\' and \'-s\' are mutual exclusive
+ fi
+
+ LOG_ERROR=${_ERROR:-${_ALL:-0}}
+ LOG_TIMEOUT=${_TIMEOUT:-${_ALL:-0}}
+ LOG_SCAN=${_SCAN:-${_ALL:-0}}
+ LOG_MLQUEUE=${_MLQUEUE:-${_ML:-${_ALL:-0}}}
+ LOG_MLCOMPLETE=${_MLCOMPLETE:-${_ML:-${_ALL:-0}}}
+ LOG_LLQUEUE=${_LLQUEUE:-${_LL:-${_ALL:-0}}}
+ LOG_LLCOMPLETE=${_LLCOMPLETE:-${_LL:-${_ALL:-0}}}
+ LOG_HLQUEUE=${_HLQUEUE:-${_HL:-${_ALL:-0}}}
+ LOG_HLCOMPLETE=${_HLCOMPLETE:-${_HL:-${_ALL:-0}}}
+ LOG_IOCTL=${_IOCTL:-${_ALL:-0}}
+}
+
+invalid_cmdline()
+{
+ echo "$SCRIPTNAME: $*"
+ echo "$SCRIPTNAME: Try '$SCRIPTNAME --help' for more information."
+ exit 1
+}
+
+get_logging_level()
+{
+ echo "Current scsi logging level:"
+# LEVEL=$(sysctl -n dev.scsi.logging_level)
+ LEVEL=$(cat /proc/sys/dev/scsi/logging_level)
+ if [ $? != 0 ]
+ then
+ echo "$SCRIPTNAME: could not read scsi logging level" \
+ "(kernel probably without SCSI_LOGGING support)"
+ exit 1
+ fi
+}
+
+show_logging_level()
+{
+ echo "/proc/sys/dev/scsi/logging_level = $LEVEL"
+
+ LOG_ERROR=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_TIMEOUT=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_SCAN=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_MLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_MLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_LLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_LLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3))
+ LOG_HLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3));
+ LOG_HLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3));
+ LOG_IOCTL=$((LEVEL & 7))
+
+ echo "SCSI_LOG_ERROR=$LOG_ERROR"
+ echo "SCSI_LOG_TIMEOUT=$LOG_TIMEOUT"
+ echo "SCSI_LOG_SCAN=$LOG_SCAN"
+ echo "SCSI_LOG_MLQUEUE=$LOG_MLQUEUE"
+ echo "SCSI_LOG_MLCOMPLETE=$LOG_MLCOMPLETE"
+ echo "SCSI_LOG_LLQUEUE=$LOG_LLQUEUE"
+ echo "SCSI_LOG_LLCOMPLETE=$LOG_LLCOMPLETE"
+ echo "SCSI_LOG_HLQUEUE=$LOG_HLQUEUE"
+ echo "SCSI_LOG_HLCOMPLETE=$LOG_HLCOMPLETE"
+ echo "SCSI_LOG_IOCTL=$LOG_IOCTL"
+}
+
+set_logging_level()
+{
+ echo "New scsi logging level:"
+# sysctl -q -w dev.scsi.logging_level=$LEVEL
+ echo $LEVEL > /proc/sys/dev/scsi/logging_level
+ if [ $? != 0 ]
+ then
+ echo "$SCRIPTNAME: could not write scsi logging level $LEVEL"
+ echo " kernel does not have SCSI_LOGGING support or needs superuser"
+ exit 1
+ fi
+}
+create_logging_level()
+{
+ LEVEL=$((LOG_IOCTL & 7)); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_HLCOMPLETE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_HLQUEUE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_LLCOMPLETE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_LLQUEUE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_MLCOMPLETE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_MLQUEUE & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_SCAN & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_TIMEOUT & 7))); LEVEL=$((LEVEL<<3))
+ LEVEL=$((LEVEL|(LOG_ERROR & 7)))
+}
+
+check_cmdline "$@"
+
+if [ $SET = "1" ]
+then
+ create_logging_level
+ set_logging_level
+ show_logging_level
+elif [ $GET = "1" ]
+then
+ get_logging_level
+ show_logging_level
+elif [ $CREATE = "1" ]
+then
+ create_logging_level
+ show_logging_level
+else
+ invalid_cmdline missing option \'-g\', \'-s\' or \'-c\'
+fi
diff --git a/scripts/scsi_mandat b/scripts/scsi_mandat
new file mode 100755
index 00000000..1f72b406
--- /dev/null
+++ b/scripts/scsi_mandat
@@ -0,0 +1,133 @@
+#!/bin/bash
+# scsi_mandat
+#
+# Script to test compliance with SCSI mandatory commands.
+# The vintage is SPC-3 and SPC-4 (see www.t10.org).
+#
+# Coverage:
+# Command Standard/Draft (is mandatory in)
+# -------------------------------------------------------
+# INQUIRY (standard) SCSI-2, SPC, SPC-2, SPC-3, SPC-4
+# INQUIRY (VPD pages 0, 0x83) SPC-2, SPC-3, SPC-4
+# REPORT LUNS SPC-3, SPC-4
+# TEST UNIT READY SCSI-2, SPC, SPC-2, SPC-3, SPC-4
+# REQUEST SENSE SCSI-2, SBC, SBC-2,3, MMC-4,5, SSC-2,3
+# SEND DIAGNOSTIC SBC, SBC-2,3, SSC-2,3
+#
+# This script uses utilities frim sg3_utils package (version
+# 1.21 or later)
+#
+# Douglas Gilbert 20131016
+
+
+log=0
+quiet=0
+verbose=""
+
+file_err=0
+inv_opcode=0
+illeg_req=0
+not_ready=0
+medium=0
+other_err=0
+recovered=0
+sanity=0
+syntax=0
+timeout=0
+unit_attention=0
+aborted_command=0
+
+## total_err=0
+
+usage()
+{
+ echo "Usage: scsi_mandat [-h] [-L] [-q] [-v] <device>"
+ echo " where: -h, --help print usage message"
+ echo " -L, --log append stderr to 'scsi_mandat.err'"
+ echo " -q, --quiet suppress some output"
+ echo " -v, --verbose increase verbosity of output"
+ echo ""
+ echo "Check <device> for mandatory SCSI command support"
+}
+
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ h|-help) usage ; exit 0 ;;
+ L|-log) let log=$log+1 ;;
+ q|-quiet) let quiet=$quiet+1 ;;
+ v|-verbose) verbose="-v" ;;
+ vv) verbose="-vv" ;;
+ vvv) verbose="-vvv" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for command in "sg_inq" "sg_luns" "sg_turs" "sg_requests" "sg_vpd" \
+ "sg_vpd -i" "sg_senddiag -t"
+do
+ if [ $quiet -eq 0 ]
+ then echo "$command" $verbose "$1"
+ fi
+
+ if [ $verbose ]
+ then
+ if [ $log -eq 0 ]
+ then
+ $command $verbose "$1"
+ else
+ $command $verbose "$1" >> scsi_mandat.err 2>> scsi_mandat.err
+ fi
+ else
+ if [ $log -eq 0 ]
+ then
+ $command "$1" > /dev/null 2>> /dev/null
+ else
+ $command "$1" > /dev/null 2>> scsi_mandat.err
+ fi
+ fi
+ res=$?
+ case "$res" in
+ 0) ;;
+ 1) echo " syntax error" ; let syntax=$syntax+1 ;;
+ 2) echo " not ready" ; let not_ready=$not_ready+1 ;;
+ 3) echo " medium error" ; let medium=$medium+1 ;;
+ 5) echo " illegal request, general" ; let illeg_req=$illeg_req+1 ;;
+ 6) echo " unit attention" ; let unit_attention=$unit_attention+1 ;;
+ 9) echo " illegal request, invalid opcode" ; let inv_opcode=$inv_opcode+1 ;;
+ 11) echo " aborted command" ; let aborted_command=$aborted_command+1 ;;
+ 15) echo " file error with $1 " ; let file_err=$file_err+1 ;;
+ 20) echo " no sense" ; let other_err=$other_err+1 ;;
+ 21) echo " recovered error" ; let recovered=$recovered+1 ;;
+ 33) echo " timeout" ; let timeout=$timeout+1 ;;
+ 97) echo " response fails sanity" ; let sanity=$sanity+1 ;;
+ 98) echo " other SCSI error" ; let other_err=$other_err+1 ;;
+ 99) echo " other error" ; let other_err=$other_err+1 ;;
+ *) echo " unknown exit status for sg_inq: $res" ; let other_err=$other_err+1 ;;
+ esac
+done
+
+echo ""
+let total_bad_err=$file_err+$inv_opcode+$illeg_req+$medium+$aborted_command
+let total_bad_err+=$other_err+$recovered+$sanity+$syntax+$timeout
+
+let total_allow_err=$not_ready+$unit_attention
+
+ echo "total number of bad errors: $total_bad_err "
+
+if [ $total_allow_err -gt 0 ]
+ then
+ echo "total number of allowable errors: $total_allow_err "
+fi
+
+exit $total_bad_err
diff --git a/scripts/scsi_readcap b/scripts/scsi_readcap
new file mode 100755
index 00000000..8f308f4f
--- /dev/null
+++ b/scripts/scsi_readcap
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+###################################################################
+#
+# Fetch READ CAPACITY information for the given SCSI device(s).
+#
+# This script assumes the sg3_utils package is installed.
+#
+##################################################################
+
+verbose=""
+brief=""
+long_opt=""
+
+usage()
+{
+ echo "Usage: scsi_readcap [-b] [-h] [-l] [-v] <device>+"
+ echo " where:"
+ echo " -b, --brief output brief capacity data"
+ echo " -h, --help print usage message"
+ echo " -l, --long send longer SCSI READ CAPACITY (16) cdb"
+ echo " -v, --verbose more verbose output"
+ echo ""
+ echo "Use SCSI READ CAPACITY command to fetch the size of each <device>"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ b|-brief) brief="-b" ;;
+ h|-help) usage ; exit 0 ;;
+ l|-long) long_opt="--16" ;;
+ v|-verbose) verbose="-v" ;;
+ vv) verbose="-vv" ;;
+ vvv) verbose="-vvv" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for i
+do
+ if [ $brief ] ; then
+ sg_readcap $brief $long_opt $verbose $i 2> /dev/null
+ else
+ echo "sg_readcap $brief $long_opt $verbose $i"
+ sg_readcap $brief $long_opt $verbose $i
+ fi
+done
diff --git a/scripts/scsi_ready b/scripts/scsi_ready
new file mode 100755
index 00000000..724c2c63
--- /dev/null
+++ b/scripts/scsi_ready
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+################################################
+#
+# Send a TEST UNIT READY SCSI command to each given device.
+#
+# This script assumes the sg3_utils package is installed and uses
+# the sg_turs utility..
+#
+###############################################
+
+verbose=""
+brief=""
+
+usage()
+{
+ echo "Usage: scsi_ready [-b] [-h] [-v] <device>+"
+ echo " where:"
+ echo " -b, --brief print 'ready' or 'device not ready' only"
+ echo " -h, --help print usage message"
+ echo " -v, --verbose more verbose output"
+ echo ""
+ echo "Send SCSI TEST UNIT READY to each <device>"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ b|-brief) brief="1" ;;
+ h|-help) usage ; exit 0 ;;
+ v|-verbose) verbose="-v" ;;
+ vv) verbose="-vv" ;;
+ vvv) verbose="-vvv" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for i
+do
+ if [ ! $brief ] ; then
+ echo "sg_turs $verbose $i"
+ fi
+ echo -n " "
+ if sg_turs $verbose $i ; then
+ echo "ready"
+ fi
+done
diff --git a/scripts/scsi_satl b/scripts/scsi_satl
new file mode 100755
index 00000000..08042ae3
--- /dev/null
+++ b/scripts/scsi_satl
@@ -0,0 +1,134 @@
+#!/bin/bash
+# scsi_satl
+#
+# Script to test compliance of SCSI commands on a SCSI to ATA
+# Translation (SAT) Layer (SATL). This script was compiled using
+# sat-r09.pdf found at www.t10.org .
+# The scripts still seems to be valid for sat2r09.pdf .
+# The vintage is SPC-3 and SPC-4 (see www.t10.org).
+#
+# Coverage:
+# Command SATL notes
+# -------------------------------------------------------
+# INQUIRY (standard)
+# INQUIRY (VPD: 0)
+# INQUIRY (VPD: 0x83) Device identification VPD page
+# INQUIRY (VPD: 0x89) ATA Information VPD page
+# REPORT LUNS SPC-3, SPC-4 (hardly mentioned in sat-r08c)
+# TEST UNIT READY
+# REQUEST SENSE
+# SEND DIAGNOSTIC default self test
+# MODE SENSE(10) draft unclear which mode pages, so ask for all
+# ATA PASS THROUGH(16) send IDENTIFY DEVICE command. Assume non-packet
+# device, if packet device add "-p" option
+#
+# This script uses utilities from sg3_utils package (version
+# 1.22 or later)
+#
+# Douglas Gilbert 20090930
+
+
+log=0
+quiet=0
+verbose=""
+
+file_err=0
+inv_opcode=0
+illeg_req=0
+not_ready=0
+medium=0
+other_err=0
+recovered=0
+sanity=0
+syntax=0
+timeout=0
+unit_attention=0
+aborted_command=0
+
+## total_err=0
+
+usage()
+{
+ echo "Usage: scsi_satl [-h] [-L] [-q] [-v] <device>"
+ echo " where: -h, --help print usage message"
+ echo " -L, --log append stderr to 'scsi_satl.err'"
+ echo " -q, --quiet suppress some output"
+ echo " -v, --verbose more verbose output"
+ echo ""
+ echo "Check <device> for SCSI to ATA Translation Layer (SATL) support"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ h|-help) usage ; exit 1 ;;
+ L|-log) let log=$log+1 ;;
+ q|-quiet) let quiet=$quiet+1 ;;
+ v|-verbose) verbose="-v" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for command in "sg_inq" "sg_vpd" "sg_vpd -p di" "sg_vpd -p ai" "sg_luns" \
+ "sg_turs" "sg_requests -s" "sg_senddiag -t" "sg_modes -a" \
+ "sg_sat_identify"
+do
+ if [ $quiet -eq 0 ]
+ then echo "$command" "$1"
+ fi
+
+ if [ $log -eq 0 ]
+ then
+ if [ $verbose ]
+ then
+ $command $verbose "$1" > /dev/null
+ else
+ $command "$1" > /dev/null 2>> /dev/null
+ fi
+ else
+ $command $verbose "$1" > /dev/null 2>> scsi_satl.err
+ fi
+ res=$?
+ case "$res" in
+ 0) ;;
+ 1) echo " syntax error" ; let syntax=$syntax+1 ;;
+ 2) echo " not ready" ; let not_ready=$not_ready+1 ;;
+ 3) echo " medium error" ; let medium=$medium+1 ;;
+ 5) echo " illegal request, general" ; let illeg_req=$illeg_req+1 ;;
+ 6) echo " unit attention" ; let unit_attention=$unit_attention+1 ;;
+ 9) echo " illegal request, invalid opcode" ; let inv_opcode=$inv_opcode+1 ;;
+ 11) echo " aborted command" ; let aborted_command=$aborted_command+1 ;;
+ 15) echo " file error with $1 " ; let file_err=$file_err+1 ;;
+ 20) echo " no sense" ; let other_err=$other_err+1 ;;
+ 21) echo " recovered error" ; let recovered=$recovered+1 ;;
+ 33) echo " timeout" ; let timeout=$timeout+1 ;;
+ 97) echo " response fails sanity" ; let sanity=$sanity+1 ;;
+ 98) echo " other SCSI error" ; let other_err=$other_err+1 ;;
+ 99) echo " other error" ; let other_err=$other_err+1 ;;
+ *) echo " unknown exit status for sg_inq: $res" ; let other_err=$other_err+1 ;;
+ esac
+done
+
+echo ""
+let total_bad_err=$file_err+$inv_opcode+$illeg_req+$medium+$aborted_command
+let total_bad_err+=$other_err+$recovered+$sanity+$syntax+$timeout
+
+let total_allow_err=$not_ready+$unit_attention
+
+ echo "total number of bad errors: $total_bad_err "
+
+if [ $total_allow_err -gt 0 ]
+ then
+ echo "total number of allowable errors: $total_allow_err "
+fi
+
+exit $total_bad_err
diff --git a/scripts/scsi_start b/scripts/scsi_start
new file mode 100755
index 00000000..aec7ab97
--- /dev/null
+++ b/scripts/scsi_start
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+################################################
+#
+# Spin up the given SCSI disk(s).
+#
+# SCSI disks (or disks that understand SCSI commands)
+# are assumed. By default, the immediate bit is set so the
+# command should return immediately. The disk however will
+# take 10 seconds or more to spin up. The '-w' option
+# causes each start to wait until the disk reports that it
+# has started.
+#
+# This script assumes the sg3_utils package is installed.
+#
+###############################################
+
+verbose=""
+immediate="-i"
+
+usage()
+{
+ echo "Usage: scsi_start [-h] [-v] [-w] <device>+"
+ echo " where:"
+ echo " -h, --help print usage message"
+ echo " -v, --verbose more verbose output"
+ echo " -w, --wait wait for each start to complete"
+ echo ""
+ echo "Send SCSI START STOP UNIT command to start each <device>"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ h|-help) usage ; exit 0 ;;
+ v|-verbose) verbose="-v" ;;
+ w|-wait) immediate="" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for i
+do
+ echo "sg_start $immediate 1 $verbose $i"
+ sg_start $immediate 1 $verbose $i
+done
diff --git a/scripts/scsi_stop b/scripts/scsi_stop
new file mode 100755
index 00000000..76807234
--- /dev/null
+++ b/scripts/scsi_stop
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+################################################
+#
+# Spin down the given SCS disk(s).
+#
+# SCSI disks (or disks that understand SCSI commands)
+# are assumed. By default, the immediate bit is set so the
+# command should return immediately. The disk however will
+# take 10 seconds or more to spin down. The '-w' option
+# causes each stop to wait until the disk reports that it
+# has stopped.
+#
+# This script assumes the sg3_utils package is installed.
+#
+###############################################
+
+verbose=""
+immediate="-i"
+
+usage()
+{
+ echo "Usage: scsi_stop [-h] [-v] [-w] <device>+"
+ echo " where:"
+ echo " -h, --help print usage message"
+ echo " -v, --verbose more verbose output"
+ echo " -w, --wait wait for each stop to complete"
+ echo ""
+ echo "Send SCSI START STOP UNIT command to stop each <device>"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ h|-help) usage ; exit 0 ;;
+ v|-verbose) verbose="-v" ;;
+ w|-wait) immediate="" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for i
+do
+# Use '-r' (read-only) otherwise using a block device node
+# (e.g. 'sg_start 0 /dev/sdb') can result in a change of state
+# event causing the disk to spin up again immediately.
+ echo "sg_start -r $immediate 0 $verbose $i"
+ sg_start -r $immediate 0 $verbose $i
+done
diff --git a/scripts/scsi_temperature b/scripts/scsi_temperature
new file mode 100755
index 00000000..f7d041cd
--- /dev/null
+++ b/scripts/scsi_temperature
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+###################################################################
+#
+# Check the temperature of the given SCSI device(s).
+#
+# This script assumes the sg3_utils package is installed.
+#
+##################################################################
+
+verbose=""
+
+usage()
+{
+ echo "Usage: scsi_temperature [-h] [-v] <device>+"
+ echo " where:"
+ echo " -h, --help print usage message"
+ echo " -v, --verbose more verbose output"
+ echo ""
+ echo "Use SCSI LOG SENSE command to fetch temperature of each <device>"
+}
+
+opt="$1"
+while test ! -z "$opt" -a -z "${opt##-*}"; do
+ opt=${opt#-}
+ case "$opt" in
+ h|-help) usage ; exit 0 ;;
+ v|-verbose) verbose="-v" ;;
+ vv) verbose="-vv" ;;
+ *) echo "Unknown option: -$opt " ; exit 1 ;;
+ esac
+ shift
+ opt="$1"
+done
+
+if [ $# -lt 1 ]
+ then
+ usage
+ exit 1
+fi
+
+for i
+do
+ echo "sg_logs -t $verbose $i"
+ sg_logs -t $verbose $i
+done
diff --git a/sg3_utils.man8.html b/sg3_utils.man8.html
new file mode 100644
index 00000000..579985d8
--- /dev/null
+++ b/sg3_utils.man8.html
@@ -0,0 +1,989 @@
+Content-type: text/html; charset=UTF-8
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML><HEAD><TITLE>Man page of SG3_UTILS</TITLE>
+</HEAD><BODY>
+<H1>SG3_UTILS</H1>
+Section: SG3_UTILS (8)<BR>Updated: November 2021<BR><A HREF="#index">Index</A>
+<A HREF="../index.html">Return to Main Contents</A><HR>
+
+<A NAME="lbAB">&nbsp;</A>
+<H2>NAME</H2>
+
+sg3_utils - a package of utilities for sending SCSI commands
+<A NAME="lbAC">&nbsp;</A>
+<H2>SYNOPSIS</H2>
+
+<B>sg_*</B>
+
+[<I>--dry-run</I>] [<I>--enumerate</I>] [<I>--help</I>] [<I>--hex</I>]
+[<I>--in=FN</I>] [<I>--inhex=FN</I>] [<I>--maxlen=LEN</I>] [<I>--raw</I>]
+[<I>--timeout=SECS</I>] [<I>--verbose</I>] [<I>--version</I>]
+[<I>OTHER_OPTIONS</I>] <I>DEVICE</I>
+<A NAME="lbAD">&nbsp;</A>
+<H2>DESCRIPTION</H2>
+
+
+<P>
+
+sg3_utils is a package of utilities that send SCSI commands to the given
+<I>DEVICE</I> via a SCSI pass through interface provided by the host
+operating system.
+<P>
+
+The names of all utilities start with &quot;sg&quot; and most start with &quot;sg_&quot; often
+followed by the name, or a shortening of the name, of the SCSI command that
+they send. For example the &quot;sg_verify&quot; utility sends the SCSI VERIFY
+command. A mapping between SCSI commands and the sg3_utils utilities that
+issue them is shown in the COVERAGE file. The sg_raw utility can be used to
+send an arbitrary SCSI command (supplied on the command line) to the
+given <I>DEVICE</I>.
+<P>
+
+sg_decode_sense can be used to decode SCSI sense data given on the command
+line or in a file. sg_raw -vvv will output the T10 name of a given SCSI
+CDB which is most often 16 bytes or less in length.
+<P>
+
+SCSI draft standards can be found at <A HREF="https://www.t10.org">https://www.t10.org</A> . The standards
+themselves can be purchased from ANSI and other standards organizations.
+A good overview of various SCSI standards can be seen in
+<A HREF="https://www.t10.org/scsi-3.htm">https://www.t10.org/scsi-3.htm</A> with the SCSI command sets in the upper part
+of the diagram. The highest level (i.e. most abstract) document is the SCSI
+Architecture Model (SAM) with SAM-5 being the most recent standard (ANSI
+INCITS 515-2016) with the most recent draft being SAM-6 revision 4 . SCSI
+commands in common with all device types can be found in SCSI Primary
+Commands (SPC) of which SPC-4 is the most recent standard (ANSI INCITS
+513-2015). The most recent SPC draft is SPC-5 revision 21. Block device
+specific commands (e.g. as used by disks) are in SBC, those for tape drives
+in SSC, those for SCSI enclosures in SES and those for CD/DVD/BD drives in
+MMC.
+<P>
+
+It is becoming more common to control ATA disks with the SCSI command set.
+This involves the translation of SCSI commands to their corresponding ATA
+equivalents (and that is an imperfect mapping in some cases). The relevant
+standard is called SCSI to ATA Translation (SAT, SAT-2 and SAT-3) are
+now standards at INCITS(ANSI) and ISO while SAT-4 is at the draft stage.
+The logic to perform the command translation is often called a SAT Layer or
+SATL and may be within an operating system, in host bus adapter firmware or
+in an external device (e.g. associated with a SAS expander). See
+<A HREF="https://www.t10.org">https://www.t10.org</A> for more information.
+<P>
+
+There is some support for SCSI tape devices but not for their basic
+operation. The reader is referred to the &quot;mt&quot; utility.
+<P>
+
+There are two generations of command line option usage. The newer
+utilities (written since July 2004) use the getopt_long() function to parse
+command line options. With that function, each option has two representations:
+a short form (e.g. '-v') and a longer form (e.g. '--verbose'). If an
+argument is required then it follows a space (optionally) in the short form
+and a &quot;=&quot; in the longer form (e.g. in the sg_verify utility '-l 2a6h'
+and '--lba=2a6h' are equivalent). Note that with getopt_long(), short form
+options can be elided, for example: '-all' is equivalent to '-a -l -l'.
+The <I>DEVICE</I> argument may appear after, between or prior to any options.
+<P>
+
+The older utilities, including as sg_inq, sg_logs, sg_modes, sg_opcode,
+sg_rbuff, sg_readcap, sg_senddiag, sg_start and sg_turs had individual
+command line processing code typically based on a single &quot;-&quot; followed by one
+or more characters. If an argument is needed then it follows a &quot;=&quot; (
+e.g. '-p=1f' in sg_modes with its older interface). Various options can be
+elided as long as it is not ambiguous (e.g. '-vv' to increase the verbosity).
+<P>
+
+Over time the command line interface of these older utilities became messy
+and overloaded with options. So in sg3_utils version 1.23 the command line
+interface of these older utilities was altered to have both a cleaner
+getopt_long() interface and their older interface for backward compatibility.
+By default these older utilities use their getopt_long() based interface.
+The getopt_long() is a GNU extension (i.e. not yet POSIX certified) but
+more recent command line utilities tend to use it. That can be overridden
+by defining the SG3_UTILS_OLD_OPTS environment variable or using '-O'
+or '--old' as the first command line option. The man pages of the older
+utilities documents the details.
+<P>
+
+Several sg3_utils utilities are based on the Unix dd command (e.g. sg_dd)
+and permit copying data at the level of SCSI READ and WRITE commands. sg_dd
+is tightly bound to Linux and hence is not ported to other OSes. A more
+generic utility (than sg_dd) called ddpt in a package of the same name has
+been ported to other OSes.
+<A NAME="lbAE">&nbsp;</A>
+<H2>ENVIRONMENT VARIABLES</H2>
+
+The SG3_UTILS_OLD_OPTS environment variable is explained in the previous
+section. It is only for backward compatibility of the command line options
+for older utilities.
+<P>
+
+The SG3_UTILS_DSENSE environment variable may be set to a number. It is
+only used by the embedded SNTL within the library used by the utilities in
+this library. SNTL is a SCSI to NVMe Translation Layer. This environment
+variable defaults to 0 which will lead to any utility that issues a SCSI
+command that is translated to a NVMe command (by the embedded SNTL) that
+fails at the NVMe dvice, to return SCSI sense in 'fixed' format. If this
+variable is non-zero then then the returned SCSI sense will be in 'descriptor'
+format.
+<P>
+
+Several utilities have their own environment variable setting (e.g.
+sg_persist has SG_PERSIST_IN_RDONLY). See individual utility man pages
+for more information.
+<P>
+
+There is a Linux specific environment variable called SG3_UTILS_LINUX_NANO
+that if defined and the sg driver in the system is 4.0.30 or later, will
+show command durations in nanoseconds rather than the default milliseconds.
+Command durations are typically only shown if --verbose is used 3 or more
+times. Due to an interface problem (a 32 bit integer that should be 64 bits
+with the benefit of hindsight) the maximum duration that can be represented
+in nanoseconds is about 4.2 seconds. If longer durations may occur then
+don't define this environment variable (or undefine it).
+<A NAME="lbAF">&nbsp;</A>
+<H2>LINUX DEVICE NAMING</H2>
+
+Most disk block devices have names like /dev/sda, /dev/sdb, /dev/sdc, etc.
+SCSI disks in Linux have always had names like that but in recent Linux
+kernels it has become more common for many other disks (including SATA
+disks and USB storage devices) to be named like that. Partitions within a
+disk are specified by a number appended to the device name, starting at
+1 (e.g. /dev/sda1 ).
+<P>
+
+Tape drives are named /dev/st&lt;num&gt; or /dev/nst&lt;num&gt; where &lt;num&gt; starts
+at zero. Additionally one letter from this list: &quot;lma&quot; may be appended to
+the name. CD, DVD and BD readers (and writers) are named /dev/sr&lt;num&gt;
+where &lt;num&gt; start at zero. There are less used SCSI device type names,
+the dmesg and the lsscsi commands may help to find if any are attached to
+a running system.
+<P>
+
+There is also a SCSI device driver which offers alternate generic access
+to SCSI devices. It uses names of the form /dev/sg&lt;num&gt; where &lt;num&gt; starts
+at zero. The &quot;lsscsi -g&quot; command may be useful in finding these and which
+generic name corresponds to a device type name (e.g. /dev/sg2 may
+correspond to /dev/sda). In the lk 2.6 series a block SCSI generic
+driver was introduced and its names are of the form
+/dev/bsg/&lt;h:c:t:l&gt; where h, c, t and l are numbers. Again see the lsscsi
+command to find the correspondence between that SCSI tuple (i.e. &lt;h:c:t:l&gt;)
+and alternate device names.
+<P>
+
+Prior to the Linux kernel 2.6 series these utilities could only use
+generic device names (e.g. /dev/sg1 ). In almost all cases in the Linux
+kernel 2.6 series, any device name can be used by these utilities.
+<P>
+
+Very little has changed in Linux device naming in the Linux kernel 3
+and 4 series.
+<A NAME="lbAG">&nbsp;</A>
+<H2>WINDOWS DEVICE NAMING</H2>
+
+Storage and related devices can have several device names in Windows.
+Probably the most common in the volume name (e.g. &quot;D:&quot;). There are also
+a &quot;class&quot; device names such as &quot;PhysicalDrive&lt;n&gt;&quot;, &quot;CDROM&lt;n&gt;&quot;
+and &quot;TAPE&lt;n&gt;&quot;. &lt;n&gt; is an integer starting at 0 allocated in ascending
+order as devices are discovered (and sometimes rediscovered).
+<P>
+
+Some storage devices have a SCSI lower level device name which starts
+with a SCSI (pseudo) adapter name of the form &quot;SCSI&lt;n&gt;:&quot;. To this is added
+sub-addressing in the form of a &quot;bus&quot; number, a &quot;target&quot; identifier and
+a LUN (Logical Unit Number). The &quot;bus&quot; number is also known as a &quot;PathId&quot;.
+These are assembled to form a device name of the
+form: &quot;SCSI&lt;n&gt;:&lt;bus&gt;,&lt;target&gt;,&lt;lun&gt;&quot;. The trailing &quot;,&lt;lun&gt;&quot; may be omitted
+in which case a LUN of zero is assumed. This lower level device name cannot
+often be used directly since Windows blocks attempts to use it if a class
+driver has &quot;claimed&quot; the device. There are SCSI device types (e.g.
+Automation/Drive interface type) for which there is no class driver. At
+least two transports (&quot;bus types&quot; in Windows jargon): USB and IEEE 1394 do
+not have a &quot;scsi&quot; device names of this form.
+<P>
+
+In keeping with DOS file system conventions, the various device names
+can be given in upper, lower or mixed case. Since &quot;PhysicalDrive&lt;n&gt;&quot; is
+tedious to write, a shortened form of &quot;PD&lt;n&gt;&quot; is permitted by all
+utilities in this package.
+<P>
+
+A single device (e.g. a disk) can have many device names. For
+example: &quot;PD0&quot; can also be &quot;C:&quot;, &quot;D:&quot; and &quot;SCSI0:0,1,0&quot;. The two volume names
+reflect that the disk has two partitions on it. Disk partitions that are
+not recognized by Windows are not usually given a volume name. However
+Vista does show a volume name for a disk which has no partitions recognized
+by it and when selected invites the user to format it (which may be rather
+unfriendly to other OSes).
+<P>
+
+These utilities assume a given device name is in the Win32 device namespace.
+To make that explicit &quot;\\.\&quot; can be prepended to the device names mentioned
+in this section. Beware that backslash is an escape character in Unix like
+shells and the C programming language. In a shell like Msys (from MinGW)
+each backslash may need to be typed twice.
+<P>
+
+The sg_scan utility within this package lists out Windows device names in
+a form that is suitable for other utilities in this package to use.
+<A NAME="lbAH">&nbsp;</A>
+<H2>FREEBSD DEVICE NAMING</H2>
+
+SCSI disks have block names of the form /dev/da&lt;num&gt; where &lt;num&gt; is an
+integer starting at zero. The &quot;da&quot; is replaced by &quot;sa&quot; for SCSI tape
+drives and &quot;cd&quot; for SCSI CD/DVD/BD drives. Each SCSI device has a
+corresponding pass-through device name of the form /dev/pass&lt;num&gt;
+where &lt;num&gt; is an integer starting at zero. The &quot;camcontrol devlist&quot;
+command may be useful for finding out which SCSI device names are
+available and the correspondence between class and pass-through names.
+<P>
+
+FreeBSD allows device names to be given without the leading &quot;/dev/&quot; (e.g.
+da0 instead of /dev/da0). That worked in this package up until version
+1.43 when the unadorned device name (e.g. &quot;da0&quot;) gave an error. The
+original action (i.e. allowing unadorned device names) has been restored
+in version 1.46 . Also note that symlinks (to device names) are followed
+before prepending &quot;/dev/&quot; if the resultant name doesn't start with a &quot;/&quot;.
+<P>
+
+FreeBSD's NVMe naming has been evolving. The controller naming is the
+same as Linux: &quot;/dev/nvme&lt;n&gt;&quot; but the namespaces have an
+extra &quot;s&quot; (e.g. &quot;/dev/nvme0ns1&quot;). The latter is not a block (GEOM)
+device (strictly speaking FreeBSD does not have block devices). Initially
+FreeBSD had &quot;/dev/nvd&lt;m&gt;&quot; GEOM devices that were not based on the CAM
+subsystem. Then in FreeBSD release 12 a new nda driver was added that is
+CAM (and GEOM) based for NVMe namespaces; it has names like &quot;/dev/nda0&quot;.
+The preferred device nodes for this package are &quot;/dev/nvme0&quot; for NVMe
+controllers and &quot;/dev/nda0&quot; for NVMe namespaces.
+<A NAME="lbAI">&nbsp;</A>
+<H2>SOLARIS DEVICE NAMING</H2>
+
+SCSI device names below the /dev directory have a form like: c5t4d3s2
+where the number following &quot;c&quot; is the controller (HBA) number, the number
+following &quot;t&quot; is the target number (from the SCSI parallel interface days)
+and the number following &quot;d&quot; is the LUN. Following the &quot;s&quot; is the slice
+number which is related to a partition and by convention &quot;s2&quot; is the whole
+disk.
+<P>
+
+OpenSolaris also has a c5t4d3p2 form where the number following the &quot;p&quot; is
+the partition number apart from &quot;p0&quot; which is the whole disk. So a whole
+disk may be referred to as either c5t4d3, c5t4d3s2 or c5t4d3p0 .
+<P>
+
+And these device names are duplicated in the /dev/dsk and /dev/rdsk
+directories. The former is the block device name and the latter is
+for &quot;raw&quot; (or char device) access which is what sg3_utils needs. So in
+OpenSolaris something of the form 'sg_inq /dev/rdsk/c5t4d3p0' should work.
+If it doesn't work then add a '-vvv' option for more debug information.
+Trying this form 'sg_inq /dev/dsk/c5t4d3p0' (note &quot;rdsk&quot; changed to &quot;dsk&quot;)
+will result in an &quot;inappropriate ioctl for device&quot; error.
+<P>
+
+The device names within the /dev directory are typically symbolic links to
+much longer topological names in the /device directory. In Solaris cd/dvd/bd
+drives seem to be treated as disks and so are found in the /dev/rdsk
+directory. Tape drives appear in the /dev/rmt directory.
+<P>
+
+There is also a sgen (SCSI generic) driver which by default does not attach
+to any device. See the /kernel/drv/sgen.conf file to control what is
+attached. Any attached device will have a device name of the
+form /dev/scsi/c5t4d3 .
+<P>
+
+Listing available SCSI devices in Solaris seems to be a challenge. &quot;Use
+the 'format' command&quot; advice works but seems a very dangerous way to list
+devices. [It does prompt again before doing any damage.] 'devfsadm -Cv'
+cleans out the clutter in the /dev/rdsk directory, only leaving what
+is &quot;live&quot;. The &quot;cfgadm -v&quot; command looks promising.
+<A NAME="lbAJ">&nbsp;</A>
+<H2>NVME SUPPORT</H2>
+
+NVMe (or NVM Express) is a relatively new storage transport and command
+set. The level of abstraction of the NVMe command set is somewhat lower
+the SCSI command sets, closer to the level of abstraction of ATA (and SATA)
+command sets. NVMe claims to be designed with flash and modern &quot;solid
+state&quot; storage in mind, something unheard of when SCSI was originally
+developed in the 1980s.
+<P>
+
+The SCSI command sets' advantage is the length of time they have been in
+place and the existing tools (like these) to support it. Plus SCSI command
+sets level of abstraction is both and advantage and disadvantage. Recently
+the NVME-MI (Management Interface) designers decide to use the SCSI
+Enclosure Services (SES-3) standard &quot;as is&quot; with the addition of two
+tunnelling NVME-MI commands: SES Send and SES Receive. This means after the
+OS interface differences are taken into account, the sg_ses, sg_ses_microcode
+and sg_senddiag utilities can be used on a NVMe device that supports a newer
+version of NVME-MI.
+<P>
+
+The NVME-MI SES Send and SES Receive commands correspond to the SCSI
+SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS commands respectively.
+There are however a few other commands that need to be translated, the
+most important of which is the SCSI INQUIRY command to the NVMe Identify
+controller/namespace. Starting in version 1.43 these utilities contain a
+small SNTL (SCSI to NVMe Translation Layer) to take care of these details.
+<P>
+
+As a side effect of this &quot;juggling&quot; if the sg_inq utility is used (without
+the --page= option) on a NVMe <I>DEVICE</I> then the actual NVMe
+Identifier (controller and possibly namespace) responses are decoded and
+output. However if 'sg_inq --page=sinq &lt;device&gt;' is given for the
+same <I>DEVICE</I> then parts of the NVMe Identify controller and namespace
+response are translated to a SCSI standard INQUIRY response which is then
+decoded and output.
+<P>
+
+Apart from the special case with the sg_inq, all other utilities in the
+package assume they are talking to a SCSI device and decode any response
+accordingly. One easy way for users to see the underlying device is a
+NVMe device is the standard INQUIRY response Vendor Identification field
+of &quot;NVMe &quot; (an 8 character long string with 4 spaces to the right).
+<P>
+
+The following SCSI commands are currently supported by the SNTL library:
+INQUIRY, MODE SELECT(10), MODE SENSE(10), READ(10,16), READ CAPACITY(10,16),
+RECEIVE DIAGNOSTIC RESULTS, REQUEST SENSE, REPORT LUNS, REPORT SUPPORTED
+OPERATION CODES, REPORT SUPPORTED TASK MANAGEMENT FUNCTIONS, SEND
+DIAGNOSTICS, START STOP UNIT, SYNCHRONIZE CACHE(10,16), TEST UNIT READY,
+VERIFY(10,16), WRITE(10,16) and WRITE SAME(10,16).
+<A NAME="lbAK">&nbsp;</A>
+<H2>EXIT STATUS</H2>
+
+To aid scripts that call these utilities, the exit status is set to indicate
+success (0) or failure (1 or more). Note that some of the lower values
+correspond to the SCSI sense key values.
+<P>
+
+The exit status values listed below can be given to the sg_decode_sense
+utility (which is found in this package) as follows:
+<BR>
+
+<BR>&nbsp;&nbsp;sg_decode_sense&nbsp;--err=&lt;exit_status&gt;
+<BR>
+
+and a short explanatory string will be output to stdout.
+<P>
+
+The exit status values are:
+<DL COMPACT>
+<DT><B>0</B>
+
+<DD>
+success. Also used for some utilities that wish to return a boolean value
+for the &quot;true&quot; case (and that no error has occurred). The false case is
+conveyed by exit status 36.
+<DT><B>1</B>
+
+<DD>
+syntax error. Either illegal command line options, options with bad
+arguments or a combination of options that is not permitted.
+<DT><B>2</B>
+
+<DD>
+the <I>DEVICE</I> reports that it is not ready for the operation requested.
+The <I>DEVICE</I> may be in the process of becoming ready (e.g. spinning up
+but not at speed) so the utility may work after a wait. In Linux the
+<I>DEVICE</I> may be temporarily blocked while error recovery is taking place.
+<DT><B>3</B>
+
+<DD>
+the <I>DEVICE</I> reports a medium or hardware error (or a blank check). For
+example an attempt to read a corrupted block on a disk will yield this value.
+<DT><B>5</B>
+
+<DD>
+the <I>DEVICE</I> reports an &quot;illegal request&quot; with an additional sense code
+other than &quot;invalid command operation code&quot;. This is often a supported
+command with a field set requesting an unsupported capability. For commands
+that require a &quot;service action&quot; field this value can indicate that the
+command with that service action value is not supported.
+<DT><B>6</B>
+
+<DD>
+the <I>DEVICE</I> reports a &quot;unit attention&quot; condition. This usually indicates
+that something unrelated to the requested command has occurred (e.g. a device
+reset) potentially before the current SCSI command was sent. The requested
+command has not been executed by the device. Note that unit attention
+conditions are usually only reported once by a device.
+<DT><B>7</B>
+
+<DD>
+the <I>DEVICE</I> reports a &quot;data protect&quot; sense key. This implies some
+mechanism has blocked writes (or possibly all access to the media).
+<DT><B>9</B>
+
+<DD>
+the <I>DEVICE</I> reports an illegal request with an additional sense code
+of &quot;invalid command operation code&quot; which means that it doesn't support the
+requested command.
+<DT><B>10</B>
+
+<DD>
+the <I>DEVICE</I> reports a &quot;copy aborted&quot;. This implies another command or
+device problem has stopped a copy operation. The EXTENDED COPY family of
+commands (including WRITE USING TOKEN) may return this sense key.
+<DT><B>11</B>
+
+<DD>
+the <I>DEVICE</I> reports an aborted command. In some cases aborted
+commands can be retried immediately (e.g. if the transport aborted
+the command due to congestion).
+<DT><B>14</B>
+
+<DD>
+the <I>DEVICE</I> reports a miscompare sense key. VERIFY and COMPARE AND
+WRITE commands may report this.
+<DT><B>15</B>
+
+<DD>
+the utility is unable to open, close or use the given <I>DEVICE</I> or some
+other file. The given file name could be incorrect or there may be
+permission problems. Adding the '-v' option may give more information.
+<DT><B>17</B>
+
+<DD>
+a SCSI &quot;Illegal request&quot; sense code received with a flag indicating the
+Info field is valid. This is often a LBA but its meaning is command specific.
+<DT><B>18</B>
+
+<DD>
+the <I>DEVICE</I> reports a medium or hardware error (or a blank check)
+with a flag indicating the Info field is valid. This is often a LBA (of
+the first encountered error) but its meaning is command specific.
+<DT><B>20</B>
+
+<DD>
+the <I>DEVICE</I> reports it has a check condition but &quot;no sense&quot;
+and non-zero information in its additional sense codes. Some polling
+commands (e.g. REQUEST SENSE) can receive this response. There may
+be useful information in the sense data such as a progress indication.
+<DT><B>21</B>
+
+<DD>
+the <I>DEVICE</I> reports a &quot;recovered error&quot;. The requested command
+was successful. Most likely a utility will report a recovered error
+to stderr and continue, probably leaving the utility with an exit
+status of 0 .
+<DT><B>22</B>
+
+<DD>
+the <I>DEVICE</I> reports that the current command or its parameters imply
+a logical block address (LBA) that is out of range. This happens surprisingly
+often when trying to access the last block on a storage device; either a
+classic &quot;off by one&quot; logic error or a misreading of the response from READ
+CAPACITY(10 or 16) in which the address of the last block rather than the
+number of blocks on the <I>DEVICE</I> is returned. Since LBAs are origin zero
+they range from 0 to n-1 where n is the number of blocks on the <I>DEVICE</I>,
+so the LBA of the last block is one less than the total number of blocks.
+<DT><B>24</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;reservation conflict&quot;. This
+means access to the <I>DEVICE</I> with the current command has been blocked
+because another machine (HBA or SCSI &quot;initiator&quot;) holds a reservation on
+this <I>DEVICE</I>. On modern SCSI systems this is related to the use of
+the PERSISTENT RESERVATION family of commands.
+<DT><B>25</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;condition met&quot;. Currently only
+the PRE-FETCH command (see SBC-4) yields this status.
+<DT><B>26</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;busy&quot;. SAM-6 defines this status
+as the logical unit is temporarily unable to process a command. It is
+recommended to re-issue the command.
+<DT><B>27</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;task set full&quot;.
+<DT><B>28</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;ACA active&quot;. ACA is &quot;auto
+contingent allegiance&quot; and is seldom used.
+<DT><B>29</B>
+
+<DD>
+the <I>DEVICE</I> reports a SCSI status of &quot;task aborted&quot;. SAM-5 says:
+&quot;This status shall be returned if a command is aborted by a command or task
+management function on another I_T nexus and the Control mode page TAS bit
+is set to one&quot;.
+<DT><B>31</B>
+
+<DD>
+error involving two or more command line options. They may be contradicting,
+select an unsupported mode, or a required option (given the context) is
+missing.
+<DT><B>32</B>
+
+<DD>
+there is a logic error in the utility. It corresponds to code comments
+like &quot;shouldn't/can't get here&quot;. Perhaps the author should be informed.
+<DT><B>33</B>
+
+<DD>
+the command sent to <I>DEVICE</I> has timed out.
+<DT><B>34</B>
+
+<DD>
+this is a Windows only exit status and indicates that the Windows error
+number (32 bits) cannot meaningfully be mapped to an equivalent Unix error
+number returned as the exit status (7 bits).
+<DT><B>35</B>
+
+<DD>
+a transport error has occurred. This will either be in the driver (e.g. HBA
+driver) or in the interconnect between the host (initiator) and the
+device (target). For example in SAS an expander can run out of paths and
+thus be unable to return the user data for a READ command.
+<DT><B>36</B>
+
+<DD>
+no error has occurred plus the utility wants to convey a boolean value
+of false. The corresponding true value is conveyed by a 0 exit status.
+<DT><B>40</B>
+
+<DD>
+the command sent to <I>DEVICE</I> has received an &quot;aborted command&quot; sense
+key with an additional sense code of 0x10. This value is related to
+problems with protection information (PI or DIF). For example this error
+may occur when reading a block on a drive that has never been written (or
+is unmapped) if that drive was formatted with type 1, 2 or 3 protection.
+<DT><B>41</B>
+
+<DD>
+the command sent to <I>DEVICE</I> has received an &quot;aborted command&quot; sense
+key with an additional sense code of 0x10 (as with error code) plus a flag
+indicating the Info field is valid.
+<DT><B>48</B>
+
+<DD>
+this is an internal message indicating a NVMe status field (SF) is other
+than zero after a command has been executed (i.e. something went wrong).
+Work in this area is currently experimental.
+<DT><B>49</B>
+
+<DD>
+low level driver reports a response's residual count (i.e. number of bytes
+actually received by HBA is 'requested_bytes - residual_count') that is
+nonsensical.
+<DT><B>50</B>
+
+<DD>
+OS system calls that fail often return a small integer number to help. In
+Unix these are called &quot;errno&quot; values where 0 implies no error. These error
+codes set aside 51 to 96 for mapping these errno values but that may not be
+sufficient. Higher errno values that cannot be mapped are all mapped to
+this value (i.e. 50).
+<BR>
+
+Note that an errno value of 0 is mapped to error code 0.
+<DT><B>50 + &lt;os_error_number&gt;</B>
+
+<DD>
+OS system calls that fail often return a small integer number to help
+indicate what the error is. For example in Unix the inability of a system
+call to allocate memory returns (in 'errno') ENOMEM which often is
+associated with the integer 12. So 62 (i.e. '50 + 12') may be returned
+by a utility in this case. It is also possible that a utility in this
+package reports 50+ENOMEM when it can't allocate memory, not necessarily
+from an OS system call. In recent versions of Linux the file showing the
+mapping between symbolic constants (e.g. ENOMEM) and the corresponding
+integer is in the kernel source code file:
+include/uapi/asm-generic/errno-base.h
+<BR>
+
+Note that errno values that are greater than or equal to 47 cannot fit in
+range provided. Instead they are all mapped to 50 as discussed in the
+previous entry.
+<DT><B>97</B>
+
+<DD>
+a SCSI command response failed sanity checks.
+<DT><B>98</B>
+
+<DD>
+the <I>DEVICE</I> reports it has a check condition but the error
+doesn't fit into any of the above categories.
+<DT><B>99</B>
+
+<DD>
+any errors that can't be categorized into values 1 to 98 may yield
+this value. This includes transport and operating system errors
+after the command has been sent to the device.
+<DT><B>100-125</B>
+
+<DD>
+these error codes are used by the ddpt utility which uses the sg3_utils
+library. They are mainly specialized error codes associated with offloaded
+copies.
+<DT><B>126</B>
+
+<DD>
+the utility was found but could not be executed. That might occur if the
+executable does not have execute permissions.
+<DT><B>127</B>
+
+<DD>
+This is the exit status for utility not found. That might occur when a
+script calls a utility in this package but the PATH environment variable
+has not been properly set up, so the script cannot find the executable.
+<DT><B>128 + &lt;signum&gt;</B>
+
+<DD>
+If a signal kills a utility then the exit status is 128 plus the signal
+number. For example if a segmentation fault occurs then a utility is
+typically killed by SIGSEGV which according to 'man 7 signal' has an
+associated signal number of 11; so the exit status will be 139 .
+<DT><B>255</B>
+
+<DD>
+the utility tried to yield an exit status of 255 or larger. That should
+not happen; given here for completeness.
+</DL>
+<P>
+
+Most of the error conditions reported above will be repeatable (an example
+of one that is not is &quot;unit attention&quot;) so the utility can be run again with
+the '-v' option (or several) to obtain more information.
+<A NAME="lbAL">&nbsp;</A>
+<H2>COMMON OPTIONS</H2>
+
+Arguments to long options are mandatory for short options as well. In the
+short form an argument to an option uses zero or more spaces as a
+separator (i.e. the short form does not use &quot;=&quot; as a separator).
+<P>
+
+If an option takes a numeric argument then that argument is assumed to
+be decimal unless otherwise indicated (e.g. with a leading &quot;0x&quot;, a
+trailing &quot;h&quot; or as noted in the usage message).
+<P>
+
+Some options are used uniformly in most of the utilities in this
+package. Those options are listed below. Note that there are some
+exceptions.
+<DL COMPACT>
+<DT><B>-d</B>, <B>--dry-run</B><DD>
+utilities that can cause lots of user data to be lost or overwritten
+sometimes have a <I>--dry-run</I> option. Device modifying actions are
+typically bypassed (or skipped) to implement a policy of &quot;do no harm&quot;.
+This allows complex command line invocations to be tested before the
+action required (e.g. format a disk) is performed. The <I>--dry-run</I>
+option has become a common feature of many command line utilities (e.g.
+the Unix 'patch' command), not just those from this package.
+<BR>
+
+Note that most hyphenated option names in this package also can be given
+with an underscore rather than a hyphen (e.g. <I>--dry_run</I>).
+<DT><B>-e</B>, <B>--enumerate</B><DD>
+some utilities (e.g. sg_ses and sg_vpd) store a lot of information in
+internal tables. This option will output that information in some readable
+form (e.g. sorted by an acronym or by page number) then exit. Note that
+with this option <I>DEVICE</I> is ignored (as are most other options) and no
+SCSI IO takes place, so the invoker does not need any elevated permissions.
+<DT><B>-h</B>, <B>-?</B>, <B>--help</B><DD>
+output the usage message then exit. In a few older utilities the '-h'
+option requests hexadecimal output. In these cases the '-?' option will
+output the usage message then exit.
+<DT><B>-H</B>, <B>--hex</B><DD>
+for SCSI commands that yield a non-trivial response, print out that
+response in ASCII hexadecimal. To produce hexadecimal that can be parsed
+by other utilities (e.g. without a relative address to the left and without
+trailing ASCII) use this option three or four times.
+<DT><B>-i</B>, <B>--in</B>=<I>FN</I><DD>
+many SCSI commands fetch a significant amount of data (returned in the
+data-in buffer) which several of these utilities decode (e.g. sg_vpd and
+sg_logs). To separate the two steps of fetching the data from a SCSI device
+and then decoding it, this option has been added. The first step (fetching
+the data) can be done using the <I>--hex</I> or <I>--raw</I> option and
+redirecting the command line output to a file (often done with &quot;&gt;&quot; in Unix
+based operating systems). The difference between <I>--hex</I> and
+<I>--raw</I> is that the former produces output in ASCII hexadecimal
+while <I>--raw</I> produces its output in &quot;raw&quot; binary.
+<BR>
+
+The second step (i.e. decoding the SCSI response data now held in a file)
+can be done using this <I>--in=FN</I> option where the file name is
+<I>FN</I>. If &quot;-&quot; is used for <I>FN</I> then stdin is assumed, again this
+allows for command line redirection (or piping). That file (or stdin)
+is assumed to contain ASCII hexadecimal unless the <I>--raw</I> option is
+also given in which case it is assumed to be binary. Notice that the meaning
+of the <I>--raw</I> option is &quot;flipped&quot; when used with <I>--in=FN</I> to
+act on the input, typically it acts on the output data.
+<BR>
+
+Since the structure of the data returned by SCSI commands varies
+considerably then the usage information or the manpage of the utility being
+used should be checked. In some cases <I>--hex</I> may need to be used
+multiple times (and is more conveniently given as '-HH' or '-HHH).
+<DT><B>-i</B>, <B>--inhex</B>=<I>FN</I><DD>
+This option has the same or similar functionality as <I>--in=FN</I>. And
+perhaps 'inhex' is more descriptive since by default, ASCII hexadecimal is
+expected in the contents of file: <I>FN</I>. Alternatively the short form
+option may be <I>-I</I> or <I>-X</I>. See the &quot;FORMAT OF FILES CONTAINING
+ASCII HEX&quot; section below for more information.
+<DT><B>-m</B>, <B>--maxlen</B>=<I>LEN</I><DD>
+several important SCSI commands (e.g. INQUIRY and MODE SENSE) have response
+lengths that vary depending on many factors, only some of which these
+utilities take into account. The maximum response length is typically
+specified in the 'allocation length' field of the cdb. In the absence of
+this option, several utilities use a default allocation length (sometimes
+recommended in the SCSI draft standards) or a &quot;double fetch&quot; strategy.
+See <A HREF="../man8/sg_logs.8.html">sg_logs</A>(8) for its description of a &quot;double fetch&quot; strategy. These
+techniques are imperfect and in the presence of faulty SCSI targets can
+cause problems (e.g. some USB mass storage devices freeze if they receive
+an INQUIRY allocation length other than 36). Also use of this option
+disables any &quot;double fetch&quot; strategy that may have otherwise been used.
+<BR>
+
+To head off a class of degenerate bugs, if <I>LEN</I> is less than 16 then
+it is ignored (usually with a warning message) and the default value is
+used instead. Some utilities use 4 (bytes), rather than 16, as the cutoff
+value.
+<DT><B>-r</B>, <B>--raw</B><DD>
+for SCSI commands that yield a non-trivial response, output that response
+in binary to stdout. If any error messages or warning are produced they are
+usually sent to stderr so as to not interfere with the output from this
+option.
+<BR>
+
+Some utilities that consume data to send to the <I>DEVICE</I> along with the
+SCSI command, use this option. Alternatively the <I>--in=FN</I> option causes
+<I>DEVICE</I> to be ignored and the response data (to be decoded) fetched
+from a file named <I>FN</I>. In these cases this option may indicate that
+binary data can be read from stdin or from a nominated file (e.g. <I>FN</I>).
+<DT><B>-t</B>, <B>--timeout</B>=<I>SECS</I><DD>
+utilities that issue potentially long-running SCSI commands often have a
+<I>--timeout=SECS</I> option. This typically instructs the operating system
+to abort the SCSI command in question once the timeout expires. Aborting
+SCSI commands is typically a messy business and in the case of format like
+commands may leave the device in a &quot;format corrupt&quot; state requiring another
+long-running re-initialization command to be sent. The argument, <I>SECS</I>,
+is usually in seconds and the short form of the option may be something
+other than <I>-t</I> since the timeout option was typically added later as
+storage devices grew in size and initialization commands took longer. Since
+many utilities had relatively long internal command timeouts before this
+option was introduced, the actual command timeout given to the operating
+systems is the higher of the internal timeout and <I>SECS</I>.
+<BR>
+
+Many long running SCSI commands have an IMMED bit which causes the command
+to finish relatively quickly but the initialization process to continue. In
+such cases the REQUEST SENSE command can be used to monitor progress with
+its progress indication field (see the sg_requests and sg_turs utilities).
+Utilities that send such SCSI command either have an <I>--immed</I> option
+or a <I>--wait</I> option which is the logical inverse of the &quot;immediate&quot;
+action.
+<DT><B>-v</B>, <B>--verbose</B><DD>
+increase the level of verbosity, (i.e. debug output). Can be used multiple
+times to further increase verbosity. The additional output caused by this
+option is almost always sent to stderr.
+<DT><B>-V</B>, <B>--version</B><DD>
+print the version string and then exit. Each utility has its own version
+number and date of last code change.
+</DL>
+<A NAME="lbAM">&nbsp;</A>
+<H2>NUMERIC ARGUMENTS</H2>
+
+Many utilities have command line options that take numeric arguments. These
+numeric arguments can be large values (e.g. a logical block address (LBA) on
+a disk) and can be inconvenient to enter in the default decimal
+representation. So various other representations are permitted.
+<P>
+
+Multiplicative suffixes are accepted. They are one, two or three letter
+strings appended directly after the number to which they apply:
+<P>
+
+<BR>&nbsp;&nbsp;&nbsp;c&nbsp;C&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;w&nbsp;W&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*2
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;b&nbsp;B&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*512
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;k&nbsp;K&nbsp;KiB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1024
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;KB&nbsp;kB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1000
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;m&nbsp;M&nbsp;MiB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1048576
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;MB&nbsp;mB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1000000
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;g&nbsp;G&nbsp;GiB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(2^30)
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;GB&nbsp;gB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(10^9)
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;t&nbsp;T&nbsp;TiB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(2^40)
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;TB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(10^12)
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;p&nbsp;P&nbsp;PiB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(2^50)
+<BR>
+
+<BR>&nbsp;&nbsp;&nbsp;PB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*(10^15)
+<P>
+
+An example is &quot;2k&quot; for 2048. The large tera and peta suffixes are only
+available for numeric arguments that might require 64 bits to represent
+internally.
+<P>
+
+These multiplicative suffixes are compatible with GNU's dd command (since
+2002) which claims compliance with SI and with IEC 60027-2.
+<P>
+
+A suffix of the form &quot;x&lt;n&gt;&quot; multiplies the preceding number by &lt;n&gt;. An
+example is &quot;2x33&quot; for &quot;66&quot;. The left argument cannot be '0' as '0x' will
+be interpreted as hexadecimal number prefix (see below). The left
+argument to the multiplication must end in a hexadecimal digit (i.e.
+0 to f) and the whole expression cannot have any embedded whitespace (e.g.
+spaces). An ugly example: &quot;0xfx0x2&quot; for 30.
+<P>
+
+A suffix of the form &quot;+&lt;n&gt;&quot; adds the preceding number to &lt;n&gt;. An example
+is &quot;3+1k&quot; for &quot;1027&quot;. The left argument to the addition must end in a
+hexadecimal digit (i.e. 0 to f) and the whole expression cannot have any
+embedded whitespace (e.g. spaces). Another example: &quot;0xf+0x2&quot; for 17.
+<P>
+
+Alternatively numerical arguments can be given in hexadecimal. There are
+two syntaxes. The number can be preceded by either &quot;0x&quot; or &quot;0X&quot; as found
+in the C programming language. The second hexadecimal representation is a
+trailing &quot;h&quot; or &quot;H&quot; as found in (storage) standards. When hex numbers are
+given, multipliers cannot be used. For example the decimal value &quot;256&quot; can
+be given as &quot;0x100&quot; or &quot;100h&quot;.
+<A NAME="lbAN">&nbsp;</A>
+<H2>FORMAT OF FILES CONTAINING ASCII HEX</H2>
+
+Such a file is assumed to contain a sequence of one or two digit ASCII
+hexadecimal values separated by whitespace. &quot;Whitespace consists of either
+spaces, tabs, blank lines, or any combination thereof&quot;. Each one or two digit
+ASCII hex pair is decoded into a byte (i.e. 8 bits). The following will be
+decoded to valid (ascending valued)
+bytes: '0', '01', '3', 'c', 'F', '4a', 'cC', 'ff'.
+Lines containing only whitespace are ignored. The contents of any line
+containing a hash mark ('#') is ignored from that point until the end of that
+line. Users are encouraged to use hash marks to introduce comments in hex
+files. The author uses the extension'.hex' on such files. Examples can be
+found in the 'inhex' directory.
+<A NAME="lbAO">&nbsp;</A>
+<H2>MICROCODE AND FIRMWARE</H2>
+
+There are two standardized methods for downloading microcode (i.e. device
+firmware) to a SCSI device. The more general way is with the SCSI WRITE
+BUFFER command, see the sg_write_buffer utility. SCSI enclosures have
+their own method based on the Download microcode control/status diagnostic
+page, see the sg_ses_microcode utility.
+<A NAME="lbAP">&nbsp;</A>
+<H2>SCRIPTS, EXAMPLES and UTILS</H2>
+
+There are several bash shell scripts in the 'scripts' subdirectory that
+invoke compiled utilities (e.g. sg_readcap). Several of the scripts start
+with 'scsi_' rather than 'sg_'. One purpose of these scripts is to call the
+same utility (e.g. sg_readcap) on multiple devices. Most of the basic
+compiled utilities only allow one device as an argument. Some distributions
+install these scripts in a more visible directory (e.g. /usr/bin). Some of
+these scripts have man page entries. See the README file in the 'scripts'
+subdirectory.
+<P>
+
+There is some example C code plus examples of complex invocations in
+the 'examples' subdirectory. There is also a README file. The example C
+may be a simpler example of how to use a SCSI pass-through in Linux
+than the main utilities (found in the 'src' subdirectory). This is due
+to the fewer abstraction layers (e.g. they don't worry the MinGW in
+Windows may open a file in text rather than binary mode).
+<P>
+
+Some utilities that the author has found useful have been placed in
+the 'utils' subdirectory.
+<A NAME="lbAQ">&nbsp;</A>
+<H2>WEB SITE</H2>
+
+There is a web page discussing this package at
+<A HREF="https://sg.danny.cz/sg/sg3_utils.html">https://sg.danny.cz/sg/sg3_utils.html</A> . The device naming used by this
+package on various operating systems is discussed at:
+<A HREF="https://sg.danny.cz/sg/device_name.html">https://sg.danny.cz/sg/device_name.html</A> . There is a git code mirror at
+<A HREF="https://github.com/hreinecke/sg3_utils">https://github.com/hreinecke/sg3_utils</A> . The principle code repository
+uses subversion and is on the author's equipment. The author keeps track
+of this via the subversion revision number which is an ascending integer
+(currently at 774 for this package). The github mirror gets updated
+periodically from the author's repository. Depending on the time of
+update, the above Downloads section at sg.danny.cz may be more up to
+date than the github mirror.
+<A NAME="lbAR">&nbsp;</A>
+<H2>AUTHORS</H2>
+
+Written by Douglas Gilbert. Some utilities have been contributed, see the
+CREDITS file and individual source files (in the 'src' directory).
+<A NAME="lbAS">&nbsp;</A>
+<H2>REPORTING BUGS</H2>
+
+Report bugs to &lt;dgilbert at interlog dot com&gt;.
+<A NAME="lbAT">&nbsp;</A>
+<H2>COPYRIGHT</H2>
+
+Copyright &#169; 1999-2021 Douglas Gilbert
+<BR>
+
+Some utilities are distributed under a GPL version 2 license while
+others, usually more recent ones, are under a FreeBSD license. The files
+that are common to almost all utilities and thus contain the most reusable
+code, namely sg_lib.[hc], sg_cmds_basic.[hc] and sg_cmds_extra.[hc] are
+under a FreeBSD license. There is NO warranty; not even for MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.
+<A NAME="lbAU">&nbsp;</A>
+<H2>SEE ALSO</H2>
+
+<B>sdparm(sdparm), ddpt(ddpt), lsscsi(lsscsi), <A HREF="../man1/dmesg.1.html">dmesg</A>(1), <A HREF="../man1/mt.1.html">mt</A>(1)</B>
+
+<P>
+
+<HR>
+<A NAME="index">&nbsp;</A><H2>Index</H2>
+<DL>
+<DT><A HREF="#lbAB">NAME</A><DD>
+<DT><A HREF="#lbAC">SYNOPSIS</A><DD>
+<DT><A HREF="#lbAD">DESCRIPTION</A><DD>
+<DT><A HREF="#lbAE">ENVIRONMENT VARIABLES</A><DD>
+<DT><A HREF="#lbAF">LINUX DEVICE NAMING</A><DD>
+<DT><A HREF="#lbAG">WINDOWS DEVICE NAMING</A><DD>
+<DT><A HREF="#lbAH">FREEBSD DEVICE NAMING</A><DD>
+<DT><A HREF="#lbAI">SOLARIS DEVICE NAMING</A><DD>
+<DT><A HREF="#lbAJ">NVME SUPPORT</A><DD>
+<DT><A HREF="#lbAK">EXIT STATUS</A><DD>
+<DT><A HREF="#lbAL">COMMON OPTIONS</A><DD>
+<DT><A HREF="#lbAM">NUMERIC ARGUMENTS</A><DD>
+<DT><A HREF="#lbAN">FORMAT OF FILES CONTAINING ASCII HEX</A><DD>
+<DT><A HREF="#lbAO">MICROCODE AND FIRMWARE</A><DD>
+<DT><A HREF="#lbAP">SCRIPTS, EXAMPLES and UTILS</A><DD>
+<DT><A HREF="#lbAQ">WEB SITE</A><DD>
+<DT><A HREF="#lbAR">AUTHORS</A><DD>
+<DT><A HREF="#lbAS">REPORTING BUGS</A><DD>
+<DT><A HREF="#lbAT">COPYRIGHT</A><DD>
+<DT><A HREF="#lbAU">SEE ALSO</A><DD>
+</DL>
+<HR>
+This document was created by
+<A HREF="/cgi-bin/man/man2html">man2html</A>,
+using the manual pages.<BR>
+Time: 03:12:28 GMT, November 11, 2021
+</BODY>
+</HTML>
diff --git a/sg3_utils.spec b/sg3_utils.spec
new file mode 100644
index 00000000..3be40df4
--- /dev/null
+++ b/sg3_utils.spec
@@ -0,0 +1,280 @@
+Summary: Utilities for devices that use SCSI command sets
+Name: sg3_utils
+Version: 1.48
+# Release: 1%{?dist}
+Release: 1
+License: GPL
+Group: Utilities/System
+Source: https://sg.danny.cz/sg/p/sg3_utils-%{version}.tar.gz
+Url: https://sg.danny.cz/sg/sg3_utils.html
+Provides: sg_utils
+# BuildRequires: libtool
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Packager: Douglas Gilbert <dgilbert@interlog.com>
+
+%description
+Collection of Linux utilities for devices that use the SCSI command set.
+Includes utilities to copy data based on "dd" syntax and semantics (called
+sg_dd, sgp_dd and sgm_dd); check INQUIRY data and VPD pages (sg_inq); check
+mode and log pages (sginfo, sg_modes and sg_logs); spin up and down
+disks (sg_start); do self tests (sg_senddiag); and various other functions.
+See the README, ChangeLog and COVERAGE files. Requires the linux kernel 2.4
+series or later. In the 2.4 series SCSI generic device names (e.g. /dev/sg0)
+must be used. In the 2.6 series and later other device names may be used as
+well (e.g. /dev/sda). Also some support for NVMe devices, especially with
+sg_ses on NVMe enclosures.
+
+Warning: Some of these tools access the internals of your system
+and the incorrect usage of them may render your system inoperable.
+
+%package libs
+Summary: Shared library for %{name}
+Group: System/Libraries
+
+%description libs
+This package contains the shared library for %{name}.
+
+%package devel
+Summary: Static library and header files for the sgutils library
+Group: Development/C
+Requires: %{name}-libs = %{version}-%{release}
+
+%description devel
+This package contains the static %{name} library and its header files for
+developing applications.
+
+%prep
+%setup -q
+
+%build
+%configure
+
+# Don't use rpath!
+sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool
+sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool
+
+%install
+if [ "$RPM_BUILD_ROOT" != "/" ]; then
+ rm -rf $RPM_BUILD_ROOT
+fi
+
+make install \
+ DESTDIR=$RPM_BUILD_ROOT
+
+%clean
+if [ "$RPM_BUILD_ROOT" != "/" ]; then
+ rm -rf $RPM_BUILD_ROOT
+fi
+
+%files
+%defattr(-,root,root)
+%doc AUTHORS ChangeLog COPYING COVERAGE CREDITS INSTALL NEWS README README.sg_start
+%attr(755,root,root) %{_bindir}/*
+%{_mandir}/man8/*
+
+%files libs
+%defattr(-,root,root)
+%{_libdir}/*.so.*
+
+%files devel
+%defattr(-,root,root)
+%{_includedir}/scsi/*.h
+%{_libdir}/*.so
+%{_libdir}/*.a
+
+%changelog
+* Sat Nov 12 2022 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.48
+
+* Tue Nov 09 2021 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.47
+
+* Mon Mar 29 2021 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.46
+
+* Sat Feb 29 2020 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.45
+
+* Wed Sep 12 2018 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.44
+
+* Tue Sep 11 2018 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.43
+
+* Wed Feb 17 2016 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.42
+
+* Tue Apr 28 2015 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.41
+
+* Mon Nov 10 2014 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.40
+
+* Thu Jun 12 2014 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.39
+
+* Tue Apr 01 2014 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.38
+
+* Mon Oct 14 2013 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.37
+
+* Fri May 31 2013 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.36
+
+* Thu Jan 17 2013 - dgilbert at interlog dot com
+- add sg_compare_and_write, track t10 changes
+ * sg3_utils-1.35
+
+* Sat Oct 13 2012 - dgilbert at interlog dot com
+- add sg_xcopy and sg_copy_results; track t10 changes
+ * sg3_utils-1.34
+
+* Wed Jan 18 2012 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.33
+
+* Wed Jun 22 2011 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.32
+
+* Wed Feb 16 2011 - dgilbert at interlog dot com
+- add sg_decode_sense; track t10 changes
+ * sg3_utils-1.31
+
+* Fri Nov 05 2010 - dgilbert at interlog dot com
+- add sg_referrals; track t10 changes
+ * sg3_utils-1.30
+
+* Wed Mar 31 2010 - dgilbert at interlog dot com
+- track t10 changes
+ * sg3_utils-1.29
+
+* Fri Oct 02 2009 - dgilbert at interlog dot com
+- add sg_get_lba_status, sg_unmap, sg_read_block_limits
+ * sg3_utils-1.28
+
+* Sat Apr 11 2009 - dgilbert at interlog dot com
+- add sg_write_same; sg_dd split; spc4r18 sync
+ * sg3_utils-1.27
+
+* Wed Jun 25 2008 - dgilbert at interlog dot com
+- add sg_sat_phy_event, sync with drafts prior to this date
+ * sg3_utils-1.26
+
+* Tue Oct 16 2007 - dgilbert at interlog dot com
+- add sg_sat_set_features, sg_stpg, sg_safte; sg_dd oflag=sparse,null
+ * sg3_utils-1.25
+
+* Mon May 07 2007 - dgilbert at interlog dot com
+- add sg_raw; sg_rtpg, sg_log, sg_inq and sg_format updates
+ * sg3_utils-1.24
+
+* Wed Jan 31 2007 - dgilbert at interlog dot com
+- add sg_read_buffer + sg_write_buffer
+ * sg3_utils-1.23
+
+* Mon Oct 16 2006 - dgilbert at interlog dot com
+- add sg_sat_identify, expand sg_format and sg_requests
+ * sg3_utils-1.22
+
+* Thu Jul 06 2006 - dgilbert at interlog dot com
+- add sg_vpd and sg_rdac, uniform exit statuses
+ * sg3_utils-1.21
+
+* Tue Apr 18 2006 - dgilbert at interlog dot com
+- sg_logs: sas port specific page decoding, sg*_dd updates
+ * sg3_utils-1.20
+
+* Fri Jan 27 2006 - dgilbert at interlog dot com
+- sg_get_config: resync features with mmc5 rev 1
+ * sg3_utils-1.19
+
+* Fri Nov 18 2005 - dgilbert at interlog dot com
+- add sg_map26; sg_inq '-rr' option to play with hdparm
+ * sg3_utils-1.18
+
+* Thu Sep 22 2005 - dgilbert at interlog dot com
+- add ATA information VPD page to sg_inq
+ * sg3_utils-1.17
+
+* Wed Aug 10 2005 - dgilbert at interlog dot com
+- add sg_ident, sg_inq VPD page extensions
+ * sg3_utils-1.16
+
+* Sun Jun 05 2005 - dgilbert at interlog dot com
+- use O_NONBLOCK on all fds that use SG_IO ioctl
+ * sg3_utils-1.15
+
+* Fri May 06 2005 - dgilbert at interlog dot com
+- produce libsgutils (+ -devel variant) as well as sg3_utils binary rpm
+ * sg3_utils-1.14
+
+* Sun Mar 13 2005 - dgilbert at interlog dot com
+- add sg_format, sg_dd extensions
+ * sg3_utils-1.13
+
+* Fri Jan 21 2005 - dgilbert at interlog dot com
+- add sg_wr_mode, sg_rtpg + sg_reassign; sginfo sas tweaks
+ * sg3_utils-1.12
+
+* Fri Nov 26 2004 - dgilbert at interlog dot com
+- add sg_sync, sg_prevent and sg_get_config; fix sg_requests
+ * sg3_utils-1.11
+
+* Sat Oct 30 2004 - dgilbert at interlog dot com
+- fix read capacity (10+16), add sg_luns
+ * sg3_utils-1.10
+
+* Thu Oct 21 2004 - dgilbert at interlog dot com
+- sg_requests, sg_ses, sg_verify, libsgutils(sg_lib.c+sg_cmds.c), devel rpm
+ * sg3_utils-1.09
+
+* Tue Aug 31 2004 - dgilbert at interlog dot com
+- 'register+move' in sg_persist, sg_opcodes sorts, sg_write_long
+ * sg3_utils-1.08
+
+* Thu Jul 08 2004 - dgilbert at interlog dot com
+- add '-fHead' to sginfo, '-i' for sg_inq, new sg_opcodes + sg_persist
+ * sg3_utils-1.07
+
+* Mon Apr 26 2004 - dgilbert at interlog dot com
+- sg3_utils.spec for mandrake; more sginfo work, sg_scan, sg_logs
+ * sg3_utils-1.06
+
+* Wed Nov 12 2003 - dgilbert at interlog dot com
+- sg_readcap: sizes; sg_logs: double fetch; sg_map 256 sg devices; sginfo
+ * sg3_utils-1.05
+
+* Tue May 13 2003 - dgilbert at interlog dot com
+- default sg_turs '-n=' to 1, sg_logs gets '-t' for temperature, CREDITS
+ * sg3_utils-1.04
+
+* Wed Apr 02 2003 - dgilbert at interlog dot com
+- 6 byte CDBs for sg_modes, sg_start on block devs, sg_senddiag, man pages
+ * sg3_utils-1.03
+
+* Wed Jan 01 2003 - dgilbert at interlog dot com
+- interwork with block SG_IO, fix in sginfo, '-t' for sg_turs
+ * sg3_utils-1.02
+
+* Wed Aug 14 2002 - dgilbert at interlog dot com
+- raw switch in sg_inq
+ * sg3_utils-1.01
+
+* Sun Jul 28 2002 - dgilbert at interlog dot com
+- decode sg_logs pages, add dio to sgm_dd, drop "gen=1" arg, "of=/dev/null"
+ * sg3_utils-1.00
diff --git a/src/BSD_LICENSE b/src/BSD_LICENSE
new file mode 100644
index 00000000..975506ad
--- /dev/null
+++ b/src/BSD_LICENSE
@@ -0,0 +1,26 @@
+
+Copyright (c) 1999-2022, Douglas Gilbert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Above is the:
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..c852833e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,219 @@
+
+bin_PROGRAMS = \
+ sg_bg_ctl sg_compare_and_write sg_decode_sense sg_format \
+ sg_get_config sg_get_elem_status sg_get_lba_status sg_ident sg_inq \
+ sg_logs sg_luns sg_modes sg_opcodes sg_persist sg_prevent sg_raw \
+ sg_rdac sg_read_attr sg_read_block_limits sg_read_buffer \
+ sg_read_long sg_readcap sg_reassign sg_referrals sg_rem_rest_elem \
+ sg_rep_density sg_rep_pip sg_rep_zones sg_requests sg_reset_wp \
+ sg_rmsn sg_rtpg sg_safte sg_sanitize sg_sat_identify sg_sat_phy_event \
+ sg_sat_read_gplog sg_sat_set_features sg_seek sg_senddiag sg_ses \
+ sg_ses_microcode sg_start sg_stpg sg_stream_ctl sg_sync sg_timestamp \
+ sg_turs sg_unmap sg_verify sg_vpd sg_wr_mode sg_write_buffer \
+ sg_write_long sg_write_same sg_write_verify sg_write_x sg_zone \
+ sg_z_act_query
+sg_scan_SOURCES =
+
+
+if OS_LINUX
+if !PT_DUMMY
+bin_PROGRAMS += \
+ sg_copy_results sg_dd sg_emc_trespass sg_map sg_map26 sg_rbuf \
+ sg_read sg_reset sg_scan sg_test_rwbuf sg_xcopy sginfo sgm_dd sgp_dd
+sg_scan_SOURCES += sg_scan_linux.c
+endif
+endif
+
+
+if OS_WIN32_MINGW
+bin_PROGRAMS += sg_scan
+sg_scan_SOURCES += sg_scan_win32.c
+endif
+
+
+if OS_WIN32_CYGWIN
+bin_PROGRAMS += sg_scan
+sg_scan_SOURCES += sg_scan_win32.c
+endif
+
+# This is active if --enable-debug given to ./configure
+# removed -Wduplicated-branches because needs gcc-8
+if DEBUG
+DBG_CFLAGS = -Wextra -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+DBG_CPPFLAGS = -DDEBUG
+else
+DBG_CFLAGS =
+DBG_CPPFLAGS =
+endif
+
+# For C++/clang testing
+## CC = gcc-9
+## CC = g++
+## CC = clang
+## CXX = clang++
+## CC = clang++
+## CC = powerpc64-linux-gnu-gcc
+
+# -std=<s> can be c99, c11, gnu11, etc. Default is gnu11
+# -Wall is no longer all warnings. Add -W (since renamed to -Wextra) for more
+AM_CPPFLAGS = -iquote ${top_srcdir}/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(DBG_CPPFLAGS)
+AM_CFLAGS = -Wall -W $(DBG_CFLAGS)
+# AM_CFLAGS = -Wall -W $(DBG_CFLAGS) -fanalyzer
+# AM_CFLAGS = -Wall -W -Wextra -Wmisleading-indentation -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+# AM_CFLAGS = -Wall -W -pedantic -std=c11
+# AM_CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# AM_CFLAGS = -Wall -W -pedantic -std=c++98
+# AM_CFLAGS = -Wall -W -pedantic -std=c++11
+# AM_CFLAGS = -Wall -W -pedantic -std=c++14
+# AM_CFLAGS = -Wall -W -pedantic -std=c++1z
+# AM_CFLAGS = -Wall -W -pedantic -std=c++20
+# AM_CFLAGS = -Wall -W -pedantic -std=c++23
+
+sg_bg_ctl_LDADD = ../lib/libsgutils2.la
+
+sg_compare_and_write_LDADD = ../lib/libsgutils2.la
+
+sg_copy_results_LDADD = ../lib/libsgutils2.la
+
+sg_dd_LDADD = ../lib/libsgutils2.la
+
+sg_decode_sense_LDADD = ../lib/libsgutils2.la
+
+sg_emc_trespass_LDADD = ../lib/libsgutils2.la
+
+sg_format_LDADD = ../lib/libsgutils2.la
+
+sg_get_config_LDADD = ../lib/libsgutils2.la
+
+sg_get_elem_status_LDADD = ../lib/libsgutils2.la
+
+sg_get_lba_status_LDADD = ../lib/libsgutils2.la
+
+sg_ident_LDADD = ../lib/libsgutils2.la
+
+sginfo_LDADD = ../lib/libsgutils2.la
+
+sg_inq_SOURCES = sg_inq.c sg_inq_data.c sg_vpd_common.c
+sg_inq_LDADD = ../lib/libsgutils2.la
+
+sg_logs_LDADD = ../lib/libsgutils2.la
+
+sg_luns_LDADD = ../lib/libsgutils2.la
+
+sg_map_LDADD = ../lib/libsgutils2.la
+
+sgm_dd_LDADD = ../lib/libsgutils2.la
+
+sg_modes_LDADD = ../lib/libsgutils2.la
+
+sg_opcodes_LDADD = ../lib/libsgutils2.la
+
+sgp_dd_LDADD = ../lib/libsgutils2.la @PTHREAD_LIB@
+
+sg_persist_LDADD = ../lib/libsgutils2.la
+
+sg_prevent_LDADD = ../lib/libsgutils2.la
+
+sg_raw_LDADD = ../lib/libsgutils2.la
+
+sg_rbuf_LDADD = ../lib/libsgutils2.la
+
+sg_rdac_LDADD = ../lib/libsgutils2.la
+
+sg_read_LDADD = ../lib/libsgutils2.la
+
+sg_read_attr_LDADD = ../lib/libsgutils2.la
+
+sg_readcap_LDADD = ../lib/libsgutils2.la
+
+sg_read_block_limits_LDADD = ../lib/libsgutils2.la
+
+sg_read_buffer_LDADD = ../lib/libsgutils2.la
+
+sg_read_long_LDADD = ../lib/libsgutils2.la
+
+sg_reassign_LDADD = ../lib/libsgutils2.la
+
+sg_referrals_LDADD = ../lib/libsgutils2.la
+
+sg_rem_rest_elem_LDADD = ../lib/libsgutils2.la
+
+sg_rep_density_LDADD = ../lib/libsgutils2.la
+
+sg_rep_pip_LDADD = ../lib/libsgutils2.la
+
+sg_rep_zones_LDADD = ../lib/libsgutils2.la
+
+sg_requests_LDADD = ../lib/libsgutils2.la
+
+sg_reset_wp_LDADD = ../lib/libsgutils2.la
+
+sg_rmsn_LDADD = ../lib/libsgutils2.la
+
+sg_rtpg_LDADD = ../lib/libsgutils2.la
+
+sg_safte_LDADD = ../lib/libsgutils2.la
+
+sg_sanitize_LDADD = ../lib/libsgutils2.la
+
+sg_sat_identify_LDADD = ../lib/libsgutils2.la
+
+sg_sat_phy_event_LDADD = ../lib/libsgutils2.la
+
+sg_sat_read_gplog_LDADD = ../lib/libsgutils2.la
+
+sg_sat_set_features_LDADD = ../lib/libsgutils2.la
+
+# sg_scan_SOURCES list is already set above in the platform-specific sections
+sg_scan_LDADD = ../lib/libsgutils2.la
+
+sg_seek_LDADD = ../lib/libsgutils2.la @RT_LIB@
+
+sg_senddiag_LDADD = ../lib/libsgutils2.la
+
+sg_ses_LDADD = ../lib/libsgutils2.la
+
+sg_ses_microcode_LDADD = ../lib/libsgutils2.la
+
+sg_start_LDADD = ../lib/libsgutils2.la
+
+sg_stpg_LDADD = ../lib/libsgutils2.la
+
+sg_stream_ctl_LDADD = ../lib/libsgutils2.la
+
+sg_sync_LDADD = ../lib/libsgutils2.la
+
+sg_test_rwbuf_LDADD = ../lib/libsgutils2.la
+
+sg_timestamp_LDADD = ../lib/libsgutils2.la
+
+sg_turs_LDADD = ../lib/libsgutils2.la @RT_LIB@
+
+sg_unmap_LDADD = ../lib/libsgutils2.la
+
+sg_verify_LDADD = ../lib/libsgutils2.la
+
+sg_vpd_SOURCES = sg_vpd.c sg_vpd_vendor.c sg_vpd_common.c
+sg_vpd_LDADD = ../lib/libsgutils2.la
+
+sg_wr_mode_LDADD = ../lib/libsgutils2.la
+
+sg_write_buffer_LDADD = ../lib/libsgutils2.la
+
+sg_write_long_LDADD = ../lib/libsgutils2.la
+
+sg_write_same_LDADD = ../lib/libsgutils2.la
+
+sg_write_verify_LDADD = ../lib/libsgutils2.la
+
+sg_write_x_LDADD = ../lib/libsgutils2.la
+
+sg_xcopy_LDADD = ../lib/libsgutils2.la
+
+sg_zone_LDADD = ../lib/libsgutils2.la
+
+sg_z_act_query_LDADD = ../lib/libsgutils2.la
+
+EXTRA_DIST = \
+ sg_vpd_common.h \
+ BSD_LICENSE
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 00000000..4dde3e60
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1608 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = sg_bg_ctl$(EXEEXT) sg_compare_and_write$(EXEEXT) \
+ sg_decode_sense$(EXEEXT) sg_format$(EXEEXT) \
+ sg_get_config$(EXEEXT) sg_get_elem_status$(EXEEXT) \
+ sg_get_lba_status$(EXEEXT) sg_ident$(EXEEXT) sg_inq$(EXEEXT) \
+ sg_logs$(EXEEXT) sg_luns$(EXEEXT) sg_modes$(EXEEXT) \
+ sg_opcodes$(EXEEXT) sg_persist$(EXEEXT) sg_prevent$(EXEEXT) \
+ sg_raw$(EXEEXT) sg_rdac$(EXEEXT) sg_read_attr$(EXEEXT) \
+ sg_read_block_limits$(EXEEXT) sg_read_buffer$(EXEEXT) \
+ sg_read_long$(EXEEXT) sg_readcap$(EXEEXT) sg_reassign$(EXEEXT) \
+ sg_referrals$(EXEEXT) sg_rem_rest_elem$(EXEEXT) \
+ sg_rep_density$(EXEEXT) sg_rep_pip$(EXEEXT) \
+ sg_rep_zones$(EXEEXT) sg_requests$(EXEEXT) \
+ sg_reset_wp$(EXEEXT) sg_rmsn$(EXEEXT) sg_rtpg$(EXEEXT) \
+ sg_safte$(EXEEXT) sg_sanitize$(EXEEXT) \
+ sg_sat_identify$(EXEEXT) sg_sat_phy_event$(EXEEXT) \
+ sg_sat_read_gplog$(EXEEXT) sg_sat_set_features$(EXEEXT) \
+ sg_seek$(EXEEXT) sg_senddiag$(EXEEXT) sg_ses$(EXEEXT) \
+ sg_ses_microcode$(EXEEXT) sg_start$(EXEEXT) sg_stpg$(EXEEXT) \
+ sg_stream_ctl$(EXEEXT) sg_sync$(EXEEXT) sg_timestamp$(EXEEXT) \
+ sg_turs$(EXEEXT) sg_unmap$(EXEEXT) sg_verify$(EXEEXT) \
+ sg_vpd$(EXEEXT) sg_wr_mode$(EXEEXT) sg_write_buffer$(EXEEXT) \
+ sg_write_long$(EXEEXT) sg_write_same$(EXEEXT) \
+ sg_write_verify$(EXEEXT) sg_write_x$(EXEEXT) sg_zone$(EXEEXT) \
+ sg_z_act_query$(EXEEXT) $(am__EXEEXT_1) $(am__EXEEXT_2) \
+ $(am__EXEEXT_3)
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__append_1 = \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_copy_results sg_dd sg_emc_trespass sg_map sg_map26 sg_rbuf \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_read sg_reset sg_scan sg_test_rwbuf sg_xcopy sginfo sgm_dd sgp_dd
+
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__append_2 = sg_scan_linux.c
+@OS_WIN32_MINGW_TRUE@am__append_3 = sg_scan
+@OS_WIN32_MINGW_TRUE@am__append_4 = sg_scan_win32.c
+@OS_WIN32_CYGWIN_TRUE@am__append_5 = sg_scan
+@OS_WIN32_CYGWIN_TRUE@am__append_6 = sg_scan_win32.c
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__EXEEXT_1 = \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_copy_results$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_dd$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_emc_trespass$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_map$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_map26$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_rbuf$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_read$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_reset$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_scan$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_test_rwbuf$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_xcopy$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sginfo$(EXEEXT) sgm_dd$(EXEEXT) \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sgp_dd$(EXEEXT)
+@OS_WIN32_MINGW_TRUE@am__EXEEXT_2 = sg_scan$(EXEEXT)
+@OS_WIN32_CYGWIN_TRUE@am__EXEEXT_3 = sg_scan$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+sg_bg_ctl_SOURCES = sg_bg_ctl.c
+sg_bg_ctl_OBJECTS = sg_bg_ctl.$(OBJEXT)
+sg_bg_ctl_DEPENDENCIES = ../lib/libsgutils2.la
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+sg_compare_and_write_SOURCES = sg_compare_and_write.c
+sg_compare_and_write_OBJECTS = sg_compare_and_write.$(OBJEXT)
+sg_compare_and_write_DEPENDENCIES = ../lib/libsgutils2.la
+sg_copy_results_SOURCES = sg_copy_results.c
+sg_copy_results_OBJECTS = sg_copy_results.$(OBJEXT)
+sg_copy_results_DEPENDENCIES = ../lib/libsgutils2.la
+sg_dd_SOURCES = sg_dd.c
+sg_dd_OBJECTS = sg_dd.$(OBJEXT)
+sg_dd_DEPENDENCIES = ../lib/libsgutils2.la
+sg_decode_sense_SOURCES = sg_decode_sense.c
+sg_decode_sense_OBJECTS = sg_decode_sense.$(OBJEXT)
+sg_decode_sense_DEPENDENCIES = ../lib/libsgutils2.la
+sg_emc_trespass_SOURCES = sg_emc_trespass.c
+sg_emc_trespass_OBJECTS = sg_emc_trespass.$(OBJEXT)
+sg_emc_trespass_DEPENDENCIES = ../lib/libsgutils2.la
+sg_format_SOURCES = sg_format.c
+sg_format_OBJECTS = sg_format.$(OBJEXT)
+sg_format_DEPENDENCIES = ../lib/libsgutils2.la
+sg_get_config_SOURCES = sg_get_config.c
+sg_get_config_OBJECTS = sg_get_config.$(OBJEXT)
+sg_get_config_DEPENDENCIES = ../lib/libsgutils2.la
+sg_get_elem_status_SOURCES = sg_get_elem_status.c
+sg_get_elem_status_OBJECTS = sg_get_elem_status.$(OBJEXT)
+sg_get_elem_status_DEPENDENCIES = ../lib/libsgutils2.la
+sg_get_lba_status_SOURCES = sg_get_lba_status.c
+sg_get_lba_status_OBJECTS = sg_get_lba_status.$(OBJEXT)
+sg_get_lba_status_DEPENDENCIES = ../lib/libsgutils2.la
+sg_ident_SOURCES = sg_ident.c
+sg_ident_OBJECTS = sg_ident.$(OBJEXT)
+sg_ident_DEPENDENCIES = ../lib/libsgutils2.la
+am_sg_inq_OBJECTS = sg_inq.$(OBJEXT) sg_inq_data.$(OBJEXT) \
+ sg_vpd_common.$(OBJEXT)
+sg_inq_OBJECTS = $(am_sg_inq_OBJECTS)
+sg_inq_DEPENDENCIES = ../lib/libsgutils2.la
+sg_logs_SOURCES = sg_logs.c
+sg_logs_OBJECTS = sg_logs.$(OBJEXT)
+sg_logs_DEPENDENCIES = ../lib/libsgutils2.la
+sg_luns_SOURCES = sg_luns.c
+sg_luns_OBJECTS = sg_luns.$(OBJEXT)
+sg_luns_DEPENDENCIES = ../lib/libsgutils2.la
+sg_map_SOURCES = sg_map.c
+sg_map_OBJECTS = sg_map.$(OBJEXT)
+sg_map_DEPENDENCIES = ../lib/libsgutils2.la
+sg_map26_SOURCES = sg_map26.c
+sg_map26_OBJECTS = sg_map26.$(OBJEXT)
+sg_map26_LDADD = $(LDADD)
+sg_modes_SOURCES = sg_modes.c
+sg_modes_OBJECTS = sg_modes.$(OBJEXT)
+sg_modes_DEPENDENCIES = ../lib/libsgutils2.la
+sg_opcodes_SOURCES = sg_opcodes.c
+sg_opcodes_OBJECTS = sg_opcodes.$(OBJEXT)
+sg_opcodes_DEPENDENCIES = ../lib/libsgutils2.la
+sg_persist_SOURCES = sg_persist.c
+sg_persist_OBJECTS = sg_persist.$(OBJEXT)
+sg_persist_DEPENDENCIES = ../lib/libsgutils2.la
+sg_prevent_SOURCES = sg_prevent.c
+sg_prevent_OBJECTS = sg_prevent.$(OBJEXT)
+sg_prevent_DEPENDENCIES = ../lib/libsgutils2.la
+sg_raw_SOURCES = sg_raw.c
+sg_raw_OBJECTS = sg_raw.$(OBJEXT)
+sg_raw_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rbuf_SOURCES = sg_rbuf.c
+sg_rbuf_OBJECTS = sg_rbuf.$(OBJEXT)
+sg_rbuf_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rdac_SOURCES = sg_rdac.c
+sg_rdac_OBJECTS = sg_rdac.$(OBJEXT)
+sg_rdac_DEPENDENCIES = ../lib/libsgutils2.la
+sg_read_SOURCES = sg_read.c
+sg_read_OBJECTS = sg_read.$(OBJEXT)
+sg_read_DEPENDENCIES = ../lib/libsgutils2.la
+sg_read_attr_SOURCES = sg_read_attr.c
+sg_read_attr_OBJECTS = sg_read_attr.$(OBJEXT)
+sg_read_attr_DEPENDENCIES = ../lib/libsgutils2.la
+sg_read_block_limits_SOURCES = sg_read_block_limits.c
+sg_read_block_limits_OBJECTS = sg_read_block_limits.$(OBJEXT)
+sg_read_block_limits_DEPENDENCIES = ../lib/libsgutils2.la
+sg_read_buffer_SOURCES = sg_read_buffer.c
+sg_read_buffer_OBJECTS = sg_read_buffer.$(OBJEXT)
+sg_read_buffer_DEPENDENCIES = ../lib/libsgutils2.la
+sg_read_long_SOURCES = sg_read_long.c
+sg_read_long_OBJECTS = sg_read_long.$(OBJEXT)
+sg_read_long_DEPENDENCIES = ../lib/libsgutils2.la
+sg_readcap_SOURCES = sg_readcap.c
+sg_readcap_OBJECTS = sg_readcap.$(OBJEXT)
+sg_readcap_DEPENDENCIES = ../lib/libsgutils2.la
+sg_reassign_SOURCES = sg_reassign.c
+sg_reassign_OBJECTS = sg_reassign.$(OBJEXT)
+sg_reassign_DEPENDENCIES = ../lib/libsgutils2.la
+sg_referrals_SOURCES = sg_referrals.c
+sg_referrals_OBJECTS = sg_referrals.$(OBJEXT)
+sg_referrals_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rem_rest_elem_SOURCES = sg_rem_rest_elem.c
+sg_rem_rest_elem_OBJECTS = sg_rem_rest_elem.$(OBJEXT)
+sg_rem_rest_elem_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rep_density_SOURCES = sg_rep_density.c
+sg_rep_density_OBJECTS = sg_rep_density.$(OBJEXT)
+sg_rep_density_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rep_pip_SOURCES = sg_rep_pip.c
+sg_rep_pip_OBJECTS = sg_rep_pip.$(OBJEXT)
+sg_rep_pip_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rep_zones_SOURCES = sg_rep_zones.c
+sg_rep_zones_OBJECTS = sg_rep_zones.$(OBJEXT)
+sg_rep_zones_DEPENDENCIES = ../lib/libsgutils2.la
+sg_requests_SOURCES = sg_requests.c
+sg_requests_OBJECTS = sg_requests.$(OBJEXT)
+sg_requests_DEPENDENCIES = ../lib/libsgutils2.la
+sg_reset_SOURCES = sg_reset.c
+sg_reset_OBJECTS = sg_reset.$(OBJEXT)
+sg_reset_LDADD = $(LDADD)
+sg_reset_wp_SOURCES = sg_reset_wp.c
+sg_reset_wp_OBJECTS = sg_reset_wp.$(OBJEXT)
+sg_reset_wp_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rmsn_SOURCES = sg_rmsn.c
+sg_rmsn_OBJECTS = sg_rmsn.$(OBJEXT)
+sg_rmsn_DEPENDENCIES = ../lib/libsgutils2.la
+sg_rtpg_SOURCES = sg_rtpg.c
+sg_rtpg_OBJECTS = sg_rtpg.$(OBJEXT)
+sg_rtpg_DEPENDENCIES = ../lib/libsgutils2.la
+sg_safte_SOURCES = sg_safte.c
+sg_safte_OBJECTS = sg_safte.$(OBJEXT)
+sg_safte_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sanitize_SOURCES = sg_sanitize.c
+sg_sanitize_OBJECTS = sg_sanitize.$(OBJEXT)
+sg_sanitize_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sat_identify_SOURCES = sg_sat_identify.c
+sg_sat_identify_OBJECTS = sg_sat_identify.$(OBJEXT)
+sg_sat_identify_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sat_phy_event_SOURCES = sg_sat_phy_event.c
+sg_sat_phy_event_OBJECTS = sg_sat_phy_event.$(OBJEXT)
+sg_sat_phy_event_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sat_read_gplog_SOURCES = sg_sat_read_gplog.c
+sg_sat_read_gplog_OBJECTS = sg_sat_read_gplog.$(OBJEXT)
+sg_sat_read_gplog_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sat_set_features_SOURCES = sg_sat_set_features.c
+sg_sat_set_features_OBJECTS = sg_sat_set_features.$(OBJEXT)
+sg_sat_set_features_DEPENDENCIES = ../lib/libsgutils2.la
+am__sg_scan_SOURCES_DIST = sg_scan_linux.c sg_scan_win32.c
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@am__objects_1 = \
+@OS_LINUX_TRUE@@PT_DUMMY_FALSE@ sg_scan_linux.$(OBJEXT)
+@OS_WIN32_MINGW_TRUE@am__objects_2 = sg_scan_win32.$(OBJEXT)
+@OS_WIN32_CYGWIN_TRUE@am__objects_3 = sg_scan_win32.$(OBJEXT)
+am_sg_scan_OBJECTS = $(am__objects_1) $(am__objects_2) \
+ $(am__objects_3)
+sg_scan_OBJECTS = $(am_sg_scan_OBJECTS)
+sg_scan_DEPENDENCIES = ../lib/libsgutils2.la
+sg_seek_SOURCES = sg_seek.c
+sg_seek_OBJECTS = sg_seek.$(OBJEXT)
+sg_seek_DEPENDENCIES = ../lib/libsgutils2.la
+sg_senddiag_SOURCES = sg_senddiag.c
+sg_senddiag_OBJECTS = sg_senddiag.$(OBJEXT)
+sg_senddiag_DEPENDENCIES = ../lib/libsgutils2.la
+sg_ses_SOURCES = sg_ses.c
+sg_ses_OBJECTS = sg_ses.$(OBJEXT)
+sg_ses_DEPENDENCIES = ../lib/libsgutils2.la
+sg_ses_microcode_SOURCES = sg_ses_microcode.c
+sg_ses_microcode_OBJECTS = sg_ses_microcode.$(OBJEXT)
+sg_ses_microcode_DEPENDENCIES = ../lib/libsgutils2.la
+sg_start_SOURCES = sg_start.c
+sg_start_OBJECTS = sg_start.$(OBJEXT)
+sg_start_DEPENDENCIES = ../lib/libsgutils2.la
+sg_stpg_SOURCES = sg_stpg.c
+sg_stpg_OBJECTS = sg_stpg.$(OBJEXT)
+sg_stpg_DEPENDENCIES = ../lib/libsgutils2.la
+sg_stream_ctl_SOURCES = sg_stream_ctl.c
+sg_stream_ctl_OBJECTS = sg_stream_ctl.$(OBJEXT)
+sg_stream_ctl_DEPENDENCIES = ../lib/libsgutils2.la
+sg_sync_SOURCES = sg_sync.c
+sg_sync_OBJECTS = sg_sync.$(OBJEXT)
+sg_sync_DEPENDENCIES = ../lib/libsgutils2.la
+sg_test_rwbuf_SOURCES = sg_test_rwbuf.c
+sg_test_rwbuf_OBJECTS = sg_test_rwbuf.$(OBJEXT)
+sg_test_rwbuf_DEPENDENCIES = ../lib/libsgutils2.la
+sg_timestamp_SOURCES = sg_timestamp.c
+sg_timestamp_OBJECTS = sg_timestamp.$(OBJEXT)
+sg_timestamp_DEPENDENCIES = ../lib/libsgutils2.la
+sg_turs_SOURCES = sg_turs.c
+sg_turs_OBJECTS = sg_turs.$(OBJEXT)
+sg_turs_DEPENDENCIES = ../lib/libsgutils2.la
+sg_unmap_SOURCES = sg_unmap.c
+sg_unmap_OBJECTS = sg_unmap.$(OBJEXT)
+sg_unmap_DEPENDENCIES = ../lib/libsgutils2.la
+sg_verify_SOURCES = sg_verify.c
+sg_verify_OBJECTS = sg_verify.$(OBJEXT)
+sg_verify_DEPENDENCIES = ../lib/libsgutils2.la
+am_sg_vpd_OBJECTS = sg_vpd.$(OBJEXT) sg_vpd_vendor.$(OBJEXT) \
+ sg_vpd_common.$(OBJEXT)
+sg_vpd_OBJECTS = $(am_sg_vpd_OBJECTS)
+sg_vpd_DEPENDENCIES = ../lib/libsgutils2.la
+sg_wr_mode_SOURCES = sg_wr_mode.c
+sg_wr_mode_OBJECTS = sg_wr_mode.$(OBJEXT)
+sg_wr_mode_DEPENDENCIES = ../lib/libsgutils2.la
+sg_write_buffer_SOURCES = sg_write_buffer.c
+sg_write_buffer_OBJECTS = sg_write_buffer.$(OBJEXT)
+sg_write_buffer_DEPENDENCIES = ../lib/libsgutils2.la
+sg_write_long_SOURCES = sg_write_long.c
+sg_write_long_OBJECTS = sg_write_long.$(OBJEXT)
+sg_write_long_DEPENDENCIES = ../lib/libsgutils2.la
+sg_write_same_SOURCES = sg_write_same.c
+sg_write_same_OBJECTS = sg_write_same.$(OBJEXT)
+sg_write_same_DEPENDENCIES = ../lib/libsgutils2.la
+sg_write_verify_SOURCES = sg_write_verify.c
+sg_write_verify_OBJECTS = sg_write_verify.$(OBJEXT)
+sg_write_verify_DEPENDENCIES = ../lib/libsgutils2.la
+sg_write_x_SOURCES = sg_write_x.c
+sg_write_x_OBJECTS = sg_write_x.$(OBJEXT)
+sg_write_x_DEPENDENCIES = ../lib/libsgutils2.la
+sg_xcopy_SOURCES = sg_xcopy.c
+sg_xcopy_OBJECTS = sg_xcopy.$(OBJEXT)
+sg_xcopy_DEPENDENCIES = ../lib/libsgutils2.la
+sg_z_act_query_SOURCES = sg_z_act_query.c
+sg_z_act_query_OBJECTS = sg_z_act_query.$(OBJEXT)
+sg_z_act_query_DEPENDENCIES = ../lib/libsgutils2.la
+sg_zone_SOURCES = sg_zone.c
+sg_zone_OBJECTS = sg_zone.$(OBJEXT)
+sg_zone_DEPENDENCIES = ../lib/libsgutils2.la
+sginfo_SOURCES = sginfo.c
+sginfo_OBJECTS = sginfo.$(OBJEXT)
+sginfo_DEPENDENCIES = ../lib/libsgutils2.la
+sgm_dd_SOURCES = sgm_dd.c
+sgm_dd_OBJECTS = sgm_dd.$(OBJEXT)
+sgm_dd_DEPENDENCIES = ../lib/libsgutils2.la
+sgp_dd_SOURCES = sgp_dd.c
+sgp_dd_OBJECTS = sgp_dd.$(OBJEXT)
+sgp_dd_DEPENDENCIES = ../lib/libsgutils2.la
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sg_bg_ctl.Po \
+ ./$(DEPDIR)/sg_compare_and_write.Po \
+ ./$(DEPDIR)/sg_copy_results.Po ./$(DEPDIR)/sg_dd.Po \
+ ./$(DEPDIR)/sg_decode_sense.Po ./$(DEPDIR)/sg_emc_trespass.Po \
+ ./$(DEPDIR)/sg_format.Po ./$(DEPDIR)/sg_get_config.Po \
+ ./$(DEPDIR)/sg_get_elem_status.Po \
+ ./$(DEPDIR)/sg_get_lba_status.Po ./$(DEPDIR)/sg_ident.Po \
+ ./$(DEPDIR)/sg_inq.Po ./$(DEPDIR)/sg_inq_data.Po \
+ ./$(DEPDIR)/sg_logs.Po ./$(DEPDIR)/sg_luns.Po \
+ ./$(DEPDIR)/sg_map.Po ./$(DEPDIR)/sg_map26.Po \
+ ./$(DEPDIR)/sg_modes.Po ./$(DEPDIR)/sg_opcodes.Po \
+ ./$(DEPDIR)/sg_persist.Po ./$(DEPDIR)/sg_prevent.Po \
+ ./$(DEPDIR)/sg_raw.Po ./$(DEPDIR)/sg_rbuf.Po \
+ ./$(DEPDIR)/sg_rdac.Po ./$(DEPDIR)/sg_read.Po \
+ ./$(DEPDIR)/sg_read_attr.Po \
+ ./$(DEPDIR)/sg_read_block_limits.Po \
+ ./$(DEPDIR)/sg_read_buffer.Po ./$(DEPDIR)/sg_read_long.Po \
+ ./$(DEPDIR)/sg_readcap.Po ./$(DEPDIR)/sg_reassign.Po \
+ ./$(DEPDIR)/sg_referrals.Po ./$(DEPDIR)/sg_rem_rest_elem.Po \
+ ./$(DEPDIR)/sg_rep_density.Po ./$(DEPDIR)/sg_rep_pip.Po \
+ ./$(DEPDIR)/sg_rep_zones.Po ./$(DEPDIR)/sg_requests.Po \
+ ./$(DEPDIR)/sg_reset.Po ./$(DEPDIR)/sg_reset_wp.Po \
+ ./$(DEPDIR)/sg_rmsn.Po ./$(DEPDIR)/sg_rtpg.Po \
+ ./$(DEPDIR)/sg_safte.Po ./$(DEPDIR)/sg_sanitize.Po \
+ ./$(DEPDIR)/sg_sat_identify.Po ./$(DEPDIR)/sg_sat_phy_event.Po \
+ ./$(DEPDIR)/sg_sat_read_gplog.Po \
+ ./$(DEPDIR)/sg_sat_set_features.Po \
+ ./$(DEPDIR)/sg_scan_linux.Po ./$(DEPDIR)/sg_scan_win32.Po \
+ ./$(DEPDIR)/sg_seek.Po ./$(DEPDIR)/sg_senddiag.Po \
+ ./$(DEPDIR)/sg_ses.Po ./$(DEPDIR)/sg_ses_microcode.Po \
+ ./$(DEPDIR)/sg_start.Po ./$(DEPDIR)/sg_stpg.Po \
+ ./$(DEPDIR)/sg_stream_ctl.Po ./$(DEPDIR)/sg_sync.Po \
+ ./$(DEPDIR)/sg_test_rwbuf.Po ./$(DEPDIR)/sg_timestamp.Po \
+ ./$(DEPDIR)/sg_turs.Po ./$(DEPDIR)/sg_unmap.Po \
+ ./$(DEPDIR)/sg_verify.Po ./$(DEPDIR)/sg_vpd.Po \
+ ./$(DEPDIR)/sg_vpd_common.Po ./$(DEPDIR)/sg_vpd_vendor.Po \
+ ./$(DEPDIR)/sg_wr_mode.Po ./$(DEPDIR)/sg_write_buffer.Po \
+ ./$(DEPDIR)/sg_write_long.Po ./$(DEPDIR)/sg_write_same.Po \
+ ./$(DEPDIR)/sg_write_verify.Po ./$(DEPDIR)/sg_write_x.Po \
+ ./$(DEPDIR)/sg_xcopy.Po ./$(DEPDIR)/sg_z_act_query.Po \
+ ./$(DEPDIR)/sg_zone.Po ./$(DEPDIR)/sginfo.Po \
+ ./$(DEPDIR)/sgm_dd.Po ./$(DEPDIR)/sgp_dd.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = sg_bg_ctl.c sg_compare_and_write.c sg_copy_results.c sg_dd.c \
+ sg_decode_sense.c sg_emc_trespass.c sg_format.c \
+ sg_get_config.c sg_get_elem_status.c sg_get_lba_status.c \
+ sg_ident.c $(sg_inq_SOURCES) sg_logs.c sg_luns.c sg_map.c \
+ sg_map26.c sg_modes.c sg_opcodes.c sg_persist.c sg_prevent.c \
+ sg_raw.c sg_rbuf.c sg_rdac.c sg_read.c sg_read_attr.c \
+ sg_read_block_limits.c sg_read_buffer.c sg_read_long.c \
+ sg_readcap.c sg_reassign.c sg_referrals.c sg_rem_rest_elem.c \
+ sg_rep_density.c sg_rep_pip.c sg_rep_zones.c sg_requests.c \
+ sg_reset.c sg_reset_wp.c sg_rmsn.c sg_rtpg.c sg_safte.c \
+ sg_sanitize.c sg_sat_identify.c sg_sat_phy_event.c \
+ sg_sat_read_gplog.c sg_sat_set_features.c $(sg_scan_SOURCES) \
+ sg_seek.c sg_senddiag.c sg_ses.c sg_ses_microcode.c sg_start.c \
+ sg_stpg.c sg_stream_ctl.c sg_sync.c sg_test_rwbuf.c \
+ sg_timestamp.c sg_turs.c sg_unmap.c sg_verify.c \
+ $(sg_vpd_SOURCES) sg_wr_mode.c sg_write_buffer.c \
+ sg_write_long.c sg_write_same.c sg_write_verify.c sg_write_x.c \
+ sg_xcopy.c sg_z_act_query.c sg_zone.c sginfo.c sgm_dd.c \
+ sgp_dd.c
+DIST_SOURCES = sg_bg_ctl.c sg_compare_and_write.c sg_copy_results.c \
+ sg_dd.c sg_decode_sense.c sg_emc_trespass.c sg_format.c \
+ sg_get_config.c sg_get_elem_status.c sg_get_lba_status.c \
+ sg_ident.c $(sg_inq_SOURCES) sg_logs.c sg_luns.c sg_map.c \
+ sg_map26.c sg_modes.c sg_opcodes.c sg_persist.c sg_prevent.c \
+ sg_raw.c sg_rbuf.c sg_rdac.c sg_read.c sg_read_attr.c \
+ sg_read_block_limits.c sg_read_buffer.c sg_read_long.c \
+ sg_readcap.c sg_reassign.c sg_referrals.c sg_rem_rest_elem.c \
+ sg_rep_density.c sg_rep_pip.c sg_rep_zones.c sg_requests.c \
+ sg_reset.c sg_reset_wp.c sg_rmsn.c sg_rtpg.c sg_safte.c \
+ sg_sanitize.c sg_sat_identify.c sg_sat_phy_event.c \
+ sg_sat_read_gplog.c sg_sat_set_features.c \
+ $(am__sg_scan_SOURCES_DIST) sg_seek.c sg_senddiag.c sg_ses.c \
+ sg_ses_microcode.c sg_start.c sg_stpg.c sg_stream_ctl.c \
+ sg_sync.c sg_test_rwbuf.c sg_timestamp.c sg_turs.c sg_unmap.c \
+ sg_verify.c $(sg_vpd_SOURCES) sg_wr_mode.c sg_write_buffer.c \
+ sg_write_long.c sg_write_same.c sg_write_verify.c sg_write_x.c \
+ sg_xcopy.c sg_z_act_query.c sg_zone.c sginfo.c sgm_dd.c \
+ sgp_dd.c
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETOPT_O_FILES = @GETOPT_O_FILES@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PTHREAD_LIB = @PTHREAD_LIB@
+RANLIB = @RANLIB@
+RT_LIB = @RT_LIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+os_cflags = @os_cflags@
+os_libs = @os_libs@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+sg_scan_SOURCES = $(am__append_2) $(am__append_4) $(am__append_6)
+@DEBUG_FALSE@DBG_CFLAGS =
+
+# This is active if --enable-debug given to ./configure
+# removed -Wduplicated-branches because needs gcc-8
+@DEBUG_TRUE@DBG_CFLAGS = -Wextra -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+@DEBUG_FALSE@DBG_CPPFLAGS =
+@DEBUG_TRUE@DBG_CPPFLAGS = -DDEBUG
+
+# For C++/clang testing
+
+# -std=<s> can be c99, c11, gnu11, etc. Default is gnu11
+# -Wall is no longer all warnings. Add -W (since renamed to -Wextra) for more
+AM_CPPFLAGS = -iquote ${top_srcdir}/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(DBG_CPPFLAGS)
+AM_CFLAGS = -Wall -W $(DBG_CFLAGS)
+# AM_CFLAGS = -Wall -W $(DBG_CFLAGS) -fanalyzer
+# AM_CFLAGS = -Wall -W -Wextra -Wmisleading-indentation -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init
+# AM_CFLAGS = -Wall -W -pedantic -std=c11
+# AM_CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# AM_CFLAGS = -Wall -W -pedantic -std=c++98
+# AM_CFLAGS = -Wall -W -pedantic -std=c++11
+# AM_CFLAGS = -Wall -W -pedantic -std=c++14
+# AM_CFLAGS = -Wall -W -pedantic -std=c++1z
+# AM_CFLAGS = -Wall -W -pedantic -std=c++20
+# AM_CFLAGS = -Wall -W -pedantic -std=c++23
+sg_bg_ctl_LDADD = ../lib/libsgutils2.la
+sg_compare_and_write_LDADD = ../lib/libsgutils2.la
+sg_copy_results_LDADD = ../lib/libsgutils2.la
+sg_dd_LDADD = ../lib/libsgutils2.la
+sg_decode_sense_LDADD = ../lib/libsgutils2.la
+sg_emc_trespass_LDADD = ../lib/libsgutils2.la
+sg_format_LDADD = ../lib/libsgutils2.la
+sg_get_config_LDADD = ../lib/libsgutils2.la
+sg_get_elem_status_LDADD = ../lib/libsgutils2.la
+sg_get_lba_status_LDADD = ../lib/libsgutils2.la
+sg_ident_LDADD = ../lib/libsgutils2.la
+sginfo_LDADD = ../lib/libsgutils2.la
+sg_inq_SOURCES = sg_inq.c sg_inq_data.c sg_vpd_common.c
+sg_inq_LDADD = ../lib/libsgutils2.la
+sg_logs_LDADD = ../lib/libsgutils2.la
+sg_luns_LDADD = ../lib/libsgutils2.la
+sg_map_LDADD = ../lib/libsgutils2.la
+sgm_dd_LDADD = ../lib/libsgutils2.la
+sg_modes_LDADD = ../lib/libsgutils2.la
+sg_opcodes_LDADD = ../lib/libsgutils2.la
+sgp_dd_LDADD = ../lib/libsgutils2.la @PTHREAD_LIB@
+sg_persist_LDADD = ../lib/libsgutils2.la
+sg_prevent_LDADD = ../lib/libsgutils2.la
+sg_raw_LDADD = ../lib/libsgutils2.la
+sg_rbuf_LDADD = ../lib/libsgutils2.la
+sg_rdac_LDADD = ../lib/libsgutils2.la
+sg_read_LDADD = ../lib/libsgutils2.la
+sg_read_attr_LDADD = ../lib/libsgutils2.la
+sg_readcap_LDADD = ../lib/libsgutils2.la
+sg_read_block_limits_LDADD = ../lib/libsgutils2.la
+sg_read_buffer_LDADD = ../lib/libsgutils2.la
+sg_read_long_LDADD = ../lib/libsgutils2.la
+sg_reassign_LDADD = ../lib/libsgutils2.la
+sg_referrals_LDADD = ../lib/libsgutils2.la
+sg_rem_rest_elem_LDADD = ../lib/libsgutils2.la
+sg_rep_density_LDADD = ../lib/libsgutils2.la
+sg_rep_pip_LDADD = ../lib/libsgutils2.la
+sg_rep_zones_LDADD = ../lib/libsgutils2.la
+sg_requests_LDADD = ../lib/libsgutils2.la
+sg_reset_wp_LDADD = ../lib/libsgutils2.la
+sg_rmsn_LDADD = ../lib/libsgutils2.la
+sg_rtpg_LDADD = ../lib/libsgutils2.la
+sg_safte_LDADD = ../lib/libsgutils2.la
+sg_sanitize_LDADD = ../lib/libsgutils2.la
+sg_sat_identify_LDADD = ../lib/libsgutils2.la
+sg_sat_phy_event_LDADD = ../lib/libsgutils2.la
+sg_sat_read_gplog_LDADD = ../lib/libsgutils2.la
+sg_sat_set_features_LDADD = ../lib/libsgutils2.la
+
+# sg_scan_SOURCES list is already set above in the platform-specific sections
+sg_scan_LDADD = ../lib/libsgutils2.la
+sg_seek_LDADD = ../lib/libsgutils2.la @RT_LIB@
+sg_senddiag_LDADD = ../lib/libsgutils2.la
+sg_ses_LDADD = ../lib/libsgutils2.la
+sg_ses_microcode_LDADD = ../lib/libsgutils2.la
+sg_start_LDADD = ../lib/libsgutils2.la
+sg_stpg_LDADD = ../lib/libsgutils2.la
+sg_stream_ctl_LDADD = ../lib/libsgutils2.la
+sg_sync_LDADD = ../lib/libsgutils2.la
+sg_test_rwbuf_LDADD = ../lib/libsgutils2.la
+sg_timestamp_LDADD = ../lib/libsgutils2.la
+sg_turs_LDADD = ../lib/libsgutils2.la @RT_LIB@
+sg_unmap_LDADD = ../lib/libsgutils2.la
+sg_verify_LDADD = ../lib/libsgutils2.la
+sg_vpd_SOURCES = sg_vpd.c sg_vpd_vendor.c sg_vpd_common.c
+sg_vpd_LDADD = ../lib/libsgutils2.la
+sg_wr_mode_LDADD = ../lib/libsgutils2.la
+sg_write_buffer_LDADD = ../lib/libsgutils2.la
+sg_write_long_LDADD = ../lib/libsgutils2.la
+sg_write_same_LDADD = ../lib/libsgutils2.la
+sg_write_verify_LDADD = ../lib/libsgutils2.la
+sg_write_x_LDADD = ../lib/libsgutils2.la
+sg_xcopy_LDADD = ../lib/libsgutils2.la
+sg_zone_LDADD = ../lib/libsgutils2.la
+sg_z_act_query_LDADD = ../lib/libsgutils2.la
+EXTRA_DIST = \
+ sg_vpd_common.h \
+ BSD_LICENSE
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+sg_bg_ctl$(EXEEXT): $(sg_bg_ctl_OBJECTS) $(sg_bg_ctl_DEPENDENCIES) $(EXTRA_sg_bg_ctl_DEPENDENCIES)
+ @rm -f sg_bg_ctl$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_bg_ctl_OBJECTS) $(sg_bg_ctl_LDADD) $(LIBS)
+
+sg_compare_and_write$(EXEEXT): $(sg_compare_and_write_OBJECTS) $(sg_compare_and_write_DEPENDENCIES) $(EXTRA_sg_compare_and_write_DEPENDENCIES)
+ @rm -f sg_compare_and_write$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_compare_and_write_OBJECTS) $(sg_compare_and_write_LDADD) $(LIBS)
+
+sg_copy_results$(EXEEXT): $(sg_copy_results_OBJECTS) $(sg_copy_results_DEPENDENCIES) $(EXTRA_sg_copy_results_DEPENDENCIES)
+ @rm -f sg_copy_results$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_copy_results_OBJECTS) $(sg_copy_results_LDADD) $(LIBS)
+
+sg_dd$(EXEEXT): $(sg_dd_OBJECTS) $(sg_dd_DEPENDENCIES) $(EXTRA_sg_dd_DEPENDENCIES)
+ @rm -f sg_dd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_dd_OBJECTS) $(sg_dd_LDADD) $(LIBS)
+
+sg_decode_sense$(EXEEXT): $(sg_decode_sense_OBJECTS) $(sg_decode_sense_DEPENDENCIES) $(EXTRA_sg_decode_sense_DEPENDENCIES)
+ @rm -f sg_decode_sense$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_decode_sense_OBJECTS) $(sg_decode_sense_LDADD) $(LIBS)
+
+sg_emc_trespass$(EXEEXT): $(sg_emc_trespass_OBJECTS) $(sg_emc_trespass_DEPENDENCIES) $(EXTRA_sg_emc_trespass_DEPENDENCIES)
+ @rm -f sg_emc_trespass$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_emc_trespass_OBJECTS) $(sg_emc_trespass_LDADD) $(LIBS)
+
+sg_format$(EXEEXT): $(sg_format_OBJECTS) $(sg_format_DEPENDENCIES) $(EXTRA_sg_format_DEPENDENCIES)
+ @rm -f sg_format$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_format_OBJECTS) $(sg_format_LDADD) $(LIBS)
+
+sg_get_config$(EXEEXT): $(sg_get_config_OBJECTS) $(sg_get_config_DEPENDENCIES) $(EXTRA_sg_get_config_DEPENDENCIES)
+ @rm -f sg_get_config$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_get_config_OBJECTS) $(sg_get_config_LDADD) $(LIBS)
+
+sg_get_elem_status$(EXEEXT): $(sg_get_elem_status_OBJECTS) $(sg_get_elem_status_DEPENDENCIES) $(EXTRA_sg_get_elem_status_DEPENDENCIES)
+ @rm -f sg_get_elem_status$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_get_elem_status_OBJECTS) $(sg_get_elem_status_LDADD) $(LIBS)
+
+sg_get_lba_status$(EXEEXT): $(sg_get_lba_status_OBJECTS) $(sg_get_lba_status_DEPENDENCIES) $(EXTRA_sg_get_lba_status_DEPENDENCIES)
+ @rm -f sg_get_lba_status$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_get_lba_status_OBJECTS) $(sg_get_lba_status_LDADD) $(LIBS)
+
+sg_ident$(EXEEXT): $(sg_ident_OBJECTS) $(sg_ident_DEPENDENCIES) $(EXTRA_sg_ident_DEPENDENCIES)
+ @rm -f sg_ident$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_ident_OBJECTS) $(sg_ident_LDADD) $(LIBS)
+
+sg_inq$(EXEEXT): $(sg_inq_OBJECTS) $(sg_inq_DEPENDENCIES) $(EXTRA_sg_inq_DEPENDENCIES)
+ @rm -f sg_inq$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_inq_OBJECTS) $(sg_inq_LDADD) $(LIBS)
+
+sg_logs$(EXEEXT): $(sg_logs_OBJECTS) $(sg_logs_DEPENDENCIES) $(EXTRA_sg_logs_DEPENDENCIES)
+ @rm -f sg_logs$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_logs_OBJECTS) $(sg_logs_LDADD) $(LIBS)
+
+sg_luns$(EXEEXT): $(sg_luns_OBJECTS) $(sg_luns_DEPENDENCIES) $(EXTRA_sg_luns_DEPENDENCIES)
+ @rm -f sg_luns$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_luns_OBJECTS) $(sg_luns_LDADD) $(LIBS)
+
+sg_map$(EXEEXT): $(sg_map_OBJECTS) $(sg_map_DEPENDENCIES) $(EXTRA_sg_map_DEPENDENCIES)
+ @rm -f sg_map$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_map_OBJECTS) $(sg_map_LDADD) $(LIBS)
+
+sg_map26$(EXEEXT): $(sg_map26_OBJECTS) $(sg_map26_DEPENDENCIES) $(EXTRA_sg_map26_DEPENDENCIES)
+ @rm -f sg_map26$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_map26_OBJECTS) $(sg_map26_LDADD) $(LIBS)
+
+sg_modes$(EXEEXT): $(sg_modes_OBJECTS) $(sg_modes_DEPENDENCIES) $(EXTRA_sg_modes_DEPENDENCIES)
+ @rm -f sg_modes$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_modes_OBJECTS) $(sg_modes_LDADD) $(LIBS)
+
+sg_opcodes$(EXEEXT): $(sg_opcodes_OBJECTS) $(sg_opcodes_DEPENDENCIES) $(EXTRA_sg_opcodes_DEPENDENCIES)
+ @rm -f sg_opcodes$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_opcodes_OBJECTS) $(sg_opcodes_LDADD) $(LIBS)
+
+sg_persist$(EXEEXT): $(sg_persist_OBJECTS) $(sg_persist_DEPENDENCIES) $(EXTRA_sg_persist_DEPENDENCIES)
+ @rm -f sg_persist$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_persist_OBJECTS) $(sg_persist_LDADD) $(LIBS)
+
+sg_prevent$(EXEEXT): $(sg_prevent_OBJECTS) $(sg_prevent_DEPENDENCIES) $(EXTRA_sg_prevent_DEPENDENCIES)
+ @rm -f sg_prevent$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_prevent_OBJECTS) $(sg_prevent_LDADD) $(LIBS)
+
+sg_raw$(EXEEXT): $(sg_raw_OBJECTS) $(sg_raw_DEPENDENCIES) $(EXTRA_sg_raw_DEPENDENCIES)
+ @rm -f sg_raw$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_raw_OBJECTS) $(sg_raw_LDADD) $(LIBS)
+
+sg_rbuf$(EXEEXT): $(sg_rbuf_OBJECTS) $(sg_rbuf_DEPENDENCIES) $(EXTRA_sg_rbuf_DEPENDENCIES)
+ @rm -f sg_rbuf$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rbuf_OBJECTS) $(sg_rbuf_LDADD) $(LIBS)
+
+sg_rdac$(EXEEXT): $(sg_rdac_OBJECTS) $(sg_rdac_DEPENDENCIES) $(EXTRA_sg_rdac_DEPENDENCIES)
+ @rm -f sg_rdac$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rdac_OBJECTS) $(sg_rdac_LDADD) $(LIBS)
+
+sg_read$(EXEEXT): $(sg_read_OBJECTS) $(sg_read_DEPENDENCIES) $(EXTRA_sg_read_DEPENDENCIES)
+ @rm -f sg_read$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_read_OBJECTS) $(sg_read_LDADD) $(LIBS)
+
+sg_read_attr$(EXEEXT): $(sg_read_attr_OBJECTS) $(sg_read_attr_DEPENDENCIES) $(EXTRA_sg_read_attr_DEPENDENCIES)
+ @rm -f sg_read_attr$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_read_attr_OBJECTS) $(sg_read_attr_LDADD) $(LIBS)
+
+sg_read_block_limits$(EXEEXT): $(sg_read_block_limits_OBJECTS) $(sg_read_block_limits_DEPENDENCIES) $(EXTRA_sg_read_block_limits_DEPENDENCIES)
+ @rm -f sg_read_block_limits$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_read_block_limits_OBJECTS) $(sg_read_block_limits_LDADD) $(LIBS)
+
+sg_read_buffer$(EXEEXT): $(sg_read_buffer_OBJECTS) $(sg_read_buffer_DEPENDENCIES) $(EXTRA_sg_read_buffer_DEPENDENCIES)
+ @rm -f sg_read_buffer$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_read_buffer_OBJECTS) $(sg_read_buffer_LDADD) $(LIBS)
+
+sg_read_long$(EXEEXT): $(sg_read_long_OBJECTS) $(sg_read_long_DEPENDENCIES) $(EXTRA_sg_read_long_DEPENDENCIES)
+ @rm -f sg_read_long$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_read_long_OBJECTS) $(sg_read_long_LDADD) $(LIBS)
+
+sg_readcap$(EXEEXT): $(sg_readcap_OBJECTS) $(sg_readcap_DEPENDENCIES) $(EXTRA_sg_readcap_DEPENDENCIES)
+ @rm -f sg_readcap$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_readcap_OBJECTS) $(sg_readcap_LDADD) $(LIBS)
+
+sg_reassign$(EXEEXT): $(sg_reassign_OBJECTS) $(sg_reassign_DEPENDENCIES) $(EXTRA_sg_reassign_DEPENDENCIES)
+ @rm -f sg_reassign$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_reassign_OBJECTS) $(sg_reassign_LDADD) $(LIBS)
+
+sg_referrals$(EXEEXT): $(sg_referrals_OBJECTS) $(sg_referrals_DEPENDENCIES) $(EXTRA_sg_referrals_DEPENDENCIES)
+ @rm -f sg_referrals$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_referrals_OBJECTS) $(sg_referrals_LDADD) $(LIBS)
+
+sg_rem_rest_elem$(EXEEXT): $(sg_rem_rest_elem_OBJECTS) $(sg_rem_rest_elem_DEPENDENCIES) $(EXTRA_sg_rem_rest_elem_DEPENDENCIES)
+ @rm -f sg_rem_rest_elem$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rem_rest_elem_OBJECTS) $(sg_rem_rest_elem_LDADD) $(LIBS)
+
+sg_rep_density$(EXEEXT): $(sg_rep_density_OBJECTS) $(sg_rep_density_DEPENDENCIES) $(EXTRA_sg_rep_density_DEPENDENCIES)
+ @rm -f sg_rep_density$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rep_density_OBJECTS) $(sg_rep_density_LDADD) $(LIBS)
+
+sg_rep_pip$(EXEEXT): $(sg_rep_pip_OBJECTS) $(sg_rep_pip_DEPENDENCIES) $(EXTRA_sg_rep_pip_DEPENDENCIES)
+ @rm -f sg_rep_pip$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rep_pip_OBJECTS) $(sg_rep_pip_LDADD) $(LIBS)
+
+sg_rep_zones$(EXEEXT): $(sg_rep_zones_OBJECTS) $(sg_rep_zones_DEPENDENCIES) $(EXTRA_sg_rep_zones_DEPENDENCIES)
+ @rm -f sg_rep_zones$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rep_zones_OBJECTS) $(sg_rep_zones_LDADD) $(LIBS)
+
+sg_requests$(EXEEXT): $(sg_requests_OBJECTS) $(sg_requests_DEPENDENCIES) $(EXTRA_sg_requests_DEPENDENCIES)
+ @rm -f sg_requests$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_requests_OBJECTS) $(sg_requests_LDADD) $(LIBS)
+
+sg_reset$(EXEEXT): $(sg_reset_OBJECTS) $(sg_reset_DEPENDENCIES) $(EXTRA_sg_reset_DEPENDENCIES)
+ @rm -f sg_reset$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_reset_OBJECTS) $(sg_reset_LDADD) $(LIBS)
+
+sg_reset_wp$(EXEEXT): $(sg_reset_wp_OBJECTS) $(sg_reset_wp_DEPENDENCIES) $(EXTRA_sg_reset_wp_DEPENDENCIES)
+ @rm -f sg_reset_wp$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_reset_wp_OBJECTS) $(sg_reset_wp_LDADD) $(LIBS)
+
+sg_rmsn$(EXEEXT): $(sg_rmsn_OBJECTS) $(sg_rmsn_DEPENDENCIES) $(EXTRA_sg_rmsn_DEPENDENCIES)
+ @rm -f sg_rmsn$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rmsn_OBJECTS) $(sg_rmsn_LDADD) $(LIBS)
+
+sg_rtpg$(EXEEXT): $(sg_rtpg_OBJECTS) $(sg_rtpg_DEPENDENCIES) $(EXTRA_sg_rtpg_DEPENDENCIES)
+ @rm -f sg_rtpg$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_rtpg_OBJECTS) $(sg_rtpg_LDADD) $(LIBS)
+
+sg_safte$(EXEEXT): $(sg_safte_OBJECTS) $(sg_safte_DEPENDENCIES) $(EXTRA_sg_safte_DEPENDENCIES)
+ @rm -f sg_safte$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_safte_OBJECTS) $(sg_safte_LDADD) $(LIBS)
+
+sg_sanitize$(EXEEXT): $(sg_sanitize_OBJECTS) $(sg_sanitize_DEPENDENCIES) $(EXTRA_sg_sanitize_DEPENDENCIES)
+ @rm -f sg_sanitize$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sanitize_OBJECTS) $(sg_sanitize_LDADD) $(LIBS)
+
+sg_sat_identify$(EXEEXT): $(sg_sat_identify_OBJECTS) $(sg_sat_identify_DEPENDENCIES) $(EXTRA_sg_sat_identify_DEPENDENCIES)
+ @rm -f sg_sat_identify$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sat_identify_OBJECTS) $(sg_sat_identify_LDADD) $(LIBS)
+
+sg_sat_phy_event$(EXEEXT): $(sg_sat_phy_event_OBJECTS) $(sg_sat_phy_event_DEPENDENCIES) $(EXTRA_sg_sat_phy_event_DEPENDENCIES)
+ @rm -f sg_sat_phy_event$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sat_phy_event_OBJECTS) $(sg_sat_phy_event_LDADD) $(LIBS)
+
+sg_sat_read_gplog$(EXEEXT): $(sg_sat_read_gplog_OBJECTS) $(sg_sat_read_gplog_DEPENDENCIES) $(EXTRA_sg_sat_read_gplog_DEPENDENCIES)
+ @rm -f sg_sat_read_gplog$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sat_read_gplog_OBJECTS) $(sg_sat_read_gplog_LDADD) $(LIBS)
+
+sg_sat_set_features$(EXEEXT): $(sg_sat_set_features_OBJECTS) $(sg_sat_set_features_DEPENDENCIES) $(EXTRA_sg_sat_set_features_DEPENDENCIES)
+ @rm -f sg_sat_set_features$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sat_set_features_OBJECTS) $(sg_sat_set_features_LDADD) $(LIBS)
+
+sg_scan$(EXEEXT): $(sg_scan_OBJECTS) $(sg_scan_DEPENDENCIES) $(EXTRA_sg_scan_DEPENDENCIES)
+ @rm -f sg_scan$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_scan_OBJECTS) $(sg_scan_LDADD) $(LIBS)
+
+sg_seek$(EXEEXT): $(sg_seek_OBJECTS) $(sg_seek_DEPENDENCIES) $(EXTRA_sg_seek_DEPENDENCIES)
+ @rm -f sg_seek$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_seek_OBJECTS) $(sg_seek_LDADD) $(LIBS)
+
+sg_senddiag$(EXEEXT): $(sg_senddiag_OBJECTS) $(sg_senddiag_DEPENDENCIES) $(EXTRA_sg_senddiag_DEPENDENCIES)
+ @rm -f sg_senddiag$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_senddiag_OBJECTS) $(sg_senddiag_LDADD) $(LIBS)
+
+sg_ses$(EXEEXT): $(sg_ses_OBJECTS) $(sg_ses_DEPENDENCIES) $(EXTRA_sg_ses_DEPENDENCIES)
+ @rm -f sg_ses$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_ses_OBJECTS) $(sg_ses_LDADD) $(LIBS)
+
+sg_ses_microcode$(EXEEXT): $(sg_ses_microcode_OBJECTS) $(sg_ses_microcode_DEPENDENCIES) $(EXTRA_sg_ses_microcode_DEPENDENCIES)
+ @rm -f sg_ses_microcode$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_ses_microcode_OBJECTS) $(sg_ses_microcode_LDADD) $(LIBS)
+
+sg_start$(EXEEXT): $(sg_start_OBJECTS) $(sg_start_DEPENDENCIES) $(EXTRA_sg_start_DEPENDENCIES)
+ @rm -f sg_start$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_start_OBJECTS) $(sg_start_LDADD) $(LIBS)
+
+sg_stpg$(EXEEXT): $(sg_stpg_OBJECTS) $(sg_stpg_DEPENDENCIES) $(EXTRA_sg_stpg_DEPENDENCIES)
+ @rm -f sg_stpg$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_stpg_OBJECTS) $(sg_stpg_LDADD) $(LIBS)
+
+sg_stream_ctl$(EXEEXT): $(sg_stream_ctl_OBJECTS) $(sg_stream_ctl_DEPENDENCIES) $(EXTRA_sg_stream_ctl_DEPENDENCIES)
+ @rm -f sg_stream_ctl$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_stream_ctl_OBJECTS) $(sg_stream_ctl_LDADD) $(LIBS)
+
+sg_sync$(EXEEXT): $(sg_sync_OBJECTS) $(sg_sync_DEPENDENCIES) $(EXTRA_sg_sync_DEPENDENCIES)
+ @rm -f sg_sync$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_sync_OBJECTS) $(sg_sync_LDADD) $(LIBS)
+
+sg_test_rwbuf$(EXEEXT): $(sg_test_rwbuf_OBJECTS) $(sg_test_rwbuf_DEPENDENCIES) $(EXTRA_sg_test_rwbuf_DEPENDENCIES)
+ @rm -f sg_test_rwbuf$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_test_rwbuf_OBJECTS) $(sg_test_rwbuf_LDADD) $(LIBS)
+
+sg_timestamp$(EXEEXT): $(sg_timestamp_OBJECTS) $(sg_timestamp_DEPENDENCIES) $(EXTRA_sg_timestamp_DEPENDENCIES)
+ @rm -f sg_timestamp$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_timestamp_OBJECTS) $(sg_timestamp_LDADD) $(LIBS)
+
+sg_turs$(EXEEXT): $(sg_turs_OBJECTS) $(sg_turs_DEPENDENCIES) $(EXTRA_sg_turs_DEPENDENCIES)
+ @rm -f sg_turs$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_turs_OBJECTS) $(sg_turs_LDADD) $(LIBS)
+
+sg_unmap$(EXEEXT): $(sg_unmap_OBJECTS) $(sg_unmap_DEPENDENCIES) $(EXTRA_sg_unmap_DEPENDENCIES)
+ @rm -f sg_unmap$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_unmap_OBJECTS) $(sg_unmap_LDADD) $(LIBS)
+
+sg_verify$(EXEEXT): $(sg_verify_OBJECTS) $(sg_verify_DEPENDENCIES) $(EXTRA_sg_verify_DEPENDENCIES)
+ @rm -f sg_verify$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_verify_OBJECTS) $(sg_verify_LDADD) $(LIBS)
+
+sg_vpd$(EXEEXT): $(sg_vpd_OBJECTS) $(sg_vpd_DEPENDENCIES) $(EXTRA_sg_vpd_DEPENDENCIES)
+ @rm -f sg_vpd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_vpd_OBJECTS) $(sg_vpd_LDADD) $(LIBS)
+
+sg_wr_mode$(EXEEXT): $(sg_wr_mode_OBJECTS) $(sg_wr_mode_DEPENDENCIES) $(EXTRA_sg_wr_mode_DEPENDENCIES)
+ @rm -f sg_wr_mode$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_wr_mode_OBJECTS) $(sg_wr_mode_LDADD) $(LIBS)
+
+sg_write_buffer$(EXEEXT): $(sg_write_buffer_OBJECTS) $(sg_write_buffer_DEPENDENCIES) $(EXTRA_sg_write_buffer_DEPENDENCIES)
+ @rm -f sg_write_buffer$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_write_buffer_OBJECTS) $(sg_write_buffer_LDADD) $(LIBS)
+
+sg_write_long$(EXEEXT): $(sg_write_long_OBJECTS) $(sg_write_long_DEPENDENCIES) $(EXTRA_sg_write_long_DEPENDENCIES)
+ @rm -f sg_write_long$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_write_long_OBJECTS) $(sg_write_long_LDADD) $(LIBS)
+
+sg_write_same$(EXEEXT): $(sg_write_same_OBJECTS) $(sg_write_same_DEPENDENCIES) $(EXTRA_sg_write_same_DEPENDENCIES)
+ @rm -f sg_write_same$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_write_same_OBJECTS) $(sg_write_same_LDADD) $(LIBS)
+
+sg_write_verify$(EXEEXT): $(sg_write_verify_OBJECTS) $(sg_write_verify_DEPENDENCIES) $(EXTRA_sg_write_verify_DEPENDENCIES)
+ @rm -f sg_write_verify$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_write_verify_OBJECTS) $(sg_write_verify_LDADD) $(LIBS)
+
+sg_write_x$(EXEEXT): $(sg_write_x_OBJECTS) $(sg_write_x_DEPENDENCIES) $(EXTRA_sg_write_x_DEPENDENCIES)
+ @rm -f sg_write_x$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_write_x_OBJECTS) $(sg_write_x_LDADD) $(LIBS)
+
+sg_xcopy$(EXEEXT): $(sg_xcopy_OBJECTS) $(sg_xcopy_DEPENDENCIES) $(EXTRA_sg_xcopy_DEPENDENCIES)
+ @rm -f sg_xcopy$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_xcopy_OBJECTS) $(sg_xcopy_LDADD) $(LIBS)
+
+sg_z_act_query$(EXEEXT): $(sg_z_act_query_OBJECTS) $(sg_z_act_query_DEPENDENCIES) $(EXTRA_sg_z_act_query_DEPENDENCIES)
+ @rm -f sg_z_act_query$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_z_act_query_OBJECTS) $(sg_z_act_query_LDADD) $(LIBS)
+
+sg_zone$(EXEEXT): $(sg_zone_OBJECTS) $(sg_zone_DEPENDENCIES) $(EXTRA_sg_zone_DEPENDENCIES)
+ @rm -f sg_zone$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sg_zone_OBJECTS) $(sg_zone_LDADD) $(LIBS)
+
+sginfo$(EXEEXT): $(sginfo_OBJECTS) $(sginfo_DEPENDENCIES) $(EXTRA_sginfo_DEPENDENCIES)
+ @rm -f sginfo$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sginfo_OBJECTS) $(sginfo_LDADD) $(LIBS)
+
+sgm_dd$(EXEEXT): $(sgm_dd_OBJECTS) $(sgm_dd_DEPENDENCIES) $(EXTRA_sgm_dd_DEPENDENCIES)
+ @rm -f sgm_dd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sgm_dd_OBJECTS) $(sgm_dd_LDADD) $(LIBS)
+
+sgp_dd$(EXEEXT): $(sgp_dd_OBJECTS) $(sgp_dd_DEPENDENCIES) $(EXTRA_sgp_dd_DEPENDENCIES)
+ @rm -f sgp_dd$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(sgp_dd_OBJECTS) $(sgp_dd_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_bg_ctl.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_compare_and_write.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_copy_results.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_dd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_decode_sense.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_emc_trespass.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_format.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_get_config.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_get_elem_status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_get_lba_status.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_ident.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_inq.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_inq_data.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_logs.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_luns.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_map.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_map26.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_modes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_opcodes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_persist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_prevent.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_raw.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rbuf.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rdac.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_read.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_read_attr.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_read_block_limits.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_read_buffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_read_long.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_readcap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_reassign.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_referrals.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rem_rest_elem.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rep_density.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rep_pip.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rep_zones.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_requests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_reset.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_reset_wp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rmsn.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_rtpg.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_safte.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sanitize.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sat_identify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sat_phy_event.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sat_read_gplog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sat_set_features.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_scan_linux.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_scan_win32.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_seek.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_senddiag.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_ses.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_ses_microcode.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_start.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_stpg.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_stream_ctl.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_sync.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_test_rwbuf.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_timestamp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_turs.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_unmap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_verify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_vpd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_vpd_common.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_vpd_vendor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_wr_mode.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_buffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_long.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_same.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_verify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_x.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_xcopy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_z_act_query.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_zone.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sginfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgm_dd.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgp_dd.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sg_bg_ctl.Po
+ -rm -f ./$(DEPDIR)/sg_compare_and_write.Po
+ -rm -f ./$(DEPDIR)/sg_copy_results.Po
+ -rm -f ./$(DEPDIR)/sg_dd.Po
+ -rm -f ./$(DEPDIR)/sg_decode_sense.Po
+ -rm -f ./$(DEPDIR)/sg_emc_trespass.Po
+ -rm -f ./$(DEPDIR)/sg_format.Po
+ -rm -f ./$(DEPDIR)/sg_get_config.Po
+ -rm -f ./$(DEPDIR)/sg_get_elem_status.Po
+ -rm -f ./$(DEPDIR)/sg_get_lba_status.Po
+ -rm -f ./$(DEPDIR)/sg_ident.Po
+ -rm -f ./$(DEPDIR)/sg_inq.Po
+ -rm -f ./$(DEPDIR)/sg_inq_data.Po
+ -rm -f ./$(DEPDIR)/sg_logs.Po
+ -rm -f ./$(DEPDIR)/sg_luns.Po
+ -rm -f ./$(DEPDIR)/sg_map.Po
+ -rm -f ./$(DEPDIR)/sg_map26.Po
+ -rm -f ./$(DEPDIR)/sg_modes.Po
+ -rm -f ./$(DEPDIR)/sg_opcodes.Po
+ -rm -f ./$(DEPDIR)/sg_persist.Po
+ -rm -f ./$(DEPDIR)/sg_prevent.Po
+ -rm -f ./$(DEPDIR)/sg_raw.Po
+ -rm -f ./$(DEPDIR)/sg_rbuf.Po
+ -rm -f ./$(DEPDIR)/sg_rdac.Po
+ -rm -f ./$(DEPDIR)/sg_read.Po
+ -rm -f ./$(DEPDIR)/sg_read_attr.Po
+ -rm -f ./$(DEPDIR)/sg_read_block_limits.Po
+ -rm -f ./$(DEPDIR)/sg_read_buffer.Po
+ -rm -f ./$(DEPDIR)/sg_read_long.Po
+ -rm -f ./$(DEPDIR)/sg_readcap.Po
+ -rm -f ./$(DEPDIR)/sg_reassign.Po
+ -rm -f ./$(DEPDIR)/sg_referrals.Po
+ -rm -f ./$(DEPDIR)/sg_rem_rest_elem.Po
+ -rm -f ./$(DEPDIR)/sg_rep_density.Po
+ -rm -f ./$(DEPDIR)/sg_rep_pip.Po
+ -rm -f ./$(DEPDIR)/sg_rep_zones.Po
+ -rm -f ./$(DEPDIR)/sg_requests.Po
+ -rm -f ./$(DEPDIR)/sg_reset.Po
+ -rm -f ./$(DEPDIR)/sg_reset_wp.Po
+ -rm -f ./$(DEPDIR)/sg_rmsn.Po
+ -rm -f ./$(DEPDIR)/sg_rtpg.Po
+ -rm -f ./$(DEPDIR)/sg_safte.Po
+ -rm -f ./$(DEPDIR)/sg_sanitize.Po
+ -rm -f ./$(DEPDIR)/sg_sat_identify.Po
+ -rm -f ./$(DEPDIR)/sg_sat_phy_event.Po
+ -rm -f ./$(DEPDIR)/sg_sat_read_gplog.Po
+ -rm -f ./$(DEPDIR)/sg_sat_set_features.Po
+ -rm -f ./$(DEPDIR)/sg_scan_linux.Po
+ -rm -f ./$(DEPDIR)/sg_scan_win32.Po
+ -rm -f ./$(DEPDIR)/sg_seek.Po
+ -rm -f ./$(DEPDIR)/sg_senddiag.Po
+ -rm -f ./$(DEPDIR)/sg_ses.Po
+ -rm -f ./$(DEPDIR)/sg_ses_microcode.Po
+ -rm -f ./$(DEPDIR)/sg_start.Po
+ -rm -f ./$(DEPDIR)/sg_stpg.Po
+ -rm -f ./$(DEPDIR)/sg_stream_ctl.Po
+ -rm -f ./$(DEPDIR)/sg_sync.Po
+ -rm -f ./$(DEPDIR)/sg_test_rwbuf.Po
+ -rm -f ./$(DEPDIR)/sg_timestamp.Po
+ -rm -f ./$(DEPDIR)/sg_turs.Po
+ -rm -f ./$(DEPDIR)/sg_unmap.Po
+ -rm -f ./$(DEPDIR)/sg_verify.Po
+ -rm -f ./$(DEPDIR)/sg_vpd.Po
+ -rm -f ./$(DEPDIR)/sg_vpd_common.Po
+ -rm -f ./$(DEPDIR)/sg_vpd_vendor.Po
+ -rm -f ./$(DEPDIR)/sg_wr_mode.Po
+ -rm -f ./$(DEPDIR)/sg_write_buffer.Po
+ -rm -f ./$(DEPDIR)/sg_write_long.Po
+ -rm -f ./$(DEPDIR)/sg_write_same.Po
+ -rm -f ./$(DEPDIR)/sg_write_verify.Po
+ -rm -f ./$(DEPDIR)/sg_write_x.Po
+ -rm -f ./$(DEPDIR)/sg_xcopy.Po
+ -rm -f ./$(DEPDIR)/sg_z_act_query.Po
+ -rm -f ./$(DEPDIR)/sg_zone.Po
+ -rm -f ./$(DEPDIR)/sginfo.Po
+ -rm -f ./$(DEPDIR)/sgm_dd.Po
+ -rm -f ./$(DEPDIR)/sgp_dd.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sg_bg_ctl.Po
+ -rm -f ./$(DEPDIR)/sg_compare_and_write.Po
+ -rm -f ./$(DEPDIR)/sg_copy_results.Po
+ -rm -f ./$(DEPDIR)/sg_dd.Po
+ -rm -f ./$(DEPDIR)/sg_decode_sense.Po
+ -rm -f ./$(DEPDIR)/sg_emc_trespass.Po
+ -rm -f ./$(DEPDIR)/sg_format.Po
+ -rm -f ./$(DEPDIR)/sg_get_config.Po
+ -rm -f ./$(DEPDIR)/sg_get_elem_status.Po
+ -rm -f ./$(DEPDIR)/sg_get_lba_status.Po
+ -rm -f ./$(DEPDIR)/sg_ident.Po
+ -rm -f ./$(DEPDIR)/sg_inq.Po
+ -rm -f ./$(DEPDIR)/sg_inq_data.Po
+ -rm -f ./$(DEPDIR)/sg_logs.Po
+ -rm -f ./$(DEPDIR)/sg_luns.Po
+ -rm -f ./$(DEPDIR)/sg_map.Po
+ -rm -f ./$(DEPDIR)/sg_map26.Po
+ -rm -f ./$(DEPDIR)/sg_modes.Po
+ -rm -f ./$(DEPDIR)/sg_opcodes.Po
+ -rm -f ./$(DEPDIR)/sg_persist.Po
+ -rm -f ./$(DEPDIR)/sg_prevent.Po
+ -rm -f ./$(DEPDIR)/sg_raw.Po
+ -rm -f ./$(DEPDIR)/sg_rbuf.Po
+ -rm -f ./$(DEPDIR)/sg_rdac.Po
+ -rm -f ./$(DEPDIR)/sg_read.Po
+ -rm -f ./$(DEPDIR)/sg_read_attr.Po
+ -rm -f ./$(DEPDIR)/sg_read_block_limits.Po
+ -rm -f ./$(DEPDIR)/sg_read_buffer.Po
+ -rm -f ./$(DEPDIR)/sg_read_long.Po
+ -rm -f ./$(DEPDIR)/sg_readcap.Po
+ -rm -f ./$(DEPDIR)/sg_reassign.Po
+ -rm -f ./$(DEPDIR)/sg_referrals.Po
+ -rm -f ./$(DEPDIR)/sg_rem_rest_elem.Po
+ -rm -f ./$(DEPDIR)/sg_rep_density.Po
+ -rm -f ./$(DEPDIR)/sg_rep_pip.Po
+ -rm -f ./$(DEPDIR)/sg_rep_zones.Po
+ -rm -f ./$(DEPDIR)/sg_requests.Po
+ -rm -f ./$(DEPDIR)/sg_reset.Po
+ -rm -f ./$(DEPDIR)/sg_reset_wp.Po
+ -rm -f ./$(DEPDIR)/sg_rmsn.Po
+ -rm -f ./$(DEPDIR)/sg_rtpg.Po
+ -rm -f ./$(DEPDIR)/sg_safte.Po
+ -rm -f ./$(DEPDIR)/sg_sanitize.Po
+ -rm -f ./$(DEPDIR)/sg_sat_identify.Po
+ -rm -f ./$(DEPDIR)/sg_sat_phy_event.Po
+ -rm -f ./$(DEPDIR)/sg_sat_read_gplog.Po
+ -rm -f ./$(DEPDIR)/sg_sat_set_features.Po
+ -rm -f ./$(DEPDIR)/sg_scan_linux.Po
+ -rm -f ./$(DEPDIR)/sg_scan_win32.Po
+ -rm -f ./$(DEPDIR)/sg_seek.Po
+ -rm -f ./$(DEPDIR)/sg_senddiag.Po
+ -rm -f ./$(DEPDIR)/sg_ses.Po
+ -rm -f ./$(DEPDIR)/sg_ses_microcode.Po
+ -rm -f ./$(DEPDIR)/sg_start.Po
+ -rm -f ./$(DEPDIR)/sg_stpg.Po
+ -rm -f ./$(DEPDIR)/sg_stream_ctl.Po
+ -rm -f ./$(DEPDIR)/sg_sync.Po
+ -rm -f ./$(DEPDIR)/sg_test_rwbuf.Po
+ -rm -f ./$(DEPDIR)/sg_timestamp.Po
+ -rm -f ./$(DEPDIR)/sg_turs.Po
+ -rm -f ./$(DEPDIR)/sg_unmap.Po
+ -rm -f ./$(DEPDIR)/sg_verify.Po
+ -rm -f ./$(DEPDIR)/sg_vpd.Po
+ -rm -f ./$(DEPDIR)/sg_vpd_common.Po
+ -rm -f ./$(DEPDIR)/sg_vpd_vendor.Po
+ -rm -f ./$(DEPDIR)/sg_wr_mode.Po
+ -rm -f ./$(DEPDIR)/sg_write_buffer.Po
+ -rm -f ./$(DEPDIR)/sg_write_long.Po
+ -rm -f ./$(DEPDIR)/sg_write_same.Po
+ -rm -f ./$(DEPDIR)/sg_write_verify.Po
+ -rm -f ./$(DEPDIR)/sg_write_x.Po
+ -rm -f ./$(DEPDIR)/sg_xcopy.Po
+ -rm -f ./$(DEPDIR)/sg_z_act_query.Po
+ -rm -f ./$(DEPDIR)/sg_zone.Po
+ -rm -f ./$(DEPDIR)/sginfo.Po
+ -rm -f ./$(DEPDIR)/sgm_dd.Po
+ -rm -f ./$(DEPDIR)/sgp_dd.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/sg_bg_ctl.c b/src/sg_bg_ctl.c
new file mode 100644
index 00000000..81e43bd4
--- /dev/null
+++ b/src/sg_bg_ctl.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2016-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.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_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI BACKGROUND CONTROL command to the given SCSI
+ * device. Based on sbc4r10.pdf .
+ */
+
+static const char * version_str = "1.13 20211114";
+
+#define BACKGROUND_CONTROL_SA 0x15
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+static const char * cmd_name = "Background control";
+
+
+static struct option long_options[] = {
+ {"ctl", required_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"time", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_bg_ctl [--ctl=CTL] [--help] [--time=TN] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n");
+ pr2serr(" where:\n"
+ " --ctl=CTL|-c CTL CTL is background operation control "
+ "value\n"
+ " default: 0 -> don't change background "
+ "operations\n"
+ " 1 -> start; 2 -> stop\n"
+ " --help|-h print out usage message\n"
+ " --time=TN|-t TN TN (units 100 ms) is max time to perform "
+ "background\n"
+ " operations (def: 0 -> no limit)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI BACKGROUND CONTROL command. It can start or "
+ "stop\n'advanced background operations'. Operations started by "
+ "this command\n(i.e. when ctl=1) are termed as 'host initiated' "
+ "and allow a resource or\nthin provisioned device (disk) to "
+ "perform garbage collection type operations.\nThese may "
+ "degrade performance while they occur. Hence it is best to\n"
+ "perform this action while the computer is not too busy.\n");
+}
+
+/* Invokes a SCSI BACKGROUND CONTROL command (SBC-4). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_background_control(int sg_fd, unsigned int bo_ctl, unsigned int bo_time,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t bcCDB[16] = {SG_SERVICE_ACTION_IN_16,
+ BACKGROUND_CONTROL_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (bo_ctl)
+ bcCDB[2] |= (bo_ctl & 0x3) << 6;
+ if (bo_time)
+ bcCDB[3] = bo_time;
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", cmd_name,
+ sg_get_command_str(bcCDB, (int)sizeof(bcCDB), false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", cmd_name);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, bcCDB, sizeof(bcCDB));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd = -1;
+ int res, c;
+ unsigned int ctl = 0;
+ unsigned int time_tnth = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:ht:vV", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ if ((1 != sscanf(optarg, "%4u", &ctl)) || (ctl > 3)) {
+ pr2serr("--ctl= expects a number from 0 to 3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 't':
+ if ((1 != sscanf(optarg, "%4u", &time_tnth)) ||
+ (time_tnth > 255)) {
+ pr2serr("--time= expects a number from 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ res = sg_ll_background_control(sg_fd, ctl, time_tnth, true, verbose);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+ }
+
+fini:
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_bg_ctl failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_compare_and_write.c b/src/sg_compare_and_write.c
new file mode 100644
index 00000000..b8ed82df
--- /dev/null
+++ b/src/sg_compare_and_write.c
@@ -0,0 +1,623 @@
+/*
+* Copyright (c) 2012-2022, Kaminario Technologies LTD
+* All rights reserved.
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of the <organization> nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This command performs a SCSI COMPARE AND WRITE. See SBC-3 at
+ * https://www.t10.org
+ *
+ */
+
+#ifndef __sun
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.32 20220127";
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_NUM_BLOCKS (1)
+#define DEF_BLOCKS_PER_TRANSFER 8
+#define DEF_TIMEOUT_SECS 60
+
+#define COMPARE_AND_WRITE_OPCODE (0x89)
+#define COMPARE_AND_WRITE_CDB_SIZE (16)
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+#define ME "sg_compare_and_write: "
+
+static struct option long_options[] = {
+ {"dpo", no_argument, 0, 'd'},
+ {"fua", no_argument, 0, 'f'},
+ {"fua_nv", no_argument, 0, 'F'},
+ {"fua-nv", no_argument, 0, 'F'},
+ {"group", required_argument, 0, 'g'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"inc", required_argument, 0, 'C'},
+ {"inw", required_argument, 0, 'D'},
+ {"lba", required_argument, 0, 'l'},
+ {"num", required_argument, 0, 'n'},
+ {"quiet", no_argument, 0, 'q'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrprotect", required_argument, 0, 'w'},
+ {"xferlen", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+struct caw_flags {
+ bool dpo;
+ bool fua;
+ bool fua_nv;
+ int group;
+ int wrprotect;
+};
+
+struct opts_t {
+ bool quiet;
+ bool verbose_given;
+ bool version_given;
+ bool wfn_given;
+ int numblocks;
+ int verbose;
+ int timeout;
+ int xfer_len;
+ uint64_t lba;
+ const char * ifn;
+ const char * wfn;
+ const char * device_name;
+ struct caw_flags flags;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_compare_and_write [--dpo] [--fua] [--fua_nv] "
+ "[--grpnum=GN] [--help]\n"
+ " --in=IF|--inc=IF [--inw=WF] "
+ "--lba=LBA "
+ "[--num=NUM]\n"
+ " [--quiet] [--timeout=TO] "
+ "[--verbose] [--version]\n"
+ " [--wrprotect=WP] [--xferlen=LEN] "
+ "DEVICE\n"
+ " where:\n"
+ " --dpo|-d set the dpo bit in cdb (def: "
+ "clear)\n"
+ " --fua|-f set the fua bit in cdb (def: "
+ "clear)\n"
+ " --fua_nv|-F set the fua_nv bit in cdb (def: "
+ "clear)\n"
+ " --grpnum=GN|-g GN GN is GROUP NUMBER to set in "
+ "cdb (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --in=IF|-i IF IF is a file containing a compare "
+ "buffer and\n"
+ " optionally a write buffer (when "
+ "--inw=WF is\n"
+ " not given)\n"
+ " --inc=IF|-C IF The same as the --in option\n"
+ " --inw=WF|-D WF WF is a file containing a write "
+ "buffer\n"
+ " --lba=LBA|-l LBA LBA of the first block to compare "
+ "and write\n"
+ " --num=NUM|-n NUM number of blocks to "
+ "compare/write (def: 1)\n"
+ " --quiet|-q suppress MISCOMPARE report to "
+ "stderr,\n"
+ " still sets exit status of 14\n"
+ " --timeout=TO|-t TO timeout for the command "
+ "(def: 60 secs)\n"
+ " --verbose|-v increase verbosity (use '-vv' for "
+ "more)\n"
+ " --version|-V print version string then exit\n"
+ " --wrprotect=WP|-w WP write protect information "
+ "(def: 0)\n"
+ " --xferlen=LEN|-x LEN number of bytes to transfer. "
+ "Default is\n"
+ " (2 * NUM * 512) or 1024 when "
+ "NUM is 1\n"
+ "\n"
+ "Performs a SCSI COMPARE AND WRITE operation. Sends a double "
+ "size\nbuffer, the first half is used to compare what is at "
+ "LBA for NUM\nblocks. If and only if the comparison is "
+ "equal, then the second\nhalf of the buffer is written to "
+ "LBA for NUM blocks.\n");
+}
+
+static int
+parse_args(int argc, char* argv[], struct opts_t * op)
+{
+ bool lba_given = false;
+ bool if_given = false;
+ int c;
+ int64_t ll;
+
+ op->numblocks = DEF_NUM_BLOCKS;
+ /* COMPARE AND WRITE defines 2*buffers compare + write */
+ op->xfer_len = 0;
+ op->timeout = DEF_TIMEOUT_SECS;
+ op->device_name = NULL;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "C:dD:fFg:hi:l:n:qt:vVw:x:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'C':
+ case 'i':
+ op->ifn = optarg;
+ if_given = true;
+ break;
+ case 'd':
+ op->flags.dpo = true;
+ break;
+ case 'D':
+ op->wfn = optarg;
+ op->wfn_given = true;
+ break;
+ case 'F':
+ op->flags.fua_nv = true;
+ break;
+ case 'f':
+ op->flags.fua = true;
+ break;
+ case 'g':
+ op->flags.group = sg_get_num(optarg);
+ if ((op->flags.group < 0) ||
+ (op->flags.group > 63)) {
+ pr2serr("argument to '--grpnum=' expected to "
+ "be 0 to 63\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ exit(0);
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ goto out_err_no_usage;
+ }
+ op->lba = (uint64_t)ll;
+ lba_given = true;
+ break;
+ case 'n':
+ op->numblocks = sg_get_num(optarg);
+ if ((op->numblocks < 0) || (op->numblocks > 255)) {
+ pr2serr("bad argument to '--num', expect 0 "
+ "to 255\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'q':
+ op->quiet = true;
+ break;
+ case 't':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout'\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->flags.wrprotect = sg_get_num(optarg);
+ if (op->flags.wrprotect >> 3) {
+ pr2serr("bad argument to '--wrprotect' not "
+ "in range 0-7\n");
+ goto out_err_no_usage;
+ }
+ break;
+ case 'x':
+ op->xfer_len = sg_get_num(optarg);
+ if (op->xfer_len < 0) {
+ pr2serr("bad argument to '--xferlen'\n");
+ goto out_err_no_usage;
+ }
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ goto out_err;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ goto out_err;
+ }
+ }
+ if (op->version_given && (! op->verbose_given))
+ return 0;
+ if (NULL == op->device_name) {
+ pr2serr("missing device name!\n");
+ goto out_err;
+ }
+ if (! if_given) {
+ pr2serr("missing input file\n");
+ goto out_err;
+ }
+ if (! lba_given) {
+ pr2serr("missing lba\n");
+ goto out_err;
+ }
+ if (0 == op->xfer_len)
+ op->xfer_len = 2 * op->numblocks * DEF_BLOCK_SIZE;
+ return 0;
+
+out_err:
+ usage();
+
+out_err_no_usage:
+ exit(1);
+}
+
+#define FLAG_FUA (0x8)
+#define FLAG_FUA_NV (0x2)
+#define FLAG_DPO (0x10)
+#define WRPROTECT_MASK (0x7)
+#define WRPROTECT_SHIFT (5)
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, unsigned int blocks,
+ int64_t start_block, struct caw_flags flags)
+{
+ memset(cdbp, 0, COMPARE_AND_WRITE_CDB_SIZE);
+ cdbp[0] = COMPARE_AND_WRITE_OPCODE;
+ cdbp[1] = (flags.wrprotect & WRPROTECT_MASK) << WRPROTECT_SHIFT;
+ if (flags.dpo)
+ cdbp[1] |= FLAG_DPO;
+ if (flags.fua)
+ cdbp[1] |= FLAG_FUA;
+ if (flags.fua_nv)
+ cdbp[1] |= FLAG_FUA_NV;
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ /* cdbp[10-12] are reserved */
+ cdbp[13] = (uint8_t)(blocks & 0xff);
+ cdbp[14] = (uint8_t)(flags.group & GRPNUM_MASK);
+ return 0;
+}
+
+/* Returns 0 for success, SG_LIB_CAT_MISCOMPARE if compare fails,
+ * various other SG_LIB_CAT_*, otherwise -1 . */
+static int
+sg_ll_compare_and_write(int sg_fd, uint8_t * buff, int blocks,
+ int64_t lba, int xfer_len, struct caw_flags flags,
+ bool noisy, int verbose)
+{
+ bool valid;
+ int sense_cat, slen, res, ret;
+ uint64_t ull = 0;
+ struct sg_pt_base * ptvp;
+ uint8_t cawCmd[COMPARE_AND_WRITE_CDB_SIZE];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (sg_build_scsi_cdb(cawCmd, blocks, lba, flags)) {
+ pr2serr(ME "bad cdb build, lba=0x%" PRIx64 ", blocks=%d\n",
+ lba, blocks);
+ return -1;
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Could not construct scsit_pt_obj, out of memory\n");
+ return -1;
+ }
+
+ set_scsi_pt_cdb(ptvp, cawCmd, COMPARE_AND_WRITE_CDB_SIZE);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, buff, xfer_len);
+ if (verbose > 1) {
+ char b[128];
+
+ pr2serr(" Compare and write cdb: %s\n",
+ sg_get_command_str(cawCmd, COMPARE_AND_WRITE_CDB_SIZE, false,
+ sizeof(b), b));
+ }
+ if ((verbose > 2) && (xfer_len > 0)) {
+ pr2serr(" Data-out buffer contents:\n");
+ hex2stderr(buff, xfer_len, 1);
+ }
+ res = do_scsi_pt(ptvp, sg_fd, DEF_TIMEOUT_SECS, verbose);
+ ret = sg_cmds_process_resp(ptvp, "COMPARE AND WRITE", res,
+ noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen,
+ &ull);
+ if (valid)
+ pr2serr("Medium or hardware error starting "
+ "at lba=%" PRIu64 " [0x%" PRIx64
+ "]\n", ull, ull);
+ else
+ pr2serr("Medium or hardware error\n");
+ ret = sense_cat;
+ break;
+ case SG_LIB_CAT_MISCOMPARE:
+ ret = sense_cat;
+ if (! (noisy || verbose))
+ break;
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid)
+ pr2serr("Miscompare at byte offset: %" PRIu64
+ " [0x%" PRIx64 "]\n", ull, ull);
+ else
+ pr2serr("Miscompare reported\n");
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (verbose)
+ sg_print_command_len(cawCmd,
+ COMPARE_AND_WRITE_CDB_SIZE);
+ /* FALL THROUGH */
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static int
+open_if(const char * fn, bool got_stdin)
+{
+ int fd;
+
+ if (got_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(fn, O_RDONLY);
+ if (fd < 0) {
+ pr2serr(ME "open error: %s: %s\n", fn,
+ safe_strerror(errno));
+ return -SG_LIB_FILE_ERROR;
+ }
+ }
+ if (sg_set_binary_mode(fd) < 0) {
+ perror("sg_set_binary_mode");
+ return -SG_LIB_FILE_ERROR;
+ }
+ return fd;
+}
+
+static int
+open_dev(const char * outf, int verbose)
+{
+ int sg_fd = sg_cmds_open_device(outf, false /* rw */, verbose);
+
+ if ((sg_fd < 0) && verbose)
+ pr2serr(ME "open error: %s: %s\n", outf,
+ safe_strerror(-sg_fd));
+ return sg_fd;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool ifn_stdin;
+ int res, half_xlen, vb;
+ int infd = -1;
+ int wfd = -1;
+ int devfd = -1;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * free_wrkBuff = NULL;
+ struct opts_t * op;
+ struct opts_t opts;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_args(argc, argv, op);
+ if (res != 0) {
+ pr2serr("Failed parsing args\n");
+ goto out;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and "
+ "continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special "
+ "action\n");
+#endif
+ if (op->version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+ vb = op->verbose;
+
+ if (vb) {
+ pr2serr("Running COMPARE AND WRITE command with the "
+ "following options:\n in=%s ", op->ifn);
+ if (op->wfn_given)
+ pr2serr("inw=%s ", op->wfn);
+ pr2serr("device=%s\n lba=0x%" PRIx64 " num_blocks=%d "
+ "xfer_len=%d timeout=%d\n", op->device_name,
+ op->lba, op->numblocks, op->xfer_len, op->timeout);
+ }
+ ifn_stdin = ((1 == strlen(op->ifn)) && ('-' == op->ifn[0]));
+ infd = open_if(op->ifn, ifn_stdin);
+ if (infd < 0) {
+ res = -infd;
+ goto out;
+ }
+ if (op->wfn_given) {
+ if ((1 == strlen(op->wfn)) && ('-' == op->wfn[0])) {
+ pr2serr(ME "don't allow stdin for write file\n");
+ res = SG_LIB_FILE_ERROR;
+ goto out;
+ }
+ wfd = open_if(op->wfn, false);
+ if (wfd < 0) {
+ res = -wfd;
+ goto out;
+ }
+ }
+
+ devfd = open_dev(op->device_name, vb);
+ if (devfd < 0) {
+ res = sg_convert_errno(-devfd);
+ goto out;
+ }
+
+ wrkBuff = (uint8_t *)sg_memalign(op->xfer_len, 0, &free_wrkBuff,
+ vb > 3);
+ if (NULL == wrkBuff) {
+ pr2serr("Not enough user memory\n");
+ res = sg_convert_errno(ENOMEM);
+ goto out;
+ }
+
+ if (op->wfn_given) {
+ half_xlen = op->xfer_len / 2;
+ res = read(infd, wrkBuff, half_xlen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->ifn);
+ goto out;
+ } else if (res < half_xlen) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, half_xlen, op->ifn);
+ goto out;
+ }
+ res = read(wfd, wrkBuff + half_xlen, half_xlen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->wfn);
+ goto out;
+ } else if (res < half_xlen) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, half_xlen, op->wfn);
+ goto out;
+ }
+ } else {
+ res = read(infd, wrkBuff, op->xfer_len);
+ if (res < 0) {
+ pr2serr("Could not read from %s", op->ifn);
+ goto out;
+ } else if (res < op->xfer_len) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n",
+ res, op->xfer_len, op->ifn);
+ goto out;
+ }
+ }
+ res = sg_ll_compare_and_write(devfd, wrkBuff, op->numblocks, op->lba,
+ op->xfer_len, op->flags, ! op->quiet,
+ vb);
+ if (0 != res) {
+ char b[80];
+
+ switch (res) {
+ case SG_LIB_CAT_MEDIUM_HARD:
+ case SG_LIB_CAT_MISCOMPARE:
+ case SG_LIB_FILE_ERROR:
+ break; /* already reported */
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr(ME "SCSI COMPARE AND WRITE: %s\n", b);
+ break;
+ }
+ }
+out:
+ if (free_wrkBuff)
+ free(free_wrkBuff);
+ if ((infd >= 0) && (! ifn_stdin))
+ close(infd);
+ if (wfd >= 0)
+ close(wfd);
+ if (devfd >= 0)
+ close(devfd);
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_compare_and_write failed: ", res))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return res;
+}
diff --git a/src/sg_copy_results.c b/src/sg_copy_results.c
new file mode 100644
index 00000000..17012be2
--- /dev/null
+++ b/src/sg_copy_results.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2011-2018 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 <stdarg.h>
+#include <stdbool.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.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.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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 const char * version_str = "1.23 20180625";
+
+
+#define MAX_XFER_LEN 10000
+
+
+#define ME "sg_copy_results: "
+
+#define EBUFF_SZ 256
+
+struct descriptor_type {
+ int code;
+ char desc[124];
+};
+
+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" },
+ { -1, "" }
+};
+
+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" },
+ { -1, "" }
+};
+
+
+static void
+scsi_failed_segment_details(uint8_t *rcBuff, unsigned int rcBuffLen)
+{
+ int senseLen;
+ unsigned int len;
+ char senseBuff[1024];
+
+ if (rcBuffLen < 4) {
+ pr2serr(" <<not enough data to procedd report>>\n");
+ return;
+ }
+ len = sg_get_unaligned_be32(rcBuff + 0);
+ if (len + 4 > rcBuffLen) {
+ pr2serr(" <<report len %d > %d too long for internal buffer, output "
+ "truncated\n", len, rcBuffLen);
+ }
+ if (len < 52) {
+ pr2serr(" <<no segment details, response data length %d\n", len);
+ return;
+ }
+ printf("Receive copy results (failed segment details):\n");
+ printf(" Extended copy command status: %d\n", rcBuff[56]);
+ senseLen = sg_get_unaligned_be16(rcBuff + 58);
+ sg_get_sense_str(" ", &rcBuff[60], senseLen, 0, 1024, senseBuff);
+ printf("%s", senseBuff);
+}
+
+static void
+scsi_copy_status(uint8_t *rcBuff, unsigned int rcBuffLen)
+{
+ unsigned int len;
+
+ if (rcBuffLen < 4) {
+ pr2serr(" <<not enough data to proceed report>>\n");
+ return;
+ }
+ len = sg_get_unaligned_be32(rcBuff + 0);
+ if (len + 4 > rcBuffLen) {
+ pr2serr(" <<report len %d > %d too long for internal buffer, output "
+ "truncated\n", len, rcBuffLen);
+ }
+ 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", sg_get_unaligned_be16(rcBuff + 5));
+ printf(" Transfer count units: %u\n", rcBuff[7]);
+ printf(" Transfer count: %u\n", sg_get_unaligned_be32(rcBuff + 8));
+}
+
+static void
+scsi_operating_parameters(uint8_t *rcBuff, unsigned int rcBuffLen)
+{
+ unsigned int len, n;
+
+ len = sg_get_unaligned_be32(rcBuff + 0);
+ if (len + 4 > rcBuffLen) {
+ pr2serr(" <<report len %d > %d too long for internal buffer, output "
+ "truncated\n", len, rcBuffLen);
+ }
+ printf("Receive copy results (report operating parameters):\n");
+ printf(" Supports no list identifier (SNLID): %s\n",
+ rcBuff[4] & 1 ? "yes" : "no");
+ n = sg_get_unaligned_be16(rcBuff + 8);
+ printf(" Maximum target descriptor count: %u\n", n);
+ n = sg_get_unaligned_be16(rcBuff + 10);
+ printf(" Maximum segment descriptor count: %u\n", n);
+ n = sg_get_unaligned_be32(rcBuff + 12);
+ printf(" Maximum descriptor list length: %u bytes\n", n);
+ n = sg_get_unaligned_be32(rcBuff + 16);
+ printf(" Maximum segment length: %u bytes\n", n);
+ n = sg_get_unaligned_be32(rcBuff + 20);
+ if (n == 0) {
+ printf(" Inline data not supported\n");
+ } else {
+ printf(" Maximum inline data length: %u bytes\n", n);
+ }
+ n = sg_get_unaligned_be32(rcBuff + 24);
+ printf(" Held data limit: %u bytes\n", n);
+ n = sg_get_unaligned_be32(rcBuff + 28);
+ printf(" Maximum stream device transfer size: %u bytes\n", n);
+ n = sg_get_unaligned_be16(rcBuff + 34);
+ printf(" Total concurrent copies: %u\n", n);
+ printf(" Maximum concurrent copies: %u\n", rcBuff[36]);
+ if (rcBuff[37] > 30)
+ printf(" Data segment granularity: 2**%u bytes\n", rcBuff[37]);
+ else
+ printf(" Data segment granularity: %u bytes\n",
+ (unsigned int)(1 << rcBuff[37]));
+ if (rcBuff[38] > 30)
+ printf(" Inline data granularity: %u bytes\n", rcBuff[38]);
+ else
+ printf(" Inline data granularity: %u bytes\n",
+ (unsigned int)(1 << rcBuff[38]));
+ if (rcBuff[39] > 30)
+ printf(" Held data granularity: 2**%u bytes\n", rcBuff[39]);
+ else
+ printf(" Held data granularity: %u bytes\n",
+ (unsigned int)(1 << rcBuff[39]));
+
+ printf(" Implemented descriptor list:\n");
+ for (n = 0; n < rcBuff[43]; n++) {
+ int code = rcBuff[44 + n];
+
+ if (code < 0x16) {
+ struct descriptor_type *seg_desc = segment_descriptor_codes;
+ while (strlen(seg_desc->desc)) {
+ if (seg_desc->code == code)
+ break;
+ seg_desc++;
+ }
+ printf(" Segment descriptor 0x%02x: %s\n", code,
+ strlen(seg_desc->desc) ? seg_desc->desc : "Reserved");
+ } 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 {
+ struct descriptor_type *tgt_desc = target_descriptor_codes;
+
+ while (strlen(tgt_desc->desc)) {
+ if (tgt_desc->code == code)
+ break;
+ tgt_desc++;
+ }
+ printf(" Target descriptor 0x%02x: %s\n", code,
+ strlen(tgt_desc->desc) ? tgt_desc->desc : "Reserved");
+ }
+ }
+ printf("\n");
+}
+
+static struct option long_options[] = {
+ {"failed", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"list_id", required_argument, 0, 'l'},
+ {"list-id", required_argument, 0, 'l'},
+ {"params", no_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'R'},
+ {"receive", no_argument, 0, 'r'},
+ {"status", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"xfer_len", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_copy_results [--failed|--params|--receive|--status] [--help]\n"
+ " [--hex] [--list_id=ID] [--readonly] "
+ "[--verbose]\n"
+ " [--version] [--xfer_len=BTL] DEVICE\n"
+ " where:\n"
+ " --failed|-f use FAILED SEGMENT DETAILS service "
+ "action\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H print out response buffer in hex\n"
+ " --list_id=ID|-l ID list identifier (default: 0)\n"
+ " --params|-p use OPERATING PARAMETERS service "
+ "action\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --receive|-r use RECEIVE DATA service action\n"
+ " --status|-s use COPY STATUS service action\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\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\nspecified by the service action parameters.\n"
+ );
+}
+
+static const char * rec_copy_name_arr[] = {
+ "Receive copy status(LID1)",
+ "Receive copy data(LID1)",
+ "Receive copy [0x2]",
+ "Receive copy operating parameters",
+ "Receive copy failure details(LID1)",
+};
+
+int
+main(int argc, char * argv[])
+{
+ bool do_hex = false;
+ bool o_readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, k;
+ int ret = 1;
+ int sa = 3;
+ int sg_fd = -1;
+ int verbose = 0;
+ int xfer_len = 520;
+ uint32_t list_id = 0;
+ const char * cp;
+ uint8_t * cpResultBuff = NULL;
+ uint8_t * free_cprb = NULL;
+ const char * device_name = NULL;
+ char file_name[256];
+
+ memset(file_name, 0, sizeof file_name);
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "fhHl:prRsvVx:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'f':
+ sa = 4;
+ break;
+ case 'H':
+ do_hex = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ k = sg_get_num(optarg);
+ if (-1 == k) {
+ pr2serr("bad argument to '--list_id'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ list_id = (uint32_t)k;
+ break;
+ case 'p':
+ sa = 3;
+ break;
+ case 'r':
+ sa = 1;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 's':
+ sa = 0;
+ break;
+ case 'v':
+ ++verbose;
+ verbose_given = true;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'x':
+ xfer_len = sg_get_num(optarg);
+ if (-1 == xfer_len) {
+ pr2serr("bad argument to '--xfer_len'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (xfer_len >= MAX_XFER_LEN) {
+ pr2serr("xfer_len (%d) is out of range ( < %d)\n", xfer_len,
+ MAX_XFER_LEN);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ cpResultBuff = (uint8_t *)sg_memalign(xfer_len, 0, &free_cprb,
+ verbose > 3);
+ if (NULL == cpResultBuff) {
+ pr2serr(ME "out of memory\n");
+ return sg_convert_errno(ENOMEM);
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto finish;
+ }
+
+ if ((sa < 0) || (sa >= (int)SG_ARRAY_SIZE(rec_copy_name_arr)))
+ cp = "Out of range service action";
+ else
+ cp = rec_copy_name_arr[sa];
+ if (verbose)
+ pr2serr(ME "issue %s to device %s\n\t\txfer_len= %d (0x%x), list_id=%"
+ PRIu32 "\n", cp, device_name, xfer_len, xfer_len, list_id);
+
+ res = sg_ll_receive_copy_results(sg_fd, sa, list_id, cpResultBuff,
+ xfer_len, true, verbose);
+ ret = res;
+ if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr(" SCSI %s failed: %s\n", cp, b);
+ goto finish;
+ }
+ if (do_hex) {
+ hex2stdout(cpResultBuff, xfer_len, 1);
+ goto finish;
+ }
+ switch (sa) {
+ case 4: /* Failed segment details */
+ scsi_failed_segment_details(cpResultBuff, xfer_len);
+ break;
+ case 3: /* Operating parameters */
+ scsi_operating_parameters(cpResultBuff, xfer_len);
+ break;
+ case 0: /* Copy status */
+ scsi_copy_status(cpResultBuff, xfer_len);
+ break;
+ default:
+ hex2stdout(cpResultBuff, xfer_len, 1);
+ break;
+ }
+
+finish:
+ if (free_cprb)
+ free(free_cprb);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr(ME "close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_copy_results failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_dd.c b/src/sg_dd.c
new file mode 100644
index 00000000..e099c335
--- /dev/null
+++ b/src/sg_dd.c
@@ -0,0 +1,2750 @@
+/* A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 1999 - 2022 D. Gilbert and P. Allworth
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a 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 logical 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. The actual size of the SCSI READ or WRITE command block can be
+ * selected with the "cdbsz" argument.
+ *
+ * This version is designed for the Linux kernel 2, 3, 4 and 5 series.
+ */
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h> /* for getrandom() system call */
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "6.35 20220826";
+
+
+#define ME "sg_dd: "
+
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+#define EBUFF_SZ 768
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SCSI_CDBSZ 10
+#define MAX_SCSI_CDBSZ 16
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define DEF_MODE_CDB_SZ 10
+#define DEF_MODE_RESP_LEN 252
+#define RW_ERR_RECOVERY_MP 1
+#define CACHING_MP 8
+#define CONTROL_MP 0xa
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+#define READ_LONG_OPCODE 0x3E
+#define READ_LONG_CMD_LEN 10
+#define READ_LONG_DEF_BLK_INC 8
+#define VERIFY10 0x2f
+#define VERIFY12 0xaf
+#define VERIFY16 0x8f
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely 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_NVME 128 /* NVMe char device (e.g. /dev/nvme2) */
+#define FT_RANDOM_0_FF 256 /* iflag=00, iflag=ff and iflag=random
+ overriding if=IFILE */
+#define FT_ERROR 512 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+
+#define SG_DD_BYPASS 999 /* failed but coe set */
+
+/* If platform does not support O_DIRECT then define it harmlessly */
+#ifndef O_DIRECT
+#define O_DIRECT 0
+#endif
+
+#define MIN_RESERVED_SIZE 8192
+
+#define MAX_UNIT_ATTENTIONS 10
+#define MAX_ABORTED_CMDS 256
+
+#define PROGRESS_TRIGGER_MS 120000 /* milliseconds: 2 minutes */
+#define PROGRESS2_TRIGGER_MS 60000 /* milliseconds: 1 minute */
+#define PROGRESS3_TRIGGER_MS 30000 /* milliseconds: 30 seconds */
+
+static int sum_of_resids = 0;
+
+static int64_t dd_count = -1;
+static int64_t req_count = 0;
+static int64_t in_full = 0;
+static int in_partial = 0;
+static int64_t out_full = 0;
+static int out_partial = 0;
+static int64_t out_sparse_num = 0;
+static int recovered_errs = 0;
+static int unrecovered_errs = 0;
+static int miscompare_errs = 0;
+static int read_longs = 0;
+static int num_retries = 0;
+static int progress = 0;
+static int dry_run = 0;
+
+static bool do_time = false;
+static bool start_tm_valid = false;
+static bool do_verify = false; /* when false: do copy */
+static int verbose = 0;
+static int blk_sz = 0;
+static int max_uas = MAX_UNIT_ATTENTIONS;
+static int max_aborted = MAX_ABORTED_CMDS;
+static int coe_limit = 0;
+static int coe_count = 0;
+static int cmd_timeout = DEF_TIMEOUT; /* in milliseconds */
+static uint32_t glob_pack_id = 0; /* pre-increment */
+static struct timeval start_tm;
+
+static uint8_t * zeros_buff = NULL;
+static uint8_t * free_zeros_buff = NULL;
+static int read_long_blk_inc = READ_LONG_DEF_BLK_INC;
+
+static long seed;
+#ifdef HAVE_SRAND48_R /* gcc extension. N.B. non-reentrant version slower */
+static struct drand48_data drand;/* opaque, used by srand48_r and mrand48_r */
+#endif
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+struct flags_t {
+ bool append;
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool flock;
+ bool ff;
+ bool fua;
+ bool nocreat;
+ bool random;
+ bool sgio;
+ bool sparse;
+ bool zero;
+ int cdbsz;
+ int cdl;
+ int coe;
+ int nocache;
+ int pdt;
+ int retries;
+};
+
+static struct flags_t iflag;
+static struct flags_t oflag;
+
+static void calc_duration_throughput(bool 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)
+ pr2serr(" remaining block count=%" PRId64 "\n", dd_count);
+ pr2serr("%s%" PRId64 "+%d records in\n", str, in_full - in_partial,
+ in_partial);
+ pr2serr("%s%" PRId64 "+%d records %s\n", str, out_full - out_partial,
+ out_partial, (do_verify ? "verified" : "out"));
+ if (oflag.sparse)
+ pr2serr("%s%" PRId64 " bypassed records out\n", str, out_sparse_num);
+ if (recovered_errs > 0)
+ pr2serr("%s%d recovered errors\n", str, recovered_errs);
+ if (num_retries > 0)
+ pr2serr("%s%d retries attempted\n", str, num_retries);
+ if (unrecovered_errs > 0) {
+ pr2serr("%s%d unrecovered error(s)\n", str, unrecovered_errs);
+ if (iflag.coe || oflag.coe)
+ pr2serr("%s%d read_longs fetched part of unrecovered read "
+ "errors\n", str, read_longs);
+ }
+ if (miscompare_errs > 0)
+ pr2serr("%s%d miscompare error(s)\n", str, miscompare_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);
+ pr2serr("Interrupted by signal,");
+ if (do_time)
+ calc_duration_throughput(false);
+ print_stats("");
+ kill(getpid (), sig);
+}
+
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time)
+ calc_duration_throughput(true);
+ print_stats(" ");
+}
+
+static bool bsg_major_checked = false;
+static int bsg_major = 0;
+
+static void
+find_bsg_major(void)
+{
+ int n;
+ char *cp;
+ FILE *fp;
+ const char *proc_devices = "/proc/devices";
+ char a[128];
+ char b[128];
+
+ if (NULL == (fp = fopen(proc_devices, "r"))) {
+ if (verbose)
+ pr2serr("fopen %s failed: %s\n", proc_devices, strerror(errno));
+ return;
+ }
+ while ((cp = fgets(b, sizeof(b), fp))) {
+ if ((1 == sscanf(b, "%126s", a)) &&
+ (0 == memcmp(a, "Character", 9)))
+ break;
+ }
+ while (cp && (cp = fgets(b, sizeof(b), fp))) {
+ if (2 == sscanf(b, "%d %126s", &n, a)) {
+ if (0 == strcmp("bsg", a)) {
+ bsg_major = n;
+ break;
+ }
+ } else
+ break;
+ }
+ if (verbose > 5) {
+ if (cp)
+ pr2serr("found bsg_major=%d\n", bsg_major);
+ else
+ pr2serr("found no bsg char device in %s\n", proc_devices);
+ }
+ fclose(fp);
+}
+
+static bool nvme_major_checked = false;
+static int nvme_major = 0;
+
+static void
+find_nvme_major(void)
+{
+ int n;
+ char *cp;
+ FILE *fp;
+ const char *proc_devices = "/proc/devices";
+ char a[128];
+ char b[128];
+
+ if (NULL == (fp = fopen(proc_devices, "r"))) {
+ if (verbose)
+ pr2serr("fopen %s failed: %s\n", proc_devices, strerror(errno));
+ return;
+ }
+ while ((cp = fgets(b, sizeof(b), fp))) {
+ if ((1 == sscanf(b, "%126s", a)) &&
+ (0 == memcmp(a, "Character", 9)))
+ break;
+ }
+ while (cp && (cp = fgets(b, sizeof(b), fp))) {
+ if (2 == sscanf(b, "%d %126s", &n, a)) {
+ if (0 == strcmp("nvme", a)) {
+ nvme_major = n;
+ break;
+ }
+ } else
+ break;
+ }
+ if (verbose > 5) {
+ if (cp)
+ pr2serr("found nvme_major=%d\n", bsg_major);
+ else
+ pr2serr("found no nvme char device in %s\n", proc_devices);
+ }
+ fclose(fp);
+}
+
+
+static int
+dd_filetype(const char * filename)
+{
+ size_t len = strlen(filename);
+ struct stat st;
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ /* 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 = true;
+ find_bsg_major();
+ }
+ if (bsg_major == (int)major(st.st_rdev))
+ return FT_SG;
+ if (! nvme_major_checked) {
+ nvme_major_checked = true;
+ find_nvme_major();
+ }
+ if (nvme_major == (int)major(st.st_rdev))
+ return FT_NVME; /* treat as sg device */
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ else if (S_ISFIFO(st.st_mode))
+ return FT_FIFO;
+ return FT_OTHER;
+}
+
+
+static char *
+dd_filetype_str(int ft, char * buff)
+{
+ int off = 0;
+
+ if (FT_DEV_NULL & ft)
+ off += sg_scnpr(buff + off, 32, "null device ");
+ if (FT_SG & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI generic (sg) device ");
+ if (FT_BLOCK & ft)
+ off += sg_scnpr(buff + off, 32, "block device ");
+ if (FT_FIFO & ft)
+ off += sg_scnpr(buff + off, 32, "fifo (named pipe) ");
+ if (FT_ST & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI tape device ");
+ if (FT_RAW & ft)
+ off += sg_scnpr(buff + off, 32, "raw device ");
+ if (FT_NVME & ft)
+ off += sg_scnpr(buff + off, 32, "NVMe char device ");
+ if (FT_OTHER & ft)
+ off += sg_scnpr(buff + off, 32, "other (perhaps ordinary file) ");
+ if (FT_ERROR & ft)
+ sg_scnpr(buff + off, 32, "unable to 'stat' file ");
+ return buff;
+}
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_dd [bs=BS] [conv=CONV] [count=COUNT] [ibs=BS] "
+ "[if=IFILE]\n"
+ " [iflag=FLAGS] [obs=BS] [of=OFILE] [oflag=FLAGS] "
+ "[seek=SEEK]\n"
+ " [skip=SKIP] [--dry-run] [--help] [--verbose] "
+ "[--version]\n\n"
+ " [blk_sgio=0|1] [bpt=BPT] [cdbsz=6|10|12|16] "
+ "[cdl=CDL]\n"
+ " [coe=0|1|2|3] [coe_limit=CL] [dio=0|1] "
+ "[odir=0|1]\n"
+ " [of2=OFILE2] [retries=RETR] [sync=0|1] "
+ "[time=0|1[,TO]]\n"
+ " [verbose=VERB] [--progress] [--verify]\n"
+ " where:\n"
+ " blk_sgio 0->block device use normal I/O(def), 1->use "
+ "SG_IO\n"
+ " bpt is blocks_per_transfer (default is 128 or 32 "
+ "when BS>=2048)\n"
+ " bs logical block size (default is 512)\n");
+ pr2serr(" cdbsz size of SCSI READ or WRITE cdb (default is "
+ "10)\n"
+ " cdl command duration limits value 0 to 7 (def: "
+ "0 (no cdl))\n"
+ " coe 0->exit on error (def), 1->continue on sg "
+ "error (zero\n"
+ " fill), 2->also try read_long on unrecovered "
+ "reads,\n"
+ " 3->and set the CORRCT bit on the read long\n"
+ " coe_limit limit consecutive 'bad' blocks on reads to CL "
+ "times\n"
+ " when COE>1 (default: 0 which is no limit)\n"
+ " conv comma separated list from: [nocreat,noerror,"
+ "notrunc,\n"
+ " null,sparse,sync]\n"
+ " count number of blocks to copy (def: device size)\n"
+ " dio for direct IO, 1->attempt, 0->indirect IO "
+ "(def)\n"
+ " ibs input logical 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: [00,coe,dio,direct,"
+ "dpo,dsync,\n"
+ " excl,ff,flock,fua,nocache,null,random,sgio]\n"
+ " obs output logical block size (if given must be "
+ "same as 'bs=')\n"
+ " odir 1->use O_DIRECT when opening block dev, "
+ "0->don't(def)\n"
+ " of file or device to write to (def: stdout), "
+ "OFILE of '.'\n");
+ pr2serr(" 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,coe,dio,"
+ "direct,dpo,\n"
+ " dsync,excl,flock,fua,nocache,nocreat,null,sgio,"
+ "sparse]\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"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on "
+ "OFILE after copy\n"
+ " time 0->no timing(def), 1->time plus calculate "
+ "throughput;\n"
+ " TO is command timeout in seconds (def: 60)\n"
+ " verbose 0->quiet(def), 1->some noise, 2->more noise, "
+ "etc\n"
+ " --dry-run do preparation but bypass copy (or read)\n"
+ " --help|-h print out this usage message then exit\n"
+ " --progress|-p print progress report every 2 minutes\n"
+ " --verbose|-v same as 'verbose=1', can be used multiple "
+ "times\n"
+ " --verify|-x do verify/compare rather than copy "
+ "(OFILE must\n"
+ " be a sg device)\n"
+ " --version|-V print version information then exit\n\n"
+ "Copy from IFILE to OFILE, similar to dd command; specialized "
+ "for SCSI\ndevices. If the --verify option is given then IFILE "
+ "is read and that data\nis used to compare with OFILE using "
+ "the VERIFY(n) SCSI command (with\nBYTCHK=1).\n");
+}
+
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res, verb;
+ unsigned int ui;
+ uint8_t rcBuff[RCAP16_REPLY_LEN];
+
+ verb = (verbose ? verbose - 1: 0);
+ res = sg_ll_readcap_10(sg_fd, false, 0, rcBuff, READ_CAP_REPLY_LEN, true,
+ 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, false, 0, rcBuff, RCAP16_REPLY_LEN,
+ true, verb);
+ if (0 != res)
+ return res;
+ ls = (int64_t)sg_get_unaligned_be64(rcBuff);
+ *num_sect = ls + 1;
+ *sect_sz = (int)sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ ui = sg_get_unaligned_be32(rcBuff);
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)ui + 1;
+ *sect_sz = (int)sg_get_unaligned_be32(rcBuff + 4);
+ }
+ if (verbose)
+ pr2serr(" number of blocks=%" PRId64 " [0x%" PRIx64 "], "
+ "logical block size=%d\n", *num_sect, *num_sect, *sect_sz);
+ return 0;
+}
+
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ if (verbose)
+ pr2serr(" [bgs64] number of blocks=%" PRId64 " [0x%" PRIx64
+ "], logical block size=%d\n", *num_sect, *num_sect,
+ *sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ if (verbose)
+ pr2serr(" [bgs] number of blocks=%" PRId64 " [0x%" PRIx64
+ "], logical block size=%d\n", *num_sect, *num_sect,
+ *sect_sz);
+ #endif
+ }
+ return 0;
+#else
+ if (verbose)
+ pr2serr(" BLKSSZGET+BLKGETSIZE ioctl not available\n");
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool is_verify, bool write_true,
+ bool fua, bool dpo, int cdl)
+{
+ int sz_ind;
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int ve_opcode[] = {0xff /* no VERIFY(6) */, VERIFY10, VERIFY12, VERIFY16};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+
+ memset(cdbp, 0, cdb_sz);
+ if (is_verify)
+ cdbp[1] = 0x2; /* (BYTCHK=1) << 1 */
+ else {
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ }
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ if (is_verify && write_true) {
+ pr2serr(ME "there is no VERIFY(6), choose a larger cdbsz\n");
+ return 1;
+ }
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr(ME "for 6 byte commands, maximum number of blocks is "
+ "256\n");
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr(ME "for 6 byte commands, can't address blocks beyond "
+ "%d\n", 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr(ME "for 6 byte commands, neither dpo nor fua bits "
+ "supported\n");
+ return 1;
+ }
+ break;
+ case 10:
+ sz_ind = 1;
+ if (is_verify && write_true)
+ cdbp[0] = ve_opcode[sz_ind];
+ else
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32(start_block, cdbp + 2);
+ sg_put_unaligned_be16(blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr(ME "for 10 byte commands, maximum number of blocks is "
+ "%d\n", 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ if (is_verify && write_true)
+ cdbp[0] = ve_opcode[sz_ind];
+ else
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32(start_block, cdbp + 2);
+ sg_put_unaligned_be32(blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ if (is_verify && write_true)
+ cdbp[0] = ve_opcode[sz_ind];
+ else
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ if ((! is_verify) && (cdl > 0)) {
+ if (cdl & 0x4)
+ cdbp[1] |= 0x1;
+ if (cdl & 0x3)
+ cdbp[14] |= ((cdl & 0x3) << 6);
+ }
+ sg_put_unaligned_be64(start_block, cdbp + 2);
+ sg_put_unaligned_be32(blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr(ME "expected cdb size of 6, 10, 12, or 16 but got %d\n",
+ cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* Does SCSI READ on IFILE. Returns 0 -> successful,
+ * SG_LIB_SYNTAX_ERROR -> unable to build cdb,
+ * SG_LIB_CAT_UNIT_ATTENTION -> try again,
+ * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> 'io_addrp' written to,
+ * SG_LIB_CAT_MEDIUM_HARD -> no info field,
+ * SG_LIB_CAT_NOT_READY, SG_LIB_CAT_ABORTED_COMMAND,
+ * -2 -> ENOMEM, -1 other errors */
+static int
+sg_read_low(int sg_fd, uint8_t * buff, int blocks, int64_t from_block,
+ int bs, const struct flags_t * ifp, bool * diop,
+ uint64_t * io_addrp)
+{
+ bool info_valid;
+ bool print_cdb_after = false;
+ int res, slen;
+ const uint8_t * sbp;
+ uint8_t rdCmd[MAX_SCSI_CDBSZ];
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+
+ if (sg_build_scsi_cdb(rdCmd, ifp->cdbsz, blocks, from_block, do_verify,
+ false, ifp->fua, ifp->dpo, ifp->cdl)) {
+ pr2serr(ME "bad rd cdb build, from_block=%" PRId64 ", blocks=%d\n",
+ from_block, blocks);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = ifp->cdbsz;
+ io_hdr.cmdp = rdCmd;
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = bs * blocks;
+ io_hdr.dxferp = buff;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = cmd_timeout;
+ io_hdr.pack_id = (int)++glob_pack_id;
+ if (diop && *diop)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+
+ if (verbose > 2)
+ sg_print_command_len(rdCmd, ifp->cdbsz);
+
+ while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return -2;
+ perror("reading (SG_IO) on sg device, error");
+ return -1;
+ }
+ if (verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ res = sg_err_category3(&io_hdr);
+ sbp = io_hdr.sbp;
+ slen = io_hdr.sb_len_wr;
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ case SG_LIB_CAT_CONDITION_MET:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ ++recovered_errs;
+ info_valid = sg_get_sense_info_fld(sbp, slen, io_addrp);
+ if (info_valid) {
+ pr2serr(" lba of last recovered error in this READ=0x%" PRIx64
+ "\n", *io_addrp);
+ if (verbose > 1)
+ sg_chk_n_print3("reading", &io_hdr, true);
+ } else {
+ pr2serr("Recovered error: [no info] reading from block=0x%" PRIx64
+ ", num=%d\n", from_block, blocks);
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ }
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ return res;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (verbose > 1)
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ ++unrecovered_errs;
+ info_valid = sg_get_sense_info_fld(sbp, slen, io_addrp);
+ /* MMC devices don't necessarily set VALID bit */
+ if (info_valid || ((5 == ifp->pdt) && (*io_addrp > 0)))
+ return SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+ else {
+ pr2serr("Medium, hardware or blank check error but no lba of "
+ "failure in sense\n");
+ return res;
+ }
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++unrecovered_errs;
+ if (verbose > 0)
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ return res;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (5 == ifp->pdt) { /* MMC READs can go down this path */
+ bool ili;
+ struct sg_scsi_sense_hdr ssh;
+
+ if (verbose > 1)
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
+ (0x64 == ssh.asc) && (0x0 == ssh.ascq)) {
+ if (sg_get_sense_filemark_eom_ili(sbp, slen, NULL, NULL,
+ &ili) && ili) {
+ sg_get_sense_info_fld(sbp, slen, io_addrp);
+ if (*io_addrp > 0) {
+ ++unrecovered_errs;
+ return SG_LIB_CAT_MEDIUM_HARD_WITH_INFO;
+ } else
+ pr2serr("MMC READ gave 'illegal mode for this track' "
+ "and ILI but no LBA of failure\n");
+ }
+ ++unrecovered_errs;
+ return SG_LIB_CAT_MEDIUM_HARD;
+ }
+ }
+ if (verbose > 0)
+ print_cdb_after = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ default:
+ ++unrecovered_errs;
+ if (verbose > 0)
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ if (print_cdb_after)
+ sg_print_command_len(rdCmd, ifp->cdbsz);
+ return res;
+ }
+ if (diop && *diop &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ *diop = false; /* flag that dio not done (completely) */
+ sum_of_resids += io_hdr.resid;
+ return 0;
+}
+
+
+/* Does repeats associated with a SCSI READ on IFILE. Returns 0 -> successful,
+ * SG_LIB_SYNTAX_ERROR -> unable to build cdb, SG_LIB_CAT_UNIT_ATTENTION ->
+ * try again, SG_LIB_CAT_NOT_READY, SG_LIB_CAT_MEDIUM_HARD,
+ * SG_LIB_CAT_ABORTED_COMMAND, -2 -> ENOMEM, -1 other errors */
+static int
+sg_read(int sg_fd, uint8_t * buff, int blocks, int64_t from_block,
+ int bs, struct flags_t * ifp, bool * diop, int * blks_readp)
+{
+ bool may_coe = false;
+ bool repeat;
+ int res, blks, xferred;
+ int ret = 0;
+ int retries_tmp;
+ uint64_t io_addr;
+ int64_t lba;
+ uint8_t * bp;
+
+ retries_tmp = ifp->retries;
+ for (xferred = 0, blks = blocks, lba = from_block, bp = buff;
+ blks > 0; blks = blocks - xferred) {
+ io_addr = 0;
+ repeat = false;
+ may_coe = false;
+ res = sg_read_low(sg_fd, bp, blks, lba, bs, ifp, diop, &io_addr);
+ switch (res) {
+ case 0:
+ if (blks_readp)
+ *blks_readp = xferred + blks;
+ if (coe_limit > 0)
+ coe_count = 0; /* good read clears coe_count */
+ return 0;
+ case -2: /* ENOMEM */
+ return res;
+ case SG_LIB_CAT_NOT_READY:
+ pr2serr("Device (r) not ready\n");
+ return res;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ if (--max_aborted > 0) {
+ pr2serr("Aborted command, continuing (r)\n");
+ repeat = true;
+ } else {
+ pr2serr("Aborted command, too many (r)\n");
+ return res;
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ if (--max_uas > 0) {
+ pr2serr("Unit attention, continuing (r)\n");
+ repeat = true;
+ } else {
+ pr2serr("Unit attention, too many (r)\n");
+ return res;
+ }
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO:
+ if (retries_tmp > 0) {
+ pr2serr(">>> retrying a sgio read, lba=0x%" PRIx64 "\n",
+ (uint64_t)lba);
+ --retries_tmp;
+ ++num_retries;
+ if (unrecovered_errs > 0)
+ --unrecovered_errs;
+ repeat = true;
+ }
+ ret = SG_LIB_CAT_MEDIUM_HARD;
+ break; /* unrecovered read error at lba=io_addr */
+ case SG_LIB_SYNTAX_ERROR:
+ ifp->coe = 0;
+ ret = res;
+ goto err_out;
+ case -1:
+ ret = res;
+ goto err_out;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ may_coe = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ default:
+ if (retries_tmp > 0) {
+ pr2serr(">>> retrying a sgio read, lba=0x%" PRIx64 "\n",
+ (uint64_t)lba);
+ --retries_tmp;
+ ++num_retries;
+ if (unrecovered_errs > 0)
+ --unrecovered_errs;
+ repeat = true;
+ break;
+ }
+ ret = res;
+ goto err_out;
+ }
+ if (repeat)
+ continue;
+ if ((io_addr < (uint64_t)lba) ||
+ (io_addr >= (uint64_t)(lba + blks))) {
+ pr2serr(" Unrecovered error lba 0x%" PRIx64 " not in "
+ "correct range:\n\t[0x%" PRIx64 ",0x%" PRIx64 "]\n",
+ io_addr, (uint64_t)lba,
+ (uint64_t)(lba + blks - 1));
+ may_coe = true;
+ goto err_out;
+ }
+ blks = (int)(io_addr - (uint64_t)lba);
+ if (blks > 0) {
+ if (verbose)
+ pr2serr(" partial read of %d blocks prior to medium error\n",
+ blks);
+ res = sg_read_low(sg_fd, bp, blks, lba, bs, ifp, diop, &io_addr);
+ switch (res) {
+ case 0:
+ break;
+ case -1:
+ ifp->coe = 0;
+ ret = res;
+ goto err_out;
+ case -2:
+ pr2serr("ENOMEM again, unexpected (r)\n");
+ return -1;
+ case SG_LIB_CAT_NOT_READY:
+ pr2serr("device (r) not ready\n");
+ return res;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ pr2serr("Unit attention, unexpected (r)\n");
+ return res;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ pr2serr("Aborted command, unexpected (r)\n");
+ return res;
+ case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO:
+ case SG_LIB_CAT_MEDIUM_HARD:
+ ret = SG_LIB_CAT_MEDIUM_HARD;
+ goto err_out;
+ case SG_LIB_SYNTAX_ERROR:
+ default:
+ pr2serr(">> unexpected result=%d from sg_read_low() 2\n",
+ res);
+ ret = res;
+ goto err_out;
+ }
+ }
+ xferred += blks;
+ if (0 == ifp->coe) {
+ /* give up at block before problem unless 'coe' */
+ if (blks_readp)
+ *blks_readp = xferred;
+ return ret;
+ }
+ if (bs < 32) {
+ pr2serr(">> bs=%d too small for read_long\n", bs);
+ return -1; /* nah, block size can't be that small */
+ }
+ bp += (blks * bs);
+ lba += blks;
+ if ((0 != ifp->pdt) || (ifp->coe < 2)) {
+ pr2serr(">> unrecovered read error at blk=%" PRId64 ", pdt=%d, "
+ "use zeros\n", lba, ifp->pdt);
+ memset(bp, 0, bs);
+ } else if (io_addr < UINT_MAX) {
+ bool corrct, ok;
+ int offset, nl, r;
+ uint8_t * buffp;
+ uint8_t * free_buffp;
+
+ buffp = sg_memalign(bs * 2, 0, &free_buffp, false);
+ if (NULL == buffp) {
+ pr2serr(">> heap problems\n");
+ return -1;
+ }
+ corrct = (ifp->coe > 2);
+ res = sg_ll_read_long10(sg_fd, /* pblock */false, corrct, lba,
+ buffp, bs + read_long_blk_inc, &offset,
+ true, verbose);
+ ok = false;
+ switch (res) {
+ case 0:
+ ok = true;
+ ++read_longs;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO:
+ nl = bs + read_long_blk_inc - offset;
+ if ((nl < 32) || (nl > (bs * 2))) {
+ pr2serr(">> read_long(10) len=%d unexpected\n", nl);
+ break;
+ }
+ /* remember for next read_long attempt, if required */
+ read_long_blk_inc = nl - bs;
+
+ if (verbose)
+ pr2serr("read_long(10): adjusted len=%d\n", nl);
+ r = sg_ll_read_long10(sg_fd, false, corrct, lba, buffp, nl,
+ &offset, true, verbose);
+ if (0 == r) {
+ ok = true;
+ ++read_longs;
+ break;
+ } else
+ pr2serr(">> unexpected result=%d on second "
+ "read_long(10)\n", r);
+ break;
+ case SG_LIB_CAT_INVALID_OP:
+ pr2serr(">> read_long(10); not supported\n");
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ pr2serr(">> read_long(10): bad cdb field\n");
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ pr2serr(">> read_long(10): device not ready\n");
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ pr2serr(">> read_long(10): unit attention\n");
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ pr2serr(">> read_long(10): aborted command\n");
+ break;
+ default:
+ pr2serr(">> read_long(10): problem (%d)\n", res);
+ break;
+ }
+ if (ok)
+ memcpy(bp, buffp, bs);
+ else
+ memset(bp, 0, bs);
+ free(free_buffp);
+ } else {
+ pr2serr(">> read_long(10) cannot handle blk=%" PRId64 ", use "
+ "zeros\n", lba);
+ memset(bp, 0, bs);
+ }
+ ++xferred;
+ bp += bs;
+ ++lba;
+ if ((coe_limit > 0) && (++coe_count > coe_limit)) {
+ if (blks_readp)
+ *blks_readp = xferred + blks;
+ pr2serr(">> coe_limit on consecutive reads exceeded\n");
+ return SG_LIB_CAT_MEDIUM_HARD;
+ }
+ }
+ if (blks_readp)
+ *blks_readp = xferred;
+ return 0;
+
+err_out:
+ if (ifp->coe) {
+ memset(bp, 0, bs * blks);
+ pr2serr(">> unable to read at blk=%" PRId64 " for %d bytes, use "
+ "zeros\n", lba, bs * blks);
+ if (blks > 1)
+ pr2serr(">> try reducing bpt to limit number of zeros written "
+ "near bad block(s)\n");
+ /* fudge success */
+ if (blks_readp)
+ *blks_readp = xferred + blks;
+ if ((coe_limit > 0) && (++coe_count > coe_limit)) {
+ pr2serr(">> coe_limit on consecutive reads exceeded\n");
+ return ret;
+ }
+ return may_coe ? 0 : ret;
+ } else
+ return ret;
+}
+
+
+/* Does a SCSI WRITE or VERIFY (if do_verify set) on OFILE. Returns:
+ * 0 -> successful, SG_LIB_SYNTAX_ERROR -> unable to build cdb,
+ * SG_LIB_CAT_NOT_READY, SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_MEDIUM_HARD,
+ * SG_LIB_CAT_ABORTED_COMMAND, -2 -> recoverable (ENOMEM),
+ * -1 -> unrecoverable error + others. SG_DD_BYPASS -> failed but coe set. */
+static int
+sg_write(int sg_fd, uint8_t * buff, int blocks, int64_t to_block,
+ int bs, const struct flags_t * ofp, bool * diop)
+{
+ bool info_valid;
+ int res;
+ uint64_t io_addr = 0;
+ const char * op_str = do_verify ? "verifying" : "writing";
+ uint8_t wrCmd[MAX_SCSI_CDBSZ];
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+
+ if (sg_build_scsi_cdb(wrCmd, ofp->cdbsz, blocks, to_block, do_verify,
+ true, ofp->fua, ofp->dpo, ofp->cdl)) {
+ pr2serr(ME "bad wr cdb build, to_block=%" PRId64 ", blocks=%d\n",
+ to_block, blocks);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = ofp->cdbsz;
+ io_hdr.cmdp = wrCmd;
+ io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ io_hdr.dxfer_len = bs * blocks;
+ io_hdr.dxferp = buff;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = cmd_timeout;
+ io_hdr.pack_id = (int)++glob_pack_id;
+ if (diop && *diop)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+
+ if (verbose > 2)
+ sg_print_command_len(wrCmd, ofp->cdbsz);
+
+ while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return -2;
+ if (do_verify)
+ perror("verifying (SG_IO) on sg device, error");
+ else
+ perror("writing (SG_IO) on sg device, error");
+ return -1;
+ }
+
+ if (verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ case SG_LIB_CAT_CONDITION_MET:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ ++recovered_errs;
+ info_valid = sg_get_sense_info_fld(io_hdr.sbp, io_hdr.sb_len_wr,
+ &io_addr);
+ if (info_valid) {
+ pr2serr(" lba of last recovered error in this WRITE=0x%" PRIx64
+ "\n", io_addr);
+ if (verbose > 1)
+ sg_chk_n_print3(op_str, &io_hdr, true);
+ } else {
+ pr2serr("Recovered error: [no info] %s to block=0x%" PRIx64
+ ", num=%d\n", op_str, to_block, blocks);
+ sg_chk_n_print3(op_str, &io_hdr, verbose > 1);
+ }
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ sg_chk_n_print3(op_str, &io_hdr, verbose > 1);
+ return res;
+ case SG_LIB_CAT_MISCOMPARE: /* must be VERIFY cpommand */
+ ++miscompare_errs;
+ if (ofp->coe) {
+ if (verbose > 1)
+ pr2serr(">> bypass due to miscompare: out blk=%" PRId64
+ " for %d blocks\n", to_block, blocks);
+ return SG_DD_BYPASS; /* fudge success */
+ } else {
+ pr2serr("VERIFY reports miscompare\n");
+ return res;
+ }
+ case SG_LIB_CAT_NOT_READY:
+ ++unrecovered_errs;
+ pr2serr("device not ready (w)\n");
+ return res;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ default:
+ sg_chk_n_print3(op_str, &io_hdr, verbose > 1);
+ if ((SG_LIB_CAT_ILLEGAL_REQ == res) && verbose)
+ sg_print_command_len(wrCmd, ofp->cdbsz);
+ ++unrecovered_errs;
+ if (ofp->coe) {
+ if (verbose > 1)
+ pr2serr(">> ignored errors for out blk=%" PRId64 " for %d "
+ "bytes\n", to_block, bs * blocks);
+ return SG_DD_BYPASS; /* fudge success */
+ } else
+ return res;
+ }
+ if (diop && *diop &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ *diop = false; /* flag that dio not done (completely) */
+ return 0;
+}
+
+
+static void
+calc_duration_throughput(bool 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;
+ pr2serr("time to %s data%s: %d.%06d secs",
+ (do_verify ? "verify" : "copy"), (contin ? " so far" : ""),
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ pr2serr(" at %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ }
+}
+
+/* Process arguments given to 'iflag=" or 'oflag=" options. Returns 0
+ * on success, 1 on error. */
+static int
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "00"))
+ fp->zero = true;
+ else if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "coe"))
+ ++fp->coe;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "flock"))
+ fp->flock = true;
+ else if (0 == strcmp(cp, "ff"))
+ fp->ff = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "nocache"))
+ ++fp->nocache;
+ else if (0 == strcmp(cp, "nocreat"))
+ fp->nocreat = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "random"))
+ fp->random = true;
+ else if (0 == strcmp(cp, "sgio"))
+ fp->sgio = true;
+ else if (0 == strcmp(cp, "sparse"))
+ fp->sparse = true;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return 1;
+ }
+ cp = np;
+ } while (cp);
+ return 0;
+}
+
+/* Process arguments given to 'conv=" option. Returns 0 on success,
+ * 1 on error. */
+static int
+process_conv(const char * arg, struct flags_t * ifp, struct flags_t * ofp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no conversions found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "nocreat"))
+ ofp->nocreat = true;
+ else if (0 == strcmp(cp, "noerror"))
+ ++ifp->coe; /* will still fail on write error */
+ else if (0 == strcmp(cp, "notrunc"))
+ ; /* this is the default action of sg_dd so ignore */
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "sparse"))
+ ofp->sparse = true;
+ else if (0 == strcmp(cp, "sync"))
+ ; /* dd(susv4): pad errored block(s) with zeros but sg_dd does
+ * that by default. Typical dd use: 'conv=noerror,sync' */
+ else {
+ pr2serr("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(const char * inf, int64_t skip, int bpt, struct flags_t * ifp,
+ int * in_typep, int vb)
+{
+ int infd = -1;
+ int flags, fl, t, res;
+ char ebuff[EBUFF_SZ];
+ struct sg_simple_inquiry_resp sir;
+
+ *in_typep = dd_filetype(inf);
+ if (vb)
+ pr2serr(" >> Input file type: %s\n",
+ dd_filetype_str(*in_typep, ebuff));
+ if (FT_ERROR & *in_typep) {
+ pr2serr(ME "unable access %s\n", inf);
+ goto file_err;
+ } else if ((FT_BLOCK & *in_typep) && ifp->sgio)
+ *in_typep |= FT_SG;
+
+ if (FT_ST & *in_typep) {
+ pr2serr(ME "unable to use scsi tape device %s\n", inf);
+ goto file_err;
+ } else if (FT_SG & *in_typep) {
+ flags = O_NONBLOCK;
+ if (ifp->direct)
+ flags |= O_DIRECT;
+ if (ifp->excl)
+ flags |= O_EXCL;
+ if (ifp->dsync)
+ flags |= O_SYNC;
+ fl = O_RDWR;
+ if ((infd = open(inf, fl | flags)) < 0) {
+ fl = O_RDONLY;
+ if ((infd = open(inf, fl | flags)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for sg reading", inf);
+ perror(ebuff);
+ goto file_err;
+ }
+ }
+ if (vb)
+ pr2serr(" open input(sg_io), flags=0x%x\n", fl | flags);
+ if (sg_simple_inquiry(infd, &sir, false, (vb ? (vb - 1) : 0))) {
+ pr2serr("INQUIRY failed on %s\n", inf);
+ goto other_err;
+ }
+ ifp->pdt = sir.peripheral_type;
+ if (vb)
+ pr2serr(" %s: %.8s %.16s %.4s [pdt=%d]\n", inf, sir.vendor,
+ sir.product, sir.revision, ifp->pdt);
+ if (! (FT_BLOCK & *in_typep)) {
+ t = blk_sz * bpt;
+ res = ioctl(infd, SG_SET_RESERVED_SIZE, &t);
+ if (res < 0)
+ perror(ME "SG_SET_RESERVED_SIZE error");
+ res = ioctl(infd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ if (FT_BLOCK & *in_typep)
+ pr2serr(ME "SG_IO unsupported on this block device\n");
+ else
+ pr2serr(ME "sg driver prior to 3.x.y\n");
+ goto file_err;
+ }
+ }
+ } else if (FT_NVME & *in_typep) {
+ pr2serr("Don't support NVMe char devices as IFILE\n");
+ goto file_err;
+ } else {
+ flags = O_RDONLY;
+ if (ifp->direct)
+ flags |= O_DIRECT;
+ if (ifp->excl)
+ flags |= O_EXCL;
+ if (ifp->dsync)
+ flags |= O_SYNC;
+ infd = open(inf, flags);
+ if (infd < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", inf);
+ perror(ebuff);
+ goto file_err;
+ } else {
+ if (vb)
+ pr2serr(" open input, flags=0x%x\n", flags);
+ if (skip > 0) {
+ off64_t offset = skip;
+
+ offset *= blk_sz; /* could exceed 32 bits here! */
+ if (lseek64(infd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to "
+ "required position on %s", inf);
+ perror(ebuff);
+ goto file_err;
+ }
+ if (vb)
+ pr2serr(" >> skip: lseek64 SEEK_SET, byte offset=0x%"
+ PRIx64 "\n", (uint64_t)offset);
+ }
+#ifdef HAVE_POSIX_FADVISE
+ if (ifp->nocache) {
+ int rt;
+
+ rt = posix_fadvise(infd, 0, 0, POSIX_FADV_SEQUENTIAL);
+ if (rt)
+ pr2serr("open_if: posix_fadvise(SEQUENTIAL), err=%d\n",
+ rt);
+ }
+#endif
+ }
+ }
+ if (ifp->flock && (infd >= 0)) {
+ 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", inf);
+ perror(ebuff);
+ return -SG_LIB_FLOCK_ERR;
+ }
+ }
+ return infd;
+
+file_err:
+ if (infd >= 0)
+ close(infd);
+ return -SG_LIB_FILE_ERROR;
+other_err:
+ if (infd >= 0)
+ close(infd);
+ return -SG_LIB_CAT_OTHER;
+}
+
+/* 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(const char * outf, int64_t seek, int bpt, struct flags_t * ofp,
+ int * out_typep, int vb)
+{
+ bool not_found;
+ int outfd = -1;
+ int flags, t, res;
+ char ebuff[EBUFF_SZ];
+ struct sg_simple_inquiry_resp sir;
+
+ *out_typep = dd_filetype(outf);
+ if (vb)
+ pr2serr(" >> Output file type: %s\n",
+ dd_filetype_str(*out_typep, ebuff));
+ not_found = (FT_ERROR == *out_typep); /* assume error was not found */
+
+ if ((FT_BLOCK & *out_typep) && ofp->sgio)
+ *out_typep |= FT_SG;
+
+ if (FT_ST & *out_typep) {
+ pr2serr(ME "unable to use scsi tape device %s\n", outf);
+ goto file_err;
+ } else if (FT_SG & *out_typep) {
+ flags = O_RDWR | O_NONBLOCK;
+ if (ofp->direct)
+ flags |= O_DIRECT;
+ if (ofp->excl)
+ flags |= O_EXCL;
+ if (ofp->dsync)
+ flags |= O_SYNC;
+ if ((outfd = open(outf, flags)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for sg writing", outf);
+ perror(ebuff);
+ goto file_err;
+ }
+ if (vb)
+ pr2serr(" open output(sg_io), flags=0x%x\n", flags);
+ if (sg_simple_inquiry(outfd, &sir, false, (vb ? (vb - 1) : 0))) {
+ pr2serr("INQUIRY failed on %s\n", outf);
+ goto other_err;
+ }
+ ofp->pdt = sir.peripheral_type;
+ if (vb)
+ pr2serr(" %s: %.8s %.16s %.4s [pdt=%d]\n", outf, sir.vendor,
+ sir.product, sir.revision, ofp->pdt);
+ if (! (FT_BLOCK & *out_typep)) {
+ t = blk_sz * bpt;
+ res = ioctl(outfd, SG_SET_RESERVED_SIZE, &t);
+ if (res < 0)
+ perror(ME "SG_SET_RESERVED_SIZE error");
+ res = ioctl(outfd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ pr2serr(ME "sg driver prior to 3.x.y\n");
+ goto file_err;
+ }
+ }
+ } else if (FT_NVME & *out_typep) {
+ pr2serr("Don't support NVMe char devices as OFILE\n");
+ goto file_err;
+ } else if (FT_DEV_NULL & *out_typep)
+ outfd = -1; /* don't bother opening */
+ else if (FT_RAW & *out_typep) {
+ flags = O_WRONLY;
+ if (ofp->direct)
+ flags |= O_DIRECT;
+ if (ofp->excl)
+ flags |= O_EXCL;
+ if (ofp->dsync)
+ flags |= O_SYNC;
+ if ((outfd = open(outf, flags)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for raw writing", outf);
+ perror(ebuff);
+ goto file_err;
+ }
+ } else { /* FT_OTHER or FT_ERROR (not found so create) */
+ flags = O_WRONLY;
+ if (! ofp->nocreat)
+ flags |= O_CREAT;
+ if (ofp->direct)
+ flags |= O_DIRECT;
+ if (ofp->excl)
+ flags |= O_EXCL;
+ if (ofp->dsync)
+ flags |= O_SYNC;
+ if (ofp->append)
+ flags |= O_APPEND;
+ if ((outfd = open(outf, flags, 0666)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for writing", outf);
+ perror(ebuff);
+ goto file_err;
+ }
+ if (vb)
+ pr2serr(" %s output, flags=0x%x\n",
+ (not_found ? "create" : "open"), flags);
+ if (seek > 0) {
+ off64_t offset = seek;
+
+ offset *= blk_sz; /* could exceed 32 bits here! */
+ if (lseek64(outfd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "couldn't seek to required position on %s", outf);
+ perror(ebuff);
+ goto file_err;
+ }
+ if (vb)
+ pr2serr(" >> seek: lseek64 SEEK_SET, byte offset=0x%" PRIx64
+ "\n", (uint64_t)offset);
+ }
+ }
+ if (ofp->flock && (outfd >= 0)) {
+ res = flock(outfd, LOCK_EX | LOCK_NB);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "flock(LOCK_EX | LOCK_NB) on %s "
+ "failed", outf);
+ perror(ebuff);
+ close(outfd);
+ return -SG_LIB_FLOCK_ERR;
+ }
+ }
+ return outfd;
+
+file_err:
+ if (outfd >= 0)
+ close(outfd);
+ return -SG_LIB_FILE_ERROR;
+other_err:
+ if (outfd >= 0)
+ close(outfd);
+ return -SG_LIB_CAT_OTHER;
+}
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+/* Returns true when it time to output a progress report; else false. */
+static bool
+check_progress(void)
+{
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ static bool have_prev, measure;
+ static struct timespec prev_true_tm;
+ static int count, threshold;
+ bool res = false;
+ uint32_t elapsed_ms, ms;
+ struct timespec now_tm, res_tm;
+
+ if (progress) {
+ if (! have_prev) {
+ have_prev = true;
+ measure = true;
+ clock_gettime(CLOCK_MONOTONIC, &prev_true_tm);
+ return false; /* starting reference */
+ }
+ if (! measure) {
+ if (++count >= threshold)
+ count = 0;
+ else
+ return false;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now_tm);
+ res_tm.tv_sec = now_tm.tv_sec - prev_true_tm.tv_sec;
+ res_tm.tv_nsec = now_tm.tv_nsec - prev_true_tm.tv_nsec;
+ if (res_tm.tv_nsec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_nsec += 1000000000;
+ }
+ elapsed_ms = (1000 * res_tm.tv_sec) + (res_tm.tv_nsec / 1000000);
+ if (measure) {
+ ++threshold;
+ if (elapsed_ms > 80) /* 80 milliseconds */
+ measure = false;
+ }
+ if (elapsed_ms >= PROGRESS3_TRIGGER_MS) {
+ if (elapsed_ms >= PROGRESS2_TRIGGER_MS) {
+ if (elapsed_ms >= PROGRESS_TRIGGER_MS) {
+ ms = PROGRESS_TRIGGER_MS;
+ res = true;
+ } else if (progress > 1) {
+ ms = PROGRESS2_TRIGGER_MS;
+ res = true;
+ }
+ } else if (progress > 2) {
+ ms = PROGRESS3_TRIGGER_MS;
+ res = true;
+ }
+ }
+ if (res) {
+ prev_true_tm.tv_sec += (ms / 1000);
+ prev_true_tm.tv_nsec += (ms % 1000) * 1000000;
+ if (prev_true_tm.tv_nsec >= 1000000000) {
+ ++prev_true_tm.tv_sec;
+ prev_true_tm.tv_nsec -= 1000000000;
+ }
+ }
+ }
+ return res;
+
+#elif defined(HAVE_GETTIMEOFDAY)
+ static bool have_prev, measure;
+ static struct timeval prev_true_tm;
+ static int count, threshold;
+ bool res = false;
+ uint32_t elapsed_ms, ms;
+ struct timeval now_tm, res_tm;
+
+ if (progress) {
+ if (! have_prev) {
+ have_prev = true;
+ gettimeofday(&prev_true_tm, NULL);
+ return false; /* starting reference */
+ }
+ if (! measure) {
+ if (++count >= threshold)
+ count = 0;
+ else
+ return false;
+ }
+ gettimeofday(&now_tm, NULL);
+ res_tm.tv_sec = now_tm.tv_sec - prev_true_tm.tv_sec;
+ res_tm.tv_usec = now_tm.tv_usec - prev_true_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ elapsed_ms = (1000 * res_tm.tv_sec) + (res_tm.tv_usec / 1000);
+ if (measure) {
+ ++threshold;
+ if (elapsed_ms > 80) /* 80 milliseconds */
+ measure = false;
+ }
+ if (elapsed_ms >= PROGRESS3_TRIGGER_MS) {
+ if (elapsed_ms >= PROGRESS2_TRIGGER_MS) {
+ if (elapsed_ms >= PROGRESS_TRIGGER_MS) {
+ ms = PROGRESS_TRIGGER_MS;
+ res = true;
+ } else if (progress > 1) {
+ ms = PROGRESS2_TRIGGER_MS;
+ res = true;
+ }
+ } else if (progress > 2) {
+ ms = PROGRESS3_TRIGGER_MS;
+ res = true;
+ }
+ }
+ if (res) {
+ prev_true_tm.tv_sec += (ms / 1000);
+ prev_true_tm.tv_usec += (ms % 1000) * 1000;
+ if (prev_true_tm.tv_usec >= 1000000) {
+ ++prev_true_tm.tv_sec;
+ prev_true_tm.tv_usec -= 1000000;
+ }
+ }
+ }
+ return res;
+
+#else /* no clock reading functions available */
+ return false;
+#endif
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool bpt_given = false;
+ bool cdbsz_given = false;
+ bool cdl_given = false;
+ bool dio_tmp, first;
+ bool do_sync = false;
+ bool penult_sparse_skip = false;
+ bool sparse_skip = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, k, n, t, buf_sz, blocks_per, infd, outfd, out2fd, keylen;
+ int retries_tmp, blks_read, bytes_read, bytes_of2, bytes_of;
+ int in_sect_sz, out_sect_sz;
+ int blocks = 0;
+ int bpt = DEF_BLOCKS_PER_TRANSFER;
+ int dio_incomplete_count = 0;
+ int ibs = 0;
+ int in_type = FT_OTHER;
+ int obs = 0;
+ int out_type = FT_OTHER;
+ int out2_type = FT_OTHER;
+ int penult_blocks = 0;
+ int ret = 0;
+ int64_t skip = 0;
+ int64_t seek = 0;
+ int64_t in_num_sect = -1;
+ int64_t out_num_sect = -1;
+ char * key;
+ char * buf;
+ const char * ccp = NULL;
+ const char * cc2p;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * wrkPos;
+ char inf[INOUTF_SZ];
+ char outf[INOUTF_SZ];
+ char out2f[INOUTF_SZ];
+ char str[STR_SZ];
+ char ebuff[EBUFF_SZ];
+
+ inf[0] = '\0';
+ outf[0] = '\0';
+ out2f[0] = '\0';
+ iflag.cdbsz = DEF_SCSI_CDBSZ;
+ oflag.cdbsz = DEF_SCSI_CDBSZ;
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ } else
+ continue;
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strncmp(key, "app", 3)) {
+ iflag.append = !! sg_get_num(buf);
+ oflag.append = iflag.append;
+ } else if (0 == strcmp(key, "blk_sgio")) {
+ iflag.sgio = !! sg_get_num(buf);
+ oflag.sgio = iflag.sgio;
+ } else if (0 == strcmp(key, "bpt")) {
+ bpt = sg_get_num(buf);
+ if (-1 == bpt) {
+ pr2serr(ME "bad argument to 'bpt='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key, "bs")) {
+ blk_sz = sg_get_num(buf);
+ if ((blk_sz < 0) || (blk_sz > MAX_BPT_VALUE)) {
+ pr2serr(ME "bad argument to 'bs='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "cdbsz")) {
+ iflag.cdbsz = sg_get_num(buf);
+ if ((iflag.cdbsz < 6) || (iflag.cdbsz > 32)) {
+ pr2serr(ME "'cdbsz' expects 6, 10, 12, 16 or 32\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ oflag.cdbsz = iflag.cdbsz;
+ cdbsz_given = true;
+ } else if (0 == strcmp(key, "cdl")) {
+ const char * cp = strchr(buf, ',');
+
+ iflag.cdl = sg_get_num(buf);
+ if ((iflag.cdl < 0) || (iflag.cdl > 7)) {
+ pr2serr(ME "bad argument to 'cdl=', expect 0 to 7\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (cp) {
+ oflag.cdl = sg_get_num(cp + 1);
+ if ((oflag.cdl < 0) || (oflag.cdl > 7)) {
+ pr2serr(ME "bad argument to 'cdl=ICDL,OCDL', expect OCDL "
+ "to be 0 to 7\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else
+ oflag.cdl = iflag.cdl;
+ cdl_given = true;
+ } else if (0 == strcmp(key, "coe")) {
+ iflag.coe = sg_get_num(buf);
+ oflag.coe = iflag.coe;
+ } else if (0 == strcmp(key, "coe_limit")) {
+ coe_limit = sg_get_num(buf);
+ if (-1 == coe_limit) {
+ pr2serr(ME "bad argument to 'coe_limit='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "conv")) {
+ if (process_conv(buf, &iflag, &oflag)) {
+ pr2serr(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 ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(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, "dio")) {
+ oflag.dio = !! sg_get_num(buf);
+ iflag.dio = oflag.dio;
+ } else if (0 == strcmp(key, "fua")) {
+ t = sg_get_num(buf);
+ oflag.fua = !! (t & 1);
+ iflag.fua = !! (t & 2);
+ } else if (0 == strcmp(key, "ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr(ME "bad argument to 'ibs='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key, "if") == 0) {
+ if ('\0' != inf[0]) {
+ pr2serr("Second IFILE argument??\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ memcpy(inf, buf, INOUTF_SZ - 1);
+ inf[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (process_flags(buf, &iflag)) {
+ pr2serr(ME "bad argument to 'iflag='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr(ME "bad argument to 'obs='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "odir")) {
+ iflag.direct = !! sg_get_num(buf);
+ oflag.direct = iflag.direct;
+ } else if (strcmp(key, "of") == 0) {
+ if ('\0' != outf[0]) {
+ pr2serr("Second OFILE argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(outf, buf, INOUTF_SZ - 1);
+ outf[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (strcmp(key, "of2") == 0) {
+ if ('\0' != out2f[0]) {
+ pr2serr("Second OFILE2 argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(out2f, buf, INOUTF_SZ - 1);
+ out2f[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (process_flags(buf, &oflag)) {
+ pr2serr(ME "bad argument to 'oflag='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "retries")) {
+ iflag.retries = sg_get_num(buf);
+ oflag.retries = iflag.retries;
+ if (-1 == iflag.retries) {
+ pr2serr(ME "bad argument to 'retries='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "seek")) {
+ seek = sg_get_llnum(buf);
+ if ((seek < 0) || (seek > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(ME "bad argument to 'seek='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "skip")) {
+ skip = sg_get_llnum(buf);
+ if ((skip < 0) || (skip > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(ME "bad argument to 'skip='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "sync"))
+ do_sync = !! sg_get_num(buf);
+ else if (0 == strcmp(key, "time")) {
+ const char * cp = strchr(buf, ',');
+
+ do_time = !! sg_get_num(buf);
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (n < 0) {
+ pr2serr(ME "bad argument to 'time=0|1,TO'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cmd_timeout = n ? (n * 1000) : DEF_TIMEOUT;
+ }
+ } else if (0 == strncmp(key, "verb", 4))
+ verbose = sg_get_num(buf);
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'd');
+ dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ if (n > 0) {
+ usage();
+ return 0;
+ }
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ progress += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ verbose += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'x');
+ if (n > 0)
+ do_verify = true;
+ res += n;
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?"))) {
+ usage();
+ return 0;
+ } else if (0 == strncmp(key, "--progress", 10))
+ ++progress;
+ else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++verbose;
+ } else if (0 == strncmp(key, "--veri", 6))
+ do_verify = true;
+ else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+ if (progress > 0 && !do_time)
+ do_time = true;
+ if (argc < 2) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (blk_sz <= 0) {
+ blk_sz = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ blk_sz);
+ }
+ if ((ibs && (ibs != blk_sz)) || (obs && (obs != blk_sz))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ((skip < 0) || (seek < 0)) {
+ pr2serr("skip and seek cannot be negative\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (oflag.append && (seek > 0)) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ((bpt < 1) || (bpt > MAX_BPT_VALUE)) {
+ pr2serr("bpt must be > 0 and <= %d\n", MAX_BPT_VALUE);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (iflag.sparse)
+ pr2serr("sparse flag ignored for iflag\n");
+
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ for the block layer in lk 2.6 and results in an EIO on the
+ SG_IO ioctl. So reduce it in that case. */
+ if ((blk_sz >= 2048) && (! bpt_given))
+ bpt = DEF_BLOCKS_PER_2048TRANSFER;
+#ifdef DEBUG
+ pr2serr(ME "if=%s skip=%" PRId64 " of=%s seek=%" PRId64 " count=%" PRId64
+ "\n", inf, skip, outf, 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;
+ iflag.pdt = -1;
+ oflag.pdt = -1;
+ if (iflag.zero && iflag.ff) {
+ ccp = "<addr_as_data>";
+ cc2p = "addr_as_data";
+ } else if (iflag.ff) {
+ ccp = "<0xff bytes>";
+ cc2p = "ff";
+ } else if (iflag.random) {
+ ccp = "<random>";
+ cc2p = "random";
+#ifdef HAVE_GETRANDOM
+ {
+ ssize_t ssz = getrandom(&seed, sizeof(seed), GRND_NONBLOCK);
+
+ if (ssz < (ssize_t)sizeof(seed)) {
+ pr2serr("getrandom() failed, ret=%d\n", (int)ssz);
+ seed = (long)time(NULL);
+ }
+ }
+#else
+ seed = (long)time(NULL); /* use seconds since epoch as proxy */
+#endif
+ if (verbose > 1)
+ pr2serr("seed=%ld\n", seed);
+#ifdef HAVE_SRAND48_R
+ srand48_r(seed, &drand);
+#else
+ srand48(seed);
+#endif
+ } else if (iflag.zero) {
+ ccp = "<zero bytes>";
+ cc2p = "00";
+ }
+ if (ccp) {
+ if (inf[0]) {
+ pr2serr("iflag=%s and if=%s contradict\n", cc2p, inf);
+ return SG_LIB_CONTRADICT;
+ }
+ in_type = FT_RANDOM_0_FF;
+ strcpy(inf, ccp);
+ infd = -1;
+ } else if (inf[0] && ('-' != inf[0])) {
+ infd = open_if(inf, skip, bpt, &iflag, &in_type, verbose);
+ if (infd < 0)
+ return -infd;
+ }
+
+ if (outf[0] && ('-' != outf[0])) {
+ outfd = open_of(outf, seek, bpt, &oflag, &out_type, verbose);
+ if (outfd < -1)
+ return -outfd;
+ }
+ if (do_verify) {
+ if (! (FT_SG & out_type)) {
+ pr2serr("--verify only supported when OFILE is a sg device or "
+ "oflag=sgio\n");
+ ret = SG_LIB_CONTRADICT;
+ goto bypass_copy;
+ }
+ if (oflag.sparse) {
+ pr2serr("--verify cannot be used with oflag=sparse\n");
+ ret = SG_LIB_CONTRADICT;
+ goto bypass_copy;
+ }
+ }
+ if (cdl_given && (! cdbsz_given)) {
+ bool changed = false;
+
+ if ((iflag.cdbsz < 16) && (iflag.cdl > 0)) {
+ iflag.cdbsz = 16;
+ changed = true;
+ }
+ if ((oflag.cdbsz < 16) && (! do_verify) && (oflag.cdl > 0)) {
+ oflag.cdbsz = 16;
+ changed = true;
+ }
+ if (changed)
+ pr2serr(">> increasing cdbsz to 16 due to cdl > 0\n");
+ }
+ if (out2f[0]) {
+ out2_type = dd_filetype(out2f);
+ if ((out2fd = open(out2f, O_WRONLY | O_CREAT, 0666)) < 0) {
+ res = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for writing", out2f);
+ perror(ebuff);
+ return res;
+ }
+ } else
+ out2fd = -1;
+
+ if ((STDIN_FILENO == infd) && (STDOUT_FILENO == outfd)) {
+ pr2serr("Can't have both 'if' as stdin _and_ 'of' as stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (oflag.sparse) {
+ if (STDOUT_FILENO == outfd) {
+ pr2serr("oflag=sparse needs seekable output file\n");
+ return SG_LIB_CONTRADICT;
+ }
+ }
+
+ if ((dd_count < 0) || ((verbose > 0) && (0 == dd_count))) {
+ in_num_sect = -1;
+ in_sect_sz = -1;
+ if (FT_SG & in_type) {
+ res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention (readcap in), continuing\n");
+ res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+ } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+ pr2serr("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)
+ pr2serr("read capacity not supported on %s\n", inf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed on %s - not ready\n", inf);
+ else
+ pr2serr("Unable to read capacity on %s\n", inf);
+ in_num_sect = -1;
+ } else if (in_sect_sz != blk_sz)
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", inf, blk_sz, in_sect_sz);
+ } else if (FT_BLOCK & in_type) {
+ if (0 != read_blkdev_capacity(infd, &in_num_sect, &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ if (blk_sz != in_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, device "
+ "claims=%d\n", inf, blk_sz, in_sect_sz);
+ in_num_sect = -1;
+ }
+ }
+ if (in_num_sect > skip)
+ in_num_sect -= skip;
+
+ out_num_sect = -1;
+ out_sect_sz = -1;
+ if (FT_SG & out_type) {
+ res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("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) {
+ pr2serr("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)
+ pr2serr("read capacity not supported on %s\n", outf);
+ else
+ pr2serr("Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ } else if (blk_sz != out_sect_sz)
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", outf, blk_sz,
+ out_sect_sz);
+ } else if (FT_BLOCK & out_type) {
+ if (0 != read_blkdev_capacity(outfd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outf);
+ out_num_sect = -1;
+ } else if (blk_sz != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, device "
+ "claims=%d\n", outf, blk_sz, out_sect_sz);
+ out_num_sect = -1;
+ }
+ }
+ if (out_num_sect > seek)
+ out_num_sect -= seek;
+#ifdef DEBUG
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64
+ ", out_num_sect=%" PRId64 "\n", dd_count, in_num_sect,
+ out_num_sect);
+#endif
+ 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;
+ }
+ }
+
+ if (dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (! cdbsz_given) {
+ if ((FT_SG & in_type) && (MAX_SCSI_CDBSZ != iflag.cdbsz) &&
+ (((dd_count + skip) > UINT_MAX) || (bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ iflag.cdbsz = MAX_SCSI_CDBSZ;
+ }
+ if ((FT_SG & out_type) && (MAX_SCSI_CDBSZ != oflag.cdbsz) &&
+ (((dd_count + seek) > UINT_MAX) || (bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ oflag.cdbsz = MAX_SCSI_CDBSZ;
+ }
+ }
+
+ if (iflag.dio || iflag.direct || oflag.direct || (FT_RAW & in_type) ||
+ (FT_RAW & out_type)) { /* want heap buffer aligned to page_size */
+
+ wrkPos = sg_memalign(blk_sz * bpt, 0, &wrkBuff, false);
+ if (NULL == wrkPos) {
+ pr2serr("sg_memalign: error, out of memory?\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ } else {
+ wrkPos = sg_memalign(blk_sz * bpt, 0, &wrkBuff, false);
+ if (0 == wrkPos) {
+ pr2serr("Not enough user memory\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ }
+
+ blocks_per = bpt;
+#ifdef DEBUG
+ pr2serr("Start of loop, count=%" PRId64 ", blocks_per=%d\n", dd_count,
+ blocks_per);
+#endif
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+ req_count = dd_count;
+
+ if (dry_run > 0) {
+ pr2serr("Since --dry-run option given, bypassing copy\n");
+ goto bypass_copy;
+ }
+
+ /* <<< main loop that does the copy >>> */
+ while (dd_count > 0) {
+ bytes_read = 0;
+ bytes_of = 0;
+ bytes_of2 = 0;
+ penult_sparse_skip = sparse_skip;
+ penult_blocks = penult_sparse_skip ? blocks : 0;
+ sparse_skip = false;
+ blocks = (dd_count > blocks_per) ? blocks_per : dd_count;
+ if (FT_SG & in_type) {
+ dio_tmp = iflag.dio;
+ res = sg_read(infd, wrkPos, blocks, skip, blk_sz, &iflag,
+ &dio_tmp, &blks_read);
+ if (-2 == res) { /* ENOMEM, find what's available+try that */
+ if (ioctl(infd, SG_GET_RESERVED_SIZE, &buf_sz) < 0) {
+ perror("RESERVED_SIZE ioctls failed");
+ ret = res;
+ break;
+ }
+ if (buf_sz < MIN_RESERVED_SIZE)
+ buf_sz = MIN_RESERVED_SIZE;
+ blocks_per = (buf_sz + blk_sz - 1) / blk_sz;
+ if (blocks_per < blocks) {
+ blocks = blocks_per;
+ pr2serr("Reducing read to %d blocks per loop\n",
+ blocks_per);
+ res = sg_read(infd, wrkPos, blocks, skip, blk_sz,
+ &iflag, &dio_tmp, &blks_read);
+ }
+ }
+ if (res) {
+ pr2serr("sg_read failed,%s at or after lba=%" PRId64 " [0x%"
+ PRIx64 "]\n", ((-2 == res) ?
+ " try reducing bpt," : ""), skip, skip);
+ ret = res;
+ break;
+ } else {
+ if (blks_read < blocks) {
+ dd_count = 0; /* force exit after write */
+ blocks = blks_read;
+ }
+ in_full += blocks;
+ if (iflag.dio && (! dio_tmp))
+ dio_incomplete_count++;
+ }
+ } else if (FT_RANDOM_0_FF == in_type) {
+ int j;
+
+ res = blocks * blk_sz;
+ if (iflag.zero && iflag.ff && (blk_sz >= 4)) {
+ uint32_t pos = (uint32_t)skip;
+ uint32_t off;
+
+ for (k = 0, off = 0; k < blocks; ++k, off += blk_sz, ++pos) {
+ for (j = 0; j < (blk_sz - 3); j += 4)
+ sg_put_unaligned_be32(pos, wrkPos + off + j);
+ }
+ } else if (iflag.zero)
+ memset(wrkPos, 0, res);
+ else if (iflag.ff)
+ memset(wrkPos, 0xff, res);
+ else {
+ int kk, jj;
+ const int jbump = sizeof(uint32_t);
+ long rn;
+ uint8_t * bp;
+
+ bp = wrkPos;
+ for (kk = 0; kk < blocks; ++kk, bp += blk_sz) {
+ for (jj = 0; jj < blk_sz; jj += jbump) {
+ /* mrand48 takes uniformly from [-2^31, 2^31) */
+#ifdef HAVE_SRAND48_R
+ mrand48_r(&drand, &rn);
+#else
+ rn = mrand48();
+#endif
+ *((uint32_t *)(bp + jj)) = (uint32_t)rn;
+ }
+ }
+ }
+ bytes_read = res;
+ in_full += blocks;
+ } else {
+ while (((res = read(infd, wrkPos, blocks * blk_sz)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("read(unix): count=%d, res=%d\n", blocks * blk_sz,
+ res);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "reading, skip=%" PRId64 " ",
+ skip);
+ perror(ebuff);
+ ret = -1;
+ break;
+ } else if (res < blocks * blk_sz) {
+ dd_count = 0;
+ blocks = res / blk_sz;
+ if ((res % blk_sz) > 0) {
+ blocks++;
+ in_partial++;
+ }
+ }
+ bytes_read = res;
+ in_full += blocks;
+ }
+
+ if (0 == blocks)
+ break; /* nothing read so leave loop */
+
+ if (out2f[0]) {
+ while (((res = write(out2fd, wrkPos, blocks * blk_sz)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("write to of2: count=%d, res=%d\n", blocks * blk_sz,
+ res);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "writing to of2, seek=%" PRId64
+ " ", seek);
+ perror(ebuff);
+ ret = -1;
+ break;
+ }
+ bytes_of2 = res;
+ }
+
+ if (oflag.sparse && (dd_count > blocks) &&
+ (! (FT_DEV_NULL & out_type))) {
+ if (NULL == zeros_buff) {
+ zeros_buff = sg_memalign(blocks * blk_sz, 0, &free_zeros_buff,
+ false);
+ if (NULL == zeros_buff) {
+ pr2serr("zeros_buff sg_memalign failed\n");
+ ret = -1;
+ break;
+ }
+ }
+ if (0 == memcmp(wrkPos, zeros_buff, blocks * blk_sz))
+ sparse_skip = true;
+ }
+ if (sparse_skip) {
+ if (FT_SG & out_type) {
+ out_sparse_num += blocks;
+ if (verbose > 2)
+ pr2serr("sparse bypassing sg_write: seek blk=%" PRId64
+ ", offset blks=%d\n", seek, blocks);
+ } else if (FT_DEV_NULL & out_type)
+ ;
+ else {
+ off64_t offset = (off64_t)blocks * blk_sz;
+ off64_t off_res;
+
+ if (verbose > 2)
+ pr2serr("sparse bypassing write: seek=%" PRId64 ", rel "
+ "offset=%" PRId64 "\n", (seek * blk_sz),
+ (int64_t)offset);
+ off_res = lseek64(outfd, offset, SEEK_CUR);
+ if (off_res < 0) {
+ pr2serr("sparse tried to bypass write: seek=%" PRId64
+ ", rel offset=%" PRId64 " but ...\n",
+ (seek * blk_sz), (int64_t)offset);
+ perror("lseek64 on output");
+ ret = SG_LIB_FILE_ERROR;
+ break;
+ } else if (verbose > 4)
+ pr2serr("oflag=sparse lseek64 result=%" PRId64 "\n",
+ (int64_t)off_res);
+ out_sparse_num += blocks;
+ }
+ } else if (FT_SG & out_type) {
+ dio_tmp = oflag.dio;
+ retries_tmp = oflag.retries;
+ first = true;
+ while (1) {
+ ret = sg_write(outfd, wrkPos, blocks, seek, blk_sz, &oflag,
+ &dio_tmp);
+ if ((0 == ret) || (SG_DD_BYPASS == ret))
+ break;
+ if ((SG_LIB_CAT_NOT_READY == ret) ||
+ (SG_LIB_SYNTAX_ERROR == ret))
+ break;
+ else if ((-2 == ret) && first) {
+ /* ENOMEM: find what's available and try that */
+ if (ioctl(outfd, SG_GET_RESERVED_SIZE, &buf_sz) < 0) {
+ perror("RESERVED_SIZE ioctls failed");
+ break;
+ }
+ if (buf_sz < MIN_RESERVED_SIZE)
+ buf_sz = MIN_RESERVED_SIZE;
+ blocks_per = (buf_sz + blk_sz - 1) / blk_sz;
+ if (blocks_per < blocks) {
+ blocks = blocks_per;
+ pr2serr("Reducing %s to %d blocks per loop\n",
+ (do_verify ? "verify" : "write"), blocks);
+ } else
+ break;
+ } else if ((SG_LIB_CAT_UNIT_ATTENTION == ret) && first) {
+ if (--max_uas > 0)
+ pr2serr("Unit attention, continuing (w)\n");
+ else {
+ pr2serr("Unit attention, too many (w)\n");
+ break;
+ }
+ } else if ((SG_LIB_CAT_ABORTED_COMMAND == ret) && first) {
+ if (--max_aborted > 0)
+ pr2serr("Aborted command, continuing (w)\n");
+ else {
+ pr2serr("Aborted command, too many (w)\n");
+ break;
+ }
+ } else if (ret < 0)
+ break;
+ else if (retries_tmp > 0) {
+ pr2serr(">>> retrying a sgio %s, lba=0x%" PRIx64 "\n",
+ (do_verify ? "verify" : "write"), (uint64_t)seek);
+ --retries_tmp;
+ ++num_retries;
+ if (unrecovered_errs > 0)
+ --unrecovered_errs;
+ } else
+ break;
+ first = false;
+ }
+ if (SG_DD_BYPASS == ret)
+ ret = 0; /* not bumping out_full */
+ else if (0 != ret) {
+ pr2serr("sg_write failed,%s seek=%" PRId64 "\n",
+ ((-2 == ret) ? " try reducing bpt," : ""), seek);
+ break;
+ } else {
+ out_full += blocks;
+ if (oflag.dio && (! dio_tmp))
+ dio_incomplete_count++;
+ }
+ } else if (FT_DEV_NULL & out_type)
+ out_full += blocks; /* act as if written out without error */
+ else {
+ while (((res = write(outfd, wrkPos, blocks * blk_sz)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("write(unix): count=%d, res=%d\n", blocks * blk_sz,
+ res);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "writing, seek=%" PRId64 " ",
+ seek);
+ perror(ebuff);
+ ret = -1;
+ break;
+ } else if (res < blocks * blk_sz) {
+ pr2serr("output file probably full, seek=%" PRId64 " ", seek);
+ blocks = res / blk_sz;
+ out_full += blocks;
+ if ((res % blk_sz) > 0)
+ out_partial++;
+ ret = -1;
+ break;
+ } else {
+ out_full += blocks;
+ bytes_of = res;
+ }
+ }
+#ifdef HAVE_POSIX_FADVISE
+ {
+ int rt, in_valid, out2_valid, out_valid;
+
+ in_valid = ((FT_OTHER == in_type) || (FT_BLOCK == in_type));
+ out2_valid = ((FT_OTHER == out2_type) || (FT_BLOCK == out2_type));
+ out_valid = ((FT_OTHER == out_type) || (FT_BLOCK == out_type));
+ if (iflag.nocache && (bytes_read > 0) && in_valid) {
+ rt = posix_fadvise(infd, 0, (skip * blk_sz) + bytes_read,
+ POSIX_FADV_DONTNEED);
+ // rt = posix_fadvise(infd, (skip * blk_sz), bytes_read,
+ // POSIX_FADV_DONTNEED);
+ // rt = posix_fadvise(infd, 0, 0, POSIX_FADV_DONTNEED);
+ if (rt) /* returns error as result */
+ pr2serr("posix_fadvise on read, skip=%" PRId64
+ " ,err=%d\n", skip, rt);
+ }
+ if ((oflag.nocache & 2) && (bytes_of2 > 0) && out2_valid) {
+ rt = posix_fadvise(out2fd, 0, 0, POSIX_FADV_DONTNEED);
+ if (rt)
+ pr2serr("posix_fadvise on of2, seek=%" PRId64
+ " ,err=%d\n", seek, rt);
+ }
+ if ((oflag.nocache & 1) && (bytes_of > 0) && out_valid) {
+ rt = posix_fadvise(outfd, 0, 0, POSIX_FADV_DONTNEED);
+ if (rt)
+ pr2serr("posix_fadvise on output, seek=%" PRId64
+ " ,err=%d\n", seek, rt);
+ }
+ }
+#endif
+ if (dd_count > 0)
+ dd_count -= blocks;
+ skip += blocks;
+ seek += blocks;
+ if (progress > 0) {
+ if (check_progress()) {
+ calc_duration_throughput(true);
+ print_stats("");
+ }
+ }
+ } /* end of main loop that does the copy ... */
+
+ if (ret && penult_sparse_skip && (penult_blocks > 0)) {
+ /* if error and skipped last output due to sparse ... */
+ if ((FT_SG & out_type) || (FT_DEV_NULL & out_type))
+ ;
+ else {
+ /* ... try writing to extend ofile to length prior to error */
+ while (((res = write(outfd, zeros_buff, penult_blocks * blk_sz))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("write(unix, sparse after error): count=%d, res=%d\n",
+ penult_blocks * blk_sz, res);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "writing(sparse after error), "
+ "seek=%" PRId64 " ", seek);
+ perror(ebuff);
+ }
+ }
+ }
+
+ if (do_sync) {
+ if (FT_SG & out_type) {
+ pr2serr(">> Synchronizing cache on %s\n", outf);
+ res = sg_ll_sync_cache_10(outfd, false, false, 0, 0, 0, true, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention (out, sync cache), continuing\n");
+ res = sg_ll_sync_cache_10(outfd, false, false, 0, 0, 0,
+ false, 0);
+ }
+ if (0 != res)
+ pr2serr("Unable to synchronize cache\n");
+ }
+ }
+
+bypass_copy:
+ if (do_time)
+ calc_duration_throughput(false);
+ if (progress > 0)
+ pr2serr("\nCompleted:\n");
+
+ if (wrkBuff)
+ free(wrkBuff);
+ if (free_zeros_buff)
+ free(free_zeros_buff);
+ if ((STDIN_FILENO != infd) && (infd >= 0))
+ close(infd);
+ if (! ((STDOUT_FILENO == outfd) || (FT_DEV_NULL & out_type))) {
+ if (outfd >= 0)
+ close(outfd);
+ }
+ if (dry_run > 0)
+ goto bypass2;
+
+ if (0 != dd_count) {
+ pr2serr("Some error occurred,");
+ if (0 == ret)
+ ret = SG_LIB_CAT_OTHER;
+ }
+ print_stats("");
+ if (dio_incomplete_count) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ dio_incomplete_count);
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (sum_of_resids)
+ pr2serr(">> Non-zero sum of residual counts=%d\n", sum_of_resids);
+
+bypass2:
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_decode_sense.c b/src/sg_decode_sense.c
new file mode 100644
index 00000000..db54e0b9
--- /dev/null
+++ b/src/sg_decode_sense.c
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) 2010-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.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_pr2serr.h"
+#include "sg_unaligned.h"
+
+
+static const char * version_str = "1.32 20220730";
+
+#define MY_NAME "sg_decode_sense"
+
+#define MAX_SENSE_LEN 8192 /* max descriptor format actually: 255+8 */
+
+static struct option long_options[] = {
+ {"binary", required_argument, 0, 'b'},
+ {"cdb", no_argument, 0, 'c'},
+ {"err", required_argument, 0, 'e'},
+ {"exit-status", required_argument, 0, 'e'},
+ {"exit_status", required_argument, 0, 'e'},
+ {"file", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* don't advertise */
+ {"inhex", required_argument, 0, 'i'}, /* same as --file */
+ {"ignore-first", no_argument, 0, 'I'},
+ {"ignore_first", no_argument, 0, 'I'},
+ {"json", optional_argument, 0, 'j'},
+ {"nodecode", no_argument, 0, 'N'},
+ {"nospace", no_argument, 0, 'n'},
+ {"status", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"write", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_binary;
+ bool do_cdb;
+ bool do_help;
+ bool no_decode;
+ bool no_space;
+ bool do_status;
+ bool verbose_given;
+ bool version_given;
+ bool err_given;
+ bool file_given;
+ bool ignore_first;
+ const char * fname;
+ int es_val;
+ int hex_count;
+ int sense_len;
+ int sstatus;
+ int verbose;
+ const char * wfname;
+ const char * no_space_str;
+ sgj_state json_st;
+ uint8_t sense[MAX_SENSE_LEN + 4];
+};
+
+static char concat_buff[1024];
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_decode_sense [--binary=BFN] [--cdb] [--err=ES] "
+ "[--file=HFN]\n"
+ " [--help] [--hex] [--inhex=HFN] "
+ "[--ignore-first]\n"
+ " [--json[=JO]] [--nodecode] [--nospace] "
+ "[--status=SS]\n"
+ " [--verbose] [--version] [--write=WFN] "
+ "H1 H2 H3 ...\n"
+ " where:\n"
+ " --binary=BFN|-b BFN BFN is a file name to read sense "
+ "data in\n"
+ " binary from. If BFN is '-' then read "
+ "from stdin\n"
+ " --cdb|-c decode given hex as cdb rather than "
+ "sense data\n"
+ " --err=ES|-e ES ES is Exit Status from utility in this "
+ "package\n"
+ " --file=HFN|-f HFN HFN is a file name from which to read "
+ "sense data\n"
+ " in ASCII hexadecimal. Interpret '-' "
+ "as stdin\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H used together with --write=WFN, to "
+ "write out\n"
+ " C language style ASCII hex (instead "
+ "of binary).\n"
+ " Otherwise don't decode, output incoming "
+ "data in\n"
+ " hex (used '-HH' or '-HHH' for different "
+ "formats)\n"
+ " --inhex=HFN|-i HFN same as action as --file=HFN\n"
+ " --ignore-first|-I when reading hex (e.g. with --file=HFN) "
+ "skip\n"
+ " the first hexadecimal value on each "
+ "line\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --nodecode|-N do not decode, may be neither sense "
+ "nor cdb\n"
+ " --nospace|-n no spaces or other separators between "
+ "pairs of\n"
+ " hex digits (e.g. '3132330A')\n"
+ " --status=SS |-s SS SCSI status value in hex\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --write=WFN |-w WFN write sense data in binary to WFN, "
+ "create if\n"
+ " required else truncate prior to "
+ "writing\n\n"
+ "Decodes SCSI sense data given on the command line as a sequence "
+ "of\nhexadecimal bytes (H1 H2 H3 ...) . Alternatively the sense "
+ "data can\nbe in a binary file or in a file containing ASCII "
+ "hexadecimal. If\n'--cdb' is given then interpret hex as SCSI CDB "
+ "rather than sense data.\n"
+ );
+}
+
+static int
+parse_cmd_line(struct opts_t *op, int argc, char *argv[])
+{
+ int c, n;
+ unsigned int ui;
+ long val;
+ char * avp;
+ char *endptr;
+
+ while (1) {
+ c = getopt_long(argc, argv, "b:ce:f:hHi:Ij::nNs:vVw:", long_options,
+ NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ if (op->fname) {
+ pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
+ "'--inhex=HFN' option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->do_binary = true;
+ op->fname = optarg;
+ break;
+ case 'c':
+ op->do_cdb = true;
+ break;
+ case 'e':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("--err= expected number from 0 to 255 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->err_given = true;
+ op->es_val = n;
+ break;
+ case 'f':
+ if (op->fname) {
+ pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
+ "'--inhex=HFN' option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->file_given = true;
+ op->fname = optarg;
+ break;
+ case 'h':
+ case '?':
+ op->do_help = true;
+ return 0;
+ case 'H':
+ op->hex_count++;
+ break;
+ case 'i':
+ if (op->fname) {
+ pr2serr("expect only one '--binary=BFN', '--file=HFN' or "
+ "'--inhex=HFN' option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->file_given = true;
+ op->fname = optarg;
+ break;
+ case 'I':
+ op->ignore_first = true;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'n':
+ op->no_space = true;
+ break;
+ case 'N':
+ op->no_decode = true;
+ break;
+ case 's':
+ if (1 != sscanf(optarg, "%x", &ui)) {
+ pr2serr("'--status=SS' expects a byte value\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (ui > 0xff) {
+ pr2serr("'--status=SS' byte value exceeds FF\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_status = true;
+ op->sstatus = ui;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->wfname = optarg;
+ break;
+ default:
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->err_given)
+ goto the_end;
+
+ while (optind < argc) {
+ avp = argv[optind++];
+ if (op->no_space) {
+ if (op->no_space_str) {
+ if ('\0' == concat_buff[0]) {
+ if (strlen(op->no_space_str) > sizeof(concat_buff)) {
+ pr2serr("'--nospace' concat_buff overflow\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ strcpy(concat_buff, op->no_space_str);
+ }
+ if ((strlen(concat_buff) + strlen(avp)) >=
+ sizeof(concat_buff)) {
+ pr2serr("'--nospace' concat_buff overflow\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->version_given)
+ pr2serr("'--nospace' and found whitespace so "
+ "concatenate\n");
+ strcat(concat_buff, avp);
+ op->no_space_str = concat_buff;
+ } else
+ op->no_space_str = avp;
+ continue;
+ }
+ val = strtol(avp, &endptr, 16);
+ if (*avp == '\0' || *endptr != '\0' || val < 0x00 || val > 0xff) {
+ pr2serr("Invalid byte '%s'\n", avp);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->sense_len > MAX_SENSE_LEN) {
+ pr2serr("sense data too long (max. %d bytes)\n", MAX_SENSE_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->sense[op->sense_len++] = (uint8_t)val;
+ }
+the_end:
+ return 0;
+}
+
+/* Keep this format (e.g. 0xff,0x12,...) for backward compatibility */
+static void
+write2wfn(FILE * fp, struct opts_t * op)
+{
+ int k, n;
+ size_t s;
+ char b[128];
+
+ for (k = 0, n = 0; k < op->sense_len; ++k) {
+ n += sprintf(b + n, "0x%02x,", op->sense[k]);
+ if (15 == (k % 16)) {
+ b[n] = '\n';
+ s = fwrite(b, 1, n + 1, fp);
+ if ((int)s != (n + 1))
+ pr2serr("only able to write %d of %d bytes to %s\n",
+ (int)s, n + 1, op->wfname);
+ n = 0;
+ }
+ }
+ if (n > 0) {
+ b[n] = '\n';
+ s = fwrite(b, 1, n + 1, fp);
+ if ((int)s != (n + 1))
+ pr2serr("only able to write %d of %d bytes to %s\n", (int)s,
+ n + 1, op->wfname);
+ }
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ bool as_json;
+ int k, err, blen;
+ int ret = 0;
+ unsigned int ui;
+ size_t s;
+ struct opts_t * op;
+ FILE * fp = NULL;
+ const char * cp;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ uint8_t * free_op_buff = NULL;
+ char b[2048];
+
+ op = (struct opts_t *)sg_memalign(sizeof(*op), 0 /* page align */,
+ &free_op_buff, false);
+ if (NULL == op) {
+ pr2serr("Unable to allocate heap for options structure\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto clean_op;
+ }
+ blen = sizeof(b);
+ memset(b, 0, blen);
+ ret = parse_cmd_line(op, argc, argv);
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ goto clean_op;
+ }
+ if (ret != 0) {
+ usage();
+ goto clean_op;
+ } else if (op->do_help) {
+ usage();
+ goto clean_op;
+ }
+ as_json = op->json_st.pr_as_json;
+ jsp = &op->json_st;
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (op->err_given) {
+ char d[128];
+ const int dlen = sizeof(d);
+
+ if (! sg_exit2str(op->es_val, op->verbose > 1, dlen, d))
+ snprintf(d, dlen, "Unable to decode exit status %d", op->es_val);
+ if (1 & op->verbose) /* odd values of verbose print to stderr */
+ pr2serr("%s\n", d);
+ else /* even values of verbose (including not given) to stdout */
+ printf("%s\n", d);
+ goto fini;
+ }
+
+ if (op->do_status) {
+ sg_get_scsi_status_str(op->sstatus, blen, b);
+ printf("SCSI status: %s\n", b);
+ }
+
+ if ((0 == op->sense_len) && op->no_space_str) {
+ if (op->verbose > 2)
+ pr2serr("no_space str: %s\n", op->no_space_str);
+ cp = op->no_space_str;
+ for (k = 0; isxdigit((uint8_t)cp[k]) &&
+ isxdigit((uint8_t)cp[k + 1]); k += 2) {
+ if (1 != sscanf(cp + k, "%2x", &ui)) {
+ pr2serr("bad no_space hex string: %s\n", cp);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ op->sense[op->sense_len++] = (uint8_t)ui;
+ }
+ }
+
+ if ((0 == op->sense_len) && (! op->do_binary) && (! op->file_given)) {
+ if (op->do_status) {
+ ret = 0;
+ goto fini;
+ }
+ pr2serr(">> Need sense/cdb/arbitrary data on the command line or "
+ "in a file\n\n");
+ usage();
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (op->sense_len && (op->do_binary || op->file_given)) {
+ pr2serr(">> Need sense data on command line or in a file, not "
+ "both\n\n");
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (op->do_binary && op->file_given) {
+ pr2serr(">> Either a binary file or a ASCII hexadecimal, file not "
+ "both\n\n");
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+
+ if (op->do_binary) {
+ fp = fopen(op->fname, "r");
+ if (NULL == fp) {
+ err = errno;
+ pr2serr("unable to open file: %s: %s\n", op->fname,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ s = fread(op->sense, 1, MAX_SENSE_LEN, fp);
+ fclose(fp);
+ if (0 == s) {
+ pr2serr("read nothing from file: %s\n", op->fname);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ op->sense_len = s;
+ } else if (op->file_given) {
+ ret = sg_f2hex_arr(op->fname, false, op->no_space, op->sense,
+ &op->sense_len,
+ (op->ignore_first ? -MAX_SENSE_LEN :
+ MAX_SENSE_LEN));
+ if (ret) {
+ pr2serr("unable to decode ASCII hex from file: %s\n", op->fname);
+ goto fini;
+ }
+ }
+
+ if (op->sense_len > 0) {
+ if (op->wfname || op->hex_count) {
+ if (op->wfname) {
+ if (NULL == ((fp = fopen(op->wfname, "w")))) {
+ err =errno;
+ perror("open");
+ pr2serr("trying to write to %s\n", op->wfname);
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ } else
+ fp = stdout;
+
+ if (op->wfname && (1 == op->hex_count))
+ write2wfn(fp, op);
+ else if (op->hex_count && (2 != op->hex_count))
+ dStrHexFp((const char *)op->sense, op->sense_len,
+ ((1 == op->hex_count) ? 1 : -1), fp);
+ else if (op->hex_count)
+ dStrHexFp((const char *)op->sense, op->sense_len, 0, fp);
+ else {
+ s = fwrite(op->sense, 1, op->sense_len, fp);
+ if ((int)s != op->sense_len)
+ pr2serr("only able to write %d of %d bytes to %s\n",
+ (int)s, op->sense_len, op->wfname);
+ }
+ if (op->wfname)
+ fclose(fp);
+ } else if (op->no_decode) {
+ if (op->verbose > 1)
+ pr2serr("Not decoding as %s because --nodecode given\n",
+ (op->do_cdb ? "cdb" : "sense"));
+ } else if (op->do_cdb) {
+ int sa, opcode;
+
+ opcode = op->sense[0];
+ if ((0x75 == opcode) || (0x7e == opcode) || (op->sense_len > 16))
+ sa = sg_get_unaligned_be16(op->sense + 8);
+ else if (op->sense_len > 1)
+ sa = op->sense[1] & 0x1f;
+ else
+ sa = 0;
+ sg_get_opcode_sa_name(opcode, sa, 0, blen, b);
+ printf("%s\n", b);
+ } else {
+ if (as_json) {
+ sgj_js_sense(jsp, jop, op->sense, op->sense_len);
+ if (jsp->pr_out_hr) {
+ sg_get_sense_str(NULL, op->sense, op->sense_len,
+ op->verbose, blen, b);
+ sgj_js_str_out(jsp, b, strlen(b));
+ }
+ } else {
+ sg_get_sense_str(NULL, op->sense, op->sense_len,
+ op->verbose, blen, b);
+ printf("%s\n", b);
+ }
+ }
+ }
+fini:
+ if (as_json) {
+ if (0 == op->hex_count)
+ sgj_js2file(&op->json_st, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+clean_op:
+ if (free_op_buff)
+ free(free_op_buff);
+ return ret;
+}
diff --git a/src/sg_emc_trespass.c b/src/sg_emc_trespass.c
new file mode 100644
index 00000000..146e26b2
--- /dev/null
+++ b/src/sg_emc_trespass.c
@@ -0,0 +1,176 @@
+/* The program allows the user to send a trespass command to change the
+ * LUN ownership from one Service-Processor to this one on an EMC
+ * CLARiiON and potentially other devices.
+ *
+ * Copyright (C) 2004-2018 Lars Marowsky-Bree <lmb@suse.de>
+ *
+ * Based on sg_start.c; credits from there also apply.
+ * Minor modifications for sg_lib, D. Gilbert 2004/10/19
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "0.23 20180219";
+
+static int debug = 0;
+
+#define TRESPASS_PAGE 0x22
+
+static int
+do_trespass(int fd, bool hr, bool short_cmd)
+{
+ uint8_t long_trespass_pg[] =
+ { 0, 0, 0, 0, 0, 0, 0, 0x00,
+ TRESPASS_PAGE, /* Page code */
+ 0x09, /* Page length - 2 */
+ 0x81, /* Trespass code + Honor reservation
+ * bit */
+ 0xff, 0xff, /* Trespass target */
+ 0, 0, 0, 0, 0, 0 /* Reserved bytes / unknown */
+ };
+ uint8_t short_trespass_pg[] =
+ { 0, 0, 0, 0,
+ TRESPASS_PAGE, /* Page code */
+ 0x02, /* Page length - 2 */
+ 0x81, /* Trespass code + Honor reservation
+ * bit */
+ 0xff, /* Trespass target */
+ };
+ int res;
+ char b[80];
+
+ if (hr) { /* override Trespass code + Honor reservation bit */
+ short_trespass_pg[6] = 0x01;
+ long_trespass_pg[10] = 0x01;
+ }
+ if (short_cmd)
+ res = sg_ll_mode_select6(fd, true /* pf */, false /* sp */,
+ short_trespass_pg, sizeof(short_trespass_pg),
+ true, (debug ? 2 : 0));
+ else
+ res = sg_ll_mode_select10(fd, true /* pf */, false /* sp */,
+ long_trespass_pg, sizeof(long_trespass_pg),
+ true, (debug ? 2 : 0));
+
+ switch (res) {
+ case 0:
+ if (debug)
+ pr2serr("%s trespass successful\n",
+ short_cmd ? "short" : "long");
+ break;
+ case SG_LIB_CAT_INVALID_OP:
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ pr2serr("%s form trepass page failed, try again %s '-s' "
+ "option\n", short_cmd ? "short" : "long",
+ short_cmd ? "without" : "with");
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ pr2serr("device not ready\n");
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ pr2serr("unit attention\n");
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, debug);
+ pr2serr("%s trespass failed: %s\n",
+ (short_cmd ? "short" : "long"), b);
+ break;
+ }
+ return res;
+}
+
+void usage ()
+{
+ pr2serr("Usage: sg_emc_trespass [-d] [-hr] [-s] [-V] DEVICE\n"
+ " Change ownership of a LUN from another SP to this one.\n"
+ " EMC CLARiiON CX-/AX-family + FC5300/FC4500/FC4700.\n"
+ " -d : output debug\n"
+ " -hr: Set Honor Reservation bit\n"
+ " -s : Send Short Trespass Command page (default: long)\n"
+ " (for FC series)\n"
+ " -V: print version string then exit\n"
+ " DEVICE sg or block device (latter in lk 2.6 or lk 3 "
+ "series)\n"
+ " Example: sg_emc_trespass /dev/sda\n");
+ exit (1);
+}
+
+int main(int argc, char * argv[])
+{
+ char **argptr;
+ char * file_name = 0;
+ int k, fd;
+ bool hr = false;
+ bool short_cmd = false;
+ int ret = 0;
+
+ if (argc < 2)
+ usage ();
+
+ for (k = 1; k < argc; ++k) {
+ argptr = argv + k;
+ if (!strcmp (*argptr, "-d"))
+ ++debug;
+ else if (!strcmp (*argptr, "-s"))
+ short_cmd = true;
+ else if (!strcmp (*argptr, "-hr"))
+ hr = true;
+ else if (!strcmp (*argptr, "-V")) {
+ printf("Version string: %s\n", version_str);
+ exit(0);
+ }
+ else if (*argv[k] == '-') {
+ pr2serr("Unrecognized switch: %s\n", argv[k]);
+ file_name = NULL;
+ break;
+ }
+ else if (NULL == file_name)
+ file_name = argv[k];
+ else {
+ pr2serr("too many arguments\n");
+ file_name = NULL;
+ break;
+ }
+ }
+ if (NULL == file_name) {
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ fd = open(file_name, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ pr2serr("Error trying to open %s\n", file_name);
+ perror("");
+ usage();
+ return SG_LIB_FILE_ERROR;
+ }
+
+ ret = do_trespass(fd, hr, short_cmd);
+
+ close (fd);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_format.c b/src/sg_format.c
new file mode 100644
index 00000000..4f3793bd
--- /dev/null
+++ b/src/sg_format.c
@@ -0,0 +1,1729 @@
+/*
+ * sg_format : format a SCSI disk
+ * potentially with a different number of blocks and block size
+ *
+ * formerly called blk512-linux.c (v0.4)
+ *
+ * Copyright (C) 2003 Grant Grundler grundler at parisc-linux dot org
+ * Copyright (C) 2003 James Bottomley jejb at parisc-linux dot org
+ * Copyright (C) 2005-2022 Douglas Gilbert dgilbert at interlog dot com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * See https://www.t10.org for relevant standards and drafts. The most recent
+ * draft is SBC-4 revision 2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <unistd.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+#include "sg_pt.h"
+
+static const char * version_str = "1.68 20220609";
+
+
+#define RW_ERROR_RECOVERY_PAGE 1 /* can give alternate with --mode=MP */
+
+#define SHORT_TIMEOUT 20 /* 20 seconds unless --wait given */
+#define FORMAT_TIMEOUT (20 * 3600) /* 20 hours ! */
+#define FOUR_TBYTE (4LL * 1000 * 1000 * 1000 * 1000)
+#define LONG_FORMAT_TIMEOUT (40 * 3600) /* 40 hours */
+#define EIGHT_TBYTE (FOUR_TBYTE * 2)
+#define VLONG_FORMAT_TIMEOUT (80 * 3600) /* 3 days, 8 hours */
+
+#define POLL_DURATION_SECS 60
+#define POLL_DURATION_FFMT_SECS 10
+#define DEF_POLL_TYPE_RS false /* false -> test unit ready;
+ true -> request sense */
+#define MAX_BUFF_SZ 252
+
+/* FORMAT UNIT (SBC) and FORMAT MEDIUM (SSC) share the same opcode */
+#define SG_FORMAT_MEDIUM_CMD 0x4
+#define SG_FORMAT_MEDIUM_CMDLEN 6
+
+/* FORMAT WITH PRESET (new in sbc4r18) */
+#define SG_FORMAT_WITH_PRESET_CMD 0x38
+#define SG_FORMAT_WITH_PRESET_CMDLEN 10
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+struct opts_t {
+ bool cmplst; /* -C value */
+ bool cmplst_given;
+ bool dry_run; /* -d */
+ bool early; /* -e */
+ bool fmtmaxlba; /* -b (only with F_WITH_PRESET) */
+ bool fwait; /* -w (negated form IMMED) */
+ bool ip_def; /* -I */
+ bool long_lba; /* -l */
+ bool mode6; /* -6 */
+ bool pinfo; /* -p, deprecated, prefer fmtpinfo */
+ bool poll_type; /* -x 0|1 */
+ bool poll_type_given;
+ bool preset; /* -E */
+ bool quick; /* -Q */
+ bool do_rcap16; /* -l */
+ bool resize; /* -r */
+ bool rto_req; /* -R, deprecated, prefer fmtpinfo */
+ bool verbose_given;
+ bool verify; /* -y */
+ bool version_given;
+ int dcrt; /* -D (can be given once or twice) */
+ int lblk_sz; /* -s value */
+ int ffmt; /* -t value; fast_format if > 0 */
+ int fmtpinfo;
+ int format; /* -F */
+ uint32_t p_id; /* set by argument of --preset=id */
+ int mode_page; /* -M value */
+ int pfu; /* -P value */
+ int pie; /* -q value */
+ int sec_init; /* -S */
+ int tape; /* -T <format>, def: -1 */
+ int timeout; /* -m SECS, def: depends on IMMED bit */
+ int verbose; /* -v */
+ int64_t blk_count; /* -c value */
+ int64_t total_byte_count; /* from READ CAPACITY command */
+ const char * device_name;
+};
+
+
+
+static struct option long_options[] = {
+ {"count", required_argument, 0, 'c'},
+ {"cmplst", required_argument, 0, 'C'},
+ {"dcrt", no_argument, 0, 'D'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"dry_run", no_argument, 0, 'd'},
+ {"early", no_argument, 0, 'e'},
+ {"ffmt", required_argument, 0, 't'},
+ {"fmtmaxlba", no_argument, 0, 'b'},
+ {"fmtpinfo", required_argument, 0, 'f'},
+ {"format", no_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"ip-def", no_argument, 0, 'I'},
+ {"ip_def", no_argument, 0, 'I'},
+ {"long", no_argument, 0, 'l'},
+ {"mode", required_argument, 0, 'M'},
+ {"pinfo", no_argument, 0, 'p'},
+ {"pfu", required_argument, 0, 'P'},
+ {"pie", required_argument, 0, 'q'},
+ {"poll", required_argument, 0, 'x'},
+ {"preset", required_argument, 0, 'E'},
+ {"quick", no_argument, 0, 'Q'},
+ {"resize", no_argument, 0, 'r'},
+ {"rto_req", no_argument, 0, 'R'},
+ {"security", no_argument, 0, 'S'},
+ {"six", no_argument, 0, '6'},
+ {"size", required_argument, 0, 's'},
+ {"tape", required_argument, 0, 'T'},
+ {"timeout", required_argument, 0, 'm'},
+ {"verbose", no_argument, 0, 'v'},
+ {"verify", no_argument, 0, 'y'},
+ {"version", no_argument, 0, 'V'},
+ {"wait", no_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+static const char * fu_s = "Format unit";
+static const char * fm_s = "Format medium";
+static const char * fwp_s = "Format with preset";
+
+
+static void
+usage()
+{
+ printf("Usage:\n"
+ " sg_format [--cmplst=0|1] [--count=COUNT] [--dcrt] "
+ "[--dry-run] [--early]\n"
+ " [--ffmt=FFMT] [--fmtmaxlba] [--fmtpinfo=FPI] "
+ "[--format] [--help]\n"
+ " [--ip-def] [--long] [--mode=MP] [--pfu=PFU] "
+ "[--pie=PIE]\n"
+ " [--pinfo] [--poll=PT] [--preset=ID] [--quick] "
+ "[--resize]\n"
+ " [--rto_req] [--security] [--six] [--size=LB_SZ] "
+ "[--tape=FM]\n"
+ " [--timeout=SECS] [--verbose] [--verify] "
+ "[--version] [--wait]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --cmplst=0|1\n"
+ " -C 0|1 sets CMPLST bit in format cdb "
+ "(def: 1; if FFMT: 0)\n"
+ " --count=COUNT|-c COUNT number of blocks to report "
+ "after format or\n"
+ " resize. Format default is "
+ "same as current\n"
+ " --dcrt|-D disable certification (doesn't "
+ "verify media)\n"
+ " use twice to enable certification and "
+ "set FOV bit\n"
+ " --dry-run|-d bypass device modifying commands (i.e. "
+ "don't format)\n"
+ " --early|-e exit once format started (user can "
+ "monitor progress)\n"
+ " --ffmt=FFMT|-t FFMT fast format (def: 0 -> slow, "
+ "may visit every\n"
+ " block). 1 and 2 are fast formats; "
+ "1: after\n"
+ " format, unwritten data read "
+ "without error\n"
+ " --fmtpinfo=FPI|-f FPI FMTPINFO field value "
+ "(default: 0)\n"
+ " --format|-F do FORMAT UNIT (default: report current "
+ "count and size)\n"
+ " use thrice for FORMAT UNIT command "
+ "only\n"
+ " --fmtmaxlba|-b sets FMTMAXLBA field in FORMAT WITH "
+ "PRESET\n"
+ " --help|-h prints out this usage message\n"
+ " --ip-def|-I use default initialization pattern\n"
+ " --long|-l allow for 64 bit lbas (default: assume "
+ "32 bit lbas)\n"
+ " --mode=MP|-M MP mode page (def: 1 -> RW error "
+ "recovery mpage)\n"
+ " --pie=PIE|-q PIE Protection Information Exponent "
+ "(default: 0)\n"
+ " --pinfo|-p set upper bit of FMTPINFO field\n"
+ " (deprecated, use '--fmtpinfo=FPI' "
+ "instead)\n"
+ " --poll=PT|-x PT PT is poll type, 0 for test unit "
+ "ready\n"
+ " 1 for request sense (def: 0 (1 "
+ "for tape and\n"
+ " format with preset))\n");
+ printf(" --preset=ID|-E ID do FORMAT WITH PRESET command "
+ "with PRESET\n"
+ " IDENTIFIER field set to ID\n"
+ " --quick|-Q start format without pause for user "
+ "intervention\n"
+ " (i.e. no time to reconsider)\n"
+ " --resize|-r resize (rather than format) to COUNT "
+ "value\n"
+ " --rto_req|-R set lower bit of FMTPINFO field\n"
+ " (deprecated use '--fmtpinfo=FPI' "
+ "instead)\n"
+ " --security|-S set security initialization (SI) bit\n"
+ " --six|-6 use 6 byte MODE SENSE/SELECT to probe "
+ "disk\n"
+ " (def: use 10 byte MODE SENSE/SELECT)\n"
+ " --size=LB_SZ|-s LB_SZ bytes per logical block, "
+ "defaults to DEVICE's\n"
+ " current logical block size. Only "
+ "needed to\n"
+ " change current logical block "
+ "size\n"
+ " --tape=FM|-T FM request FORMAT MEDIUM with FORMAT "
+ "field set\n"
+ " to FM (def: 0 --> default format)\n"
+ " --timeout=SECS|-m SECS FORMAT UNIT/MEDIUM command "
+ "timeout in seconds\n"
+ " --verbose|-v increase verbosity\n"
+ " --verify|-y sets VERIFY bit in FORMAT MEDIUM (tape)\n"
+ " --version|-V print version details and exit\n"
+ " --wait|-w format commands wait until format "
+ "operations complete\n"
+ " (default: set IMMED=1 and poll with "
+ "Test Unit Ready)\n\n"
+ "\tExample: sg_format --format /dev/sdc\n\n"
+ "This utility formats a SCSI disk [FORMAT UNIT] or resizes "
+ "it. Alternatively\nif '--tape=FM' is given formats a tape "
+ "[FORMAT MEDIUM]. Another alternative\nis doing the FORMAT "
+ "WITH PRESET command when '--preset=ID' is given.\n\n");
+ printf("WARNING: This utility will destroy all the data on the "
+ "DEVICE when\n\t '--format', '--tape=FM' or '--preset=ID' "
+ "is given. Double check\n\t that you have specified the "
+ "correct DEVICE.\n");
+}
+
+/* Invokes a SCSI FORMAT MEDIUM command (SSC). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_format_medium(int sg_fd, bool verify, bool immed, int format,
+ void * paramp, int transfer_len, int timeout, bool noisy,
+ int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t fm_cdb[SG_FORMAT_MEDIUM_CMDLEN] =
+ {SG_FORMAT_MEDIUM_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (verify)
+ fm_cdb[1] |= 0x2;
+ if (immed)
+ fm_cdb[1] |= 0x1;
+ if (format)
+ fm_cdb[2] |= (0xf & format);
+ if (transfer_len > 0)
+ sg_put_unaligned_be16(transfer_len, fm_cdb + 3);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", fm_s,
+ sg_get_command_str(fm_cdb, SG_FORMAT_MEDIUM_CMDLEN,
+ false, sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ set_scsi_pt_cdb(ptvp, fm_cdb, sizeof(fm_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, transfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout, verbose);
+ ret = sg_cmds_process_resp(ptvp, fm_s, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ ret = 0;
+ if (verbose)
+ pr2serr("%s command %s without error\n", fm_s,
+ (immed ? "launched" : "completed"));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI FORMAT WITH PRESET command (SBC). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_format_with_preset(int sg_fd, bool immed, bool fmtmaxlba,
+ uint32_t preset_id, int timeout, bool noisy,
+ int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t fwp_cdb[SG_FORMAT_WITH_PRESET_CMDLEN] =
+ {SG_FORMAT_WITH_PRESET_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (immed)
+ fwp_cdb[1] |= 0x80;
+ if (fmtmaxlba)
+ fwp_cdb[1] |= 0x40;
+ if (preset_id > 0)
+ sg_put_unaligned_be32(preset_id, fwp_cdb + 2);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", fwp_s,
+ sg_get_command_str(fwp_cdb,
+ SG_FORMAT_WITH_PRESET_CMDLEN,
+ false, sizeof(b), b));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ set_scsi_pt_cdb(ptvp, fwp_cdb, sizeof(fwp_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, timeout, verbose);
+ ret = sg_cmds_process_resp(ptvp, fwp_s, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ ret = 0;
+ if (verbose)
+ pr2serr("%s command %s without error\n", fwp_s,
+ (immed ? "launched" : "completed"));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Return 0 on success, else see sg_ll_format_unit_v2() */
+static int
+scsi_format_unit(int fd, const struct opts_t * op)
+{
+ bool need_param_lst, longlist, ip_desc, first;
+ bool immed = ! op->fwait;
+ int res, progress, pr, rem, param_sz, off, resp_len, tmout;
+ int poll_wait_secs;
+ int vb = op->verbose;
+ const int SH_FORMAT_HEADER_SZ = 4;
+ const int LONG_FORMAT_HEADER_SZ = 8;
+ const int INIT_PATTERN_DESC_SZ = 4;
+ const int max_param_sz = LONG_FORMAT_HEADER_SZ + INIT_PATTERN_DESC_SZ;
+ uint8_t * param;
+ uint8_t * free_param = NULL;
+ char b[80];
+
+ param = sg_memalign(max_param_sz, 0, &free_param, false);
+ if (NULL == param) {
+ pr2serr("%s: unable to obtain heap for parameter list\n",
+ __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ if (immed)
+ tmout = SHORT_TIMEOUT;
+ else {
+ if (op->total_byte_count > EIGHT_TBYTE)
+ tmout = VLONG_FORMAT_TIMEOUT;
+ else if (op->total_byte_count > FOUR_TBYTE)
+ tmout = LONG_FORMAT_TIMEOUT;
+ else
+ tmout = FORMAT_TIMEOUT;
+ }
+ if (op->timeout > tmout)
+ tmout = op->timeout;
+ longlist = (op->pie > 0); /* only set LONGLIST if PI_EXPONENT>0 */
+ ip_desc = (op->ip_def || op->sec_init);
+ off = longlist ? LONG_FORMAT_HEADER_SZ : SH_FORMAT_HEADER_SZ;
+ param[0] = op->pfu & 0x7; /* PROTECTION_FIELD_USAGE (bits 2-0) */
+ param[1] = (immed ? 0x2 : 0); /* FOV=0, [DPRY,DCRT,STPF,IP=0] */
+ if (1 == op->dcrt)
+ param[1] |= 0xa0; /* FOV=1, DCRT=1 */
+ else if (op->dcrt > 1)
+ param[1] |= 0x80; /* FOV=1, DCRT=0 */
+ if (ip_desc) {
+ param[1] |= 0x88; /* FOV=1, IP=1 */
+ if (op->sec_init)
+ param[off + 0] = 0x20; /* SI=1 in IP desc */
+ }
+ if (longlist)
+ param[3] = (op->pie & 0xf);/* PROTECTION_INTERVAL_EXPONENT */
+ /* with the long parameter list header, P_I_INFORMATION is always 0 */
+
+ need_param_lst = (immed || op->cmplst || (op->dcrt > 0) || ip_desc ||
+ (op->pfu > 0) || (op->pie > 0));
+ param_sz = need_param_lst ?
+ (off + (ip_desc ? INIT_PATTERN_DESC_SZ : 0)) : 0;
+
+ if (op->dry_run) {
+ res = 0;
+ pr2serr("Due to --dry-run option bypassing FORMAT UNIT "
+ "command\n");
+ if (vb) {
+ if (need_param_lst) {
+ pr2serr(" %s would have received parameter "
+ "list: ", fu_s);
+ hex2stderr(param, max_param_sz, -1);
+ } else
+ pr2serr(" %s would not have received a "
+ "parameter list\n", fu_s);
+ pr2serr(" %s cdb fields: fmtpinfo=0x%x, "
+ "longlist=%d, fmtdata=%d, cmplst=%d, "
+ "ffmt=%d [timeout=%d secs]\n", fu_s,
+ op->fmtpinfo, longlist, need_param_lst,
+ op->cmplst, op->ffmt, tmout);
+ }
+ } else
+ res = sg_ll_format_unit_v2(fd, op->fmtpinfo, longlist,
+ need_param_lst, op->cmplst, 0,
+ op->ffmt, tmout, param, param_sz,
+ true, vb);
+ if (free_param)
+ free(free_param);
+
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s command: %s\n", fu_s, b);
+ return res;
+ } else if (op->verbose)
+ pr2serr("%s command %s without error\n", fu_s,
+ (immed ? "launched" : "completed"));
+ if (! immed)
+ return 0;
+
+ if (! op->dry_run)
+ printf("\n%s has started\n", fu_s);
+
+ if (op->early) {
+ if (immed)
+ printf("%s continuing,\n request sense or "
+ "test unit ready can be used to monitor "
+ "progress\n", fu_s);
+ return 0;
+ }
+
+ if (op->dry_run) {
+ printf("No point in polling for progress, so exit\n");
+ return 0;
+ }
+ poll_wait_secs = op->ffmt ? POLL_DURATION_FFMT_SECS :
+ POLL_DURATION_SECS;
+ if (! op->poll_type) {
+ for(first = true; ; first = false) {
+ sg_sleep_secs(poll_wait_secs);
+ progress = -1;
+ res = sg_ll_test_unit_ready_progress(fd, 0, &progress,
+ true, (vb > 1) ? (vb - 1) : 0);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fu_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fu_s);
+ break;
+ }
+ }
+ }
+ if (op->poll_type || (SG_LIB_CAT_NOT_READY == res)) {
+ uint8_t * reqSense;
+ uint8_t * free_reqSense = NULL;
+
+ reqSense = sg_memalign(MAX_BUFF_SZ, 0, &free_reqSense, false);
+ if (NULL == reqSense) {
+ pr2serr("%s: unable to obtain heap for Request "
+ "Sense\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ for(first = true; ; first = false) {
+ sg_sleep_secs(poll_wait_secs);
+ memset(reqSense, 0x0, MAX_BUFF_SZ);
+ res = sg_ll_request_sense(fd, false, reqSense,
+ MAX_BUFF_SZ, false,
+ (vb > 1) ? (vb - 1) : 0);
+ if (res) {
+ pr2serr("polling with Request Sense command "
+ "failed [res=%d]\n", res);
+ break;
+ }
+ resp_len = reqSense[7] + 8;
+ if (vb > 1) {
+ pr2serr("Parameter data in hex:\n");
+ hex2stderr(reqSense, resp_len, 1);
+ }
+ progress = -1;
+ sg_get_sense_progress_fld(reqSense, resp_len,
+ &progress);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fu_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fu_s);
+ break;
+ }
+ }
+ if (free_reqSense)
+ free(free_reqSense);
+ }
+ printf("FORMAT UNIT Complete\n");
+ return 0;
+}
+
+/* Return 0 on success, else see sg_ll_format_medium() above */
+static int
+scsi_format_medium(int fd, const struct opts_t * op)
+{
+ bool first;
+ bool immed = ! op->fwait;
+ int res, progress, pr, rem, resp_len, tmout;
+ int vb = op->verbose;
+ char b[80];
+
+ if (immed)
+ tmout = SHORT_TIMEOUT;
+ else {
+ if (op->total_byte_count > EIGHT_TBYTE)
+ tmout = VLONG_FORMAT_TIMEOUT;
+ else if (op->total_byte_count > FOUR_TBYTE)
+ tmout = LONG_FORMAT_TIMEOUT;
+ else
+ tmout = FORMAT_TIMEOUT;
+ }
+ if (op->timeout > tmout)
+ tmout = op->timeout;
+ if (op->dry_run) {
+ res = 0;
+ pr2serr("Due to --dry-run option bypassing %s command\n",
+ fm_s);
+ } else
+ res = sg_ll_format_medium(fd, op->verify, immed,
+ 0xf & op->tape, NULL, 0, tmout,
+ true, vb);
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s command: %s\n", fm_s, b);
+ return res;
+ }
+ if (! immed)
+ return 0;
+
+ if (! op->dry_run)
+ printf("\n%s has started\n", fm_s);
+ if (op->early) {
+ if (immed)
+ printf("%s continuing,\n request sense or "
+ "test unit ready can be used to monitor "
+ "progress\n", fm_s);
+ return 0;
+ }
+
+ if (op->dry_run) {
+ printf("No point in polling for progress, so exit\n");
+ return 0;
+ }
+ if (! op->poll_type) {
+ for(first = true; ; first = false) {
+ sg_sleep_secs(POLL_DURATION_SECS);
+ progress = -1;
+ res = sg_ll_test_unit_ready_progress(fd, 0, &progress,
+ true, (vb > 1) ? (vb - 1) : 0);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fm_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fm_s);
+ break;
+ }
+ }
+ }
+ if (op->poll_type || (SG_LIB_CAT_NOT_READY == res)) {
+ uint8_t * reqSense;
+ uint8_t * free_reqSense = NULL;
+
+ reqSense = sg_memalign(MAX_BUFF_SZ, 0, &free_reqSense, false);
+ if (NULL == reqSense) {
+ pr2serr("%s: unable to obtain heap for Request "
+ "Sense\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ for(first = true; ; first = false) {
+ sg_sleep_secs(POLL_DURATION_SECS);
+ memset(reqSense, 0x0, MAX_BUFF_SZ);
+ res = sg_ll_request_sense(fd, false, reqSense,
+ MAX_BUFF_SZ, false,
+ (vb > 1) ? (vb - 1) : 0);
+ if (res) {
+ pr2serr("polling with Request Sense command "
+ "failed [res=%d]\n", res);
+ break;
+ }
+ resp_len = reqSense[7] + 8;
+ if (vb > 1) {
+ pr2serr("Parameter data in hex:\n");
+ hex2stderr(reqSense, resp_len, 1);
+ }
+ progress = -1;
+ sg_get_sense_progress_fld(reqSense, resp_len,
+ &progress);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fm_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fm_s);
+ break;
+ }
+ }
+ if (free_reqSense)
+ free(free_reqSense);
+ }
+ printf("FORMAT MEDIUM Complete\n");
+ return 0;
+}
+
+/* Return 0 on success, else see sg_ll_format_medium() above */
+static int
+scsi_format_with_preset(int fd, const struct opts_t * op)
+{
+ bool first;
+ bool immed = ! op->fwait;
+ int res, progress, pr, rem, resp_len, tmout;
+ int vb = op->verbose;
+ char b[80];
+
+ if (immed)
+ tmout = SHORT_TIMEOUT;
+ else {
+ if (op->total_byte_count > EIGHT_TBYTE)
+ tmout = VLONG_FORMAT_TIMEOUT;
+ else if (op->total_byte_count > FOUR_TBYTE)
+ tmout = LONG_FORMAT_TIMEOUT;
+ else
+ tmout = FORMAT_TIMEOUT;
+ }
+ if (op->timeout > tmout)
+ tmout = op->timeout;
+ if (op->dry_run) {
+ res = 0;
+ pr2serr("Due to --dry-run option bypassing FORMAT WITH "
+ "PRESET command\n");
+ } else
+ res = sg_ll_format_with_preset(fd, immed, op->fmtmaxlba,
+ op->p_id, tmout, true, vb);
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s command: %s\n", fwp_s, b);
+ return res;
+ }
+ if (! immed)
+ return 0;
+
+ if (! op->dry_run)
+ printf("\n%s has started\n", fwp_s);
+ if (op->early) {
+ if (immed)
+ printf("%s continuing,\n Request sense can "
+ "be used to monitor progress\n", fwp_s);
+ return 0;
+ }
+
+ if (op->dry_run) {
+ printf("No point in polling for progress, so exit\n");
+ return 0;
+ }
+ if (! op->poll_type) {
+ for(first = true; ; first = false) {
+ sg_sleep_secs(POLL_DURATION_SECS);
+ progress = -1;
+ res = sg_ll_test_unit_ready_progress(fd, 0, &progress,
+ true, (vb > 1) ? (vb - 1) : 0);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fwp_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fwp_s);
+ break;
+ }
+ }
+ }
+ if (op->poll_type || (SG_LIB_CAT_NOT_READY == res)) {
+ uint8_t * reqSense;
+ uint8_t * free_reqSense = NULL;
+
+ reqSense = sg_memalign(MAX_BUFF_SZ, 0, &free_reqSense, false);
+ if (NULL == reqSense) {
+ pr2serr("%s: unable to obtain heap for Request "
+ "Sense\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ for(first = true; ; first = false) {
+ sg_sleep_secs(POLL_DURATION_SECS);
+ memset(reqSense, 0x0, MAX_BUFF_SZ);
+ res = sg_ll_request_sense(fd, false, reqSense,
+ MAX_BUFF_SZ, false,
+ (vb > 1) ? (vb - 1) : 0);
+ if (res) {
+ pr2serr("polling with Request Sense command "
+ "failed [res=%d]\n", res);
+ break;
+ }
+ resp_len = reqSense[7] + 8;
+ if (vb > 1) {
+ pr2serr("Parameter data in hex:\n");
+ hex2stderr(reqSense, resp_len, 1);
+ }
+ progress = -1;
+ sg_get_sense_progress_fld(reqSense, resp_len,
+ &progress);
+ if (progress >= 0) {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("%s in progress, %d.%02d%% done\n",
+ fwp_s, pr, rem);
+ } else {
+ if (first && op->verbose)
+ pr2serr("%s seems to be successful "
+ "and finished quickly\n",
+ fwp_s);
+ break;
+ }
+ }
+ if (free_reqSense)
+ free(free_reqSense);
+ }
+ printf("FORMAT WITH PRESET Complete\n");
+ return 0;
+}
+
+#define VPD_DEVICE_ID 0x83
+#define VPD_ASSOC_LU 0
+#define VPD_ASSOC_TPORT 1
+#define TPROTO_ISCSI 5
+
+static char *
+get_lu_name(const uint8_t * bp, int u_len, char * b, int b_len)
+{
+ int len, off, sns_dlen, dlen, k;
+ uint8_t u_sns[512];
+ char * cp;
+
+ len = u_len - 4;
+ bp += 4;
+ off = -1;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 8 /* SCSI name string (sns) */,
+ 3 /* UTF-8 */)) {
+ sns_dlen = bp[off + 3];
+ memcpy(u_sns, bp + off + 4, sns_dlen);
+ /* now want to check if this is iSCSI */
+ off = -1;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_TPORT,
+ 8 /* SCSI name string (sns) */,
+ 3 /* UTF-8 */)) {
+ if ((0x80 & bp[1]) &&
+ (TPROTO_ISCSI == (bp[0] >> 4))) {
+ snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
+ return b;
+ }
+ }
+ } else
+ sns_dlen = 0;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 3 /* NAA */, 1 /* binary */)) {
+ dlen = bp[off + 3];
+ if (! ((8 == dlen) || (16 ==dlen)))
+ return b;
+ cp = b;
+ for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
+ snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
+ cp += 2;
+ b_len -= 2;
+ }
+ } else if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 2 /* EUI */, 1 /* binary */)) {
+ dlen = bp[off + 3];
+ if (! ((8 == dlen) || (12 == dlen) || (16 ==dlen)))
+ return b;
+ cp = b;
+ for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
+ snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
+ cp += 2;
+ b_len -= 2;
+ }
+ } else if (sns_dlen > 0)
+ snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
+ return b;
+}
+
+#define SAFE_STD_INQ_RESP_LEN 36
+#define VPD_SUPPORTED_VPDS 0x0
+#define VPD_UNIT_SERIAL_NUM 0x80
+#define VPD_DEVICE_ID 0x83
+#define MAX_VPD_RESP_LEN 256
+
+static int
+print_dev_id(int fd, uint8_t * sinq_resp, int max_rlen,
+ const struct opts_t * op)
+{
+ int k, n, verb, pdt, has_sn, has_di;
+ int res = 0;
+ uint8_t * b;
+ uint8_t * free_b = NULL;
+ char a[MAX_VPD_RESP_LEN];
+ char pdt_name[64];
+
+ verb = (op->verbose > 1) ? op->verbose - 1 : 0;
+ memset(sinq_resp, 0, max_rlen);
+ b = sg_memalign(MAX_VPD_RESP_LEN, 0, &free_b, false);
+ if (NULL == b) {
+ res = sg_convert_errno(ENOMEM);
+ goto out;
+ }
+ /* Standard INQUIRY */
+ res = sg_ll_inquiry(fd, false, false, 0, b, SAFE_STD_INQ_RESP_LEN,
+ true, verb);
+ if (res)
+ goto out;
+ n = b[4] + 5;
+ if (n > SAFE_STD_INQ_RESP_LEN)
+ n = SAFE_STD_INQ_RESP_LEN;
+ memcpy(sinq_resp, b, (n < max_rlen) ? n : max_rlen);
+ if (n == SAFE_STD_INQ_RESP_LEN) {
+ pdt = b[0] & PDT_MASK;
+ printf(" %.8s %.16s %.4s peripheral_type: %s [0x%x]\n",
+ (const char *)(b + 8), (const char *)(b + 16),
+ (const char *)(b + 32),
+ sg_get_pdt_str(pdt, sizeof(pdt_name), pdt_name), pdt);
+ if (op->verbose)
+ printf(" PROTECT=%d\n", !!(b[5] & 1));
+ if (b[5] & 1)
+ printf(" << supports protection information>>"
+ "\n");
+ } else {
+ pr2serr("Short INQUIRY response: %d bytes, expect at least "
+ "36\n", n);
+ res = SG_LIB_CAT_OTHER;
+ goto out;
+ }
+ res = sg_ll_inquiry(fd, false, true, VPD_SUPPORTED_VPDS, b,
+ SAFE_STD_INQ_RESP_LEN, true, verb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("VPD_SUPPORTED_VPDS gave res=%d\n", res);
+ res = 0;
+ goto out;
+ }
+ if (VPD_SUPPORTED_VPDS != b[1]) {
+ if (op->verbose)
+ pr2serr("VPD_SUPPORTED_VPDS corrupted\n");
+ goto out;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (SAFE_STD_INQ_RESP_LEN - 4))
+ n = (SAFE_STD_INQ_RESP_LEN - 4);
+ for (k = 0, has_sn = 0, has_di = 0; k < n; ++k) {
+ if (VPD_UNIT_SERIAL_NUM == b[4 + k])
+ ++has_sn;
+ else if (VPD_DEVICE_ID == b[4 + k]) {
+ ++has_di;
+ break;
+ }
+ }
+ if (has_sn) {
+ res = sg_ll_inquiry(fd, false, true /* evpd */,
+ VPD_UNIT_SERIAL_NUM, b, MAX_VPD_RESP_LEN,
+ true, verb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("VPD_UNIT_SERIAL_NUM gave res=%d\n",
+ res);
+ res = 0;
+ goto out;
+ }
+ if (VPD_UNIT_SERIAL_NUM != b[1]) {
+ if (op->verbose)
+ pr2serr("VPD_UNIT_SERIAL_NUM corrupted\n");
+ goto out;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (int)(MAX_VPD_RESP_LEN - 4))
+ n = (MAX_VPD_RESP_LEN - 4);
+ printf(" Unit serial number: %.*s\n", n,
+ (const char *)(b + 4));
+ }
+ if (has_di) {
+ res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_DEVICE_ID,
+ b, MAX_VPD_RESP_LEN, true, verb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("VPD_DEVICE_ID gave res=%d\n", res);
+ res = 0;
+ goto out;
+ }
+ if (VPD_DEVICE_ID != b[1]) {
+ if (op->verbose)
+ pr2serr("VPD_DEVICE_ID corrupted\n");
+ goto out;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (int)(MAX_VPD_RESP_LEN - 4))
+ n = (MAX_VPD_RESP_LEN - 4);
+ n = strlen(get_lu_name(b, n + 4, a, sizeof(a)));
+ if (n > 0)
+ printf(" LU name: %.*s\n", n, a);
+ }
+out:
+ if (free_b)
+ free(free_b);
+ return res;
+}
+
+#define RCAP_REPLY_LEN 32
+
+/* Returns block size or -2 if do_16==0 and the number of blocks is too
+ * big, or returns -1 for other error. */
+static int
+print_read_cap(int fd, struct opts_t * op)
+{
+ int res = 0;
+ uint8_t * resp_buff;
+ uint8_t * free_resp_buff = NULL;
+ unsigned int last_blk_addr, block_size;
+ uint64_t llast_blk_addr;
+ int64_t ll;
+ char b[80];
+
+ resp_buff = sg_memalign(RCAP_REPLY_LEN, 0, &free_resp_buff, false);
+ if (NULL == resp_buff) {
+ pr2serr("%s: unable to obtain heap\n", __func__);
+ res = -1;
+ goto out;
+ }
+ if (op->do_rcap16) {
+ res = sg_ll_readcap_16(fd, false /* pmi */, 0 /* llba */,
+ resp_buff, RCAP_REPLY_LEN, true,
+ op->verbose);
+ if (0 == res) {
+ llast_blk_addr = sg_get_unaligned_be64(resp_buff + 0);
+ block_size = sg_get_unaligned_be32(resp_buff + 8);
+ printf("Read Capacity (16) results:\n");
+ printf(" Protection: prot_en=%d, p_type=%d, "
+ "p_i_exponent=%d\n",
+ !!(resp_buff[12] & 0x1),
+ ((resp_buff[12] >> 1) & 0x7),
+ ((resp_buff[13] >> 4) & 0xf));
+ printf(" Logical block provisioning: lbpme=%d, "
+ "lbprz=%d\n", !!(resp_buff[14] & 0x80),
+ !!(resp_buff[14] & 0x40));
+ printf(" Logical blocks per physical block "
+ "exponent=%d\n", resp_buff[13] & 0xf);
+ printf(" Lowest aligned logical block address=%d\n",
+ 0x3fff & sg_get_unaligned_be16(resp_buff +
+ 14));
+ printf(" Number of logical blocks=%" PRIu64 "\n",
+ llast_blk_addr + 1);
+ printf(" Logical block size=%u bytes\n",
+ block_size);
+ ll = (int64_t)(llast_blk_addr + 1) * block_size;
+ if (ll > op->total_byte_count)
+ op->total_byte_count = ll;
+ res = (int)block_size;
+ goto out;
+ }
+ } else {
+ res = sg_ll_readcap_10(fd, false /* pmi */, 0 /* lba */,
+ resp_buff, 8, true, op->verbose);
+ if (0 == res) {
+ last_blk_addr = sg_get_unaligned_be32(resp_buff + 0);
+ block_size = sg_get_unaligned_be32(resp_buff + 4);
+ if (0xffffffff == last_blk_addr) {
+ if (op->verbose)
+ printf("Read Capacity (10) response "
+ "indicates that Read Capacity "
+ "(16) is required\n");
+ res = -2;
+ goto out;
+ }
+ printf("Read Capacity (10) results:\n");
+ printf(" Number of logical blocks=%u\n",
+ last_blk_addr + 1);
+ printf(" Logical block size=%u bytes\n",
+ block_size);
+ ll = (int64_t)(last_blk_addr + 1) * block_size;
+ if (ll > op->total_byte_count)
+ op->total_byte_count = ll;
+ res = (int)block_size;
+ goto out;
+ }
+ }
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("READ CAPACITY (%d): %s\n", (op->do_rcap16 ? 16 : 10), b);
+ res = -1;
+out:
+ if (free_resp_buff)
+ free(free_resp_buff);
+ return res;
+}
+
+/* Use MODE SENSE(6 or 10) to fetch blocks descriptor(s), if any. Analyze
+ * the first block descriptor and if required, start preparing for a
+ * MODE SELECT(6 or 10). Returns 0 on success. */
+static int
+fetch_block_desc(int fd, uint8_t * dbuff, int * calc_lenp, int * bd_lb_szp,
+ struct opts_t * op)
+{
+ bool first = true;
+ bool prob;
+ int bd_lbsz, bd_len, dev_specific_param, offset, res, rq_lb_sz;
+ int rsp_len;
+ int resid = 0;
+ int vb = op->verbose;
+ uint64_t ull;
+ int64_t ll;
+ char b[80];
+
+again_with_long_lba:
+ memset(dbuff, 0, MAX_BUFF_SZ);
+ if (op->mode6)
+ res = sg_ll_mode_sense6(fd, false /* DBD */, 0 /* current */,
+ op->mode_page, 0 /* subpage */, dbuff,
+ MAX_BUFF_SZ, true, vb);
+ else
+ res = sg_ll_mode_sense10_v2(fd, op->long_lba, false /* DBD */,
+ 0 /* current */, op->mode_page,
+ 0 /* subpage */, dbuff,
+ MAX_BUFF_SZ, 0, &resid, true,
+ vb);
+ if (res) {
+ if (SG_LIB_CAT_ILLEGAL_REQ == res) {
+ if (op->long_lba && (! op->mode6))
+ pr2serr("bad field in MODE SENSE (%d) "
+ "[longlba flag not supported?]\n",
+ (op->mode6 ? 6 : 10));
+ else
+ pr2serr("bad field in MODE SENSE (%d) "
+ "[mode_page %d not supported?]\n",
+ (op->mode6 ? 6 : 10), op->mode_page);
+ } else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("MODE SENSE (%d) command: %s\n",
+ (op->mode6 ? 6 : 10), b);
+ }
+ if (0 == vb)
+ pr2serr(" try '-v' for more information\n");
+ return res;
+ }
+ rsp_len = (resid > 0) ? (MAX_BUFF_SZ - resid) : MAX_BUFF_SZ;
+ if (rsp_len < 0) {
+ pr2serr("%s: resid=%d implies negative response "
+ "length of %d\n", __func__, resid, rsp_len);
+ return SG_LIB_WILD_RESID;
+ }
+ *calc_lenp = sg_msense_calc_length(dbuff, rsp_len, op->mode6, &bd_len);
+ if (op->mode6) {
+ if (rsp_len < 4) {
+ pr2serr("%s: MS(6) response length too short (%d)\n",
+ __func__, rsp_len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ dev_specific_param = dbuff[2];
+ op->long_lba = false;
+ offset = 4;
+ /* prepare for mode select */
+ dbuff[0] = 0;
+ dbuff[1] = 0;
+ dbuff[2] = 0;
+ } else { /* MODE SENSE(10) */
+ if (rsp_len < 8) {
+ pr2serr("%s: MS(10) response length too short (%d)\n",
+ __func__, rsp_len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ dev_specific_param = dbuff[3];
+ op->long_lba = !! (dbuff[4] & 1);
+ offset = 8;
+ /* prepare for mode select */
+ dbuff[0] = 0;
+ dbuff[1] = 0;
+ dbuff[2] = 0;
+ dbuff[3] = 0;
+ }
+ if (rsp_len < *calc_lenp) {
+ pr2serr("%s: MS response length truncated (%d < %d)\n",
+ __func__, rsp_len, *calc_lenp);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if ((offset + bd_len) < *calc_lenp)
+ dbuff[offset + bd_len] &= 0x7f; /* clear PS bit in mpage */
+ prob = false;
+ bd_lbsz = 0;
+ *bd_lb_szp = bd_lbsz;
+ rq_lb_sz = op->lblk_sz;
+ if (first) {
+ first = false;
+ printf("Mode Sense (block descriptor) data, prior to "
+ "changes:\n");
+ }
+ if (dev_specific_param & 0x40)
+ printf(" <<< Write Protect (WP) bit set >>>\n");
+ if (bd_len > 0) {
+ ull = op->long_lba ? sg_get_unaligned_be64(dbuff + offset) :
+ sg_get_unaligned_be32(dbuff + offset);
+ bd_lbsz = op->long_lba ?
+ sg_get_unaligned_be32(dbuff + offset + 12) :
+ sg_get_unaligned_be24(dbuff + offset + 5);
+ *bd_lb_szp = bd_lbsz;
+ if (! op->long_lba) {
+ if (0xffffffff == ull) {
+ if (vb)
+ pr2serr("block count maxed out, set "
+ "<<longlba>>\n");
+ op->long_lba = true;
+ op->mode6 = false;
+ op->do_rcap16 = true;
+ goto again_with_long_lba;
+ } else if ((rq_lb_sz > 0) && (rq_lb_sz < bd_lbsz) &&
+ (((ull * bd_lbsz) / rq_lb_sz) >=
+ 0xffffffff)) {
+ if (vb)
+ pr2serr("number of blocks will max "
+ "out, set <<longlba>>\n");
+ op->long_lba = true;
+ op->mode6 = false;
+ op->do_rcap16 = true;
+ goto again_with_long_lba;
+ }
+ }
+ if (op->long_lba) {
+ printf(" <<< longlba flag set (64 bit lba) >>>\n");
+ if (bd_len != 16)
+ prob = true;
+ } else if (bd_len != 8)
+ prob = true;
+ printf(" Number of blocks=%" PRIu64 " [0x%" PRIx64 "]\n",
+ ull, ull);
+ printf(" Block size=%d [0x%x]\n", bd_lbsz, bd_lbsz);
+ ll = (int64_t)ull * bd_lbsz;
+ if (ll > op->total_byte_count)
+ op->total_byte_count = ll;
+ } else {
+ printf(" No block descriptors present\n");
+ prob = true;
+ }
+ if (op->resize || (op->format && ((op->blk_count != 0) ||
+ ((rq_lb_sz > 0) && (rq_lb_sz != bd_lbsz))))) {
+ /* want to run MODE SELECT, prepare now */
+
+ if (prob) {
+ pr2serr("Need to perform MODE SELECT (to change "
+ "number or blocks or block length)\n");
+ pr2serr("but (single) block descriptor not found "
+ "in earlier MODE SENSE\n");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (op->blk_count != 0) { /* user supplied blk count */
+ if (op->long_lba)
+ sg_put_unaligned_be64(op->blk_count,
+ dbuff + offset);
+ else
+ sg_put_unaligned_be32(op->blk_count,
+ dbuff + offset);
+ } else if ((rq_lb_sz > 0) && (rq_lb_sz != bd_lbsz))
+ /* 0 implies max capacity with new LB size */
+ memset(dbuff + offset, 0, op->long_lba ? 8 : 4);
+
+ if ((rq_lb_sz > 0) && (rq_lb_sz != bd_lbsz)) {
+ if (op->long_lba)
+ sg_put_unaligned_be32((uint32_t)rq_lb_sz,
+ dbuff + offset + 12);
+ else
+ sg_put_unaligned_be24((uint32_t)rq_lb_sz,
+ dbuff + offset + 5);
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char **argv)
+{
+ int j;
+ int64_t ll;
+
+ op->cmplst = true; /* will be set false if FFMT > 0 */
+ op->mode_page = RW_ERROR_RECOVERY_PAGE;
+ op->poll_type = DEF_POLL_TYPE_RS;
+ op->tape = -1;
+ while (1) {
+ int option_index = 0;
+ int c;
+
+ c = getopt_long(argc, argv,
+ "bc:C:dDeE:f:FhIlm:M:pP:q:QrRs:St:T:vVwx:y6",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ op->fmtmaxlba = true;
+ break;
+ case 'c':
+ if (0 == strcmp("-1", optarg))
+ op->blk_count = -1;
+ else {
+ op->blk_count = sg_get_llnum(optarg);
+ if (-1 == op->blk_count) {
+ pr2serr("bad argument to '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ break;
+ case 'C':
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > 1)) {
+ pr2serr("bad argument to '--cmplst', want 0 "
+ "or 1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->cmplst_given = true;
+ op->cmplst = !! j;
+ break;
+ case 'd':
+ op->dry_run = true;
+ break;
+ case 'D':
+ ++op->dcrt;
+ break;
+ case 'e':
+ op->early = true;
+ break;
+ case 'E':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--preset', need 32 "
+ "bit integer\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->p_id = (uint32_t)ll;
+ op->preset = true;
+ op->poll_type = 1; /* poll with REQUEST SENSE */
+ break;
+ case 'f':
+ op->fmtpinfo = sg_get_num(optarg);
+ if ((op->fmtpinfo < 0) || ( op->fmtpinfo > 3)) {
+ pr2serr("bad argument to '--fmtpinfo', "
+ "accepts 0 to 3 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'F':
+ ++op->format;
+ break;
+ case 'h':
+ usage();
+ return SG_LIB_OK_FALSE;
+ case 'I':
+ op->ip_def = true;
+ break;
+ case 'l':
+ op->long_lba = true;
+ op->do_rcap16 = true;
+ break;
+ case 'm':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout=', "
+ "accepts 0 or more\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'M':
+ op->mode_page = sg_get_num(optarg);
+ if ((op->mode_page < 0) || ( op->mode_page > 62)) {
+ pr2serr("bad argument to '--mode', accepts "
+ "0 to 62 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ op->pinfo = true;
+ break;
+ case 'P':
+ op->pfu = sg_get_num(optarg);
+ if ((op->pfu < 0) || ( op->pfu > 7)) {
+ pr2serr("bad argument to '--pfu', accepts 0 "
+ "to 7 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'q':
+ op->pie = sg_get_num(optarg);
+ if ((op->pie < 0) || (op->pie > 15)) {
+ pr2serr("bad argument to '--pie', accepts 0 "
+ "to 15 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'Q':
+ op->quick = true;
+ break;
+ case 'r':
+ op->resize = true;
+ break;
+ case 'R':
+ op->rto_req = true;
+ break;
+ case 's':
+ op->lblk_sz = sg_get_num(optarg);
+ if (op->lblk_sz <= 0) {
+ pr2serr("bad argument to '--size', want arg "
+ "> 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'S':
+ op->sec_init = true;
+ break;
+ case 't':
+ op->ffmt = sg_get_num(optarg);
+ if ((op->ffmt < 0) || ( op->ffmt > 3)) {
+ pr2serr("bad argument to '--ffmt', "
+ "accepts 0 to 3 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'T':
+ if (('-' == optarg[0]) && ('1' == optarg[1]) &&
+ ('\0' == optarg[2])) {
+ op->tape = -1;
+ break;
+ }
+ op->tape = sg_get_num(optarg);
+ if ((op->tape < 0) || ( op->tape > 15)) {
+ pr2serr("bad argument to '--tape', accepts "
+ "0 to 15 inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ op->verbose++;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->fwait = true;
+ break;
+ case 'x': /* false: TUR; true: request sense */
+ op->poll_type = !! sg_get_num(optarg);
+ op->poll_type_given = true;
+ break;
+ case 'y':
+ op->verify = true;
+ break;
+ case '6':
+ op->mode6 = true;
+ break;
+ default:
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and "
+ "continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("sg_format version: %s\n", version_str);
+ return SG_LIB_OK_FALSE;
+ }
+ if (NULL == op->device_name) {
+ pr2serr("no DEVICE name given\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (((int)(op->format > 0) + (int)(op->tape >= 0) + (int)op->preset)
+ > 1) {
+ pr2serr("Can choose only one of: '--format', '--tape=' and "
+ "'--preset='\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->ip_def && op->sec_init) {
+ pr2serr("'--ip_def' and '--security' contradict, choose "
+ "one\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->resize) {
+ if (op->format) {
+ pr2serr("both '--format' and '--resize' not "
+ "permitted\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ } else if (0 == op->blk_count) {
+ pr2serr("'--resize' needs a '--count' (other than "
+ "0)\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ } else if (0 != op->lblk_sz) {
+ pr2serr("'--resize' not compatible with '--size'\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ if ((op->pinfo > 0) || (op->rto_req > 0) || (op->fmtpinfo > 0)) {
+ if ((op->pinfo || op->rto_req) && op->fmtpinfo) {
+ pr2serr("confusing with both '--pinfo' or "
+ "'--rto_req' together with\n'--fmtpinfo', "
+ "best use '--fmtpinfo' only\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->pinfo)
+ op->fmtpinfo |= 2;
+ if (op->rto_req)
+ op->fmtpinfo |= 1;
+ }
+ if ((op->ffmt > 0) && (! op->cmplst_given))
+ op->cmplst = false; /* SBC-4 silent; FFMT&&CMPLST unlikely */
+ return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int bd_lb_sz, calc_len, pdt, res, rq_lb_sz, vb;
+ int fd = -1;
+ int ret = 0;
+ const int dbuff_sz = MAX_BUFF_SZ;
+ const int inq_resp_sz = SAFE_STD_INQ_RESP_LEN;
+ struct opts_t * op;
+ uint8_t * dbuff;
+ uint8_t * free_dbuff = NULL;
+ uint8_t * inq_resp;
+ uint8_t * free_inq_resp = NULL;
+ struct opts_t opts;
+ char b[80];
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ ret = parse_cmd_line(op, argc, argv);
+ if (ret)
+ return (SG_LIB_OK_FALSE == ret) ? 0 : ret;
+ vb = op->verbose;
+
+ dbuff = sg_memalign(dbuff_sz, 0, &free_dbuff, false);
+ inq_resp = sg_memalign(inq_resp_sz, 0, &free_inq_resp, false);
+ if ((NULL == dbuff) || (NULL == inq_resp)) {
+ pr2serr("Unable to allocate heap\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto out;
+ }
+
+ if ((fd = sg_cmds_open_device(op->device_name, false, vb)) < 0) {
+ pr2serr("error opening device file: %s: %s\n",
+ op->device_name, safe_strerror(-fd));
+ ret = sg_convert_errno(-fd);
+ goto out;
+ }
+
+ if (op->format > 2)
+ goto format_only;
+
+ ret = print_dev_id(fd, inq_resp, inq_resp_sz, op);
+ if (ret) {
+ if (op->dry_run) {
+ pr2serr("INQUIRY failed, assume device is a disk\n");
+ pdt = 0;
+ } else
+ goto out;
+ } else
+ pdt = PDT_MASK & inq_resp[0];
+ if (op->format) {
+ if ((PDT_DISK != pdt) && (PDT_OPTICAL != pdt) &&
+ (PDT_RBC != pdt) && (PDT_ZBC != pdt)) {
+ pr2serr("This format is only defined for disks "
+ "(using SBC-2+, ZBC or RBC) and MO media\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ goto out;
+ }
+ } else if (op->tape >= 0) {
+ if (! ((PDT_TAPE == pdt) || (PDT_MCHANGER == pdt) ||
+ (PDT_ADC == pdt))) {
+ pr2serr("This format is only defined for tapes\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ goto out;
+ }
+ goto format_med;
+ } else if (op->preset)
+ goto format_with_pre;
+
+ ret = fetch_block_desc(fd, dbuff, &calc_len, &bd_lb_sz, op);
+ if (ret) {
+ if (op->dry_run) {
+ /* pick some numbers ... */
+ calc_len = 1024 * 1024 * 1024;
+ bd_lb_sz = 512;
+ } else
+ goto out;
+ }
+ rq_lb_sz = op->lblk_sz;
+ if (op->resize || (op->format && ((op->blk_count != 0) ||
+ ((rq_lb_sz > 0) && (rq_lb_sz != bd_lb_sz))))) {
+ /* want to run MODE SELECT */
+ if (op->dry_run) {
+ pr2serr("Due to --dry-run option bypass MODE "
+ "SELECT(%d) command\n", (op->mode6 ? 6 : 10));
+ res = 0;
+ } else {
+ bool sp = true; /* may not be able to save pages */
+
+again_sp_false:
+ if (op->mode6)
+ res = sg_ll_mode_select6(fd, true /* PF */,
+ sp, dbuff, calc_len,
+ true, vb);
+ else
+ res = sg_ll_mode_select10(fd, true /* PF */,
+ sp, dbuff, calc_len,
+ true, vb);
+ if ((SG_LIB_CAT_ILLEGAL_REQ == res) && sp) {
+ pr2serr("Try MODE SELECT again with SP=0 "
+ "this time\n");
+ sp = false;
+ goto again_sp_false;
+ }
+ }
+ ret = res;
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("MODE SELECT command: %s\n", b);
+ if (0 == vb)
+ pr2serr(" try '-v' for more information\n");
+ goto out;
+ }
+ }
+ if (op->resize) {
+ printf("Resize operation seems to have been successful\n");
+ goto out;
+ } else if (! op->format) {
+ res = print_read_cap(fd, op);
+ if (-2 == res) {
+ op->do_rcap16 = true;
+ res = print_read_cap(fd, op);
+ }
+ if (res < 0)
+ ret = -1;
+ if ((res > 0) && (bd_lb_sz > 0) &&
+ (res != (int)bd_lb_sz)) {
+ printf(" Warning: mode sense and read capacity "
+ "report different block sizes [%d,%d]\n",
+ bd_lb_sz, res);
+ printf(" Probably needs format\n");
+ }
+ if ((PDT_TAPE == pdt) || (PDT_MCHANGER == pdt) ||
+ (PDT_ADC == pdt))
+ printf("No changes made. To format use '--tape='.\n");
+ else
+ printf("No changes made. To format use '--format'. "
+ "To resize use '--resize'\n");
+ goto out;
+ }
+
+ if (op->format) {
+format_only:
+ if (! op->quick)
+ sg_warn_and_wait("FORMAT UNIT", op->device_name, true);
+ res = scsi_format_unit(fd, op);
+ ret = res;
+ if (res) {
+ pr2serr("FORMAT UNIT failed\n");
+ if (0 == vb)
+ pr2serr(" try '-v' for more "
+ "information\n");
+ }
+ }
+ goto out;
+
+format_med:
+ if (! op->poll_type_given) /* SSC-5 specifies REQUEST SENSE polling */
+ op->poll_type = true;
+ if (! op->quick)
+ sg_warn_and_wait("FORMAT MEDIUM", op->device_name, true);
+ res = scsi_format_medium(fd, op);
+ ret = res;
+ if (res) {
+ pr2serr("FORMAT MEDIUM failed\n");
+ if (0 == vb)
+ pr2serr(" try '-v' for more information\n");
+ }
+ goto out;
+
+format_with_pre:
+ if (! op->quick)
+ sg_warn_and_wait("FORMAT WITH PRESET", op->device_name, true);
+ res = scsi_format_with_preset(fd, op);
+ ret = res;
+ if (res) {
+ pr2serr("FORMAT WITH PRESET failed\n");
+ if (0 == vb)
+ pr2serr(" try '-v' for more information\n");
+ }
+
+out:
+ if (free_dbuff)
+ free(free_dbuff);
+ if (free_inq_resp)
+ free(free_inq_resp);
+ if (fd >= 0) {
+ res = sg_cmds_close_device(fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == vb) {
+ if (! sg_if_can2stderr("sg_format failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_get_config.c b/src/sg_get_config.c
new file mode 100644
index 00000000..ad3bce9e
--- /dev/null
+++ b/src/sg_get_config.c
@@ -0,0 +1,1145 @@
+/*
+ * Copyright (c) 2004-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_mmc.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This program outputs information provided by a SCSI "Get Configuration"
+ command [0x46] which is only defined for CD/DVDs (in MMC-2,3,4,5,6).
+
+*/
+
+static const char * version_str = "0.49 20180626"; /* mmc6r02 */
+
+#define MX_ALLOC_LEN 8192
+#define NAME_BUFF_SZ 64
+
+#define ME "sg_get_config: "
+
+
+static uint8_t resp_buffer[MX_ALLOC_LEN];
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'},
+ {"current", no_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"inner-hex", no_argument, 0, 'i'},
+ {"list", no_argument, 0, 'l'},
+ {"raw", no_argument, 0, 'R'},
+ {"readonly", no_argument, 0, 'q'},
+ {"rt", required_argument, 0, 'r'},
+ {"starting", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_get_config [--brief] [--current] [--help] [--hex] "
+ "[--inner-hex]\n"
+ " [--list] [--raw] [--readonly] [--rt=RT]\n"
+ " [--starting=FC] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --brief|-b only give feature names of DEVICE "
+ "(don't decode)\n"
+ " --current|-c equivalent to '--rt=1' (show "
+ "current)\n"
+ " --help|-h print usage message then exit\n"
+ " --hex|-H output response in hex\n"
+ " --inner-hex|-i decode to feature name, then output "
+ "features in hex\n"
+ " --list|-l list all known features + profiles "
+ "(ignore DEVICE)\n"
+ " --raw|-R output in binary (to stdout)\n"
+ " --readonly|-q open DEVICE read-only (def: open it "
+ "read-write)\n"
+ " --rt=RT|-r RT default value is 0\n"
+ " 0 -> all feature descriptors (regardless "
+ "of currency)\n"
+ " 1 -> all current feature descriptors\n"
+ " 2 -> only feature descriptor matching "
+ "'starting'\n"
+ " --starting=FC|-s FC starting from feature "
+ "code (FC) value\n"
+ " --verbose|-v verbose\n"
+ " --version|-V output version string\n\n"
+ "Get configuration information for MMC drive and/or media\n");
+}
+
+struct val_desc_t {
+ int val;
+ const char * desc;
+};
+
+static struct val_desc_t profile_desc_arr[] = {
+ {0x0, "No current profile"},
+ {0x1, "Non-removable disk (obs)"},
+ {0x2, "Removable disk"},
+ {0x3, "Magneto optical erasable"},
+ {0x4, "Optical write once"},
+ {0x5, "AS-MO"},
+ {0x8, "CD-ROM"},
+ {0x9, "CD-R"},
+ {0xa, "CD-RW"},
+ {0x10, "DVD-ROM"},
+ {0x11, "DVD-R sequential recording"},
+ {0x12, "DVD-RAM"},
+ {0x13, "DVD-RW restricted overwrite"},
+ {0x14, "DVD-RW sequential recording"},
+ {0x15, "DVD-R dual layer sequental recording"},
+ {0x16, "DVD-R dual layer jump recording"},
+ {0x17, "DVD-RW dual layer"},
+ {0x18, "DVD-Download disc recording"},
+ {0x1a, "DVD+RW"},
+ {0x1b, "DVD+R"},
+ {0x20, "DDCD-ROM"},
+ {0x21, "DDCD-R"},
+ {0x22, "DDCD-RW"},
+ {0x2a, "DVD+RW dual layer"},
+ {0x2b, "DVD+R dual layer"},
+ {0x40, "BD-ROM"},
+ {0x41, "BD-R SRM"},
+ {0x42, "BD-R RRM"},
+ {0x43, "BD-RE"},
+ {0x50, "HD DVD-ROM"},
+ {0x51, "HD DVD-R"},
+ {0x52, "HD DVD-RAM"},
+ {0x53, "HD DVD-RW"},
+ {0x58, "HD DVD-R dual layer"},
+ {0x5a, "HD DVD-RW dual layer"},
+ {0xffff, "Non-conforming profile"},
+ {-1, NULL},
+};
+
+static const char *
+get_profile_str(int profile_num, char * buff)
+{
+ const struct val_desc_t * pdp;
+
+ for (pdp = profile_desc_arr; pdp->desc; ++pdp) {
+ if (pdp->val == profile_num) {
+ strcpy(buff, pdp->desc);
+ return buff;
+ }
+ }
+ snprintf(buff, 64, "0x%x", profile_num);
+ return buff;
+}
+
+static struct val_desc_t feature_desc_arr[] = {
+ {0x0, "Profile list"},
+ {0x1, "Core"},
+ {0x2, "Morphing"},
+ {0x3, "Removable media"},
+ {0x4, "Write Protect"},
+ {0x10, "Random readable"},
+ {0x1d, "Multi-read"},
+ {0x1e, "CD read"},
+ {0x1f, "DVD read"},
+ {0x20, "Random writable"},
+ {0x21, "Incremental streaming writable"},
+ {0x22, "Sector erasable"},
+ {0x23, "Formattable"},
+ {0x24, "Hardware defect management"},
+ {0x25, "Write once"},
+ {0x26, "Restricted overwrite"},
+ {0x27, "CD-RW CAV write"},
+ {0x28, "MRW"}, /* Mount Rainier reWritable */
+ {0x29, "Enhanced defect reporting"},
+ {0x2a, "DVD+RW"},
+ {0x2b, "DVD+R"},
+ {0x2c, "Rigid restricted overwrite"},
+ {0x2d, "CD track-at-once"},
+ {0x2e, "CD mastering (session at once)"},
+ {0x2f, "DVD-R/-RW write"},
+ {0x30, "Double density CD read"},
+ {0x31, "Double density CD-R write"},
+ {0x32, "Double density CD-RW write"},
+ {0x33, "Layer jump recording"},
+ {0x34, "LJ rigid restricted oberwrite"},
+ {0x35, "Stop long operation"},
+ {0x37, "CD-RW media write support"},
+ {0x38, "BD-R POW"},
+ {0x3a, "DVD+RW dual layer"},
+ {0x3b, "DVD+R dual layer"},
+ {0x40, "BD read"},
+ {0x41, "BD write"},
+ {0x42, "TSR (timely safe recording)"},
+ {0x50, "HD DVD read"},
+ {0x51, "HD DVD write"},
+ {0x52, "HD DVD-RW fragment recording"},
+ {0x80, "Hybrid disc"},
+ {0x100, "Power management"},
+ {0x101, "SMART"},
+ {0x102, "Embedded changer"},
+ {0x103, "CD audio external play"},
+ {0x104, "Microcode upgrade"},
+ {0x105, "Timeout"},
+ {0x106, "DVD CSS"},
+ {0x107, "Real time streaming"},
+ {0x108, "Drive serial number"},
+ {0x109, "Media serial number"},
+ {0x10a, "Disc control blocks"},
+ {0x10b, "DVD CPRM"},
+ {0x10c, "Firmware information"},
+ {0x10d, "AACS"},
+ {0x10e, "DVD CSS managed recording"},
+ {0x110, "VCPS"},
+ {0x113, "SecurDisc"},
+ {0x120, "BD CPS"},
+ {0x142, "OSSC"},
+};
+
+static const char *
+get_feature_str(int feature_num, char * buff)
+{
+ int k, num;
+
+ num = SG_ARRAY_SIZE(feature_desc_arr);
+ for (k = 0; k < num; ++k) {
+ if (feature_desc_arr[k].val == feature_num) {
+ strcpy(buff, feature_desc_arr[k].desc);
+ return buff;
+ }
+ }
+ snprintf(buff, 64, "0x%x", feature_num);
+ return buff;
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static void
+decode_feature(int feature, uint8_t * bp, int len)
+{
+ int k, num, n, profile;
+ char buff[128];
+ const char * cp;
+
+ switch (feature) {
+ case 0: /* Profile list */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 2), !!(bp[2] & 1),
+ feature);
+ printf(" available profiles [more recent typically higher "
+ "in list]:\n");
+ for (k = 4; k < len; k += 4) {
+ profile = sg_get_unaligned_be16(bp + k);
+ printf(" profile: %s , currentP=%d\n",
+ get_profile_str(profile, buff), !!(bp[k + 2] & 1));
+ }
+ break;
+ case 1: /* Core */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 2), !!(bp[2] & 1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = sg_get_unaligned_be32(bp + 4);
+ switch (num) {
+ case 0: cp = "unspecified"; break;
+ case 1: cp = "SCSI family"; break;
+ case 2: cp = "ATAPI"; break;
+ case 3: cp = "IEEE 1394 - 1995"; break;
+ case 4: cp = "IEEE 1394A"; break;
+ case 5: cp = "Fibre channel"; break;
+ case 6: cp = "IEEE 1394B"; break;
+ case 7: cp = "Serial ATAPI"; break;
+ case 8: cp = "USB (both 1 and 2)"; break;
+ case 0xffff: cp = "vendor unique"; break;
+ default:
+ snprintf(buff, sizeof(buff), "[0x%x]", num);
+ cp = buff;
+ break;
+ }
+ printf(" Physical interface standard: %s", cp);
+ if (len > 8)
+ printf(", INQ2=%d, DBE=%d\n", !!(bp[8] & 2), !!(bp[8] & 1));
+ else
+ printf("\n");
+ break;
+ case 2: /* Morphing */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 2), !!(bp[2] & 1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" OCEvent=%d, ASYNC=%d\n", !!(bp[4] & 2), !!(bp[4] & 1));
+ break;
+ case 3: /* Removable medium */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 2), !!(bp[2] & 1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = (bp[4] >> 5) & 0x7;
+ switch (num) {
+ case 0: cp = "Caddy/slot type"; break;
+ case 1: cp = "Tray type"; break;
+ case 2: cp = "Pop-up type"; break;
+ case 4: cp = "Embedded changer with individually changeable discs";
+ break;
+ case 5: cp = "Embedded changer using a magazine"; break;
+ default:
+ snprintf(buff, sizeof(buff), "[0x%x]", num);
+ cp = buff;
+ break;
+ }
+ printf(" Loading mechanism: %s\n", cp);
+ printf(" Load=%d, Eject=%d, Prevent jumper=%d, Lock=%d\n",
+ !!(bp[4] & 0x10), !!(bp[4] & 0x8), !!(bp[4] & 0x4),
+ !!(bp[4] & 0x1));
+ break;
+ case 4: /* Write protect */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" DWP=%d, WDCB=%d, SPWP=%d, SSWPP=%d\n", !!(bp[4] & 0x8),
+ !!(bp[4] & 0x4), !!(bp[4] & 0x2), !!(bp[4] & 0x1));
+ break;
+ case 0x10: /* Random readable */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 12) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = sg_get_unaligned_be32(bp + 4);
+ printf(" Logical block size=0x%x, blocking=0x%x, PP=%d\n",
+ num, sg_get_unaligned_be16(bp + 8), !!(bp[10] & 0x1));
+ break;
+ case 0x1d: /* Multi-read */
+ case 0x22: /* Sector erasable */
+ case 0x26: /* Restricted overwrite */
+ case 0x27: /* CDRW CAV write */
+ case 0x35: /* Stop long operation */
+ case 0x38: /* BD-R pseudo-overwrite (POW) */
+ case 0x42: /* TSR (timely safe recording) */
+ case 0x100: /* Power management */
+ case 0x109: /* Media serial number */
+ case 0x110: /* VCPS */
+ case 0x113: /* SecurDisc */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ break;
+ case 0x1e: /* CD read */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" DAP=%d, C2 flags=%d, CD-Text=%d\n", !!(bp[4] & 0x80),
+ !!(bp[4] & 0x2), !!(bp[4] & 0x1));
+ break;
+ case 0x1f: /* DVD read */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len > 7)
+ printf(" MULTI110=%d, Dual-RW=%d, Dual-R=%d\n",
+ !!(bp[4] & 0x1), !!(bp[6] & 0x2), !!(bp[6] & 0x1));
+ break;
+ case 0x20: /* Random writable */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 16) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = sg_get_unaligned_be32(bp + 4);
+ n = sg_get_unaligned_be32(bp + 8);
+ printf(" Last lba=0x%x, Logical block size=0x%x, blocking=0x%x,"
+ " PP=%d\n", num, n, sg_get_unaligned_be16(bp + 12),
+ !!(bp[14] & 0x1));
+ break;
+ case 0x21: /* Incremental streaming writable */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Data block types supported=0x%x, TRIO=%d, ARSV=%d, "
+ "BUF=%d\n", sg_get_unaligned_be16(bp + 4), !!(bp[6] & 0x4),
+ !!(bp[6] & 0x2), !!(bp[6] & 0x1));
+ num = bp[7];
+ printf(" Number of link sizes=%d\n", num);
+ for (k = 0; k < num; ++k)
+ printf(" %d\n", bp[8 + k]);
+ break;
+ /* case 0x22: Sector erasable -> see 0x1d entry */
+ case 0x23: /* Formattable */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len > 4)
+ printf(" BD-RE: RENoSA=%d, Expand=%d, QCert=%d, Cert=%d, "
+ "FRF=%d\n", !!(bp[4] & 0x8), !!(bp[4] & 0x4),
+ !!(bp[4] & 0x2), !!(bp[4] & 0x1), !!(bp[5] & 0x80));
+ if (len > 8)
+ printf(" BD-R: RRM=%d\n", !!(bp[8] & 0x1));
+ break;
+ case 0x24: /* Hardware defect management */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len > 4)
+ printf(" SSA=%d\n", !!(bp[4] & 0x80));
+ break;
+ case 0x25: /* Write once */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 12) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = sg_get_unaligned_be16(bp + 4);
+ printf(" Logical block size=0x%x, blocking=0x%x, PP=%d\n",
+ num, sg_get_unaligned_be16(bp + 8), !!(bp[10] & 0x1));
+ break;
+ /* case 0x26: Restricted overwrite -> see 0x1d entry */
+ /* case 0x27: CDRW CAV write -> see 0x1d entry */
+ case 0x28: /* MRW (Mount Rainier reWriteable) */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len > 4)
+ printf(" DVD+Write=%d, DVD+Read=%d, Write=%d\n",
+ !!(bp[4] & 0x4), !!(bp[4] & 0x2), !!(bp[4] & 0x1));
+ break;
+ case 0x29: /* Enhanced defect reporting */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" DRT-DM=%d, number of DBI cache zones=0x%x, number of "
+ "entries=0x%x\n", !!(bp[4] & 0x1), bp[5],
+ sg_get_unaligned_be16(bp + 6));
+ break;
+ case 0x2a: /* DVD+RW */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Write=%d, Quick start=%d, Close only=%d\n",
+ !!(bp[4] & 0x1), !!(bp[5] & 0x2), !!(bp[5] & 0x1));
+ break;
+ case 0x2b: /* DVD+R */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Write=%d\n", !!(bp[4] & 0x1));
+ break;
+ case 0x2c: /* Rigid restricted overwrite */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" DSDG=%d, DSDR=%d, Intermediate=%d, Blank=%d\n",
+ !!(bp[4] & 0x8), !!(bp[4] & 0x4), !!(bp[4] & 0x2),
+ !!(bp[4] & 0x1));
+ break;
+ case 0x2d: /* CD Track at once */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BUF=%d, R-W raw=%d, R-W pack=%d, Test write=%d\n",
+ !!(bp[4] & 0x40), !!(bp[4] & 0x10), !!(bp[4] & 0x8),
+ !!(bp[4] & 0x4));
+ printf(" CD-RW=%d, R-W sub-code=%d, Data type supported=%d\n",
+ !!(bp[4] & 0x2), !!(bp[4] & 0x1),
+ sg_get_unaligned_be16(bp + 6));
+ break;
+ case 0x2e: /* CD mastering (session at once) */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BUF=%d, SAO=%d, Raw MS=%d, Raw=%d\n",
+ !!(bp[4] & 0x40), !!(bp[4] & 0x20), !!(bp[4] & 0x10),
+ !!(bp[4] & 0x8));
+ printf(" Test write=%d, CD-RW=%d, R-W=%d\n",
+ !!(bp[4] & 0x4), !!(bp[4] & 0x2), !!(bp[4] & 0x1));
+ printf(" Maximum cue sheet length=0x%x\n",
+ sg_get_unaligned_be24(bp + 5));
+ break;
+ case 0x2f: /* DVD-R/-RW write */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BUF=%d, RDL=%d, Test write=%d, DVD-RW SL=%d\n",
+ !!(bp[4] & 0x40), !!(bp[4] & 0x8), !!(bp[4] & 0x4),
+ !!(bp[4] & 0x2));
+ break;
+ case 0x33: /* Layer jump recording */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ num = bp[7];
+ printf(" Number of link sizes=%d\n", num);
+ for (k = 0; k < num; ++k)
+ printf(" %d\n", bp[8 + k]);
+ break;
+ case 0x34: /* Layer jump rigid restricted overwrite */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" CLJB=%d\n", !!(bp[4] & 0x1));
+ printf(" Buffer block size=%d\n", bp[7]);
+ break;
+ /* case 0x35: Stop long operation -> see 0x1d entry */
+ case 0x37: /* CD-RW media write support */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" CD-RW media sub-type support (bitmask)=0x%x\n", bp[5]);
+ break;
+ /* case 0x38: BD-R pseudo-overwrite (POW) -> see 0x1d entry */
+ case 0x3a: /* DVD+RW dual layer */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" write=%d, quick_start=%d, close_only=%d\n",
+ !!(bp[4] & 0x1), !!(bp[5] & 0x2), !!(bp[5] & 0x1));
+ break;
+ case 0x3b: /* DVD+R dual layer */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" write=%d\n", !!(bp[4] & 0x1));
+ break;
+ case 0x40: /* BD Read */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 32) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Bitmaps for BD-RE read support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 8),
+ sg_get_unaligned_be16(bp + 10),
+ sg_get_unaligned_be16(bp + 12),
+ sg_get_unaligned_be16(bp + 14));
+ printf(" Bitmaps for BD-R read support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 16),
+ sg_get_unaligned_be16(bp + 18),
+ sg_get_unaligned_be16(bp + 20),
+ sg_get_unaligned_be16(bp + 22));
+ printf(" Bitmaps for BD-ROM read support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 24),
+ sg_get_unaligned_be16(bp + 26),
+ sg_get_unaligned_be16(bp + 28),
+ sg_get_unaligned_be16(bp + 30));
+ break;
+ case 0x41: /* BD Write */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 32) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" SVNR=%d\n", !!(bp[4] & 0x1));
+ printf(" Bitmaps for BD-RE write support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 8),
+ sg_get_unaligned_be16(bp + 10),
+ sg_get_unaligned_be16(bp + 12),
+ sg_get_unaligned_be16(bp + 14));
+ printf(" Bitmaps for BD-R write support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 16),
+ sg_get_unaligned_be16(bp + 18),
+ sg_get_unaligned_be16(bp + 20),
+ sg_get_unaligned_be16(bp + 22));
+ printf(" Bitmaps for BD-ROM write support:\n");
+ printf(" Class 0=0x%x, Class 1=0x%x, Class 2=0x%x, "
+ "Class 3=0x%x\n", sg_get_unaligned_be16(bp + 24),
+ sg_get_unaligned_be16(bp + 26),
+ sg_get_unaligned_be16(bp + 28),
+ sg_get_unaligned_be16(bp + 30));
+ break;
+ /* case 0x42: TSR (timely safe recording) -> see 0x1d entry */
+ case 0x50: /* HD DVD Read */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" HD DVD-R=%d, HD DVD-RAM=%d\n", !!(bp[4] & 0x1),
+ !!(bp[6] & 0x1));
+ break;
+ case 0x51: /* HD DVD Write */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" HD DVD-R=%d, HD DVD-RAM=%d\n", !!(bp[4] & 0x1),
+ !!(bp[6] & 0x1));
+ break;
+ case 0x52: /* HD DVD-RW fragment recording */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BGP=%d\n", !!(bp[4] & 0x1));
+ break;
+ case 0x80: /* Hybrid disc */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" RI=%d\n", !!(bp[4] & 0x1));
+ break;
+ /* case 0x100: Power management -> see 0x1d entry */
+ case 0x101: /* SMART */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" PP=%d\n", !!(bp[4] & 0x1));
+ break;
+ case 0x102: /* Embedded changer */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" SCC=%d, SDP=%d, highest slot number=%d\n",
+ !!(bp[4] & 0x10), !!(bp[4] & 0x4), (bp[7] & 0x1f));
+ break;
+ case 0x103: /* CD audio external play (obsolete) */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Scan=%d, SCM=%d, SV=%d, number of volume levels=%d\n",
+ !!(bp[4] & 0x4), !!(bp[4] & 0x2), !!(bp[4] & 0x1),
+ sg_get_unaligned_be16(bp + 6));
+ break;
+ case 0x104: /* Firmware upgrade */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 4) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ if (len > 4)
+ printf(" M5=%d\n", !!(bp[4] & 0x1));
+ break;
+ case 0x105: /* Timeout */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len > 7) {
+ printf(" Group 3=%d, unit length=%d\n",
+ !!(bp[4] & 0x1), sg_get_unaligned_be16(bp + 6));
+ }
+ break;
+ case 0x106: /* DVD CSS */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" CSS version=%d\n", bp[7]);
+ break;
+ case 0x107: /* Real time streaming */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" RBCB=%d, SCS=%d, MP2A=%d, WSPD=%d, SW=%d\n",
+ !!(bp[4] & 0x10), !!(bp[4] & 0x8), !!(bp[4] & 0x4),
+ !!(bp[4] & 0x2), !!(bp[4] & 0x1));
+ break;
+ case 0x108: /* Drive serial number */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ num = len - 4;
+ n = sizeof(buff) - 1;
+ n = ((num < n) ? num : n);
+ strncpy(buff, (const char *)(bp + 4), n);
+ buff[n] = '\0';
+ printf(" Drive serial number: %s\n", buff);
+ break;
+ /* case 0x109: Media serial number -> see 0x1d entry */
+ case 0x10a: /* Disc control blocks */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ printf(" Disc control blocks:\n");
+ for (k = 4; k < len; k += 4) {
+ printf(" 0x%x\n", sg_get_unaligned_be32(bp + k));
+ }
+ break;
+ case 0x10b: /* DVD CPRM */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" CPRM version=%d\n", bp[7]);
+ break;
+ case 0x10c: /* firmware information */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 20) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" %.2s%.2s/%.2s/%.2s %.2s:%.2s:%.2s\n", bp + 4,
+ bp + 6, bp + 8, bp + 10, bp + 12, bp + 14, bp + 16);
+ break;
+ case 0x10d: /* AACS */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BNG=%d, Block count for binding nonce=%d\n",
+ !!(bp[4] & 0x1), bp[5]);
+ printf(" Number of AGIDs=%d, AACS version=%d\n",
+ (bp[6] & 0xf), bp[7]);
+ break;
+ case 0x10e: /* DVD CSS managed recording */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" Maximum number of scrambled extent information "
+ "entries=%d\n", bp[4]);
+ break;
+ /* case 0x110: VCPS -> see 0x1d entry */
+ /* case 0x113: SecurDisc -> see 0x1d entry */
+ case 0x120: /* BD CPS */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" BD CPS major:minor version number=%d:%d, max open "
+ "SACs=%d\n", ((bp[5] >> 4) & 0xf), (bp[5] & 0xf),
+ bp[6] & 0x3);
+ break;
+ case 0x142: /* OSSC (Optical Security Subsystem Class) */
+ printf(" version=%d, persist=%d, current=%d [0x%x]\n",
+ ((bp[2] >> 2) & 0xf), !!(bp[2] & 0x2), !!(bp[2] & 0x1),
+ feature);
+ if (len < 8) {
+ printf(" additional length [%d] too short\n", len - 4);
+ break;
+ }
+ printf(" PSAU=%d, LOSPB=%d, ME=%d\n", !!(bp[4] & 0x80),
+ !!(bp[4] & 0x40), !!(bp[4] & 0x1));
+ num = bp[5];
+ printf(" Profile numbers:\n");
+ for (k = 6; (num > 0) && (k < len); --num, k += 2) {
+ printf(" %u\n", sg_get_unaligned_be16(bp + k));
+ }
+ break;
+ default:
+ pr2serr(" Unknown feature [0x%x], version=%d persist=%d, "
+ "current=%d\n", feature, ((bp[2] >> 2) & 0xf),
+ !!(bp[2] & 0x2), !!(bp[2] & 0x1));
+ hex2stderr(bp, len, 1);
+ break;
+ }
+}
+
+static void
+decode_config(uint8_t * resp, int max_resp_len, int len, bool brief,
+ bool inner_hex)
+{
+ int k, curr_profile, extra_len, feature;
+ uint8_t * bp;
+ char buff[128];
+
+ if (max_resp_len < len) {
+ pr2serr("<<<warning: response to long for buffer, resp_len=%d>>>\n",
+ len);
+ len = max_resp_len;
+ }
+ if (len < 8) {
+ pr2serr("response length too short: %d\n", len);
+ return;
+ }
+ curr_profile = sg_get_unaligned_be16(resp + 6);
+ if (0 == curr_profile)
+ pr2serr("No current profile\n");
+ else
+ printf("Current profile: %s\n", get_profile_str(curr_profile, buff));
+ printf("Features%s:\n", (brief ? " (in brief)" : ""));
+ bp = resp + 8;
+ len -= 8;
+ for (k = 0; k < len; k += extra_len, bp += extra_len) {
+ extra_len = 4 + bp[3];
+ feature = sg_get_unaligned_be16(bp + 0);
+ printf(" %s feature\n", get_feature_str(feature, buff));
+ if (brief)
+ continue;
+ if (inner_hex) {
+ hex2stdout(bp, extra_len, 1);
+ continue;
+ }
+ if (0 != (extra_len % 4))
+ printf(" additional length [%d] not a multiple of 4, ignore\n",
+ extra_len - 4);
+ else
+ decode_feature(feature, bp, extra_len);
+ }
+}
+
+static void
+list_known(bool brief)
+{
+ int k, num;
+
+ num = SG_ARRAY_SIZE(feature_desc_arr);
+ printf("Known features:\n");
+ for (k = 0; k < num; ++k)
+ printf(" %s [0x%x]\n", feature_desc_arr[k].desc,
+ feature_desc_arr[k].val);
+ if (! brief) {
+ printf("Known profiles:\n");
+ num = SG_ARRAY_SIZE(profile_desc_arr);
+ for (k = 0; k < num; ++k)
+ printf(" %s [0x%x]\n", profile_desc_arr[k].desc,
+ profile_desc_arr[k].val);
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool brief = false;
+ bool inner_hex = false;
+ bool list = false;
+ bool do_raw = false;
+ bool readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, res, c, len;
+ int peri_type = 0;
+ int rt = 0;
+ int starting = 0;
+ int verbose = 0;
+ int do_hex = 0;
+ const char * device_name = NULL;
+ char buff[64];
+ const char * cp;
+ struct sg_simple_inquiry_resp inq_resp;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bchHilqr:Rs:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ brief = true;
+ break;
+ case 'c':
+ rt = 1;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ inner_hex = true;
+ break;
+ case 'l':
+ list = true;
+ break;
+ case 'q':
+ readonly = true;
+ break;
+ case 'r':
+ rt = sg_get_num(optarg);
+ if ((rt < 0) || (rt > 3)) {
+ pr2serr("bad argument to '--rt'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'R':
+ do_raw = true;
+ break;
+ case 's':
+ starting = sg_get_num(optarg);
+ if ((starting < 0) || (starting > 0xffff)) {
+ pr2serr("bad argument to '--starting'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (list) {
+ list_known(brief);
+ return 0;
+ }
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((sg_fd = sg_cmds_open_device(device_name, true /* ro */, verbose))
+ < 0) {
+ pr2serr(ME "error opening file: %s (ro): %s\n", device_name,
+ safe_strerror(-sg_fd));
+ return sg_convert_errno(-sg_fd);
+ }
+ if (0 == sg_simple_inquiry(sg_fd, &inq_resp, true, verbose)) {
+ if (! do_raw)
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor, inq_resp.product,
+ inq_resp.revision);
+ peri_type = inq_resp.peripheral_type;
+ cp = sg_get_pdt_str(peri_type, sizeof(buff), buff);
+ if (! do_raw) {
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_type);
+ }
+ } else {
+ pr2serr(ME "%s doesn't respond to a SCSI INQUIRY\n", device_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ sg_cmds_close_device(sg_fd);
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ pr2serr(ME "open error (rw): %s\n", safe_strerror(-sg_fd));
+ return sg_convert_errno(-sg_fd);
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ res = sg_ll_get_config(sg_fd, rt, starting, resp_buffer,
+ sizeof(resp_buffer), true, verbose);
+ ret = res;
+ if (0 == res) {
+ len = sg_get_unaligned_be32(resp_buffer + 0) + 4;
+ if (do_hex) {
+ if (len > (int)sizeof(resp_buffer))
+ len = sizeof(resp_buffer);
+ hex2stdout(resp_buffer, len, 0);
+ } else if (do_raw)
+ dStrRaw((const char *)resp_buffer, len);
+ else
+ decode_config(resp_buffer, sizeof(resp_buffer), len, brief,
+ inner_hex);
+ } else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Get Configuration command: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' option for more information\n");
+ }
+
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-ret);
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_get_config failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_get_elem_status.c b/src/sg_get_elem_status.c
new file mode 100644
index 00000000..574052ce
--- /dev/null
+++ b/src/sg_get_elem_status.c
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 2019-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.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_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI GET PHYSICAL ELEMENT STATUS command to the
+ * given SCSI device.
+ */
+
+static const char * version_str = "1.15 20220807"; /* sbc5r03 */
+
+#define MY_NAME "sg_get_elem_status"
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+#define GET_PHY_ELEM_STATUS_SA 0x17
+#define DEF_GPES_BUFF_LEN (1024 + 32)
+#define MAX_GPES_BUFF_LEN ((1024 * 1024) + DEF_GPES_BUFF_LEN)
+#define GPES_DESC_OFFSET 32 /* descriptors starts at this byte offset */
+#define GPES_DESC_LEN 32
+#define MIN_MAXLEN 16
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+struct gpes_desc_t { /* info in returned physical status descriptor */
+ bool restoration_allowed; /* RALWD bit in sbc4r20a */
+ uint32_t elem_id;
+ uint8_t phys_elem_type;
+ uint8_t phys_elem_health;
+ uint64_t assoc_cap; /* number of LBs removed if depopulated */
+};
+
+static uint8_t gpesBuff[DEF_GPES_BUFF_LEN];
+
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'},
+ {"filter", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* silent, same as --inhex= */
+ {"inhex", required_argument, 0, 'i'},
+ {"json", optional_argument, 0, 'j'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"report-type", required_argument, 0, 't'},
+ {"report_type", required_argument, 0, 't'},
+ {"starting", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_get_elem_status [--brief] [--filter=FLT] [--help] "
+ "[--hex]\n"
+ " [--inhex=FN] [--json[=JO]] "
+ "[--maxlen=LEN]\n"
+ " [--raw] [--readonly] "
+ "[--report-type=RT]\n"
+ " [--starting=ELEM] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --brief|-b one descriptor per line\n"
+ " --filter=FLT|-f FLT FLT is 0 (def) for all physical "
+ "elements;\n"
+ " 1 for out of spec and depopulated "
+ "elements\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output in hexadecimal\n"
+ " --inhex=FN|-i FN input taken from file FN rather than "
+ "DEVICE,\n"
+ " assumed to be ASCII hex or, if --raw, "
+ "in binary\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text\n"
+ " use --json=? for JSON help\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> %d bytes)\n",
+ DEF_GPES_BUFF_LEN );
+ pr2serr(" --raw|-r output in binary, unless --inhex=FN is "
+ "given in\n"
+ " in which case the input is assumed to be "
+ "binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --report-type=RT|-t RT report type: 0-> physical "
+ "elements (def);\n"
+ " 1-> storage "
+ "elements\n"
+ " --starting=ELEM|-s ELEM ELEM is the lowest identifier "
+ "returned\n"
+ " (def: 1 which is lowest "
+ "identifier)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI GET PHYSICAL ELEMENT STATUS command (see SBC-3 "
+ "or SBC-4).\nStorage elements are a sub-set of physical "
+ "elements. Currently the only\ntype of physical element is a "
+ "storage element. If --inhex=FN is given then\ncontents of FN "
+ "is assumed to be a response to this command in ASCII hex.\n"
+ "Returned element descriptors should be in ascending "
+ "identifier order.\n"
+ );
+}
+
+/* Invokes a SCSI GET PHYSICAL ELEMENT STATUS command (SBC-4). Return of
+ * 0 -> success, various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_get_phy_elem_status(int sg_fd, uint32_t starting_elem, uint8_t filter,
+ uint8_t report_type, uint8_t * resp,
+ uint32_t alloc_len, int * residp, bool noisy,
+ int verbose)
+{
+ int k, ret, res, sense_cat;
+ uint8_t gpesCmd[16] = {SG_SERVICE_ACTION_IN_16,
+ GET_PHY_ELEM_STATUS_SA, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ static const char * const cmd_name = "Get physical element status";
+
+ if (starting_elem)
+ sg_put_unaligned_be32(starting_elem, gpesCmd + 6);
+ sg_put_unaligned_be32(alloc_len, gpesCmd + 10);
+ if (filter)
+ gpesCmd[14] |= filter << 6;
+ if (report_type)
+ gpesCmd[14] |= (0xf & report_type);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", cmd_name,
+ sg_get_command_str(gpesCmd, (int)sizeof(gpesCmd), false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", cmd_name);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, gpesCmd, sizeof(gpesCmd));
+ set_scsi_pt_data_in(ptvp, resp, alloc_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ k = ret ? (int)alloc_len : get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = k;
+ if ((verbose > 2) && ((alloc_len - k) > 0)) {
+ pr2serr("%s: parameter data returned:\n", cmd_name);
+ hex2stderr((const uint8_t *)resp, alloc_len - k,
+ ((verbose > 3) ? -1 : 1));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Decodes given physical element status descriptor. */
+static void
+decode_elem_status_desc(const uint8_t * bp, struct gpes_desc_t * pedp)
+{
+ if ((NULL == bp) || (NULL == pedp))
+ return;
+ pedp->elem_id = sg_get_unaligned_be32(bp + 4);
+ pedp->restoration_allowed = (bool)(bp[13] & 1);
+ pedp->phys_elem_type = bp[14];
+ pedp->phys_elem_health = bp[15];
+ pedp->assoc_cap = sg_get_unaligned_be64(bp + 16);
+}
+
+static bool
+fetch_health_str(uint8_t health, char * bp, int max_blen)
+{
+ bool add_val = false;
+ const char * cp = NULL;
+
+ if (0 == health)
+ cp = "not reported";
+ else if (health < 0x64) {
+ cp = "within manufacturer's specification limits";
+ add_val = true;
+ } else if (0x64 == health) {
+ cp = "at manufacturer's specification limits";
+ add_val = true;
+ } else if (health < 0xd0) {
+ cp = "outside manufacturer's specification limits";
+ add_val = true;
+ } else if (health < 0xfb) {
+ cp = "reserved";
+ add_val = true;
+ } else if (0xfb == health)
+ cp = "depopulation revocation completed, errors detected";
+ else if (0xfc == health)
+ cp = "depopulation revocation in progress";
+ else if (0xfd == health)
+ cp = "depopulation completed, errors detected";
+ else if (0xfe == health)
+ cp = "depopulation operations in progress";
+ else if (0xff == health)
+ cp = "depopulation completed, no errors";
+ snprintf(bp, max_blen, "%s", cp);
+ return add_val;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_raw = false;
+ bool no_final_msg = false;
+ bool o_readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int k, j, m, n, res, c, rlen, in_len;
+ int sg_fd = -1;
+ int do_brief = 0;
+ int do_hex = 0;
+ int resid = 0;
+ int ret = 0;
+ int maxlen = DEF_GPES_BUFF_LEN;
+ int verbose = 0;
+ uint8_t filter = 0;
+ uint8_t rt = 0;
+ uint32_t num_desc, num_desc_ret, id_elem_depop;
+ uint32_t starting_elem = 0;
+ int64_t ll;
+ const char * device_name = NULL;
+ const char * in_fn = NULL;
+ const char * cp;
+ const uint8_t * bp;
+ uint8_t * gpesBuffp = gpesBuff;
+ uint8_t * free_gpesBuffp = NULL;
+ sgj_opaque_p jop = NULL;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jap = NULL;
+ struct gpes_desc_t a_ped;
+ sgj_state json_st SG_C_CPP_ZERO_INIT;
+ sgj_state * jsp = &json_st;
+ char b[80];
+ static const int blen = sizeof(b);
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bf:hHi:j::m:rRs:St:TvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ ++do_brief;
+ break;
+ case 'f':
+ n = sg_get_num_nomult(optarg);
+ if ((n < 0) || (n > 15)) {
+ pr2serr("'--filter=RT' should be between 0 and 15 "
+ "(inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ filter = n;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ in_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&json_st, optarg)) {
+ int bad_char = json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_GPES_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_GPES_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == maxlen)
+ maxlen = DEF_GPES_BUFF_LEN;
+ else if (maxlen < MIN_MAXLEN) {
+ pr2serr("Warning: --maxlen=LEN less than %d ignored\n",
+ MIN_MAXLEN);
+ maxlen = DEF_GPES_BUFF_LEN;
+ }
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 's':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--starting='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ starting_elem = (uint32_t)ll;
+ break;
+ case 't': /* --report-type=RT */
+ n = sg_get_num_nomult(optarg);
+ if ((n < 0) || (n > 15)) {
+ pr2serr("'--report-type=RT' should be between 0 and 15 "
+ "(inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ rt = n;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+ if (jsp->pr_as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (maxlen > DEF_GPES_BUFF_LEN) {
+ gpesBuffp = (uint8_t *)sg_memalign(maxlen, 0, &free_gpesBuffp,
+ verbose > 3);
+ if (NULL == gpesBuffp) {
+ pr2serr("unable to allocate %d bytes on heap\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ }
+ if (device_name && in_fn) {
+ pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
+ "not both\n");
+ device_name = NULL;
+ }
+ if (NULL == device_name) {
+ if (in_fn) {
+ if ((ret = sg_f2hex_arr(in_fn, do_raw, false, gpesBuffp,
+ &in_len, maxlen))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
+ pr2serr("--maxlen=%d needs to be increased", maxlen);
+ if (in_len > 7) {
+ n = (sg_get_unaligned_be32(gpesBuffp + 4) *
+ GPES_DESC_LEN) + GPES_DESC_OFFSET;
+ pr2serr(" to at least %d\n", n);
+ } else
+ pr2serr("\n");
+ pr2serr("... decode what we have\n");
+ no_final_msg = true;
+ } else
+ goto fini;
+ }
+ if (verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (do_raw)
+ do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n",
+ in_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ res = 0;
+ goto start_response;
+ } else {
+ pr2serr("missing device name!\n\n");
+ usage();
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto fini;
+ }
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ }
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ pr2serr("open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ res = sg_ll_get_phy_elem_status(sg_fd, starting_elem, filter, rt,
+ gpesBuffp, maxlen, &resid, true, verbose);
+ ret = res;
+ if (res)
+ goto error;
+
+start_response:
+ k = maxlen - resid;
+ if (k < 4) {
+ pr2serr("Response too short (%d bytes) due to resid (%d)\n", k,
+ resid);
+ if ((k > 0) && (do_raw || do_hex)) {
+ if (do_hex) {
+ if (do_hex > 2)
+ hex2stdout(gpesBuffp, k, -1);
+ else
+ hex2stdout(gpesBuffp, k, (2 == do_hex) ? 0 : 1);
+ } else
+ dStrRaw((const char *)gpesBuffp, k);
+ }
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ } else
+ maxlen -= resid;
+ num_desc = sg_get_unaligned_be32(gpesBuffp + 0);
+ if (maxlen > 7) {
+ num_desc_ret = sg_get_unaligned_be32(gpesBuffp + 4);
+ id_elem_depop = (maxlen > 11) ? sg_get_unaligned_be32(gpesBuffp + 8) :
+ 0;
+ } else {
+ num_desc_ret = 0;
+ id_elem_depop = 0;
+ }
+ rlen = (num_desc_ret * GPES_DESC_LEN) + GPES_DESC_OFFSET;
+ if ((verbose > 1) || (verbose && (rlen > maxlen))) {
+ pr2serr("response length %d bytes\n", rlen);
+ if (rlen > maxlen)
+ pr2serr(" ... which is greater than maxlen (allocation "
+ "length %d), truncation\n", maxlen);
+ }
+ if (rlen > maxlen)
+ rlen = maxlen;
+ if (do_raw) {
+ dStrRaw((const char *)gpesBuffp, rlen);
+ goto fini;
+ }
+ if (do_hex) {
+ if (do_hex > 2)
+ hex2stdout(gpesBuffp, rlen, -1);
+ else
+ hex2stdout(gpesBuffp, rlen, (2 == do_hex) ? 0 : 1);
+ goto fini;
+ }
+
+ sgj_haj_vi(jsp, jop, 0, "Number of descriptors",
+ SGJ_SEP_COLON_1_SPACE, num_desc, true);
+ sgj_haj_vi(jsp, jop, 0, "Number of descriptors returned",
+ SGJ_SEP_COLON_1_SPACE, num_desc_ret, true);
+ sgj_haj_vi(jsp, jop, 0, "Identifier of element being depopulated",
+ SGJ_SEP_COLON_1_SPACE, id_elem_depop, true);
+ if (rlen < 64) {
+ sgj_pr_hr(jsp, "No complete physical element status descriptors "
+ "available\n");
+ goto fini;
+ } else {
+ if (do_brief > 2)
+ goto fini;
+ sgj_pr_hr(jsp, "\n");
+ }
+
+ if (jsp->pr_as_json)
+ jap = sgj_named_subarray_r(jsp, jop,
+ "physical_element_status_descriptor");
+ for (bp = gpesBuffp + GPES_DESC_OFFSET, k = 0; k < (int)num_desc_ret;
+ bp += GPES_DESC_LEN, ++k) {
+ if ((0 == k) && (do_brief < 2))
+ sgj_pr_hr(jsp, "Element descriptors:\n");
+ decode_elem_status_desc(bp, &a_ped);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jo2p, "element_identifier",
+ (int64_t)a_ped.elem_id);
+ cp = (1 == a_ped.phys_elem_type) ? "storage" : "reserved";
+ sgj_js_nv_istr(jsp, jo2p, "physical_element_type",
+ a_ped.phys_elem_type, "meaning", cp);
+ j = a_ped.phys_elem_health;
+ fetch_health_str(j, b, blen);
+ sgj_js_nv_istr(jsp, jo2p, "physical_element_health", j, NULL, b);
+ sgj_js_nv_ihex(jsp, jo2p, "associated_capacity",
+ (int64_t)a_ped.assoc_cap);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ } else if (do_brief) {
+ sgj_pr_hr(jsp, "%u: %u,%u\n", a_ped.elem_id, a_ped.phys_elem_type,
+ a_ped.phys_elem_health);
+ } else {
+ char b2[144];
+ static const int b2len = sizeof(b2);
+
+ m = 0;
+ m += sg_scnpr(b2 + m, b2len - m, "[%d] identifier: 0x%06x",
+ k + 1, a_ped.elem_id);
+ if (sg_all_ffs((const uint8_t *)&a_ped.assoc_cap, 8))
+ m += sg_scnpr(b2 + m, b2len - m,
+ " associated LBs: not specified; ");
+ else
+ m += sg_scnpr(b2 + m, b2len - m, " associated LBs: 0x%"
+ PRIx64 "; ", a_ped.assoc_cap);
+ m += sg_scnpr(b2 + m, b2len - m, "health: ");
+ j = a_ped.phys_elem_health;
+ if (fetch_health_str(j, b, blen))
+ m += sg_scnpr(b2 + m, b2len - m, "%s <%d>", b, j);
+ else
+ m += sg_scnpr(b2 + m, b2len - m, "%s", b);
+ if (a_ped.restoration_allowed)
+ m += sg_scnpr(b2 + m, b2len - m,
+ " [restoration allowed [RALWD]]");
+ sgj_pr_hr(jsp, "%s\n", b2);
+ }
+ }
+ goto fini;
+
+error:
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Get LBA Status command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("Get LBA Status command: bad field in cdb\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Get LBA Status command: %s\n", b);
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (free_gpesBuffp)
+ free(free_gpesBuffp);
+ if ((0 == verbose) && (! no_final_msg)) {
+ if (! sg_if_can2stderr("sg_get_elem_status failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (jsp->pr_as_json) {
+ if (0 == do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
diff --git a/src/sg_get_lba_status.c b/src/sg_get_lba_status.c
new file mode 100644
index 00000000..97b967dd
--- /dev/null
+++ b/src/sg_get_lba_status.c
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 2009-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI GET LBA STATUS command to the given SCSI
+ * device.
+ */
+
+static const char * version_str = "1.31 20220807"; /* sbc5r03 */
+
+#define MY_NAME "sg_get_lba_status"
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+#define MAX_GLBAS_BUFF_LEN (1024 * 1024)
+#define DEF_GLBAS_BUFF_LEN 1024
+#define MIN_MAXLEN 16
+
+static uint8_t glbasFixedBuff[DEF_GLBAS_BUFF_LEN];
+
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'S'},
+ {"32", no_argument, 0, 'T'},
+ {"brief", no_argument, 0, 'b'},
+ {"blockhex", no_argument, 0, 'B'},
+ {"element-id", required_argument, 0, 'e'},
+ {"element_id", required_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* silent, same as --inhex= */
+ {"inhex", required_argument, 0, 'i'},
+ {"json", optional_argument, 0, 'j'},
+ {"lba", required_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"report-type", required_argument, 0, 't'},
+ {"report_type", required_argument, 0, 't'},
+ {"scan-len", required_argument, 0, 's'},
+ {"scan_len", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_get_lba_status [--16] [--32] [--blockhex] "
+ "[--brief]\n"
+ " [--element-id=EI] [--help] [--hex] "
+ "[--inhex=FN]\n"
+ " [--lba=LBA] [--maxlen=LEN] [--raw] "
+ "[--readonly]\n"
+ " [--report-type=RT] [--scan-len=SL] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --16|-S use GET LBA STATUS(16) cdb (def)\n"
+ " --32|-T use GET LBA STATUS(32) cdb\n"
+ " --blockhex|-B outputs the (number of) blocks field "
+ " in hex\n"
+ " --brief|-b a descriptor per line:\n"
+ " <lba_hex blocks_hex p_status "
+ "add_status>\n"
+ " use twice ('-bb') for given LBA "
+ "provisioning status\n"
+ " --element-id=EI|-e EI EI is the element identifier "
+ "(def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output in hexadecimal\n"
+ " --inhex=FN|-i FN input taken from file FN rather than "
+ "DEVICE,\n"
+ " assumed to be ASCII hex or, if --raw, "
+ "in binary\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --lba=LBA|-l LBA starting LBA (logical block address) "
+ "(def: 0)\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> %d bytes)\n",
+ DEF_GLBAS_BUFF_LEN );
+ pr2serr(" --raw|-r output in binary, unless if --inhex=FN "
+ "is given,\n"
+ " in which case input file is binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --report-type=RT|-t RT report type: 0->all LBAs (def);\n"
+ " 1-> LBAs with non-zero "
+ "provisioning status\n"
+ " 2-> LBAs that are mapped\n"
+ " 3-> LBAs that are deallocated\n"
+ " 4-> LBAs that are anchored\n"
+ " 16-> LBAs that may return "
+ "unrecovered error\n"
+ " --scan-len=SL|-s SL SL in maximum scan length (unit: "
+ "logical blocks)\n"
+ " (def: 0 which implies no limit)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) "
+ "command (SBC-3 and\nSBC-4). The --element-id=EI and the "
+ "--scan-len=SL fields are only active\non the 32 byte cdb "
+ "variant. If --inhex=FN is given then contents of FN is\n"
+ "assumed to be a response to this command.\n"
+ );
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Decodes given LBA status descriptor passing back the starting LBA,
+ * the number of blocks and returns the provisioning status, -1 for error.
+ */
+static int
+decode_lba_status_desc(const uint8_t * bp, uint64_t * slbap,
+ uint32_t * blocksp, uint8_t * add_statusp)
+{
+ uint32_t blocks;
+ uint64_t ull;
+
+ if (NULL == bp)
+ return -1;
+ ull = sg_get_unaligned_be64(bp + 0);
+ blocks = sg_get_unaligned_be32(bp + 8);
+ if (slbap)
+ *slbap = ull;
+ if (blocksp)
+ *blocksp = blocks;
+ if (add_statusp)
+ *add_statusp = bp[13];
+ return bp[12] & 0xf;
+}
+
+static char *
+get_prov_status_str(int ps, char * b, int blen)
+{
+ switch (ps) {
+ case 0:
+ sg_scnpr(b, blen, "mapped (or unknown)");
+ break;
+ case 1:
+ sg_scnpr(b, blen, "deallocated");
+ break;
+ case 2:
+ sg_scnpr(b, blen, "anchored");
+ break;
+ case 3:
+ sg_scnpr(b, blen, "mapped"); /* sbc4r12 */
+ break;
+ case 4:
+ sg_scnpr(b, blen, "unknown"); /* sbc4r12 */
+ break;
+ default:
+ sg_scnpr(b, blen, "unknown provisioning status: %d", ps);
+ break;
+ }
+ return b;
+}
+
+static char *
+get_pr_status_str(int as, char * b, int blen)
+{
+ switch (as) {
+ case 0:
+ sg_scnpr(b, blen, "%s", "");
+ break;
+ case 1:
+ sg_scnpr(b, blen, "may contain unrecovered errors");
+ break;
+ default:
+ sg_scnpr(b, blen, "unknown additional status: %d", as);
+ break;
+ }
+ return b;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_16 = false;
+ bool do_32 = false;
+ bool do_raw = false;
+ bool no_final_msg = false;
+ bool o_readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int k, j, res, c, n, rlen, num_descs, completion_cond, in_len;
+ int sg_fd = -1;
+ int blockhex = 0;
+ int do_brief = 0;
+ int do_hex = 0;
+ int ret = 0;
+ int maxlen = DEF_GLBAS_BUFF_LEN;
+ int rt = 0;
+ int verbose = 0;
+ uint8_t add_status = 0; /* keep gcc quiet */
+ uint64_t d_lba = 0;
+ uint32_t d_blocks = 0;
+ uint32_t element_id = 0;
+ uint32_t scan_len = 0;
+ int64_t ll;
+ uint64_t lba = 0;
+ const char * device_name = NULL;
+ const char * in_fn = NULL;
+ const uint8_t * bp;
+ uint8_t * glbasBuffp = glbasFixedBuff;
+ uint8_t * free_glbasBuffp = NULL;
+ sgj_opaque_p jop = NULL;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jap = NULL;
+ sgj_state json_st SG_C_CPP_ZERO_INIT;
+ sgj_state * jsp = &json_st;
+ char b[144];
+ static const size_t blen = sizeof(b);
+ static const char * prov_stat_s = "Provisoning status";
+ static const char * add_stat_s = "Additional status";
+ static const char * compl_cond_s = "Completion condition";
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bBe:hi:j::Hl:m:rRs:St:TvV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ ++do_brief;
+ break;
+ case 'B':
+ ++blockhex;
+ break;
+ case 'e':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--element-id'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ element_id = (uint32_t)ll;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ in_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&json_st, optarg)) {
+ int bad_char = json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba = (uint64_t)ll;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_GLBAS_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_GLBAS_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == maxlen)
+ maxlen = DEF_GLBAS_BUFF_LEN;
+ else if (maxlen < MIN_MAXLEN) {
+ pr2serr("Warning: --maxlen=LEN less than %d ignored\n",
+ MIN_MAXLEN);
+ maxlen = DEF_GLBAS_BUFF_LEN;
+ }
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 's':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--scan-len'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ scan_len = (uint32_t)ll;
+ break;
+ case 'S':
+ do_16 = true;
+ break;
+ case 't':
+ rt = sg_get_num_nomult(optarg);
+ if ((rt < 0) || (rt > 255)) {
+ pr2serr("'--report-type=RT' should be between 0 and 255 "
+ "(inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'T':
+ do_32 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+ if (jsp->pr_as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (maxlen > DEF_GLBAS_BUFF_LEN) {
+ glbasBuffp = (uint8_t *)sg_memalign(maxlen, 0, &free_glbasBuffp,
+ verbose > 3);
+ if (NULL == glbasBuffp) {
+ pr2serr("unable to allocate %d bytes on heap\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ }
+ if (device_name && in_fn) {
+ pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
+ "not both\n");
+ device_name = NULL;
+ }
+ if (NULL == device_name) {
+ if (in_fn) {
+ if ((ret = sg_f2hex_arr(in_fn, do_raw, false, glbasBuffp,
+ &in_len, maxlen))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
+ no_final_msg = true;
+ pr2serr("... decode what we have, --maxlen=%d needs to "
+ "be increased\n", maxlen);
+ } else
+ goto fini;
+ }
+ if (verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (do_raw)
+ do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n",
+ in_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ goto start_response;
+ } else {
+ pr2serr("missing device name!\n\n");
+ usage();
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto fini;
+ }
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ }
+ if (do_16 && do_32) {
+ pr2serr("both --16 and --32 given, choose --16\n");
+ do_32 = false;
+ } else if ((! do_16) && (! do_32)) {
+ if (verbose > 3)
+ pr2serr("choosing --16\n");
+ do_16 = true;
+ }
+ if (do_16) {
+ if (element_id != 0)
+ pr2serr("Warning: --element_id= ignored with 16 byte cdb\n");
+ if (scan_len != 0)
+ pr2serr("Warning: --scan_len= ignored with 16 byte cdb\n");
+ }
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ pr2serr("open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ res = 0;
+ if (do_16)
+ res = sg_ll_get_lba_status16(sg_fd, lba, rt, glbasBuffp, maxlen, true,
+ verbose);
+ else if (do_32) /* keep analyser happy since do_32 must be true */
+ res = sg_ll_get_lba_status32(sg_fd, lba, scan_len, element_id, rt,
+ glbasBuffp, maxlen, true, verbose);
+
+ ret = res;
+ if (res)
+ goto error;
+
+start_response:
+ /* in sbc3r25 offset for calculating the 'parameter data length'
+ * (rlen variable below) was reduced from 8 to 4. */
+ if (maxlen >= 4)
+ rlen = sg_get_unaligned_be32(glbasBuffp + 0) + 4;
+ else
+ rlen = maxlen;
+ k = (rlen > maxlen) ? maxlen : rlen;
+ if (do_raw) {
+ dStrRaw((const char *)glbasBuffp, k);
+ goto fini;
+ }
+ if (do_hex) {
+ if (do_hex > 2)
+ hex2stdout(glbasBuffp, k, -1);
+ else
+ hex2stdout(glbasBuffp, k, (2 == do_hex) ? 0 : 1);
+ goto fini;
+ }
+ if (maxlen < 4) {
+ if (verbose)
+ pr2serr("Exiting because allocation length (maxlen) less "
+ "than 4\n");
+ goto fini;
+ }
+ if ((verbose > 1) || (verbose && (rlen > maxlen))) {
+ pr2serr("response length %d bytes\n", rlen);
+ if (rlen > maxlen)
+ pr2serr(" ... which is greater than maxlen (allocation "
+ "length %d), truncation\n", maxlen);
+ }
+ if (rlen > maxlen)
+ rlen = maxlen;
+
+ if (do_brief > 1) {
+ if (rlen > DEF_GLBAS_BUFF_LEN) {
+ pr2serr("Need maxlen and response length to be at least %d, "
+ "have %d bytes\n", DEF_GLBAS_BUFF_LEN, rlen);
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ res = decode_lba_status_desc(glbasBuffp + 8, &d_lba, &d_blocks,
+ &add_status);
+ if ((res < 0) || (res > 15)) {
+ pr2serr("first LBA status descriptor returned %d ??\n", res);
+ ret = SG_LIB_LOGIC_ERROR;
+ goto fini;
+ }
+ if ((lba < d_lba) || (lba >= (d_lba + d_blocks))) {
+ pr2serr("given LBA not in range of first descriptor:\n"
+ " descriptor LBA: 0x");
+ for (j = 0; j < 8; ++j)
+ pr2serr("%02x", glbasBuffp[8 + j]);
+ pr2serr(" blocks: 0x%x p_status: %d add_status: 0x%x\n",
+ (unsigned int)d_blocks, res,
+ (unsigned int)add_status);
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ sgj_pr_hr(jsp,"p_status: %d add_status: 0x%x\n", res,
+ (unsigned int)add_status);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_i(jsp, jop, prov_stat_s, res);
+ sgj_js_nv_i(jsp, jop, add_stat_s, add_status);
+ }
+ goto fini;
+ }
+
+ if (rlen < 24) {
+ sgj_pr_hr(jsp, "No complete LBA status descriptors available\n");
+ goto fini;
+ }
+ num_descs = (rlen - 8) / 16;
+ completion_cond = (*(glbasBuffp + 7) >> 1) & 7; /* added sbc4r14 */
+ if (do_brief)
+ sgj_haj_vi(jsp, jop, 0, compl_cond_s, SGJ_SEP_EQUAL_NO_SPACE,
+ completion_cond, true);
+ else {
+ switch (completion_cond) {
+ case 0:
+ snprintf(b, blen, "No indication of the completion condition");
+ break;
+ case 1:
+ snprintf(b, blen, "Command completed due to meeting allocation "
+ "length");
+ break;
+ case 2:
+ snprintf(b, blen, "Command completed due to meeting scan length");
+ break;
+ case 3:
+ snprintf(b, blen, "Command completed due to meeting capacity of "
+ "medium");
+ break;
+ default:
+ snprintf(b, blen, "Command completion is reserved [%d]",
+ completion_cond);
+ break;
+ }
+ sgj_pr_hr(jsp, "%s\n", b);
+ sgj_js_nv_istr(jsp, jop, compl_cond_s, completion_cond,
+ NULL /* "meaning" */, b);
+ }
+ sgj_haj_vi(jsp, jop, 0, "RTP", SGJ_SEP_EQUAL_NO_SPACE,
+ *(glbasBuffp + 7) & 0x1, true); /* added sbc4r12 */
+ if (verbose)
+ pr2serr("%d complete LBA status descriptors found\n", num_descs);
+ if (jsp->pr_as_json)
+ jap = sgj_named_subarray_r(jsp, jop, "lba_status_descriptor");
+
+ for (bp = glbasBuffp + 8, k = 0; k < num_descs; bp += 16, ++k) {
+ res = decode_lba_status_desc(bp, &d_lba, &d_blocks, &add_status);
+ if ((res < 0) || (res > 15))
+ pr2serr("descriptor %d: bad LBA status descriptor returned "
+ "%d\n", k + 1, res);
+ if (jsp->pr_as_json)
+ jo2p = sgj_new_unattached_object_r(jsp);
+ if (do_brief) {
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, "0x");
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", bp[j]);
+ if ((0 == blockhex) || (1 == (blockhex % 2)))
+ n += sg_scnpr(b + n, blen - n, " 0x%x %d %d",
+ (unsigned int)d_blocks, res, add_status);
+ else
+ n += sg_scnpr(b + n, blen - n, " %u %d %d",
+ (unsigned int)d_blocks, res, add_status);
+ sgj_pr_hr(jsp, "%s\n", b);
+ sgj_js_nv_ihex(jsp, jo2p, "lba", d_lba);
+ sgj_js_nv_ihex(jsp, jo2p, "blocks", d_blocks);
+ sgj_js_nv_i(jsp, jo2p, prov_stat_s, res);
+ sgj_js_nv_i(jsp, jo2p, add_stat_s, add_status);
+ } else {
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihex(jsp, jo2p, "lba", d_lba);
+ sgj_js_nv_ihex(jsp, jo2p, "blocks", d_blocks);
+ sgj_js_nv_istr(jsp, jo2p, prov_stat_s, res, NULL,
+ get_prov_status_str(res, b, blen));
+ sgj_js_nv_istr(jsp, jo2p, add_stat_s, add_status, NULL,
+ get_pr_status_str(add_status, b, blen));
+ } else {
+ char d[64];
+
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, "[%d] LBA: 0x", k + 1);
+ for (j = 0; j < 8; ++j)
+ n += sg_scnpr(b + n, blen - n, "%02x", bp[j]);
+ if (1 == (blockhex % 2)) {
+
+ snprintf(d, sizeof(d), "0x%x", d_blocks);
+ n += sg_scnpr(b + n, blen - n, " blocks: %10s", d);
+ } else
+ n += sg_scnpr(b + n, blen - n, " blocks: %10u",
+ (unsigned int)d_blocks);
+ get_prov_status_str(res, d, sizeof(d));
+ n += sg_scnpr(b + n, blen - n, " %s", d);
+ get_pr_status_str(add_status, d, sizeof(d));
+ if (strlen(d) > 0)
+ n += sg_scnpr(b + n, blen - n, " [%s]", d);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if ((num_descs * 16) + 8 < rlen)
+ pr2serr("incomplete trailing LBA status descriptors found\n");
+ goto fini;
+
+error:
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Get LBA Status command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("Get LBA Status command: bad field in cdb\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Get LBA Status command: %s\n", b);
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (free_glbasBuffp)
+ free(free_glbasBuffp);
+ if ((0 == verbose) && (! no_final_msg)) {
+ if (! sg_if_can2stderr("sg_get_lba_status failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (jsp->pr_as_json) {
+ if (0 == do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
diff --git a/src/sg_ident.c b/src/sg_ident.c
new file mode 100644
index 00000000..2bf00bb8
--- /dev/null
+++ b/src/sg_ident.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2005-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues these SCSI commands: REPORT IDENTIFYING INFORMATION
+ * and SET IDENTIFYING INFORMATION. These commands were called REPORT
+ * DEVICE IDENTIFIER and SET DEVICE IDENTIFIER prior to spc4r07.
+ */
+
+static const char * version_str = "1.23 20180814";
+
+#define ME "sg_ident: "
+
+#define REPORT_ID_INFO_SANITY_LEN 512
+
+
+static struct option long_options[] = {
+ {"ascii", no_argument, 0, 'A'},
+ {"clear", no_argument, 0, 'C'},
+ {"help", no_argument, 0, 'h'},
+ {"itype", required_argument, 0, 'i'},
+ {"raw", no_argument, 0, 'r'},
+ {"set", no_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+decode_ii(const uint8_t * iip, int ii_len, int itype, bool ascii,
+ bool raw, int verbose)
+{
+ int k;
+
+ if (raw) {
+ if (ii_len > 0) {
+ int n;
+
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0)
+ perror("sg_set_binary_mode");
+#if 0
+ n = fwrite(iip, 1, ii_len, stdout);
+#else
+ n = write(STDOUT_FILENO, iip, ii_len);
+#endif
+ if (verbose && (n < 1))
+ pr2serr("unable to write to stdout\n");
+ }
+ return;
+ }
+ if (0x7f == itype) { /* list of available information types */
+ for (k = 0; k < (ii_len - 3); k += 4)
+ printf(" Information type: %d, Maximum information length: "
+ "%d bytes\n", iip[k], sg_get_unaligned_be16(iip + 2));
+ } else { /* single element */
+ if (verbose)
+ printf("Information:\n");
+ if (ii_len > 0) {
+ if (ascii)
+ printf("%.*s\n", ii_len, (const char *)iip);
+ else
+ hex2stdout(iip, ii_len, 0);
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ pr2serr("Usage: sg_ident [--ascii] [--clear] [--help] [--itype=IT] "
+ "[--raw] [--set]\n"
+ " [--verbose] [--version] DEVICE\n"
+ " where:\n"
+ " --ascii|-A report identifying information as ASCII "
+ "(or UTF8) string\n"
+ " --clear|-C clear (set to zero length) identifying "
+ "information\n"
+ " --help|-h print out usage message\n"
+ " --itype=IT|-i IT specify identifying information type "
+ "(def: 0)\n"
+ " --raw|-r output identifying information to "
+ "stdout\n"
+ " --set|-S invoke set identifying information with "
+ "data from stdin\n"
+ " --verbose|-v increase verbosity of output\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REPORT (or SET) IDENTIFYING INFORMATION "
+ "command. When no\noptions are given then REPORT IDENTIFYING "
+ "INFORMATION is sent and the\nresponse is output in "
+ "hexadecimal with ASCII to the right.\n");
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool ascii = false;
+ bool do_clear = false;
+ bool raw = false;
+ bool do_set = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, res, c, ii_len;
+ uint8_t rdi_buff[REPORT_ID_INFO_SANITY_LEN + 4];
+ char b[80];
+ uint8_t * bp = NULL;
+ int itype = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "AChi:rSvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'A':
+ ascii = true;
+ break;
+ case 'C':
+ do_clear = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ itype = sg_get_num(optarg);
+ if ((itype < 0) || (itype > 127)) {
+ pr2serr("argument to '--itype' should be in range 0 to 127\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r':
+ raw = true;
+ break;
+ case 'S':
+ do_set = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (do_set && do_clear) {
+ pr2serr("only one of '--clear' and '--set' can be given\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (ascii && raw) {
+ pr2serr("only one of '--ascii' and '--raw' can be given\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if ((do_set || do_clear) && (raw || ascii)) {
+ pr2serr("'--set' cannot be used with either '--ascii' or '--raw'\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ sg_fd = sg_cmds_open_device(device_name, false /* rw=false */, verbose);
+ if (sg_fd < 0) {
+ pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ return sg_convert_errno(-sg_fd);
+ }
+
+ memset(rdi_buff, 0x0, sizeof(rdi_buff));
+ if (do_set || do_clear) {
+ if (do_set) {
+ res = fread(rdi_buff, 1, REPORT_ID_INFO_SANITY_LEN + 2, stdin);
+ if (res <= 0) {
+ pr2serr("no data read from stdin; to clear identifying "
+ "information use '--clear' instead\n");
+ ret = -1;
+ goto err_out;
+ } else if (res > REPORT_ID_INFO_SANITY_LEN) {
+ pr2serr("SPC-4 limits information length to 512 bytes\n");
+ ret = -1;
+ goto err_out;
+ }
+ ii_len = res;
+ res = sg_ll_set_id_info(sg_fd, itype, rdi_buff, ii_len, true,
+ verbose);
+ } else /* do_clear */
+ res = sg_ll_set_id_info(sg_fd, itype, rdi_buff, 0, true, verbose);
+ if (res) {
+ ret = res;
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Set identifying information: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' for more information\n");
+ }
+ } else { /* do report identifying information */
+ res = sg_ll_report_id_info(sg_fd, itype, rdi_buff, 4, true, verbose);
+ if (0 == res) {
+ ii_len = sg_get_unaligned_be32(rdi_buff + 0);
+ if ((! raw) && (verbose > 0))
+ printf("Reported identifying information length = %d\n",
+ ii_len);
+ if (0 == ii_len) {
+ if (verbose > 1)
+ pr2serr(" This implies the device has an empty "
+ "information field\n");
+ goto err_out;
+ }
+ if (ii_len > REPORT_ID_INFO_SANITY_LEN) {
+ pr2serr(" That length (%d) seems too long for an "
+ "information\n", ii_len);
+ ret = -1;
+ goto err_out;
+ }
+ bp = rdi_buff;
+ res = sg_ll_report_id_info(sg_fd, itype, bp, ii_len + 4, true,
+ verbose);
+ if (0 == res) {
+ ii_len = sg_get_unaligned_be32(bp + 0);
+ decode_ii(bp + 4, ii_len, itype, ascii, raw, verbose);
+ } else
+ ret = res;
+ } else
+ ret = res;
+ if (ret) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Report identifying information: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' for more information\n");
+ }
+ }
+
+err_out:
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_ident failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_inq.c b/src/sg_inq.c
new file mode 100644
index 00000000..bcf5960e
--- /dev/null
+++ b/src/sg_inq.c
@@ -0,0 +1,4881 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program outputs information provided by a SCSI INQUIRY command.
+ * It is mainly based on the SCSI SPC-6 document at https://www.t10.org .
+ *
+ * Acknowledgment:
+ * - Martin Schwenke <martin at meltin dot net> added the raw switch and
+ * other improvements [20020814]
+ * - Lars Marowsky-Bree <lmb at suse dot de> contributed Unit Path Report
+ * VPD page decoding for EMC CLARiiON devices [20041016]
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef SG_LIB_LINUX
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/hdreg.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+#if (HAVE_NVME && (! IGNORE_NVME))
+#include "sg_pt_nvme.h"
+#endif
+
+#include "sg_vpd_common.h" /* for shared VPD page processing with sg_vpd */
+
+static const char * version_str = "2.31 20220915"; /* spc6r06, sbc5r03 */
+
+#define MY_NAME "sg_inq"
+
+/* INQUIRY notes:
+ * It is recommended that the initial allocation length given to a
+ * standard INQUIRY is 36 (bytes), especially if this is the first
+ * SCSI command sent to a logical unit. This is compliant with SCSI-2
+ * and another major operating system. There are devices out there
+ * that use one of the SCSI commands sets and lock up if they receive
+ * an allocation length other than 36. This technique is sometimes
+ * referred to as a "36 byte INQUIRY".
+ *
+ * A "standard" INQUIRY is one that has the EVPD and the CmdDt bits
+ * clear.
+ *
+ * When doing device discovery on a SCSI transport (e.g. bus scanning)
+ * the first SCSI command sent to a device should be a standard (36
+ * byte) INQUIRY.
+ *
+ * The allocation length field in the INQUIRY command was changed
+ * from 1 to 2 bytes in SPC-3, revision 9, 17 September 2002.
+ * Be careful using allocation lengths greater than 252 bytes, especially
+ * if the lower byte is 0x0 (e.g. a 512 byte allocation length may
+ * not be a good arbitrary choice (as 512 == 0x200) ).
+ *
+ * From SPC-3 revision 16 the CmdDt bit in an INQUIRY is obsolete. There
+ * is now a REPORT SUPPORTED OPERATION CODES command that yields similar
+ * information [MAINTENANCE IN, service action = 0xc]; see sg_opcodes.
+ */
+
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TESTING
+// #undef SG_SCSI_STRINGS
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TESTING
+
+#define VPD_NOPE_WANT_STD_INQ -2 /* request for standard inquiry */
+
+/* Vendor specific VPD pages (typically >= 0xc0) */
+#define VPD_UPR_EMC 0xc0
+#define VPD_RDAC_VERS 0xc2
+#define VPD_RDAC_VAC 0xc9
+
+/* values for selection one or more associations (2**vpd_assoc),
+ except _AS_IS */
+#define VPD_DI_SEL_LU 1
+#define VPD_DI_SEL_TPORT 2
+#define VPD_DI_SEL_TARGET 4
+#define VPD_DI_SEL_AS_IS 32
+
+#define DEF_ALLOC_LEN 252 /* highest 1 byte value that is modulo 4 */
+#define SAFE_STD_INQ_RESP_LEN 36
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+#define VPD_ATA_INFO_LEN 572
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+uint8_t * rsp_buff;
+
+static uint8_t * free_rsp_buff;
+static const int rsp_buff_sz = MX_ALLOC_LEN + 1;
+
+static char xtra_buff[MX_ALLOC_LEN + 1];
+static char usn_buff[MX_ALLOC_LEN + 1];
+
+static const char * find_version_descriptor_str(int value);
+static void decode_dev_ids(const char * leadin, uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static int vpd_decode(int sg_fd, struct opts_t * op, sgj_opaque_p jop,
+ int off);
+
+// Test define that will only work for Linux
+// #define HDIO_GET_IDENTITY 1
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+#include <sys/ioctl.h>
+
+static int try_ata_identify(int ata_fd, int do_hex, int do_raw,
+ int verbose);
+static void prepare_ata_identify(const struct opts_t * op, int inhex_len);
+#endif
+
+
+/* Note that this table is sorted by acronym */
+static struct svpd_values_name_t t10_vpd_pg[] = {
+ {VPD_AUTOMATION_DEV_SN, 0, 1, "adsn", "Automation device serial "
+ "number (SSC)"},
+ {VPD_ATA_INFO, 0, -1, "ai", "ATA information (SAT)"},
+ {VPD_BLOCK_DEV_CHARS, 0, 0, "bdc",
+ "Block device characteristics (SBC)"},
+ {VPD_BLOCK_DEV_C_EXTENS, 0, 0, "bdce", "Block device characteristics "
+ "extension (SBC)"},
+ {VPD_BLOCK_LIMITS, 0, 0, "bl", "Block limits (SBC)"},
+ {VPD_BLOCK_LIMITS_EXT, 0, 0, "ble", "Block limits extension (SBC)"},
+ {VPD_CFA_PROFILE_INFO, 0, 0, "cfa", "CFA profile information"},
+ {VPD_CON_POS_RANGE, 0, 0, "cpr", "Concurrent positioning ranges "
+ "(SBC)"},
+ {VPD_DEVICE_CONSTITUENTS, 0, -1, "dc", "Device constituents"},
+ {VPD_DEVICE_ID, 0, -1, "di", "Device identification"},
+#if 0 /* following found in sg_vpd */
+ {VPD_DEVICE_ID, VPD_DI_SEL_AS_IS, -1, "di_asis", "Like 'di' "
+ "but designators ordered as found"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_LU, -1, "di_lu", "Device identification, "
+ "lu only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TPORT, -1, "di_port", "Device "
+ "identification, target port only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TARGET, -1, "di_target", "Device "
+ "identification, target device only"},
+#endif
+ {VPD_EXT_INQ, 0, -1, "ei", "Extended inquiry data"},
+ {VPD_FORMAT_PRESETS, 0, 0, "fp", "Format presets"},
+ {VPD_LB_PROTECTION, 0, 0, "lbpro", "Logical block protection (SSC)"},
+ {VPD_LB_PROVISIONING, 0, 0, "lbpv", "Logical block provisioning "
+ "(SBC)"},
+ {VPD_MAN_ASS_SN, 0, 1, "mas", "Manufacturer assigned serial number (SSC)"},
+ {VPD_MAN_ASS_SN, 0, 0x12, "masa",
+ "Manufacturer assigned serial number (ADC)"},
+ {VPD_MAN_NET_ADDR, 0, -1, "mna", "Management network addresses"},
+ {VPD_MODE_PG_POLICY, 0, -1, "mpp", "Mode page policy"},
+ {VPD_POWER_CONDITION, 0, -1, "po", "Power condition"},/* "pc" in sg_vpd */
+ {VPD_POWER_CONSUMPTION, 0, -1, "psm", "Power consumption"},
+ {VPD_PROTO_LU, 0, -1, "pslu", "Protocol-specific logical unit "
+ "information"},
+ {VPD_PROTO_PORT, 0, -1, "pspo", "Protocol-specific port information"},
+ {VPD_REFERRALS, 0, 0, "ref", "Referrals (SBC)"},
+ {VPD_SA_DEV_CAP, 0, 1, "sad",
+ "Sequential access device capabilities (SSC)"},
+ {VPD_SUP_BLOCK_LENS, 0, 0, "sbl", "Supported block lengths and "
+ "protection types (SBC)"},
+ {VPD_SCSI_FEATURE_SETS, 0, -1, "sfs", "SCSI Feature sets"},
+ {VPD_SOFTW_INF_ID, 0, -1, "sii", "Software interface identification"},
+ {VPD_NOPE_WANT_STD_INQ, 0, -1, "sinq", "Standard inquiry data format"},
+ {VPD_UNIT_SERIAL_NUM, 0, -1, "sn", "Unit serial number"},
+ {VPD_SCSI_PORTS, 0, -1, "sp", "SCSI ports"},
+ {VPD_SUPPORTED_VPDS, 0, -1, "sv", "Supported VPD pages"},
+ {VPD_TA_SUPPORTED, 0, 1, "tas", "TapeAlert supported flags (SSC)"},
+ {VPD_3PARTY_COPY, 0, -1, "tpc", "Third party copy"},
+ {VPD_ZBC_DEV_CHARS, 0, 0, "zbdch", "Zoned block device "
+ "characteristics"},
+ {0, 0, 0, NULL, NULL},
+};
+
+/* Some alternate acronyms for T10 VPD pages (compatibility with sg_vpd) */
+static struct svpd_values_name_t alt_t10_vpd_pg[] = {
+ {VPD_NOPE_WANT_STD_INQ, 0, -1, "stdinq", "Standard inquiry data format"},
+ {VPD_POWER_CONDITION, 0, -1, "pc", "Power condition"},
+ {0, 0, 0, NULL, NULL},
+};
+
+static struct svpd_values_name_t vs_vpd_pg[] = {
+ /* Following are vendor specific */
+ {SG_NVME_VPD_NICR, 0, -1, "nicr",
+ "NVMe Identify Controller Response (sg3_utils)"},
+ {VPD_RDAC_VAC, 0, -1, "rdac_vac", "RDAC volume access control (RDAC)"},
+ {VPD_RDAC_VERS, 0, -1, "rdac_vers", "RDAC software version (RDAC)"},
+ {VPD_UPR_EMC, 0, -1, "upr", "Unit path report (EMC)"},
+ {0, 0, 0, NULL, NULL},
+};
+
+static struct option long_options[] = {
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ {"ata", no_argument, 0, 'a'},
+#endif
+ {"block", required_argument, 0, 'B'},
+ {"cmddt", no_argument, 0, 'c'},
+ {"descriptors", no_argument, 0, 'd'},
+ {"export", no_argument, 0, 'u'},
+ {"extended", no_argument, 0, 'x'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"id", no_argument, 0, 'i'},
+ {"inhex", required_argument, 0, 'I'},
+ {"len", required_argument, 0, 'l'},
+ {"long", no_argument, 0, 'L'},
+ {"maxlen", required_argument, 0, 'm'},
+#ifdef SG_SCSI_STRINGS
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+#endif
+ {"only", no_argument, 0, 'o'},
+ {"page", required_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"sinq_inraw", required_argument, 0, 'Q'},
+ {"sinq-inraw", required_argument, 0, 'Q'},
+ {"vendor", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"vpd", no_argument, 0, 'e'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+
+ pr2serr("Usage: sg_inq [--ata] [--block=0|1] [--cmddt] [--descriptors] "
+ "[--export]\n"
+ " [--extended] [--help] [--hex] [--id] "
+ "[--inhex=FN]\n"
+ " [--json[=JO]] [--len=LEN] [--long] "
+ "[--maxlen=LEN]\n"
+ " [--only] [--page=PG] [--raw] [--sinq_inraw=RFN] "
+ "[--vendor]\n"
+ " [--verbose] [--version] [--vpd] DEVICE\n"
+ " where:\n"
+ " --ata|-a treat DEVICE as (directly attached) ATA "
+ "device\n");
+#else
+ pr2serr("Usage: sg_inq [--block=0|1] [--cmddt] [--descriptors] "
+ "[--export]\n"
+ " [--extended] [--help] [--hex] [--id] "
+ "[--inhex=FN]\n"
+ " [--json[=JO]] [--len=LEN] [--long] "
+ "[--maxlen=LEN]\n"
+ " [--only] [--page=PG] [--raw] [--sinq_inraw=RFN] "
+ "[--verbose]\n"
+ " [--version] [--vpd] DEVICE\n"
+ " where:\n");
+#endif
+ pr2serr(" --block=0|1 0-> open(non-blocking); 1-> "
+ "open(blocking)\n"
+ " -B 0|1 (def: depends on OS; Linux pt: 0)\n"
+ " --cmddt|-c command support data mode (set opcode "
+ "with '--page=PG')\n"
+ " use twice for list of supported "
+ "commands; obsolete\n"
+ " --descriptors|-d fetch and decode version descriptors\n"
+ " --export|-u SCSI_IDENT_<assoc>_<type>=<ident> output "
+ "format.\n"
+ " Defaults to device id page (0x83) if --page "
+ "not given,\n"
+ " only supported for VPD pages 0x80 and 0x83\n"
+ " --extended|-E|-x decode extended INQUIRY data VPD page "
+ "(0x86)\n"
+ " --force|-f skip VPD page 0 check; directly fetch "
+ "requested page\n"
+ " --help|-h print usage message then exit\n"
+ " --hex|-H output response in hex\n"
+ " --id|-i decode device identification VPD page "
+ "(0x83)\n"
+ " --inhex=FN|-I FN read ASCII hex from file FN instead of "
+ "DEVICE;\n"
+ " if used with --raw then read binary "
+ "from FN\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --len=LEN|-l LEN requested response length (def: 0 "
+ "-> fetch 36\n"
+ " bytes first, then fetch again as "
+ "indicated)\n"
+ " --long|-L supply extra information on NVMe devices\n"
+ " --maxlen=LEN|-m LEN same as '--len='\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --only|-o for std inquiry do not fetch serial number "
+ "vpd page;\n"
+ " for NVMe device only do Identify "
+ "controller\n"
+ " --page=PG|-p PG Vital Product Data (VPD) page number "
+ "or\n"
+ " abbreviation (opcode number if "
+ "'--cmddt' given)\n"
+ " --raw|-r output response in binary (to stdout)\n"
+ " --sinq_inraw=RFN|-Q RFN read raw (binary) standard "
+ "INQUIRY\n"
+ " response from the RFN filename\n"
+ " --vendor|-s show vendor specific fields in std "
+ "inquiry\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --vpd|-e vital product data (set page with "
+ "'--page=PG')\n\n"
+ "Sends a SCSI INQUIRY command to the DEVICE and decodes the "
+ "response.\nAlternatively it decodes the INQUIRY response held "
+ "in file FN. If no\noptions given then it sends a 'standard' "
+ "INQUIRY command to DEVICE. Can\nlist VPD pages with '--vpd' or "
+ "'--page=PG' option.\n");
+}
+
+#ifdef SG_SCSI_STRINGS
+static void
+usage_old()
+{
+#ifdef SG_LIB_LINUX
+ pr2serr("Usage: sg_inq [-a] [-A] [-b] [-B=0|1] [-c] [-cl] [-d] [-e] "
+ "[-h]\n"
+ " [-H] [-i] [-I=FN] [-j[=JO]] [-l=LEN] [-L] [-m] "
+ "[-M]\n"
+ " [-o] [-p=VPD_PG] [-P] [-r] [-s] [-u] [-U] [-v] "
+ "[-V]\n"
+ " [-x] [-36] [-?] DEVICE\n"
+ " where:\n"
+ " -a decode ATA information VPD page (0x89)\n"
+ " -A treat <device> as (directly attached) ATA device\n");
+#else
+ pr2serr("Usage: sg_inq [-a] [-b] [-B 0|1] [-c] [-cl] [-d] [-e] [-h] "
+ "[-H]\n"
+ " [-i] [-l=LEN] [-L] [-m] [-M] [-o] "
+ "[-p=VPD_PG]\n"
+ " [-P] [-r] [-s] [-u] [-v] [-V] [-x] [-36] "
+ "[-?]\n"
+ " DEVICE\n"
+ " where:\n"
+ " -a decode ATA information VPD page (0x89)\n");
+
+#endif /* SG_LIB_LINUX */
+ pr2serr(" -b decode Block limits VPD page (0xb0) (SBC)\n"
+ " -B=0|1 0-> open(non-blocking); 1->open(blocking)\n"
+ " -c set CmdDt mode (use -o for opcode) [obsolete]\n"
+ " -cl list supported commands using CmdDt mode [obsolete]\n"
+ " -d decode: version descriptors or VPD page\n"
+ " -e set VPD mode (use -p for page code)\n"
+ " -h output in hex (ASCII to the right)\n"
+ " -H output in hex (ASCII to the right) [same as '-h']\n"
+ " -i decode device identification VPD page (0x83)\n"
+ " -I=FN use ASCII hex in file FN instead of DEVICE\n"
+ " -j[=JO] output in JSON instead of human readable "
+ "text.\n"
+ " -l=LEN requested response length (def: 0 "
+ "-> fetch 36\n"
+ " bytes first, then fetch again as "
+ "indicated)\n"
+ " -L supply extra information on NVMe devices\n"
+ " -m decode management network addresses VPD page "
+ "(0x85)\n"
+ " -M decode mode page policy VPD page (0x87)\n"
+ " -N|--new use new interface\n"
+ " -o for std inquiry only do that, not serial number vpd "
+ "page\n"
+ " -p=VPD_PG vpd page code in hex (def: 0)\n"
+ " -P decode Unit Path Report VPD page (0xc0) (EMC)\n"
+ " -r output response in binary ('-rr': output for hdparm)\n"
+ " -s decode SCSI Ports VPD page (0x88)\n"
+ " -u SCSI_IDENT_<assoc>_<type>=<ident> output format\n"
+ " -v verbose (output cdb and, if non-zero, resid)\n"
+ " -V output version string\n"
+ " -x decode extended INQUIRY data VPD page (0x86)\n"
+ " -36 perform standard INQUIRY with a 36 byte response\n"
+ " -? output this usage message\n\n"
+ "If no options given then sends a standard SCSI INQUIRY "
+ "command and\ndecodes the response.\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+#else /* SG_SCSI_STRINGS */
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op) { } /* suppress warning */
+ usage();
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+/* Processes command line options according to new option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+
+ while (1) {
+ int option_index = 0;
+
+#ifdef SG_LIB_LINUX
+#ifdef SG_SCSI_STRINGS
+ c = getopt_long(argc, argv, "aB:cdeEfhHiI:j::l:Lm:M:NoOp:Q:rsuvVx",
+ long_options, &option_index);
+#else
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:op:Q:rsuvVx",
+ long_options, &option_index);
+#endif /* SG_SCSI_STRINGS */
+#else /* SG_LIB_LINUX */
+#ifdef SG_SCSI_STRINGS
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:NoOp:Q:rsuvVx",
+ long_options, &option_index);
+#else
+ c = getopt_long(argc, argv, "B:cdeEfhHiI:j::l:Lm:M:op:Q:rsuvVx",
+ long_options, &option_index);
+#endif /* SG_SCSI_STRINGS */
+#endif /* SG_LIB_LINUX */
+ if (c == -1)
+ break;
+
+ switch (c) {
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ case 'a':
+ op->do_ata = true;
+ break;
+#endif
+ case 'B':
+ if ('-' == optarg[0])
+ n = -1;
+ else {
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 1)) {
+ pr2serr("bad argument to '--block=' want 0 or 1\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ op->do_block = n;
+ break;
+ case 'c':
+ ++op->do_cmddt;
+ break;
+ case 'd':
+ op->do_descriptors = true;
+ break;
+ case 'e':
+ op->do_vpd = true;
+ break;
+ case 'E': /* --extended */
+ case 'x':
+ op->do_decode = true;
+ op->do_vpd = true;
+ op->vpd_pn = VPD_EXT_INQ;
+ op->page_given = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ ++op->do_help;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'o':
+ op->do_only = true;
+ break;
+ case '?':
+ if (! op->do_help)
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->do_decode = true;
+ op->do_vpd = true;
+ op->vpd_pn = VPD_DEVICE_ID;
+ op->page_given = true;
+ break;
+ case 'I':
+ op->inhex_fn = optarg;
+ break;
+ case 'l':
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 65532)) {
+ pr2serr("bad argument to '--len='\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '--maxlen=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ break;
+ case 'M':
+ if (op->vend_prod) {
+ pr2serr("only one '--vendor=' option permitted\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->vend_prod = optarg;
+ break;
+ case 'L':
+ ++op->do_long;
+ break;
+#ifdef SG_SCSI_STRINGS
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+#endif
+ case 'p':
+ op->page_str = optarg;
+ op->page_given = true;
+ break;
+ case 'Q':
+ op->sinq_inraw_fn = optarg;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 's':
+ ++op->do_vendor;
+ break;
+ case 'u':
+ op->do_export = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+#ifdef SG_SCSI_STRINGS
+/* Processes command line options according to old option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num, n;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case '3':
+ if ('6' == *(cp + 1)) {
+ op->maxlen = 36;
+ --plen;
+ ++cp;
+ } else
+ jmp_out = true;
+ break;
+ case 'a':
+ op->vpd_pn = VPD_ATA_INFO;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+#ifdef SG_LIB_LINUX
+ case 'A':
+ op->do_ata = true;
+ break;
+#endif
+ case 'b':
+ op->vpd_pn = VPD_BLOCK_LIMITS;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'c':
+ ++op->do_cmddt;
+ if ('l' == *(cp + 1)) {
+ ++op->do_cmddt;
+ --plen;
+ ++cp;
+ }
+ break;
+ case 'd':
+ op->do_descriptors = true;
+ op->do_decode = true;
+ break;
+ case 'e':
+ op->do_vpd = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->vpd_pn = VPD_DEVICE_ID;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'L':
+ ++op->do_long;
+ break;
+ case 'm':
+ op->vpd_pn = VPD_MAN_NET_ADDR;
+ op->do_vpd = true;
+ ++op->num_pages;
+ op->page_given = true;
+ break;
+ case 'M':
+ op->vpd_pn = VPD_MODE_PG_POLICY;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'o':
+ op->do_only = true;
+ break;
+ case 'O':
+ break;
+ case 'P':
+ op->vpd_pn = VPD_UPR_EMC;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 's':
+ op->vpd_pn = VPD_SCSI_PORTS;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case 'u':
+ op->do_export = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'x':
+ op->vpd_pn = VPD_EXT_INQ;
+ op->do_vpd = true;
+ op->page_given = true;
+ ++op->num_pages;
+ break;
+ case '?':
+ if (! op->do_help)
+ ++op->do_help;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ else if (0 == strncmp("B=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 0) || (n > 1)) {
+ pr2serr("'B=' option expects 0 or 1\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_block = n;
+ } else if (0 == strncmp("I=", cp, 2))
+ op->inhex_fn = cp + 2;
+ else if ('j' == *cp) { /* handle either '-j' or '-j=<JO>' */
+ const char * c2p = (('=' == *(cp + 1)) ? cp + 2 : NULL);
+
+ if (! sgj_init_state(&op->json_st, c2p)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp("l=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 1)) {
+ pr2serr("Inappropriate value after 'l=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (n > MX_ALLOC_LEN) {
+ pr2serr("value after 'l=' option too large\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '-l=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ } else if (0 == strncmp("p=", cp, 2)) {
+ op->page_str = cp + 2;
+ op->page_given = true;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Process command line options. First check using new option format unless
+ * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the
+ * old option format to be checked first. Both new and old format can be
+ * countermanded by a '-O' and '-N' options respectively. As soon as either
+ * of these options is detected (when processing the other format), processing
+ * stops and is restarted using the other format. Clear? */
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+#else /* SG_SCSI_STRINGS */
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ return new_parse_cmd_line(op, argc, argv);
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+
+static const struct svpd_values_name_t *
+sdp_find_vpd_by_acron(const char * ap)
+{
+ const struct svpd_values_name_t * vnp;
+
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ for (vnp = alt_t10_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ return NULL;
+}
+
+static void
+enumerate_vpds()
+{
+ const struct svpd_values_name_t * vnp;
+
+ printf("T10 defined VPD pages:\n");
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->name) {
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+ printf("Vendor specific VPD pages:\n");
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->name) {
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Strip initial and trailing whitespaces; convert one or repeated
+ * whitespaces to a single "_"; convert non-printable characters to "."
+ * and if there are no valid (i.e. printable) characters return 0.
+ * Process 'str' in place (i.e. it's input and output) and return the
+ * length of the output, excluding the trailing '\0'. To cover any
+ * potential unicode string an intermediate zero is skipped; two
+ * consecutive zeroes indicate a string termination.
+ */
+static int
+encode_whitespaces(uint8_t *str, int inlen)
+{
+ int k, res;
+ int j;
+ bool valid = false;
+ int outlen = inlen, zeroes = 0;
+
+ /* Skip initial whitespaces */
+ for (j = 0; (j < inlen) && isblank(str[j]); ++j)
+ ;
+ if (j < inlen) {
+ /* Skip possible unicode prefix characters */
+ for ( ; (j < inlen) && (str[j] < 0x20); ++j)
+ ;
+ }
+ k = j;
+ /* Strip trailing whitespaces */
+ while ((outlen > k) &&
+ (isblank(str[outlen - 1]) || ('\0' == str[outlen - 1]))) {
+ str[outlen - 1] = '\0';
+ outlen--;
+ }
+ for (res = 0; k < outlen; ++k) {
+ if (isblank(str[k])) {
+ if ((res > 0) && ('_' != str[res - 1])) {
+ str[res++] = '_';
+ valid = true;
+ }
+ zeroes = 0;
+ } else if (! isprint(str[k])) {
+ if (str[k] == 0x00) {
+ /* Stop on more than one consecutive zero */
+ if (zeroes)
+ break;
+ zeroes++;
+ continue;
+ }
+ str[res++] = '.';
+ zeroes = 0;
+ } else {
+ str[res++] = str[k];
+ valid = true;
+ zeroes = 0;
+ }
+ }
+ if (! valid)
+ res = 0;
+ if (res < inlen)
+ str[res] = '\0';
+ return res;
+}
+
+static int
+encode_unicode(uint8_t *str, int inlen)
+{
+ int k = 0, res;
+ int zeroes = 0;
+
+ for (res = 0; k < inlen; ++k) {
+ if (str[k] == 0x00) {
+ if (zeroes) {
+ str[res++] = '\0';
+ break;
+ }
+ zeroes++;
+ } else {
+ zeroes = 0;
+ if (isprint(str[k]))
+ str[res++] = str[k];
+ else
+ str[res++] = ' ';
+ }
+ }
+
+ return res;
+}
+
+static int
+encode_string(char *out, const uint8_t *in, int inlen)
+{
+ int i, j = 0;
+
+ for (i = 0; (i < inlen); ++i) {
+ if (isblank(in[i]) || !isprint(in[i])) {
+ sprintf(&out[j], "\\x%02x", in[i]);
+ j += 4;
+ } else {
+ out[j] = in[i];
+ j++;
+ }
+ }
+ out[j] = '\0';
+ return j;
+}
+
+static const struct svpd_values_name_t *
+get_vpd_page_info(int vpd_page_num, int dev_pdt)
+{
+ int decay_pdt;
+ const struct svpd_values_name_t * vnp;
+ const struct svpd_values_name_t * prev_vnp;
+
+ if (vpd_page_num < 0xb0) { /* take T10 first match */
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ return vnp;
+ }
+ return NULL;
+ } else if (vpd_page_num < 0xc0) {
+ for (vnp = t10_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ if (NULL == vnp->acron)
+ return NULL;
+ if (vnp->pdt == dev_pdt) /* exact match */
+ return vnp;
+ prev_vnp = vnp;
+
+ for (++vnp; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ decay_pdt = sg_lib_pdt_decay(dev_pdt);
+ if (NULL == vnp->acron) {
+ if (decay_pdt == prev_vnp->pdt)
+ return prev_vnp;
+ return NULL;
+ }
+ if ((vnp->pdt == dev_pdt) || (vnp->pdt == decay_pdt))
+ return vnp;
+ if (decay_pdt == prev_vnp->pdt)
+ return prev_vnp;
+
+ for (++vnp; vnp->acron; ++vnp) {
+ if (vnp->value == vpd_page_num)
+ break;
+ }
+ if (NULL == vnp->acron)
+ return NULL;
+ if ((vnp->pdt == dev_pdt) || (vnp->pdt == decay_pdt))
+ return vnp;
+ return NULL; /* give up */
+ } else { /* vendor specific: vpd >= 0xc0 */
+ for (vnp = vs_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->pdt == dev_pdt)
+ return vnp;
+ }
+ return NULL;
+ }
+}
+
+static int
+svpd_inhex_decode_all(struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, res, pn;
+ int max_pn = 255;
+ int bump, off;
+ int in_len = op->maxlen;
+ int prev_pn = -1;
+ sgj_state * jsp = &op->json_st;
+ uint8_t vpd0_buff[512];
+ uint8_t * rp = vpd0_buff;
+
+ if (op->vpd_pn > 0)
+ max_pn = op->vpd_pn;
+
+ res = 0;
+ if (op->page_given && (VPD_NOPE_WANT_STD_INQ == op->vpd_pn))
+ return vpd_decode(-1, op, jop, 0);
+
+ for (k = 0, off = 0; off < in_len; ++k, off += bump) {
+ rp = rsp_buff + off;
+ pn = rp[1];
+ bump = sg_get_unaligned_be16(rp + 2) + 4;
+ if ((off + bump) > in_len) {
+ pr2serr("%s: page 0x%x size (%d) exceeds buffer\n", __func__,
+ pn, bump);
+ bump = in_len - off;
+ }
+ if (op->page_given && (pn != op->vpd_pn))
+ continue;
+ if (pn <= prev_pn) {
+ pr2serr("%s: prev_pn=0x%x, this pn=0x%x, not ascending so "
+ "exit\n", __func__, prev_pn, pn);
+ break;
+ }
+ prev_pn = pn;
+ op->vpd_pn = pn;
+ if (pn > max_pn) {
+ if (op->verbose > 2)
+ pr2serr("%s: skipping as this pn=0x%x exceeds "
+ "max_pn=0x%x\n", __func__, pn, max_pn);
+ continue;
+ }
+ if (op->do_long) {
+ if (jsp->pr_as_json)
+ sgj_pr_hr(jsp, "[0x%x]:\n", pn);
+ else
+ sgj_pr_hr(jsp, "[0x%x] ", pn);
+ }
+
+ res = vpd_decode(-1, op, jop, off);
+ if (SG_LIB_CAT_OTHER == res) {
+ if (op->verbose)
+ pr2serr("Can't decode VPD page=0x%x\n", pn);
+ }
+ }
+ return res;
+}
+
+static void
+decode_supported_vpd_4inq(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int vpd, k, rlen, pdt;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const struct svpd_values_name_t * vnp;
+ char b[64];
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (len < 4) {
+ pr2serr("Supported VPD pages VPD page length too short=%d\n", len);
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ rlen = buff[3] + 4;
+ if (rlen > len)
+ pr2serr("Supported VPD pages VPD page truncated, indicates %d, got "
+ "%d\n", rlen, len);
+ else
+ len = rlen;
+ sgj_pr_hr(jsp, " Supported VPD pages:\n");
+ for (k = 0; k < len - 4; ++k) {
+ vpd = buff[4 + k];
+ snprintf(b, sizeof(b), "0x%x", vpd);
+ vnp = get_vpd_page_info(vpd, pdt);
+ if (jsp->pr_as_json && jap) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_i(jsp, jo2p, "i", vpd);
+ sgj_js_nv_s(jsp, jo2p, "hex", b + 2);
+ sgj_js_nv_s(jsp, jo2p, "name", vnp ? vnp->name : "unknown");
+ sgj_js_nv_s(jsp, jo2p, "acronym", vnp ? vnp->acron : "unknown");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if (vnp)
+ sgj_pr_hr(jsp, " %s\t%s\n", b, vnp->name);
+ else
+ sgj_pr_hr(jsp, " %s\n", b);
+ }
+}
+
+static bool
+vpd_page_is_supported(uint8_t * vpd_pg0, int v0_len, int pg_num, int vb)
+{
+ int k, rlen;
+
+ if (v0_len < 4)
+ return false;
+
+ rlen = vpd_pg0[3] + 4;
+ if (rlen > v0_len)
+ pr2serr("Supported VPD pages VPD page truncated, indicates %d, got "
+ "%d\n", rlen, v0_len);
+ else
+ v0_len = rlen;
+ if (vb > 1) {
+ pr2serr("Supported VPD pages, hex list: ");
+ hex2stderr(vpd_pg0 + 4, v0_len - 4, -1);
+ }
+ for (k = 4; k < v0_len; ++k) {
+ if(vpd_pg0[k] == pg_num)
+ return true;
+ }
+ return false;
+}
+
+/* ASCII Information VPD pages (page numbers: 0x1 to 0x7f) */
+static void
+decode_ascii_inf(uint8_t * buff, int len, struct opts_t * op)
+{
+ int al, k, bump;
+ uint8_t * bp;
+ uint8_t * p;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (len < 4) {
+ pr2serr("ASCII information VPD page length too short=%d\n", len);
+ return;
+ }
+ if (4 == len)
+ return;
+ al = buff[4];
+ if ((al + 5) > len)
+ al = len - 5;
+ for (k = 0, bp = buff + 5; k < al; k += bump, bp += bump) {
+ p = (uint8_t *)memchr(bp, 0, al - k);
+ if (! p) {
+ sgj_pr_hr(jsp, " %.*s\n", al - k, (const char *)bp);
+ break;
+ }
+ sgj_pr_hr(jsp, " %s\n", (const char *)bp);
+ bump = (p - bp) + 1;
+ }
+ bp = buff + 5 + al;
+ if (bp < (buff + len)) {
+ sgj_pr_hr(jsp, "Vendor specific information in hex:\n");
+ hex2stdout(bp, len - (al + 5), 0);
+ }
+}
+
+static void
+decode_id_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jap)
+{
+ if (len < 4) {
+ pr2serr("Device identification VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ decode_dev_ids("Device identification", buff + 4, len - 4, op, jap);
+}
+
+/* VPD_SCSI_PORTS 0x88 ["sp"] */
+static void
+decode_scsi_ports_vpd_4inq(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, rel_port, ip_tid_len, tpd_len;
+ uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+
+ if (len < 4) {
+ pr2serr("SCSI Ports VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex > 2) {
+ hex2stdout(buff, len, -1);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ rel_port = sg_get_unaligned_be16(bp + 2);
+ sgj_pr_hr(jsp, "Relative port=%d\n", rel_port);
+ sgj_js_nv_i(jsp, jo2p, "relative_port", rel_port);
+ ip_tid_len = sg_get_unaligned_be16(bp + 6);
+ bump = 8 + ip_tid_len;
+ if ((k + bump) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor "
+ "length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (ip_tid_len > 0) {
+ if (op->do_hex) {
+ printf(" Initiator port transport id:\n");
+ hex2stdout((bp + 8), ip_tid_len, no_ascii_4hex(op));
+ } else {
+ char b[1024];
+
+ sg_decode_transportid_str(" ", bp + 8, ip_tid_len,
+ true, sizeof(b), b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_s(jsp, jo2p, "initiator_port_transport_id", b);
+ sgj_pr_hr(jsp, "%s",
+ sg_decode_transportid_str(" ", bp + 8,
+ ip_tid_len, true, sizeof(b), b));
+ }
+ }
+ tpd_len = sg_get_unaligned_be16(bp + bump + 2);
+ if ((k + bump + tpd_len + 4) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor(tgt) "
+ "length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (tpd_len > 0) {
+ sgj_pr_hr(jsp, " Target port descriptor(s):\n");
+ if (op->do_hex)
+ hex2stdout(bp + bump + 4, tpd_len, no_ascii_4hex(op));
+ else {
+ sgj_opaque_p ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "target_port_descriptor_list");
+
+ decode_dev_ids("SCSI Ports", bp + bump + 4, tpd_len,
+ op, ja2p);
+ }
+ }
+ bump += tpd_len + 4;
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* These are target port, device server (i.e. target) and LU identifiers */
+static void
+decode_dev_ids(const char * leadin, uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap)
+{
+ int u, j, m, id_len, p_id, c_set, piv, assoc, desig_type, i_len;
+ int off, ci_off, c_id, d_id, naa, vsi, k, n;
+ uint64_t vsei, id_ext, ccc_id;
+ const uint8_t * bp;
+ const uint8_t * ip;
+ const char * cp;
+ sgj_state * jsp = &op->json_st;
+ char b[256];
+ char d[64];
+ static const int blen = sizeof(b);
+ static const int dlen = sizeof(d);
+
+ if (jsp->pr_as_json) {
+ int ret = filter_json_dev_ids(buff, len, -1, op, jap);
+
+ if (ret || (! jsp->pr_out_hr))
+ return;
+ }
+ if (buff[2] > 2) { /* SPC-3,4,5 buff[2] is upper byte of length */
+ /*
+ * Reference the 3rd byte of the first Identification descriptor
+ * of a page 83 reply to determine whether the reply is compliant
+ * with SCSI-2 or SPC-2/3 specifications. A zero value in the
+ * 3rd byte indicates an SPC-2/3 conforming reply ( the field is
+ * reserved ). This byte will be non-zero for a SCSI-2
+ * conforming page 83 reply from these EMC Symmetrix models since
+ * the 7th byte of the reply corresponds to the 4th and 5th
+ * nibbles of the 6-byte OUI for EMC, that is, 0x006048.
+ */
+ i_len = len;
+ ip = bp = buff;
+ c_set = 1;
+ assoc = 0;
+ piv = 0;
+ p_id = 0xf;
+ desig_type = 3;
+ j = 1;
+ off = 16;
+ sgj_pr_hr(jsp, " Pre-SPC descriptor, descriptor length: %d\n",
+ i_len);
+ goto decode;
+ }
+
+ for (j = 1, off = -1;
+ (u = sg_vpd_dev_id_iter(buff, len, &off, -1, -1, -1)) == 0;
+ ++j) {
+ bp = buff + off;
+ i_len = bp[3];
+ id_len = i_len + 4;
+ sgj_pr_hr(jsp, " Designation descriptor number %d, "
+ "descriptor length: %d\n", j, id_len);
+ if ((off + id_len) > len) {
+ pr2serr("%s VPD page error: designator length longer "
+ "than\n remaining response length=%d\n", leadin,
+ (len - off));
+ return;
+ }
+ ip = bp + 4;
+ p_id = ((bp[0] >> 4) & 0xf); /* protocol identifier */
+ c_set = (bp[0] & 0xf); /* code set */
+ piv = ((bp[1] & 0x80) ? 1 : 0); /* protocol identifier valid */
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ decode:
+ if (piv && ((1 == assoc) || (2 == assoc)))
+ sgj_pr_hr(jsp, " transport: %s\n",
+ sg_get_trans_proto_str(p_id, dlen, d));
+ n = 0;
+ cp = sg_get_desig_type_str(desig_type);
+ n += sg_scnpr(b + n, blen - n, " designator_type: %s, ",
+ cp ? cp : "-");
+ cp = sg_get_desig_code_set_str(c_set);
+ sgj_pr_hr(jsp, "%scode_set: %s\n", b, cp ? cp : "-");
+ cp = sg_get_desig_assoc_str(assoc);
+ sgj_pr_hr(jsp, " associated with the %s\n", cp ? cp : "-");
+ if (op->do_hex) {
+ sgj_pr_hr(jsp, " designator header(hex): %.2x %.2x %.2x %.2x\n",
+ bp[0], bp[1], bp[2], bp[3]);
+ sgj_pr_hr(jsp, " designator:\n");
+ hex2stdout(ip, i_len, 0);
+ continue;
+ }
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ k = 0;
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ for (k = 0; (k < i_len) && isprint(ip[k]); ++k)
+ ;
+ if (k >= i_len)
+ k = 1;
+ }
+ if (k)
+ sgj_pr_hr(jsp, " vendor specific: %.*s\n", i_len, ip);
+ else {
+ sgj_pr_hr(jsp, " vendor specific:\n");
+ hex2stdout(ip, i_len, -1);
+ }
+ break;
+ case 1: /* T10 vendor identification */
+ sgj_pr_hr(jsp, " vendor id: %.8s\n", ip);
+ if (i_len > 8) {
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ sgj_pr_hr(jsp, " vendor specific: %.*s\n", i_len - 8,
+ ip + 8);
+ } else {
+ n = 0;
+ n += sg_scnpr(b + n, blen - n,
+ " vendor specific: 0x");
+ for (m = 8; m < i_len; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ }
+ break;
+ case 2: /* EUI-64 based */
+ sgj_pr_hr(jsp, " EUI-64 based %d byte identifier\n", i_len);
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ ci_off = 0;
+ n = 0;
+ b[0] = '\0';
+ if (16 == i_len) {
+ ci_off = 8;
+ id_ext = sg_get_unaligned_be64(ip);
+ n += sg_scnpr(b + n, blen - n,
+ " Identifier extension: 0x%" PRIx64 "\n",
+ id_ext);
+ } else if ((8 != i_len) && (12 != i_len)) {
+ pr2serr(" << can only decode 8, 12 and 16 "
+ "byte ids>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ ccc_id = sg_get_unaligned_be64(ip + ci_off);
+ sgj_pr_hr(jsp, "%s IEEE identifier: 0x%" PRIx64 "\n", b,
+ ccc_id);
+ if (12 == i_len) {
+ d_id = sg_get_unaligned_be32(ip + 8);
+ sgj_pr_hr(jsp, " Directory ID: 0x%x\n", d_id);
+ }
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < i_len; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 3: /* NAA <n> */
+ naa = (ip[0] >> 4) & 0xff;
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set (1), got %d for "
+ "NAA=%d>>\n", c_set, naa);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ switch (naa) {
+ case 2: /* NAA 2: IEEE Extended */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 2 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+ c_id = sg_get_unaligned_be24(ip + 2);
+ vsi = sg_get_unaligned_be24(ip + 5);
+ sgj_pr_hr(jsp, " NAA 2, vendor specific identifier A: "
+ "0x%x\n", d_id);
+ sgj_pr_hr(jsp, " AOI: 0x%x\n", c_id);
+ sgj_pr_hr(jsp, " vendor specific identifier B: 0x%x\n",
+ vsi);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 3: /* NAA 3: Locally assigned */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 3 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ sgj_pr_hr(jsp, " NAA 3, Locally assigned:\n");
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 5: /* NAA 5: IEEE Registered */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 5 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, -1);
+ 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];
+ }
+ sgj_pr_hr(jsp, " NAA 5, AOI: 0x%x\n", c_id);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Vendor Specific "
+ "Identifier: 0x%" PRIx64 "\n", vsei);
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ case 6: /* NAA 6: IEEE Registered extended */
+ if (16 != i_len) {
+ pr2serr(" << unexpected NAA 6 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(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];
+ }
+ sgj_pr_hr(jsp, " NAA 6, AOI: 0x%x\n", c_id);
+ sgj_pr_hr(jsp, " Vendor Specific Identifier: 0x%"
+ PRIx64 "\n", vsei);
+ vsei = sg_get_unaligned_be64(ip + 8);
+ sgj_pr_hr(jsp, " Vendor Specific Identifier Extension: "
+ "0x%" PRIx64 "\n", vsei);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " [0x");
+ for (m = 0; m < 16; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[m]);
+ sgj_pr_hr(jsp, "%s]\n", b);
+ break;
+ default:
+ pr2serr(" << bad NAA nibble , expect 2, 3, 5 or 6, "
+ "got %d>>\n", naa);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ break;
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Relative target port: 0x%x\n", d_id);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Target port group: 0x%x\n", d_id);
+ break;
+ case 6: /* Logical unit group */
+ if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association, length 4>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ sgj_pr_hr(jsp, " Logical unit group: 0x%x\n", d_id);
+ break;
+ case 7: /* MD5 logical unit identifier */
+ if ((1 != c_set) || (0 != assoc)) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ sgj_pr_hr(jsp, " MD5 logical unit identifier:\n");
+ if (jsp->pr_out_hr)
+ sgj_js_str_out(jsp, (const char *)ip, i_len);
+ else
+ hex2stdout(ip, i_len, -1);
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) {
+ if (2 == c_set) {
+ if (op->verbose)
+ pr2serr(" << expected UTF-8, use ASCII>>\n");
+ } else {
+ pr2serr(" << expected UTF-8 code_set>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ }
+ sgj_pr_hr(jsp, " 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
+ */
+ sgj_pr_hr(jsp, " %.*s\n", i_len, (const char *)ip);
+ break;
+ case 9: /* Protocol specific port identifier */
+ /* added in spc4r36, PIV must be set, proto_id indicates */
+ /* whether UAS (USB) or SOP (PCIe) or ... */
+ if (! piv)
+ pr2serr(" >>>> Protocol specific port identifier "
+ "expects protocol\n"
+ " identifier to be valid and it is not\n");
+ if (TPROTO_UAS == p_id) {
+ sgj_pr_hr(jsp, " USB device address: 0x%x\n",
+ 0x7f & ip[0]);
+ sgj_pr_hr(jsp, " USB interface number: 0x%x\n", ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ sgj_pr_hr(jsp, " PCIe routing ID, bus number: 0x%x\n",
+ ip[0]);
+ sgj_pr_hr(jsp, " function number: 0x%x\n", ip[1]);
+ sgj_pr_hr(jsp, " [or device number: 0x%x, function "
+ "number: 0x%x]\n", (0x1f & (ip[1] >> 3)),
+ 0x7 & ip[1]);
+ } else
+ sgj_pr_hr(jsp, " >>>> unexpected protocol identifier: "
+ "%s\n with Protocol specific port "
+ "identifier\n", sg_get_trans_proto_str(p_id, dlen,
+ d));
+ break;
+ case 0xa: /* UUID identifier [spc5r08] RFC 4122 */
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set >>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != i_len)) {
+ pr2serr(" << expected locally assigned UUID, 16 bytes "
+ "long >>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Locally assigned UUID: ");
+ for (m = 0; m < 16; ++m) {
+ if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
+ n += sg_scnpr(b + n, blen - n, "-");
+ n += sg_scnpr(b + n, blen - n, "%02x", ip[2 + m]);
+ }
+ sgj_pr_hr(jsp, "%s\n", b);
+ break;
+ default: /* reserved */
+ pr2serr(" reserved designator=0x%x\n", desig_type);
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ }
+ if (-2 == u)
+ pr2serr("%s VPD page error: around offset=%d\n", leadin, off);
+}
+
+/* The --export and --json options are assumed to be mutually exclusive.
+ * Here the former takes precedence. */
+static void
+export_dev_ids(uint8_t * buff, int len, int verbose)
+{
+ int u, j, m, id_len, c_set, assoc, desig_type, i_len;
+ int off, d_id, naa, k, p_id;
+ uint8_t * bp;
+ uint8_t * ip;
+ const char * assoc_str;
+ const char * suffix;
+
+ if (buff[2] != 0) {
+ /*
+ * Cf decode_dev_ids() for details
+ */
+ i_len = len;
+ ip = buff;
+ c_set = 1;
+ assoc = 0;
+ p_id = 0xf;
+ desig_type = 3;
+ j = 1;
+ off = 16;
+ goto decode;
+ }
+
+ for (j = 1, off = -1;
+ (u = sg_vpd_dev_id_iter(buff, len, &off, -1, -1, -1)) == 0;
+ ++j) {
+ bp = buff + off;
+ i_len = bp[3];
+ id_len = i_len + 4;
+ if ((off + id_len) > len) {
+ if (verbose)
+ pr2serr("Device Identification VPD page error: designator "
+ "length longer than\n remaining response "
+ "length=%d\n", (len - off));
+ return;
+ }
+ ip = bp + 4;
+ p_id = ((bp[0] >> 4) & 0xf); /* protocol identifier */
+ c_set = (bp[0] & 0xf);
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ decode:
+ switch (assoc) {
+ case 0:
+ assoc_str = "LUN";
+ break;
+ case 1:
+ assoc_str = "PORT";
+ break;
+ case 2:
+ assoc_str = "TARGET";
+ break;
+ default:
+ if (verbose)
+ pr2serr(" Invalid association %d\n", assoc);
+ return;
+ }
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ if (i_len == 0 || i_len > 128)
+ break;
+ if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
+ k = encode_whitespaces(ip, i_len);
+ /* udev-conforming character encoding */
+ if (k > 0) {
+ printf("SCSI_IDENT_%s_VENDOR=", assoc_str);
+ for (m = 0; m < k; ++m) {
+ if ((ip[m] >= '0' && ip[m] <= '9') ||
+ (ip[m] >= 'A' && ip[m] <= 'Z') ||
+ (ip[m] >= 'a' && ip[m] <= 'z') ||
+ strchr("#+-.:=@_", ip[m]) != NULL)
+ printf("%c", ip[m]);
+ else
+ printf("\\x%02x", ip[m]);
+ }
+ printf("\n");
+ }
+ } else {
+ printf("SCSI_IDENT_%s_VENDOR=", assoc_str);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ }
+ break;
+ case 1: /* T10 vendor identification */
+ printf("SCSI_IDENT_%s_T10=", assoc_str);
+ if ((2 == c_set) || (3 == c_set)) {
+ k = encode_whitespaces(ip, i_len);
+ /* udev-conforming character encoding */
+ for (m = 0; m < k; ++m) {
+ if ((ip[m] >= '0' && ip[m] <= '9') ||
+ (ip[m] >= 'A' && ip[m] <= 'Z') ||
+ (ip[m] >= 'a' && ip[m] <= 'z') ||
+ strchr("#+-.:=@_", ip[m]) != NULL)
+ printf("%c", ip[m]);
+ else
+ printf("\\x%02x", ip[m]);
+ }
+ printf("\n");
+ if (!memcmp(ip, "ATA_", 4)) {
+ printf("SCSI_IDENT_%s_ATA=%.*s\n", assoc_str,
+ k - 4, ip + 4);
+ }
+ } else {
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ }
+ break;
+ case 2: /* EUI-64 based */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_EUI64=", assoc_str);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 3: /* NAA */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ /*
+ * Unfortunately, there are some (broken) implementations
+ * which return _several_ NAA descriptors.
+ * So add a suffix to differentiate between them.
+ */
+ naa = (ip[0] >> 4) & 0xff;
+ switch (naa) {
+ case 6:
+ suffix="REGEXT";
+ break;
+ case 5:
+ suffix="REG";
+ break;
+ case 2:
+ suffix="EXT";
+ break;
+ case 3:
+ default:
+ suffix="LOCAL";
+ break;
+ }
+ printf("SCSI_IDENT_%s_NAA_%s=", assoc_str, suffix);
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_RELATIVE=%d\n", assoc_str, d_id);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, target "
+ "port association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_TARGET_PORT_GROUP=0x%x\n", assoc_str, d_id);
+ break;
+ case 6: /* Logical unit group */
+ if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ d_id = sg_get_unaligned_be16(ip + 2);
+ printf("SCSI_IDENT_%s_LOGICAL_UNIT_GROUP=0x%x\n", assoc_str, d_id);
+ break;
+ case 7: /* MD5 logical unit identifier */
+ if ((1 != c_set) || (0 != assoc)) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set, logical "
+ "unit association>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_MD5=", assoc_str);
+ hex2stdout(ip, i_len, -1);
+ break;
+ case 8: /* SCSI name string */
+ if (3 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected UTF-8 code_set>>\n");
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+ if (! (strncmp((const char *)ip, "eui.", 4) ||
+ strncmp((const char *)ip, "EUI.", 4) ||
+ strncmp((const char *)ip, "naa.", 4) ||
+ strncmp((const char *)ip, "NAA.", 4) ||
+ strncmp((const char *)ip, "iqn.", 4))) {
+ if (verbose) {
+ pr2serr(" << expected name string prefix>>\n");
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+
+ printf("SCSI_IDENT_%s_NAME=%.*s\n", assoc_str, i_len,
+ (const char *)ip);
+ break;
+ case 9: /* Protocol specific port identifier */
+ if (TPROTO_UAS == p_id) {
+ if ((4 != i_len) || (1 != assoc)) {
+ if (verbose) {
+ pr2serr(" << UAS (USB) expected target "
+ "port association>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_UAS_DEVICE_ADDRESS=0x%x\n", assoc_str,
+ ip[0] & 0x7f);
+ printf("SCSI_IDENT_%s_UAS_INTERFACE_NUMBER=0x%x\n", assoc_str,
+ ip[2]);
+ } else if (TPROTO_SOP == p_id) {
+ if ((4 != i_len) && (8 != i_len)) { /* spc4r36h confused */
+ if (verbose) {
+ pr2serr(" << SOP (PCIe) descriptor "
+ "length=%d >>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_SOP_ROUTING_ID=0x%x\n", assoc_str,
+ sg_get_unaligned_be16(ip + 0));
+ } else {
+ pr2serr(" << Protocol specific port identifier "
+ "protocol_id=0x%x>>\n", p_id);
+ }
+ break;
+ case 0xa: /* UUID based */
+ if (1 != c_set) {
+ if (verbose) {
+ pr2serr(" << expected binary code_set (1)>>\n");
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ if (i_len < 18) {
+ if (verbose) {
+ pr2serr(" << short UUID field expected 18 or more, "
+ "got %d >>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ }
+ break;
+ }
+ printf("SCSI_IDENT_%s_UUID=", assoc_str);
+ for (m = 2; m < i_len; ++m) {
+ if ((6 == m) || (8 == m) || (10 == m) || (12 == m))
+ printf("-%02x", (unsigned int)ip[m]);
+ else
+ printf("%02x", (unsigned int)ip[m]);
+ }
+ printf("\n");
+ break;
+ default: /* reserved */
+ if (verbose) {
+ pr2serr(" reserved designator=0x%x\n", desig_type);
+ hex2stderr(ip, i_len, -1);
+ }
+ break;
+ }
+ }
+ if (-2 == u && verbose)
+ pr2serr("Device identification VPD page error: "
+ "around offset=%d\n", off);
+}
+
+/* VPD_BLOCK_LIMITS 0xb0 ["bl"] (SBC) */
+/* VPD_SA_DEV_CAP 0xb0 ["sad"] (SSC) */
+/* Sequential access device characteristics, ssc+smc */
+/* OSD information, osd */
+static void
+decode_b0_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt = PDT_MASK & buff[0];
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* done by decode_block_limits_vpd() */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_haj_vi_nex(jsp, jop, 2, "TSMC", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x2), false, "Tape Stream Mirror "
+ "Capable");
+ sgj_haj_vi_nex(jsp, jop, 2, "WORM", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x1), false, "Write Once Read Multiple "
+ "supported");
+
+ break;
+ case PDT_OSD:
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_BLOCK_DEV_CHARS sbc 0xb1 ["bdc"] */
+/* VPD_MAN_ASS_SN ssc */
+static void
+decode_b1_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_block_dev_ch_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER: case PDT_ADC:
+ sgj_pr_hr(jsp, " Manufacturer-assigned serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "manufacturer_assigned_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_REFERRALS sbc 0xb3 ["ref"] */
+/* VPD_AUTOMATION_DEV_SN ssc 0xb3 ["adsn"] */
+static void
+decode_b3_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = buff[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done in decode_referrals_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_pr_hr(jsp, " Automation device serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "automation_device_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+#if 0
+static void
+decode_rdac_vpd_c9_rtpg_data(uint8_t aas, uint8_t vendor)
+{
+ printf(" Asymmetric Access State:");
+ switch(aas & 0x0F) {
+ case 0x0:
+ printf(" Active/Optimized");
+ break;
+ case 0x1:
+ printf(" Active/Non-Optimized");
+ break;
+ case 0x2:
+ printf(" Standby");
+ break;
+ case 0x3:
+ printf(" Unavailable");
+ break;
+ case 0xE:
+ printf(" Offline");
+ break;
+ case 0xF:
+ printf(" Transitioning");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+
+ printf(" Vendor Specific Field:");
+ switch(vendor) {
+ case 0x01:
+ printf(" Operating normally");
+ break;
+ case 0x02:
+ printf(" Non-responsive to queries");
+ break;
+ case 0x03:
+ printf(" Controller being held in reset");
+ break;
+ case 0x04:
+ printf(" Performing controller firmware download (1st "
+ "controller)");
+ break;
+ case 0x05:
+ printf(" Performing controller firmware download (2nd "
+ "controller)");
+ break;
+ case 0x06:
+ printf(" Quiesced as a result of an administrative request");
+ break;
+ case 0x07:
+ printf(" Service mode as a result of an administrative request");
+ break;
+ case 0xFF:
+ printf(" Details are not available");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+}
+
+static void
+decode_rdac_vpd_c9(uint8_t * buff, int len, struct opts_t * op)
+{
+ if (len < 3) {
+ pr2serr("Volume Access Control VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (buff[4] != 'v' && buff[5] != 'a' && buff[6] != 'c') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding "
+ "not possible.\n" , buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ if (buff[7] != '1') {
+ pr2serr("Invalid page version '%c' (should be 1)\n", buff[7]);
+ }
+ if ( (buff[8] & 0xE0) == 0xE0 ) {
+ printf(" IOShipping (ALUA): Enabled\n");
+ } else {
+ printf(" AVT:");
+ if (buff[8] & 0x80) {
+ printf(" Enabled");
+ if (buff[8] & 0x40)
+ printf(" (Allow reads on sector 0)");
+ printf("\n");
+ } else {
+ printf(" Disabled\n");
+ }
+ }
+ printf(" Volume Access via: ");
+ if (buff[8] & 0x01)
+ printf("primary controller\n");
+ else
+ printf("alternate controller\n");
+
+ if (buff[8] & 0x08) {
+ printf(" Path priority: %d ", buff[15] & 0xf);
+ switch(buff[15] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+
+ printf(" Preferred Path Auto Changeable:");
+ switch(buff[14] & 0x3C) {
+ case 0x14:
+ printf(" No (User Disabled and Host Type Restricted)\n");
+ break;
+ case 0x18:
+ printf(" No (User Disabled)\n");
+ break;
+ case 0x24:
+ printf(" No (Host Type Restricted)\n");
+ break;
+ case 0x28:
+ printf(" Yes\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+
+ printf(" Implicit Failback:");
+ switch(buff[14] & 0x03) {
+ case 0x1:
+ printf(" Disabled\n");
+ break;
+ case 0x2:
+ printf(" Enabled\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+ } else {
+ printf(" Path priority: %d ", buff[9] & 0xf);
+ switch(buff[9] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+ }
+
+ if (buff[8] & 0x80) {
+ printf(" Target Port Group Data (This controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[10], buff[11]);
+
+ printf(" Target Port Group Data (Alternate controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[12], buff[13]);
+ }
+
+ return;
+}
+#endif
+
+extern const char * sg_ansi_version_arr[];
+
+static const char *
+get_ansi_version_str(int version, char * b, int blen)
+{
+ version &= 0xf;
+ b[blen - 1] = '\0';
+ strncpy(b, sg_ansi_version_arr[version], blen - 1);
+ return b;
+}
+
+static void
+std_inq_decode(struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ int len, pqual, pdt, ansi_version, k, j;
+ sgj_state * jsp = &op->json_st;
+ bool as_json = jsp->pr_as_json;
+ const char * cp;
+ const uint8_t * rp;
+ int vdesc_arr[8];
+ char b[128];
+ static const int blen = sizeof(b);
+
+ rp = rsp_buff + off;
+ memset(vdesc_arr, 0, sizeof(vdesc_arr));
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, op->maxlen);
+ return;
+ } else if (op->do_hex) {
+ /* with -H, print with address, -HH without */
+ hex2stdout(rp, op->maxlen, no_ascii_4hex(op));
+ return;
+ }
+ pqual = (rp[0] & 0xe0) >> 5;
+ if (! op->do_raw && ! op->do_export) {
+ strcpy(b, "standard INQUIRY:");
+ if (0 == pqual)
+ sgj_pr_hr(jsp, "%s\n", b);
+ else if (1 == pqual)
+ sgj_pr_hr(jsp, "%s [PQ indicates LU temporarily unavailable]\n",
+ b);
+ else if (3 == pqual)
+ sgj_pr_hr(jsp, "%s [PQ indicates LU not accessible via this "
+ "port]\n", b);
+ else
+ sgj_pr_hr(jsp, "%s [reserved or vendor specific qualifier "
+ "[%d]]\n", b, pqual);
+ }
+ len = rp[4] + 5;
+ /* N.B. rp[2] full byte is 'version' in SPC-2,3,4 but in SPC
+ * [spc-r11a (1997)] bits 6,7: ISO/IEC version; bits 3-5: ECMA
+ * version; bits 0-2: SCSI version */
+ ansi_version = rp[2] & 0x7; /* Only take SCSI version */
+ pdt = rp[0] & PDT_MASK;
+ if (op->do_export) {
+ printf("SCSI_TPGS=%d\n", (rp[5] & 0x30) >> 4);
+ cp = sg_get_pdt_str(pdt, blen, b);
+ if (strlen(cp) > 0)
+ printf("SCSI_TYPE=%s\n", cp);
+ } else {
+ sgj_pr_hr(jsp, " PQual=%d PDT=%d RMB=%d LU_CONG=%d "
+ "hot_pluggable=%d version=0x%02x ", pqual, pdt,
+ !!(rp[1] & 0x80), !!(rp[1] & 0x40), (rp[1] >> 4) & 0x3,
+ (unsigned int)rp[2]);
+ sgj_pr_hr(jsp, " [%s]\n", get_ansi_version_str(ansi_version, b,
+ blen));
+ sgj_pr_hr(jsp, " [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d "
+ " Resp_data_format=%d\n SCCS=%d ", !!(rp[3] & 0x80),
+ !!(rp[3] & 0x40), !!(rp[3] & 0x20), !!(rp[3] & 0x10),
+ rp[3] & 0x0f, !!(rp[5] & 0x80));
+ sgj_pr_hr(jsp, "ACC=%d TPGS=%d 3PC=%d Protect=%d ",
+ !!(rp[5] & 0x40), ((rp[5] & 0x30) >> 4), !!(rp[5] & 0x08),
+ !!(rp[5] & 0x01));
+ sgj_pr_hr(jsp, " [BQue=%d]\n EncServ=%d ", !!(rp[6] & 0x80),
+ !!(rp[6] & 0x40));
+ if (rp[6] & 0x10)
+ sgj_pr_hr(jsp, "MultiP=1 (VS=%d) ", !!(rp[6] & 0x20));
+ else
+ sgj_pr_hr(jsp, "MultiP=0 ");
+ sgj_pr_hr(jsp, "[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n "
+ "[RelAdr=%d] ", !!(rp[6] & 0x08), !!(rp[6] & 0x04),
+ !!(rp[6] & 0x01), !!(rp[7] & 0x80));
+ sgj_pr_hr(jsp, "WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ",
+ !!(rp[7] & 0x20), !!(rp[7] & 0x10), !!(rp[7] & 0x08),
+ !!(rp[7] & 0x04));
+ sgj_pr_hr(jsp, "CmdQue=%d\n", !!(rp[7] & 0x02));
+ if (op->maxlen > 56)
+ sgj_pr_hr(jsp, " [SPI: Clocking=0x%x QAS=%d IUS=%d]\n",
+ (rp[56] & 0x0c) >> 2, !!(rp[56] & 0x2),
+ !!(rp[56] & 0x1));
+ if (op->maxlen >= len)
+ sgj_pr_hr(jsp, " length=%d (0x%x)", len, len);
+ else
+ sgj_pr_hr(jsp, " length=%d (0x%x), but only fetched %d bytes",
+ len, len, op->maxlen);
+ if ((ansi_version >= 2) && (len < SAFE_STD_INQ_RESP_LEN))
+ sgj_pr_hr(jsp, "\n [for SCSI>=2, len>=36 is expected]");
+ cp = sg_get_pdt_str(pdt, blen, b);
+ if (strlen(cp) > 0)
+ sgj_pr_hr(jsp, " Peripheral device type: %s\n", cp);
+ }
+ if (op->maxlen <= 8) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Inquiry response length=%d, no vendor, product "
+ "or revision data\n", op->maxlen);
+ } else {
+ int i;
+
+ memcpy(xtra_buff, &rp[8], 8);
+ xtra_buff[8] = '\0';
+ /* Fixup any tab characters */
+ for (i = 0; i < 8; ++i)
+ if (xtra_buff[i] == 0x09)
+ xtra_buff[i] = ' ';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 8);
+ if (len > 0) {
+ printf("SCSI_VENDOR=%s\n", xtra_buff);
+ encode_string(xtra_buff, &rp[8], 8);
+ printf("SCSI_VENDOR_ENC=%s\n", xtra_buff);
+ }
+ } else
+ sgj_pr_hr(jsp, " Vendor identification: %s\n", xtra_buff);
+ if (op->maxlen <= 16) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Product identification: <none>\n");
+ } else {
+ memcpy(xtra_buff, &rp[16], 16);
+ xtra_buff[16] = '\0';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 16);
+ if (len > 0) {
+ printf("SCSI_MODEL=%s\n", xtra_buff);
+ encode_string(xtra_buff, &rp[16], 16);
+ printf("SCSI_MODEL_ENC=%s\n", xtra_buff);
+ }
+ } else
+ sgj_pr_hr(jsp, " Product identification: %s\n", xtra_buff);
+ }
+ if (op->maxlen <= 32) {
+ if (! op->do_export)
+ sgj_pr_hr(jsp, " Product revision level: <none>\n");
+ } else {
+ memcpy(xtra_buff, &rp[32], 4);
+ xtra_buff[4] = '\0';
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 4);
+ if (len > 0)
+ printf("SCSI_REVISION=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Product revision level: %s\n", xtra_buff);
+ }
+ if (op->do_vendor && (op->maxlen > 36) && ('\0' != rp[36]) &&
+ (' ' != rp[36])) {
+ memcpy(xtra_buff, &rp[36], op->maxlen < 56 ? op->maxlen - 36 :
+ 20);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 20);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific: %s\n", xtra_buff);
+ }
+ if (op->do_descriptors) {
+ for (j = 0, k = 58; ((j < 8) && ((k + 1) < op->maxlen));
+ k +=2, ++j)
+ vdesc_arr[j] = sg_get_unaligned_be16(rp + k);
+ }
+ if ((op->do_vendor > 1) && (op->maxlen > 96)) {
+ memcpy(xtra_buff, &rp[96], op->maxlen - 96);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff,
+ op->maxlen - 96);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific: %s\n", xtra_buff);
+ }
+ if (op->do_vendor && (op->maxlen > 243) &&
+ (0 == strncmp("OPEN-V", (const char *)&rp[16], 6))) {
+ memcpy(xtra_buff, &rp[212], 32);
+ if (op->do_export) {
+ len = encode_whitespaces((uint8_t *)xtra_buff, 32);
+ if (len > 0)
+ printf("VENDOR_SPECIFIC_OPEN-V_LDEV_NAME=%s\n", xtra_buff);
+ } else
+ sgj_pr_hr(jsp, " Vendor specific OPEN-V LDEV Name: %s\n",
+ xtra_buff);
+ }
+ }
+ if (! op->do_export) {
+ sgj_opaque_p jo2p = NULL;
+
+ if (as_json)
+ jo2p = std_inq_decode_js(rp, op->maxlen, op, jop);
+ if ((0 == op->maxlen) && usn_buff[0])
+ sgj_pr_hr(jsp, " Unit serial number: %s\n", usn_buff);
+ if (op->do_descriptors) {
+ sgj_opaque_p jap = sgj_named_subarray_r(jsp, jo2p,
+ "version_descriptor_list");
+ if (0 == vdesc_arr[0]) {
+ sgj_pr_hr(jsp, "\n");
+ sgj_pr_hr(jsp, " No version descriptors available\n");
+ } else {
+ sgj_pr_hr(jsp, "\n");
+ sgj_pr_hr(jsp, " Version descriptors:\n");
+ for (k = 0; k < 8; ++k) {
+ sgj_opaque_p jo3p = sgj_new_unattached_object_r(jsp);
+ int vdv = vdesc_arr[k];
+
+ if (0 == vdv)
+ break;
+ cp = find_version_descriptor_str(vdv);
+ if (cp)
+ sgj_pr_hr(jsp, " %s\n", cp);
+ else
+ sgj_pr_hr(jsp, " [unrecognised version descriptor "
+ "code: 0x%x]\n", vdv);
+ sgj_js_nv_ihexstr(jsp, jo3p, "version_descriptor", vdv,
+ NULL, cp ? cp : "unknown");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ }
+ }
+ }
+}
+
+/* Returns 0 if Unit Serial Number VPD page contents found, else see
+ * sg_ll_inquiry_v2() return values */
+static int
+fetch_unit_serial_num(int sg_fd, char * obuff, int obuff_len, int verbose)
+{
+ int len, k, res, c;
+ uint8_t * b;
+ uint8_t * free_b;
+
+ b = sg_memalign(DEF_ALLOC_LEN, 0, &free_b, false);
+ if (NULL == b) {
+ pr2serr("%s: unable to allocate on heap\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = vpd_fetch_page(sg_fd, b, VPD_SUPPORTED_VPDS,
+ -1 /* 1 byte alloc_len */, false, verbose, &len);
+ if (res) {
+ if (verbose > 2)
+ pr2serr("%s: no supported VPDs page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ if (! vpd_page_is_supported(b, len, VPD_UNIT_SERIAL_NUM, verbose)) {
+ res = sg_convert_errno(EDOM); /* was SG_LIB_CAT_ILLEGAL_REQ */
+ goto fini;
+ }
+
+ memset(b, 0xff, 4); /* guard against empty response */
+ res = vpd_fetch_page(sg_fd, b, VPD_UNIT_SERIAL_NUM, -1, false, verbose,
+ &len);
+ if ((0 == res) && (len > 3)) {
+ len -= 4;
+ len = (len < (obuff_len - 1)) ? len : (obuff_len - 1);
+ if (len > 0) {
+ /* replace non-printable characters (except NULL) with space */
+ for (k = 0; k < len; ++k) {
+ c = b[4 + k];
+ if (c)
+ obuff[k] = isprint(c) ? c : ' ';
+ else
+ break;
+ }
+ obuff[k] = '\0';
+ res = 0;
+ goto fini;
+ } else {
+ if (verbose > 2)
+ pr2serr("%s: bad sn VPD page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ if (verbose > 2)
+ pr2serr("%s: no supported VPDs page\n", __func__);
+ res = SG_LIB_CAT_MALFORMED;
+ }
+fini:
+ if (free_b)
+ free(free_b);
+ return res;
+}
+
+
+/* Process a standard INQUIRY data format (response).
+ * Returns 0 if successful */
+static int
+std_inq_process(int sg_fd, struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ int res, len, rlen, act_len;
+ int vb, resid;
+ char buff[48];
+
+ if (sg_fd < 0) { /* assume --inhex=FD usage */
+ std_inq_decode(op, jop, off);
+ return 0;
+ }
+ rlen = (op->maxlen > 0) ? op->maxlen : SAFE_STD_INQ_RESP_LEN;
+ vb = op->verbose;
+ res = sg_ll_inquiry_v2(sg_fd, false, 0, rsp_buff, rlen, DEF_PT_TIMEOUT,
+ &resid, false, vb);
+ if (0 == res) {
+ if ((vb > 4) && ((rlen - resid) > 0)) {
+ pr2serr("Safe (36 byte) Inquiry response:\n");
+ hex2stderr(rsp_buff, rlen - resid, 0);
+ }
+ len = rsp_buff[4] + 5;
+ if ((len > SAFE_STD_INQ_RESP_LEN) && (len < 256) &&
+ (0 == op->maxlen)) {
+ rlen = len;
+ memset(rsp_buff, 0, rlen);
+ if (sg_ll_inquiry_v2(sg_fd, false, 0, rsp_buff, rlen,
+ DEF_PT_TIMEOUT, &resid, true, vb)) {
+ pr2serr("second INQUIRY (%d byte) failed\n", len);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (len != (rsp_buff[4] + 5)) {
+ pr2serr("strange, consecutive INQUIRYs yield different "
+ "'additional lengths'\n");
+ len = rsp_buff[4] + 5;
+ }
+ }
+ if (op->maxlen > 0)
+ act_len = rlen;
+ else
+ act_len = (rlen < len) ? rlen : len;
+ /* don't use more than HBA's resid says was transferred from LU */
+ if (act_len > (rlen - resid))
+ act_len = rlen - resid;
+ if (act_len < SAFE_STD_INQ_RESP_LEN)
+ rsp_buff[act_len] = '\0';
+ if ((! op->do_only) && (! op->do_export) && (0 == op->maxlen)) {
+ if (fetch_unit_serial_num(sg_fd, usn_buff, sizeof(usn_buff), vb))
+ usn_buff[0] = '\0';
+ }
+ op->maxlen = act_len;
+ std_inq_decode(op, jop, 0);
+ return 0;
+ } else if (res < 0) { /* could be an ATA device */
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ /* Try an ATA Identify Device command */
+ res = try_ata_identify(sg_fd, op->do_hex, op->do_raw, vb);
+ if (0 != res) {
+ pr2serr("SCSI INQUIRY, NVMe Identify and fetching ATA "
+ "information failed on %s\n", op->device_name);
+ return (res < 0) ? SG_LIB_CAT_OTHER : res;
+ }
+#else
+ pr2serr("SCSI INQUIRY failed on %s, res=%d\n",
+ op->device_name, res);
+ return res;
+#endif
+ } else {
+ char b[80];
+
+ if (vb > 0) {
+ pr2serr(" inquiry: failed requesting %d byte response: ", rlen);
+ if (resid && (vb > 1))
+ snprintf(buff, sizeof(buff), " [resid=%d]", resid);
+ else
+ buff[0] = '\0';
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s%s\n", b, buff);
+ }
+ return res;
+ }
+ return 0;
+}
+
+#ifdef SG_SCSI_STRINGS
+/* Returns 0 if successful */
+static int
+cmddt_process(int sg_fd, const struct opts_t * op)
+{
+ int k, j, num, len, pdt, reserved_cmddt, support_num, res;
+ char op_name[128];
+
+ memset(rsp_buff, 0, rsp_buff_sz);
+ if (op->do_cmddt > 1) {
+ printf("Supported command list:\n");
+ for (k = 0; k < 256; ++k) {
+ res = sg_ll_inquiry(sg_fd, true /* cmddt */, false, k, rsp_buff,
+ DEF_ALLOC_LEN, true, op->verbose);
+ if (0 == res) {
+ pdt = rsp_buff[0] & PDT_MASK;
+ support_num = rsp_buff[1] & 7;
+ reserved_cmddt = rsp_buff[4];
+ if ((3 == support_num) || (5 == support_num)) {
+ num = rsp_buff[5];
+ for (j = 0; j < num; ++j)
+ printf(" %.2x", (int)rsp_buff[6 + j]);
+ if (5 == support_num)
+ printf(" [vendor specific manner (5)]");
+ sg_get_opcode_name((uint8_t)k, pdt,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf(" %s\n", op_name);
+ } else if ((4 == support_num) || (6 == support_num))
+ printf(" opcode=0x%.2x vendor specific (%d)\n",
+ k, support_num);
+ else if ((0 == support_num) && (reserved_cmddt > 0)) {
+ printf(" opcode=0x%.2x ignored cmddt bit, "
+ "given standard INQUIRY response, stop\n", k);
+ break;
+ }
+ } else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ break;
+ else {
+ pr2serr("CmdDt INQUIRY on opcode=0x%.2x: failed\n", k);
+ break;
+ }
+ }
+ }
+ else {
+ res = sg_ll_inquiry(sg_fd, true /* cmddt */, false, op->vpd_pn,
+ rsp_buff, DEF_ALLOC_LEN, true, op->verbose);
+ if (0 == res) {
+ pdt = rsp_buff[0] & PDT_MASK;
+ if (! op->do_raw) {
+ printf("CmdDt INQUIRY, opcode=0x%.2x: [", op->vpd_pn);
+ sg_get_opcode_name((uint8_t)op->vpd_pn, pdt,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf("%s]\n", op_name);
+ }
+ len = rsp_buff[5] + 6;
+ reserved_cmddt = rsp_buff[4];
+ if (op->do_hex)
+ hex2stdout(rsp_buff, len, no_ascii_4hex(op));
+ else if (op->do_raw)
+ dStrRaw((const char *)rsp_buff, len);
+ else {
+ bool prnt_cmd = false;
+ const char * desc_p;
+
+ support_num = rsp_buff[1] & 7;
+ num = rsp_buff[5];
+ switch (support_num) {
+ case 0:
+ if (0 == reserved_cmddt)
+ desc_p = "no data available";
+ else
+ desc_p = "ignored cmddt bit, standard INQUIRY "
+ "response";
+ break;
+ case 1: desc_p = "not supported"; break;
+ case 2: desc_p = "reserved (2)"; break;
+ case 3: desc_p = "supported as per standard";
+ prnt_cmd = true;
+ break;
+ case 4: desc_p = "vendor specific (4)"; break;
+ case 5: desc_p = "supported in vendor specific way";
+ prnt_cmd = true;
+ break;
+ case 6: desc_p = "vendor specific (6)"; break;
+ case 7: desc_p = "reserved (7)"; break;
+ }
+ if (prnt_cmd) {
+ printf(" Support field: %s [", desc_p);
+ for (j = 0; j < num; ++j)
+ printf(" %.2x", (int)rsp_buff[6 + j]);
+ printf(" ]\n");
+ } else
+ printf(" Support field: %s\n", desc_p);
+ }
+ } else if (SG_LIB_CAT_ILLEGAL_REQ != res) {
+ if (! op->do_raw) {
+ printf("CmdDt INQUIRY, opcode=0x%.2x: [", op->vpd_pn);
+ sg_get_opcode_name((uint8_t)op->vpd_pn, 0,
+ sizeof(op_name) - 1, op_name);
+ op_name[sizeof(op_name) - 1] = '\0';
+ printf("%s]\n", op_name);
+ }
+ pr2serr("CmdDt INQUIRY on opcode=0x%.2x: failed\n", op->vpd_pn);
+ }
+ }
+ return res;
+}
+
+#else /* SG_SCSI_STRINGS */
+
+/* Returns 0. */
+static int
+cmddt_process(int sg_fd, const struct opts_t * op)
+{
+ if (sg_fd) { } /* suppress warning */
+ if (op) { } /* suppress warning */
+ pr2serr("'--cmddt' not implemented, use sg_opcodes\n");
+ return 0;
+}
+
+#endif /* SG_SCSI_STRINGS */
+
+
+/* Returns 0 if successful */
+static int
+vpd_mainly_hex(int sg_fd, struct opts_t * op, sgj_opaque_p jap, int off)
+{
+ bool as_json;
+ bool json_o_hr;
+ int res, len, n;
+ char b[128];
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ uint8_t * rp;
+
+ as_json = jsp->pr_as_json;
+ json_o_hr = as_json && jsp->pr_out_hr;
+ rp = rsp_buff + off;
+ if ((! op->do_raw) && (op->do_hex < 3)) {
+ if (op->do_hex)
+ printf("VPD INQUIRY, page code=0x%.2x:\n", op->vpd_pn);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY, page code=0x%.2x:\n", op->vpd_pn);
+ }
+ if (sg_fd < 0) {
+ len = sg_get_unaligned_be16(rp + 2) + 4;
+ res = 0;
+ } else {
+ memset(rp, 0, DEF_ALLOC_LEN);
+ res = vpd_fetch_page(sg_fd, rp, op->vpd_pn, op->maxlen,
+ op->do_quiet, op->verbose, &len);
+ }
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ int pdt = pdt = rp[0] & PDT_MASK;
+
+ if (0 == op->vpd_pn)
+ decode_supported_vpd_4inq(rp, len, op, jap);
+ else {
+ if (op->verbose) {
+ cp = sg_get_pdt_str(pdt, sizeof(b), b);
+ if (op->do_hex)
+ printf(" [PQual=%d Peripheral device type: %s]\n",
+ (rp[0] & 0xe0) >> 5, cp);
+ else
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device "
+ "type: %s]\n", (rp[0] & 0xe0) >> 5, cp);
+ }
+ if (json_o_hr && (0 == op->do_hex) && (len > 0) &&
+ (len < UINT16_MAX)) {
+ char * p;
+
+ n = len * 4;
+ p = malloc(n);
+ if (p) {
+ n = hex2str(rp, len, NULL, 1, n - 1, p);
+ sgj_js_str_out(jsp, p, n);
+ }
+ } else
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ }
+ }
+ } else {
+ if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr(" inquiry: field in cdb illegal (page not "
+ "supported)\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr(" inquiry: %s\n", b);
+ }
+ }
+ return res;
+}
+
+static int
+recurse_vpd_decode(struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ return vpd_decode(-1, op, jop, off);
+}
+
+/* Returns 0 if successful */
+static int
+vpd_decode(int sg_fd, struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ bool bad = false;
+ bool qt = op->do_quiet;
+ int len, pdt, pn, vb /*, pqual */;
+ int res = 0;
+ sgj_state * jsp = &op->json_st;
+ bool as_json = jsp->pr_as_json;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jap = NULL;
+ const char * np;
+ const char * ep = "";
+ uint8_t * rp;
+
+ rp = rsp_buff + off;
+ vb = op->verbose;
+ if ((off > 0) && (VPD_NOPE_WANT_STD_INQ != op->vpd_pn))
+ pn = rp[1];
+ else
+ pn = op->vpd_pn;
+ if (sg_fd != -1 && !op->do_force && pn != VPD_SUPPORTED_VPDS) {
+ res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen,
+ qt, vb, &len);
+ if (res)
+ goto out;
+ if (! vpd_page_is_supported(rp, len, pn, vb)) {
+ if (vb)
+ pr2serr("Given VPD page not in supported list, use --force "
+ "to override this check\n");
+ res = sg_convert_errno(EDOM); /* was SG_LIB_CAT_ILLEGAL_REQ */
+ goto out;
+ }
+ }
+ switch (pn) {
+ case VPD_SUPPORTED_VPDS: /* 0x0 ["sv"] */
+ np = "Supported VPD pages VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "supported_vpd_page_list");
+ }
+ decode_supported_vpd_4inq(rp, len, op, jap);
+ }
+ break;
+ case VPD_UNIT_SERIAL_NUM: /* 0x80 ["sn"] */
+ np = "Unit serial number VPD page";
+ if (! op->do_raw && ! op->do_export && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ char obuff[DEF_ALLOC_LEN];
+ int k, m;
+
+ memset(obuff, 0, sizeof(obuff));
+ len -= 4;
+ if (len >= (int)sizeof(obuff))
+ len = sizeof(obuff) - 1;
+ memcpy(obuff, rp + 4, len);
+ if (op->do_export) {
+ k = encode_whitespaces((uint8_t *)obuff, len);
+ if (k > 0) {
+ printf("SCSI_IDENT_SERIAL=");
+ /* udev-conforming character encoding */
+ for (m = 0; m < k; ++m) {
+ if ((obuff[m] >= '0' && obuff[m] <= '9') ||
+ (obuff[m] >= 'A' && obuff[m] <= 'Z') ||
+ (obuff[m] >= 'a' && obuff[m] <= 'z') ||
+ strchr("#+-.:=@_", obuff[m]) != NULL)
+ printf("%c", obuff[m]);
+ else
+ printf("\\x%02x", obuff[m]);
+ }
+ printf("\n");
+ }
+ } else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ k = encode_unicode((uint8_t *)obuff, len);
+ if (k > 0) {
+ sgj_pr_hr(jsp, " Unit serial number: %s\n", obuff);
+ sgj_js_nv_s(jsp, jo2p, "unit_serial_number", obuff);
+ }
+ }
+ }
+ break;
+ case VPD_DEVICE_ID: /* 0x83 ["di"] */
+ np = "Device Identification VPD page";
+ if (! op->do_raw && ! op->do_export && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else if (op->do_hex > 2)
+ hex2stdout(rp, len, -1);
+ else if (op->do_export && (! as_json))
+ export_dev_ids(rp + 4, len - 4, op->verbose);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "designation_descriptor_list");
+ }
+ decode_id_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SOFTW_INF_ID: /* 0x84 ["sii"] */
+ np = "Software interface identification VPD page";
+ if (! op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "software_interface_identifier_list");
+ }
+ decode_softw_inf_id(rp, len, op, jap);
+ }
+ break;
+ case VPD_MAN_NET_ADDR: /* 0x85 ["mna"] */
+ np = "Management network addresses page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ // pdt = rp[0] & PDT_MASK;
+ // pdt_str = sg_get_pdt_str(pdt, sizeof(d), d);
+ // pqual = (rp[0] & 0xe0) >> 5;
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "network_services_descriptor_list");
+ }
+ decode_net_man_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_EXT_INQ: /* 0x86 ["ei"] */
+ np = "Extended INQUIRY data";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s page\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ bool protect = false;
+
+ op->protect_not_sure = false;
+ if ((sg_fd >= 0) && (! op->do_force)) {
+ struct sg_simple_inquiry_resp sir;
+
+ res = sg_simple_inquiry(sg_fd, &sir, false, vb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("%s: sg_simple_inquiry() failed, res=%d\n",
+ __func__, res);
+ op->protect_not_sure = true;
+ } else
+ protect = !!(sir.byte_5 & 0x1); /* SPC-3 and later */
+ } else
+ op->protect_not_sure = true;
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_x_inq_vpd(rp, len, protect, op, jo2p);
+ }
+ break;
+ case VPD_MODE_PG_POLICY: /* 0x87 ["mpp"] */
+ np = "Mode page policy";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "mode_page_policy_descriptor_list");
+ }
+ decode_mode_policy_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SCSI_PORTS: /* 0x88 ["sp"] */
+ np = "SCSI Ports VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "scsi_ports_descriptor_list");
+ }
+ decode_scsi_ports_vpd_4inq(rp, len, op, jap);
+ }
+ break;
+ case VPD_ATA_INFO: /* 0x89 ["ai"] */
+ np = "ATA information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ /* format output for 'hdparm --Istdin' with '-rr' or '-HHH' */
+ if ((2 == op->do_raw) || (3 == op->do_hex))
+ dWordHex((const unsigned short *)(rp + 60), 256, -2,
+ sg_is_big_endian());
+ else if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ else
+ op->do_long = true;
+ decode_ata_info_vpd(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_POWER_CONDITION: /* 0x8a ["pc"] */
+ np = "Power condition VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_power_condition(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_DEVICE_CONSTITUENTS: /* 0x8b ["dc"] */
+ np = "Device constituents page VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "constituent_descriptor_list");
+ }
+ decode_dev_constit_vpd(rp, len, op, jap, recurse_vpd_decode);
+ }
+ break;
+ case VPD_CFA_PROFILE_INFO: /* 0x8c ["cfa"] */
+ np = "CFA profile information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "%s:\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "cfa_profile_descriptor_list");
+ }
+ decode_cga_profile_vpd(rp, len, op, jap);
+ }
+ }
+ break;
+ case VPD_POWER_CONSUMPTION: /* 0x8d ["psm"] */
+ np = "Power consumption VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "power_consumption_descriptor_list");
+ }
+ decode_power_consumption(rp, len, op, jap);
+ }
+ break;
+ case VPD_3PARTY_COPY: /* 0x8f ["tpc"] */
+ np = "Third party copy VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "third_party_copy_descriptor_list");
+ }
+ decode_3party_copy_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_PROTO_LU: /* 0x90 ["pslu"] */
+ np = "Protocol specific logical unit information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_unit_information_descriptor_list");
+ }
+ decode_proto_lu_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_PROTO_PORT: /* 0x91 ["pspo"] */
+ np = "Protocol specific port information VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "port_information_descriptor_list");
+ }
+ decode_proto_port_vpd(rp, len, op, jap);
+ }
+ break;
+ case VPD_SCSI_FEATURE_SETS: /* 0x92 ["sfs"] */
+ np = "SCSI Feature sets VPD page";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s\n", np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "feature_set_code_list");
+ }
+ decode_feature_sets_vpd(rp, len, op, jap);
+ }
+ break;
+ case 0xb0: /* VPD pages in B0h to BFh range depend on pdt */
+ np = NULL;
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bl = false;
+ bool sad = false;
+ bool oi = false;
+
+ ep = "";
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits VPD page";
+ ep = "(SBC)";
+ bl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Sequential-access device capabilities VPD page";
+ ep = "(SSC)";
+ sad = true;
+ break;
+ case PDT_OSD:
+ np = "OSD information VPD page";
+ ep = "(OSD)";
+ oi = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bl)
+ decode_block_limits_vpd(rp, len, op, jo2p);
+ else if (sad || oi)
+ decode_b0_vpd(rp, len, op, jop);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb0\n");
+ break;
+ case 0xb1: /* VPD pages in B0h to BFh range depend on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdc = false;
+ static const char * masn =
+ "Manufactured-assigned serial number VPD page";
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics VPD page";
+ ep = "(SBC)";
+ bdc = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = masn;
+ ep = "(SSC)";
+ break;
+ case PDT_OSD:
+ np = "Security token VPD page";
+ ep = "(OSD)";
+ break;
+ case PDT_ADC:
+ np = masn;
+ ep = "(ADC)";
+ break;
+ default:
+ np = NULL;
+ printf("VPD INQUIRY: page=0x%x, pdt=0x%x\n", 0xb1, pdt);
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdc)
+ decode_block_dev_ch_vpd(rp, len, op, jo2p);
+ else
+ decode_b1_vpd(rp, len, op, jo2p);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb1\n");
+ break;
+ case 0xb2: /* VPD pages in B0h to BFh range depend on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool lbpv = false;
+ bool tas = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Logical block provisioning VPD page";
+ ep = "(SBC)";
+ lbpv = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "TapeAlert supported flags VPD page";
+ ep = "(SSC)";
+ tas = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (lbpv)
+ return decode_block_lb_prov_vpd(rp, len, op, jo2p);
+ else if (tas)
+ decode_tapealert_supported_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb2\n");
+ break;
+ case 0xb3:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ref = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Referrals VPD page";
+ ep = "(SBC)";
+ ref = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ref)
+ decode_referrals_vpd(rp, len, op, jo2p);
+ else
+ decode_b3_vpd(rp, len, op, jo2p);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb3\n");
+ break;
+ case 0xb4:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool sbl = false;
+ bool dtde = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Supported block lengths and protection types VPD page";
+ ep = "(SBC)";
+ sbl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Device transfer data element VPD page";
+ ep = "(SSC)";
+ dtde = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (sbl) {
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p, "logical_block_"
+ "length_and_protection_types_descriptor_list");
+ decode_sup_block_lens_vpd(rp, len, op, jap);
+ } else if (dtde) {
+ if (! jsp->pr_as_json)
+ hex2stdout(rp + 4, len - 4, 1);
+ sgj_js_nv_hex_bytes(jsp, jop, "device_transfer_data_element",
+ rp + 4, len - 4);
+ } else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb4\n");
+ break;
+ case 0xb5:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdce = false;
+ bool lbp = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics VPD page";
+ ep = "(SBC)";
+ bdce = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Logical block protection VPD page";
+ ep = "(SSC)";
+ lbp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdce)
+ decode_block_dev_char_ext_vpd(rp, len, op, jo2p);
+ else if (lbp) { /* VPD_LB_PROTECTION 0xb5 ["lbpro"] (SSC) */
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_block_protection_method_descriptor_list");
+ decode_lb_protection_vpd(rp, len, op, jap);
+ } else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb5\n");
+ break;
+ case 0xb6:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool zbdch = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Zoned block device characteristics VPD page";
+ ep = "(SBC, ZBC)";
+ zbdch = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (zbdch)
+ decode_zbdch_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb6\n");
+ break;
+ case 0xb7:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ble = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits extension VPD page";
+ ep = "(SBC)";
+ ble = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ble)
+ decode_block_limits_ext_vpd(rp, len, op, jo2p);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb7\n");
+ break;
+ case 0xb8:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool fp = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Format presets VPD page";
+ ep = "(SBC)";
+ fp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "format_preset_"
+ "descriptor_list");
+ }
+ if (fp)
+ decode_format_presets_vpd(rp, len, op, jap);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb8\n");
+ break;
+ case 0xb9:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool cpr = false;
+
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Concurrent positioning LBAs VPD page";
+ ep = "(SBC)";
+ cpr = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "lba_range_"
+ "descriptor_list");
+ }
+ if (cpr)
+ decode_con_pos_range_vpd(rp, len, op, jap);
+ else
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ return 0;
+ } else if (! op->do_raw)
+ pr2serr("VPD INQUIRY: page=0xb8\n");
+ break;
+ /* Vendor specific VPD pages (>= 0xc0) */
+ case VPD_UPR_EMC: /* 0xc0 */
+ np = "Unit path report VPD page";
+ ep = "(EMC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_upr_vpd_c0_emc(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_RDAC_VERS: /* 0xc2 */
+ np = "Software Version VPD page";
+ ep = "(RDAC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_rdac_vpd_c2(rp, len, op, jo2p);
+ }
+ break;
+ case VPD_RDAC_VAC: /* 0xc9 */
+ np = "Volume access control VPD page";
+ ep = "(RDAC)";
+ if (!op->do_raw && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ res = vpd_fetch_page(sg_fd, rp, pn, -1, qt, vb, &len);
+ if (res)
+ break;
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_rdac_vpd_c9(rp, len, op, jo2p);
+ }
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde */
+ np = "NVMe Identify Controller Response VPD page";
+ /* NVMe: Identify Controller data structure (CNS 01h) */
+ ep = "(sg3_utils)";
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (res) {
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ break;
+ }
+ if (op->do_raw) {
+ dStrRaw((const char *)rp, len);
+ break;
+ }
+ pdt = rp[0] & PDT_MASK;
+ if (op->do_hex < 3) {
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ }
+ if (len < 16) {
+ pr2serr("%s expected to be > 15 bytes long (got: %d)\n", ep, len);
+ break;
+ } else {
+ int n = len - 16;
+
+ if (n > 4096) {
+ pr2serr("NVMe Identify response expected to be <= 4096 "
+ "bytes (got: %d)\n", n);
+ break;
+ }
+ if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ sgj_js_nv_hex_bytes(jsp, jo2p, "response_bytes", rp + 16, n);
+ } else
+ hex2stdout(rp + 16, n, 1);
+ }
+ break;
+ default:
+ bad = true;
+ break;
+ }
+ if (bad) {
+ if ((pn > 0) && (pn < 0x80)) {
+ if (!op->do_raw && (op->do_hex < 3))
+ printf("VPD INQUIRY: ASCII information page, FRU code=0x%x\n",
+ pn);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw((const char *)rp, len);
+ else
+ decode_ascii_inf(rp, len, op);
+ }
+ } else {
+ if (op->do_hex < 3)
+ pr2serr(" Only hex output supported.\n");
+ return vpd_mainly_hex(sg_fd, op, NULL, off);
+ }
+ }
+out:
+ if (res) {
+ char b[80];
+
+ if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr(" inquiry: field in cdb illegal (page not "
+ "supported)\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr(" inquiry: %s\n", b);
+ }
+ }
+ return res;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+
+static void
+nvme_hex_raw(const uint8_t * b, int b_len, const struct opts_t * op)
+{
+ if (op->do_raw)
+ dStrRaw((const char *)b, b_len);
+ else if (op->do_hex) {
+ if (op->do_hex < 3) {
+ printf("data_in buffer:\n");
+ hex2stdout(b, b_len, (2 == op->do_hex));
+ } else
+ hex2stdout(b, b_len, -1);
+ }
+}
+
+static const char * rperf[] = {"Best", "Better", "Good", "Degraded"};
+
+static void
+show_nvme_id_ns(const uint8_t * dinp, int do_long)
+{
+ bool got_eui_128 = false;
+ uint32_t u, k, off, num_lbaf, flbas, flba_info, md_size, lb_size;
+ uint64_t ns_sz, eui_64;
+
+ num_lbaf = dinp[25] + 1; /* spec says this is "0's based value" */
+ flbas = dinp[26] & 0xf; /* index of active LBA format (for this ns) */
+ ns_sz = sg_get_unaligned_le64(dinp + 0);
+ eui_64 = sg_get_unaligned_be64(dinp + 120); /* N.B. big endian */
+ if (! sg_all_zeros(dinp + 104, 16))
+ got_eui_128 = true;
+ printf(" Namespace size/capacity: %" PRIu64 "/%" PRIu64
+ " blocks\n", ns_sz, sg_get_unaligned_le64(dinp + 8));
+ printf(" Namespace utilization: %" PRIu64 " blocks\n",
+ sg_get_unaligned_le64(dinp + 16));
+ if (got_eui_128) { /* N.B. big endian */
+ printf(" NGUID: 0x%02x", dinp[104]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[104 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" NGUID: 0x0\n");
+ if (eui_64)
+ printf(" EUI-64: 0x%" PRIx64 "\n", eui_64); /* N.B. big endian */
+ printf(" Number of LBA formats: %u\n", num_lbaf);
+ printf(" Index LBA size: %u\n", flbas);
+ for (k = 0, off = 128; k < num_lbaf; ++k, off += 4) {
+ printf(" LBA format %u support:", k);
+ if (k == flbas)
+ printf(" <-- active\n");
+ else
+ printf("\n");
+ flba_info = sg_get_unaligned_le32(dinp + off);
+ md_size = flba_info & 0xffff;
+ lb_size = flba_info >> 16 & 0xff;
+ if (lb_size > 31) {
+ pr2serr("%s: logical block size exponent of %u implies a LB "
+ "size larger than 4 billion bytes, ignore\n", __func__,
+ lb_size);
+ continue;
+ }
+ lb_size = 1U << lb_size;
+ ns_sz *= lb_size;
+ ns_sz /= 500*1000*1000;
+ if (ns_sz & 0x1)
+ ns_sz = (ns_sz / 2) + 1;
+ else
+ ns_sz = ns_sz / 2;
+ u = (flba_info >> 24) & 0x3;
+ printf(" Logical block size: %u bytes\n", lb_size);
+ printf(" Approximate namespace size: %" PRIu64 " GB\n", ns_sz);
+ printf(" Metadata size: %u bytes\n", md_size);
+ printf(" Relative performance: %s [0x%x]\n", rperf[u], u);
+ }
+}
+
+/* Send Identify(CNS=0, nsid) and decode the Identify namespace response */
+static int
+nvme_id_namespace(struct sg_pt_base * ptvp, uint32_t nsid,
+ struct sg_nvme_passthru_cmd * id_cmdp, uint8_t * id_dinp,
+ int id_din_len, const struct opts_t * op)
+{
+ int ret = 0;
+ int vb = op->verbose;
+ uint8_t resp[16];
+
+ clear_scsi_pt_obj(ptvp);
+ id_cmdp->nsid = nsid;
+ id_cmdp->cdw10 = 0x0; /* CNS=0x0 Identify NS (CNTID=0) */
+ id_cmdp->cdw11 = 0x0; /* NVMSETID=0 (only valid when CNS=0x4) */
+ id_cmdp->cdw14 = 0x0; /* UUID index (assume not supported) */
+ set_scsi_pt_data_in(ptvp, id_dinp, id_din_len);
+ set_scsi_pt_sense(ptvp, resp, sizeof(resp));
+ set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
+ ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
+ if (vb > 2)
+ pr2serr("%s: do_scsi_pt() result is %d\n", __func__, ret);
+ if (ret) {
+ if (SCSI_PT_DO_BAD_PARAMS == ret)
+ ret = SG_LIB_SYNTAX_ERROR;
+ else if (SCSI_PT_DO_TIMEOUT == ret)
+ ret = SG_LIB_CAT_TIMEOUT;
+ else if (ret < 0)
+ ret = sg_convert_errno(-ret);
+ return ret;
+ }
+ if (op->do_hex || op->do_raw) {
+ nvme_hex_raw(id_dinp, id_din_len, op);
+ return 0;
+ }
+ show_nvme_id_ns(id_dinp, op->do_long);
+ return 0;
+}
+
+static void
+show_nvme_id_ctrl(const uint8_t *dinp, const char *dev_name, int do_long)
+{
+ bool got_fguid;
+ uint8_t ver_min, ver_ter, mtds;
+ uint16_t ver_maj, oacs, oncs;
+ uint32_t k, ver, max_nsid, npss, j, n, m;
+ uint64_t sz1, sz2;
+ const uint8_t * up;
+
+ max_nsid = sg_get_unaligned_le32(dinp + 516); /* NN */
+ printf("Identify controller for %s:\n", dev_name);
+ printf(" Model number: %.40s\n", (const char *)(dinp + 24));
+ printf(" Serial number: %.20s\n", (const char *)(dinp + 4));
+ printf(" Firmware revision: %.8s\n", (const char *)(dinp + 64));
+ ver = sg_get_unaligned_le32(dinp + 80);
+ ver_maj = (ver >> 16);
+ ver_min = (ver >> 8) & 0xff;
+ ver_ter = (ver & 0xff);
+ printf(" Version: %u.%u", ver_maj, ver_min);
+ if ((ver_maj > 1) || ((1 == ver_maj) && (ver_min > 2)) ||
+ ((1 == ver_maj) && (2 == ver_min) && (ver_ter > 0)))
+ printf(".%u\n", ver_ter);
+ else
+ printf("\n");
+ oacs = sg_get_unaligned_le16(dinp + 256);
+ if (0x1ff & oacs) {
+ printf(" Optional admin command support:\n");
+ if (0x200 & oacs)
+ printf(" Get LBA status\n"); /* NVMe 1.4 */
+ if (0x100 & oacs)
+ printf(" Doorbell buffer config\n");
+ if (0x80 & oacs)
+ printf(" Virtualization management\n");
+ if (0x40 & oacs)
+ printf(" NVMe-MI send and NVMe-MI receive\n");
+ if (0x20 & oacs)
+ printf(" Directive send and directive receive\n");
+ if (0x10 & oacs)
+ printf(" Device self-test\n");
+ if (0x8 & oacs)
+ printf(" Namespace management and attachment\n");
+ if (0x4 & oacs)
+ printf(" Firmware download and commit\n");
+ if (0x2 & oacs)
+ printf(" Format NVM\n");
+ if (0x1 & oacs)
+ printf(" Security send and receive\n");
+ } else
+ printf(" No optional admin command support\n");
+ oncs = sg_get_unaligned_le16(dinp + 256);
+ if (0x7f & oncs) {
+ printf(" Optional NVM command support:\n");
+ if (0x80 & oncs)
+ printf(" Verify\n"); /* NVMe 1.4 */
+ if (0x40 & oncs)
+ printf(" Timestamp feature\n");
+ if (0x20 & oncs)
+ printf(" Reservations\n");
+ if (0x10 & oncs)
+ printf(" Save and Select fields non-zero\n");
+ if (0x8 & oncs)
+ printf(" Write zeroes\n");
+ if (0x4 & oncs)
+ printf(" Dataset management\n");
+ if (0x2 & oncs)
+ printf(" Write uncorrectable\n");
+ if (0x1 & oncs)
+ printf(" Compare\n");
+ } else
+ printf(" No optional NVM command support\n");
+ printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n",
+ sg_get_unaligned_le16(dinp + 0),
+ sg_get_unaligned_le16(dinp + 2));
+ printf(" IEEE OUI Identifier: 0x%x\n", /* this has been renamed AOI */
+ sg_get_unaligned_le24(dinp + 73));
+ got_fguid = ! sg_all_zeros(dinp + 112, 16);
+ if (got_fguid) {
+ printf(" FGUID: 0x%02x", dinp[112]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[112 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" FGUID: 0x0\n");
+ printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78));
+ if (do_long) { /* Bytes 240 to 255 are reserved for NVME-MI */
+ printf(" NVMe Management Interface [MI] settings:\n");
+ printf(" Enclosure: %d [NVMEE]\n", !! (0x2 & dinp[253]));
+ printf(" NVMe Storage device: %d [NVMESD]\n",
+ !! (0x1 & dinp[253]));
+ printf(" Management endpoint capabilities, over a PCIe port: %d "
+ "[PCIEME]\n",
+ !! (0x2 & dinp[255]));
+ printf(" Management endpoint capabilities, over a SMBus/I2C port: "
+ "%d [SMBUSME]\n", !! (0x1 & dinp[255]));
+ }
+ printf(" Number of namespaces: %u\n", max_nsid);
+ sz1 = sg_get_unaligned_le64(dinp + 280); /* lower 64 bits */
+ sz2 = sg_get_unaligned_le64(dinp + 288); /* upper 64 bits */
+ if (sz2)
+ printf(" Total NVM capacity: huge ...\n");
+ else if (sz1)
+ printf(" Total NVM capacity: %" PRIu64 " bytes\n", sz1);
+ mtds = dinp[77];
+ printf(" Maximum data transfer size: ");
+ if (mtds)
+ printf("%u pages\n", 1U << mtds);
+ else
+ printf("<unlimited>\n");
+
+ if (do_long) {
+ const char * const non_op = "does not process I/O";
+ const char * const operat = "processes I/O";
+ const char * cp;
+
+ printf(" Total NVM capacity: 0 bytes\n");
+ npss = dinp[263] + 1;
+ up = dinp + 2048;
+ for (k = 0; k < npss; ++k, up += 32) {
+ n = sg_get_unaligned_le16(up + 0);
+ n *= (0x1 & up[3]) ? 1 : 100; /* unit: 100 microWatts */
+ j = n / 10; /* unit: 1 milliWatts */
+ m = j % 1000;
+ j /= 1000;
+ cp = (0x2 & up[3]) ? non_op : operat;
+ printf(" Power state %u: Max power: ", k);
+ if (0 == j) {
+ m = n % 10;
+ n /= 10;
+ printf("%u.%u milliWatts, %s\n", n, m, cp);
+ } else
+ printf("%u.%03u Watts, %s\n", j, m, cp);
+ n = sg_get_unaligned_le32(up + 4);
+ if (0 == n)
+ printf(" [ENLAT], ");
+ else
+ printf(" ENLAT=%u, ", n);
+ n = sg_get_unaligned_le32(up + 8);
+ if (0 == n)
+ printf("[EXLAT], ");
+ else
+ printf("EXLAT=%u, ", n);
+ n = 0x1f & up[12];
+ printf("RRT=%u, ", n);
+ n = 0x1f & up[13];
+ printf("RRL=%u, ", n);
+ n = 0x1f & up[14];
+ printf("RWT=%u, ", n);
+ n = 0x1f & up[15];
+ printf("RWL=%u\n", n);
+ }
+ }
+}
+
+/* Send a NVMe Identify(CNS=1) and decode Controller info. If the
+ * device name includes a namespace indication (e.g. /dev/nvme0ns1) then
+ * an Identify namespace command is sent to that namespace (e.g. 1). If the
+ * device name does not contain a namespace indication (e.g. /dev/nvme0)
+ * and --only is not given then nvme_id_namespace() is sent for each
+ * namespace in the controller. Namespaces number sequentially starting at
+ * 1 . The CNS (Controller or Namespace Structure) field is CDW10 7:0, was
+ * only bit 0 in NVMe 1.0 and bits 1:0 in NVMe 1.1, thereafter 8 bits. */
+static int
+do_nvme_identify_ctrl(int pt_fd, const struct opts_t * op)
+{
+ int ret = 0;
+ int vb = op->verbose;
+ uint32_t k, nsid, max_nsid;
+ struct sg_pt_base * ptvp;
+ struct sg_nvme_passthru_cmd identify_cmd;
+ struct sg_nvme_passthru_cmd * id_cmdp = &identify_cmd;
+ uint8_t * id_dinp = NULL;
+ uint8_t * free_id_dinp = NULL;
+ const uint32_t pg_sz = sg_get_page_size();
+ uint8_t resp[16];
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(pt_fd, vb);
+ if (NULL == ptvp) {
+ pr2serr("%s: memory problem\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ memset(id_cmdp, 0, sizeof(*id_cmdp));
+ id_cmdp->opcode = 0x6;
+ nsid = get_pt_nvme_nsid(ptvp);
+ id_cmdp->cdw10 = 0x1; /* CNS=0x1 --> Identify controller */
+ /* id_cmdp->nsid is a "don't care" when CNS=1, so leave as 0 */
+ id_dinp = sg_memalign(pg_sz, pg_sz, &free_id_dinp, false);
+ if (NULL == id_dinp) {
+ pr2serr("%s: sg_memalign problem\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ set_scsi_pt_data_in(ptvp, id_dinp, pg_sz);
+ set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
+ set_scsi_pt_sense(ptvp, resp, sizeof(resp));
+ ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
+ if (vb > 2)
+ pr2serr("%s: do_scsi_pt result is %d\n", __func__, ret);
+ if (ret) {
+ if (SCSI_PT_DO_BAD_PARAMS == ret)
+ ret = SG_LIB_SYNTAX_ERROR;
+ else if (SCSI_PT_DO_TIMEOUT == ret)
+ ret = SG_LIB_CAT_TIMEOUT;
+ else if (ret < 0)
+ ret = sg_convert_errno(-ret);
+ goto err_out;
+ }
+ max_nsid = sg_get_unaligned_le32(id_dinp + 516); /* NN */
+ if (op->do_raw || op->do_hex) {
+ if (op->do_only || (SG_NVME_CTL_NSID == nsid ) ||
+ (SG_NVME_BROADCAST_NSID == nsid)) {
+ nvme_hex_raw(id_dinp, pg_sz, op);
+ goto fini;
+ }
+ goto skip1;
+ }
+ show_nvme_id_ctrl(id_dinp, op->device_name, op->do_long);
+skip1:
+ if (op->do_only)
+ goto fini;
+ if (nsid > 0) {
+ if (! (op->do_raw || (op->do_hex > 2))) {
+ printf(" Namespace %u (deduced from device name):\n", nsid);
+ if (nsid > max_nsid)
+ pr2serr("NSID from device (%u) should not exceed number of "
+ "namespaces (%u)\n", nsid, max_nsid);
+ }
+ ret = nvme_id_namespace(ptvp, nsid, id_cmdp, id_dinp, pg_sz, op);
+ if (ret)
+ goto err_out;
+
+ } else { /* nsid=0 so char device; loop over all namespaces */
+ for (k = 1; k <= max_nsid; ++k) {
+ if ((! op->do_raw) || (op->do_hex < 3))
+ printf(" Namespace %u (of %u):\n", k, max_nsid);
+ ret = nvme_id_namespace(ptvp, k, id_cmdp, id_dinp, pg_sz, op);
+ if (ret)
+ goto err_out;
+ if (op->do_raw || op->do_hex)
+ goto fini;
+ }
+ }
+fini:
+ ret = 0;
+err_out:
+ destruct_scsi_pt_obj(ptvp);
+ free(free_id_dinp);
+ return ret;
+}
+#endif /* (HAVE_NVME && (! IGNORE_NVME)) */
+
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json;
+ int res, n, err;
+ int sg_fd = -1;
+ int ret = 0;
+ int subvalue = 0;
+ int inhex_len = 0;
+ int inraw_len = 0;
+ const char * cp;
+ const struct svpd_values_name_t * vnp;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op;
+
+ op = &opts;
+ op->invoker = SG_VPD_INV_SG_INQ;
+ op->vpd_pn = -1;
+ op->vend_prod_num = -1;
+ op->page_pdt = -1;
+ op->do_block = -1; /* use default for OS */
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ usage_for(op);
+ if (op->do_help > 1) {
+ pr2serr("\n>>> Available VPD page abbreviations:\n");
+ enumerate_vpds();
+ }
+ return 0;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+ jsp = &op->json_st;
+ as_json = jsp->pr_as_json;
+ if (op->page_str) {
+ if (op->vpd_pn >= 0) {
+ pr2serr("Given '-p' option and another option that "
+ "implies a page\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ('-' == op->page_str[0])
+ op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
+ else if (isalpha((uint8_t)op->page_str[0])) {
+ vnp = sdp_find_vpd_by_acron(op->page_str);
+ if (NULL == vnp) {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new)
+ pr2serr("abbreviation %s given to '--page=' "
+ "not recognized\n", op->page_str);
+ else
+ pr2serr("abbreviation %s given to '-p=' "
+ "not recognized\n", op->page_str);
+#else
+ pr2serr("abbreviation %s given to '--page=' "
+ "not recognized\n", op->page_str);
+#endif
+ pr2serr(">>> Available abbreviations:\n");
+ enumerate_vpds();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ // if ((1 != op->do_hex) && (0 == op->do_raw))
+ if (0 == op->do_raw)
+ op->do_decode = true;
+ op->vpd_pn = vnp->value;
+ subvalue = vnp->subvalue;
+ op->page_pdt = vnp->pdt;
+ } else {
+ cp = strchr(op->page_str, ',');
+ if (cp && op->vend_prod) {
+ pr2serr("the --page=pg,vp and the --vendor=vp forms overlap, "
+ "choose one or the other\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ op->vpd_pn = sg_get_num_nomult(op->page_str);
+ if ((op->vpd_pn < 0) || (op->vpd_pn > 255)) {
+ pr2serr("Bad page code value after '-p' option\n");
+ printf("Available standard VPD pages:\n");
+ enumerate_vpds(/* 1, 1 */);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (cp) {
+ if (isdigit((uint8_t)*(cp + 1)))
+ op->vend_prod_num = sg_get_num_nomult(cp + 1);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(cp + 1);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after comma in '-p' "
+ "option\n");
+ if (op->vend_prod_num < 0)
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num =
+ svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ }
+ }
+ if (op->verbose > 3)
+ pr2serr("'--page=' matched pn=%d [0x%x], subvalue=%d\n",
+ op->vpd_pn, op->vpd_pn, subvalue);
+#if 0
+ else {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new) {
+ n = sg_get_num(op->page_str);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Bad argument to '--page=', "
+ "expecting 0 to 255 inclusive\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+ } else {
+ int num;
+ unsigned int u;
+
+ num = sscanf(op->page_str, "%x", &u);
+ if ((1 != num) || (u > 255)) {
+ pr2serr("Inappropriate value after '-o=' "
+ "or '-p=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ n = u;
+ }
+#else
+ n = sg_get_num(op->page_str);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Bad argument to '--page=', "
+ "expecting 0 to 255 inclusive\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+#endif /* SG_SCSI_STRINGS */
+ op->vpd_pn = n;
+ }
+#endif
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ subvalue = op->vend_prod_num;
+ }
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ rsp_buff = sg_memalign(rsp_buff_sz, 0 /* page align */, &free_rsp_buff,
+ false);
+ if (NULL == rsp_buff) {
+ pr2serr("Unable to allocate %d bytes on heap\n", rsp_buff_sz);
+ return sg_convert_errno(ENOMEM);
+ }
+ if (op->sinq_inraw_fn) {
+ if (op->do_cmddt) {
+ pr2serr("Don't support --cmddt with --sinq-inraw= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if ((ret = sg_f2hex_arr(op->sinq_inraw_fn, true, false, rsp_buff,
+ &inraw_len, rsp_buff_sz))) {
+ goto err_out;
+ }
+ if (inraw_len < 36) {
+ pr2serr("Unable to read 36 or more bytes from %s\n",
+ op->sinq_inraw_fn);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ memcpy(op->std_inq_a, rsp_buff, 36);
+ op->std_inq_a_valid = true;
+ }
+ if (op->inhex_fn) {
+ if (op->device_name) {
+ pr2serr("Cannot have both a DEVICE and --inhex= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_cmddt) {
+ pr2serr("Don't support --cmddt with --inhex= option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ err = sg_f2hex_arr(op->inhex_fn, !!op->do_raw, false, rsp_buff,
+ &inhex_len, rsp_buff_sz);
+ if (err) {
+ if (err < 0)
+ err = sg_convert_errno(-err);
+ ret = err;
+ goto err_out;
+ }
+ op->do_raw = 0; /* don't want raw on output with --inhex= */
+ if (-1 == op->vpd_pn) { /* may be able to deduce VPD page */
+ if (op->page_pdt < 0)
+ op->page_pdt = PDT_MASK & rsp_buff[0];
+ if ((0x2 == (0xf & rsp_buff[3])) && (rsp_buff[2] > 2)) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is a standard "
+ "INQUIRY\n");
+ } else if (rsp_buff[2] <= 2) {
+ /*
+ * Removable devices have the RMB bit set, which would
+ * present itself as vpd page 0x80 output if we're not
+ * careful
+ *
+ * Serial number must be right-aligned ASCII data in
+ * bytes 5-7; standard INQUIRY will have flags here.
+ */
+ if (rsp_buff[1] == 0x80 &&
+ (rsp_buff[5] < 0x20 || rsp_buff[5] > 0x80 ||
+ rsp_buff[6] < 0x20 || rsp_buff[6] > 0x80 ||
+ rsp_buff[7] < 0x20 || rsp_buff[7] > 0x80)) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is a "
+ "standard INQUIRY\n");
+ } else {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is VPD "
+ "page 0x%x\n", rsp_buff[1]);
+ op->vpd_pn = rsp_buff[1];
+ op->do_vpd = true;
+ if ((1 != op->do_hex) && (0 == op->do_raw))
+ op->do_decode = true;
+ }
+ } else {
+ if (op->verbose)
+ pr2serr("page number unclear from --inhex, hope it's a "
+ "standard INQUIRY\n");
+ }
+ } else
+ op->do_vpd = true;
+ if (op->do_vpd) { /* Allow for multiple VPD pages from 'sg_vpd -a' */
+ op->maxlen = inhex_len;
+ ret = svpd_inhex_decode_all(op, jop);
+ goto fini2;
+ }
+ } else if (0 == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (VPD_NOPE_WANT_STD_INQ == op->vpd_pn)
+ op->vpd_pn = -1; /* now past guessing, set to normal indication */
+
+ if (op->do_export) {
+ if (op->vpd_pn != -1) {
+ if (op->vpd_pn != VPD_DEVICE_ID &&
+ op->vpd_pn != VPD_UNIT_SERIAL_NUM) {
+ pr2serr("Option '--export' only supported for VPD pages 0x80 "
+ "and 0x83\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ op->do_decode = true;
+ op->do_vpd = true;
+ }
+ }
+
+ if ((0 == op->do_cmddt) && (op->vpd_pn >= 0) && op->page_given)
+ op->do_vpd = true;
+
+ if (op->do_raw && op->do_hex) {
+ pr2serr("Can't do hex and raw at the same time\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_vpd && op->do_cmddt) {
+#ifdef SG_SCSI_STRINGS
+ if (op->opt_new)
+ pr2serr("Can't use '--cmddt' with VPD pages\n");
+ else
+ pr2serr("Can't have both '-e' and '-c' (or '-cl')\n");
+#else
+ pr2serr("Can't use '--cmddt' with VPD pages\n");
+#endif
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (((op->do_vpd || op->do_cmddt)) && (op->vpd_pn < 0))
+ op->vpd_pn = 0;
+ if (op->num_pages > 1) {
+ pr2serr("Can only fetch one page (VPD or Cmd) at a time\n");
+ usage_for(op);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->do_descriptors) {
+ if ((op->maxlen > 0) && (op->maxlen < 60)) {
+ pr2serr("version descriptors need INQUIRY response "
+ "length >= 60 bytes\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->do_vpd || op->do_cmddt) {
+ pr2serr("version descriptors require standard INQUIRY\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ }
+ if (op->num_pages && op->do_ata) {
+ pr2serr("Can't use '-A' with an explicit decode VPD page option\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+ if (op->inhex_fn) {
+ if (op->do_vpd) {
+ if (op->do_decode)
+ ret = vpd_decode(-1, op, jop, 0);
+ else
+ ret = vpd_mainly_hex(-1, op, NULL, 0);
+ goto err_out;
+ }
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ else if (op->do_ata) {
+ prepare_ata_identify(op, inhex_len);
+ ret = 0;
+ goto err_out;
+ }
+#endif
+ else {
+ op->maxlen = inhex_len;
+ ret = std_inq_process(-1, op, jop, 0);
+ goto err_out;
+ }
+ }
+
+#if defined(O_NONBLOCK) && defined(O_RDONLY)
+ if (op->do_block >= 0) {
+ n = O_RDONLY | (op->do_block ? 0 : O_NONBLOCK);
+ if ((sg_fd = sg_cmds_open_flags(op->device_name, n,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+
+ } else {
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+#else
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("sg_inq: error opening file: %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+#endif
+ memset(rsp_buff, 0, rsp_buff_sz);
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+ n = check_pt_file_handle(sg_fd, op->device_name, op->verbose);
+ if (op->verbose > 1)
+ pr2serr("check_pt_file_handle()-->%d, page_given: %s\n", n,
+ (op->page_given ? "yes" : "no"));
+ if (n > 2) { /* NVMe char or NVMe block */
+ op->possible_nvme = true;
+ if (! op->page_given) {
+ ret = do_nvme_identify_ctrl(sg_fd, op);
+ goto fini2;
+ }
+ }
+#endif
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+ if (op->do_ata) {
+ res = try_ata_identify(sg_fd, op->do_hex, op->do_raw,
+ op->verbose);
+ if (0 != res) {
+ pr2serr("fetching ATA information failed on %s\n",
+ op->device_name);
+ ret = SG_LIB_CAT_OTHER;
+ } else
+ ret = 0;
+ goto fini3;
+ }
+#endif
+
+ if ((! op->do_cmddt) && (! op->do_vpd)) {
+ /* So it's a standard INQUIRY, try ATA IDENTIFY if that fails */
+ ret = std_inq_process(sg_fd, op, jop, 0);
+ if (ret)
+ goto err_out;
+ } else if (op->do_cmddt) {
+ if (op->vpd_pn < 0)
+ op->vpd_pn = 0;
+ ret = cmddt_process(sg_fd, op);
+ if (ret)
+ goto err_out;
+ } else if (op->do_vpd) {
+ if (op->do_decode) {
+ ret = vpd_decode(sg_fd, op, jop, 0);
+ if (ret)
+ goto err_out;
+ } else {
+ ret = vpd_mainly_hex(sg_fd, op, NULL, 0);
+ if (ret)
+ goto err_out;
+ }
+ }
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+fini2:
+#endif
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+fini3:
+#endif
+
+err_out:
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if ((0 == op->verbose) && (! op->do_export)) {
+ if (! sg_if_can2stderr("sg_inq failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ res = (sg_fd >= 0) ? sg_cmds_close_device(sg_fd) : 0;
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
+
+
+#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \
+ defined(HDIO_GET_IDENTITY)
+/* Following code permits ATA IDENTIFY commands to be performed on
+ ATA non "Packet Interface" devices (e.g. ATA disks).
+ GPL-ed code borrowed from smartmontools (smartmontools.sf.net).
+ Copyright (C) 2002-4 Bruce Allen
+ <smartmontools-support@lists.sourceforge.net>
+ */
+#ifndef ATA_IDENTIFY_DEVICE
+#define ATA_IDENTIFY_DEVICE 0xec
+#define ATA_IDENTIFY_PACKET_DEVICE 0xa1
+#endif
+#ifndef HDIO_DRIVE_CMD
+#define HDIO_DRIVE_CMD 0x031f
+#endif
+
+/* Needed parts of the ATA DRIVE IDENTIFY Structure. Those labeled
+ * word* are NOT used.
+ */
+struct ata_identify_device {
+ unsigned short words000_009[10];
+ uint8_t serial_no[20];
+ unsigned short words020_022[3];
+ uint8_t fw_rev[8];
+ uint8_t model[40];
+ unsigned short words047_079[33];
+ unsigned short major_rev_num;
+ unsigned short minor_rev_num;
+ unsigned short command_set_1;
+ unsigned short command_set_2;
+ unsigned short command_set_extension;
+ unsigned short cfs_enable_1;
+ unsigned short word086;
+ unsigned short csf_default;
+ unsigned short words088_255[168];
+};
+
+#define ATA_IDENTIFY_BUFF_SZ sizeof(struct ata_identify_device)
+#define HDIO_DRIVE_CMD_OFFSET 4
+
+static int
+ata_command_interface(int device, char *data, bool * atapi_flag, int verbose)
+{
+ int err;
+ uint8_t buff[ATA_IDENTIFY_BUFF_SZ + HDIO_DRIVE_CMD_OFFSET];
+ unsigned short get_ident[256];
+
+ if (atapi_flag)
+ *atapi_flag = false;
+ memset(buff, 0, sizeof(buff));
+ if (ioctl(device, HDIO_GET_IDENTITY, &get_ident) < 0) {
+ err = errno;
+ if (ENOTTY == err) {
+ if (verbose > 1)
+ pr2serr("HDIO_GET_IDENTITY failed with ENOTTY, "
+ "try HDIO_DRIVE_CMD ioctl ...\n");
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) "
+ "ioctl failed:\n\t%s [%d]\n",
+ safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
+ return 0;
+ } else {
+ if (verbose)
+ pr2serr("HDIO_GET_IDENTITY ioctl failed:\n"
+ "\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ } else if (verbose > 1)
+ pr2serr("HDIO_GET_IDENTITY succeeded\n");
+ if (0x2 == ((get_ident[0] >> 14) &0x3)) { /* ATAPI device */
+ if (verbose > 1)
+ pr2serr("assume ATAPI device from HDIO_GET_IDENTITY response\n");
+ memset(buff, 0, sizeof(buff));
+ buff[0] = ATA_IDENTIFY_PACKET_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_PACKET_DEVICE) ioctl "
+ "failed:\n\t%s [%d]\n", safe_strerror(err), err);
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) ioctl "
+ "failed:\n\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ }
+ } else if (atapi_flag) {
+ *atapi_flag = true;
+ if (verbose > 1)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) succeeded\n");
+ }
+ } else { /* assume non-packet device */
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ if (ioctl(device, HDIO_DRIVE_CMD, buff) < 0) {
+ err = errno;
+ if (verbose)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) ioctl failed:"
+ "\n\t%s [%d]\n", safe_strerror(err), err);
+ return sg_convert_errno(err);
+ } else if (verbose > 1)
+ pr2serr("HDIO_DRIVE_CMD(ATA_IDENTIFY_DEVICE) succeeded\n");
+ }
+ /* if the command returns data, copy it back */
+ memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
+ return 0;
+}
+
+static void
+show_ata_identify(const struct ata_identify_device * aidp, bool atapi,
+ int vb)
+{
+ int res;
+ char model[64];
+ char serial[64];
+ char firm[64];
+
+ printf("%s device: model, serial number and firmware revision:\n",
+ (atapi ? "ATAPI" : "ATA"));
+ res = sg_ata_get_chars((const unsigned short *)aidp->model,
+ 0, 20, sg_is_big_endian(), model);
+ model[res] = '\0';
+ res = sg_ata_get_chars((const unsigned short *)aidp->serial_no,
+ 0, 10, sg_is_big_endian(), serial);
+ serial[res] = '\0';
+ res = sg_ata_get_chars((const unsigned short *)aidp->fw_rev,
+ 0, 4, sg_is_big_endian(), firm);
+ firm[res] = '\0';
+ printf(" %s %s %s\n", model, serial, firm);
+ if (vb) {
+ if (atapi)
+ printf("ATA IDENTIFY PACKET DEVICE response "
+ "(256 words):\n");
+ else
+ printf("ATA IDENTIFY DEVICE response (256 words):\n");
+ dWordHex((const unsigned short *)aidp, 256, 0,
+ sg_is_big_endian());
+ }
+}
+
+static void
+prepare_ata_identify(const struct opts_t * op, int inhex_len)
+{
+ int n = inhex_len;
+ struct ata_identify_device ata_ident;
+
+ if (n < 16) {
+ pr2serr("%s: got only %d bytes, give up\n", __func__, n);
+ return;
+ } else if (n < 512)
+ pr2serr("%s: expect 512 bytes or more, got %d, continue\n", __func__,
+ n);
+ else if (n > 512)
+ n = 512;
+ memset(&ata_ident, 0, sizeof(ata_ident));
+ memcpy(&ata_ident, rsp_buff, n);
+ show_ata_identify(&ata_ident, false, op->verbose);
+}
+
+/* Returns 0 if successful, else errno of error */
+static int
+try_ata_identify(int ata_fd, int do_hex, int do_raw, int verbose)
+{
+ bool atapi;
+ int res;
+ struct ata_identify_device ata_ident;
+
+ memset(&ata_ident, 0, sizeof(ata_ident));
+ res = ata_command_interface(ata_fd, (char *)&ata_ident, &atapi, verbose);
+ if (res)
+ return res;
+ if ((2 == do_raw) || (3 == do_hex))
+ dWordHex((const unsigned short *)&ata_ident, 256, -2,
+ sg_is_big_endian());
+ else if (do_raw)
+ dStrRaw((const char *)&ata_ident, 512);
+ else {
+ if (do_hex) {
+ if (atapi)
+ printf("ATA IDENTIFY PACKET DEVICE response ");
+ else
+ printf("ATA IDENTIFY DEVICE response ");
+ if (do_hex > 1) {
+ printf("(512 bytes):\n");
+ hex2stdout((const uint8_t *)&ata_ident, 512, 0);
+ } else {
+ printf("(256 words):\n");
+ dWordHex((const unsigned short *)&ata_ident, 256, 0,
+ sg_is_big_endian());
+ }
+ } else
+ show_ata_identify(&ata_ident, atapi, verbose);
+ }
+ return 0;
+}
+#endif
+
+/* structure defined in sg_lib_data.h */
+extern struct sg_lib_simple_value_name_t sg_version_descriptor_arr[];
+
+
+static const char *
+find_version_descriptor_str(int value)
+{
+ int k;
+ const struct sg_lib_simple_value_name_t * vdp;
+
+ for (k = 0; ((vdp = sg_version_descriptor_arr + k) && vdp->name); ++k) {
+ if (value == vdp->value)
+ return vdp->name;
+ if (value < vdp->value)
+ break;
+ }
+ return NULL;
+}
diff --git a/src/sg_inq_data.c b/src/sg_inq_data.c
new file mode 100644
index 00000000..f01bc5f0
--- /dev/null
+++ b/src/sg_inq_data.c
@@ -0,0 +1,555 @@
+/*
+ * A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This is an auxiliary file holding data tables for the sg_inq utility.
+ * It is mainly based on the SCSI SPC-6 document at https://www.t10.org .
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+
+/* Assume index is less than 16 */
+const char * sg_ansi_version_arr[16] =
+{
+ "no conformance claimed",
+ "SCSI-1", /* obsolete, ANSI X3.131-1986 */
+ "SCSI-2", /* obsolete, ANSI X3.131-1994 */
+ "SPC", /* withdrawn, ANSI INCITS 301-1997 */
+ "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */
+ "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */
+ "SPC-4", /* ANSI INCITS 513-2015 */
+ "SPC-5", /* ANSI INCITS 502-2020 */
+ "ecma=1, [8h]",
+ "ecma=1, [9h]",
+ "ecma=1, [Ah]",
+ "ecma=1, [Bh]",
+ "reserved [Ch]",
+ "reserved [Dh]",
+ "reserved [Eh]",
+ "reserved [Fh]",
+};
+
+/* table from SPC-5 revision 16 [sorted numerically (from Annex E.9)] */
+/* Can also be obtained from : https://www.t10.org/lists/stds.txt 20170114 */
+/* Corrected against spc5r21 on 20190312 */
+
+#ifdef SG_SCSI_STRINGS
+
+struct sg_lib_simple_value_name_t sg_version_descriptor_arr[] = {
+ {0x0, "Version Descriptor not supported or No standard identified"},
+ {0x20, "SAM (no version claimed)"},
+ {0x3b, "SAM T10/0994-D revision 18"},
+ {0x3c, "SAM ANSI INCITS 270-1996"},
+ {0x40, "SAM-2 (no version claimed)"},
+ {0x54, "SAM-2 T10/1157-D revision 23"},
+ {0x55, "SAM-2 T10/1157-D revision 24"},
+ {0x5c, "SAM-2 ANSI INCITS 366-2003"},
+ {0x5e, "SAM-2 ISO/IEC 14776-412"},
+ {0x60, "SAM-3 (no version claimed)"},
+ {0x62, "SAM-3 T10/1561-D revision 7"},
+ {0x75, "SAM-3 T10/1561-D revision 13"},
+ {0x76, "SAM-3 T10/1561-D revision 14"},
+ {0x77, "SAM-3 ANSI INCITS 402-2005"},
+ {0x80, "SAM-4 (no version claimed)"},
+ {0x87, "SAM-4 T10/1683-D revision 13"},
+ {0x8b, "SAM-4 T10/1683-D revision 14"},
+ {0x90, "SAM-4 ANSI INCITS 447-2008"},
+ {0x92, "SAM-4 ISO/IEC 14776-414"},
+ {0xa0, "SAM-5 (no version claimed)"},
+ {0xa2, "SAM-5 T10/2104-D revision 4"},
+ {0xa4, "SAM-5 T10/2104-D revision 20"},
+ {0xa6, "SAM-5 T10/2104-D revision 21"},
+ {0xa8, "SAM-5 ANSI INCITS 515-2016"},
+ {0xc0, "SAM-6 (no version claimed)"},
+ {0x120, "SPC (no version claimed)"},
+ {0x13b, "SPC T10/0995-D revision 11a"},
+ {0x13c, "SPC ANSI INCITS 301-1997"},
+ {0x140, "MMC (no version claimed)"},
+ {0x15b, "MMC T10/1048-D revision 10a"},
+ {0x15c, "MMC ANSI INCITS 304-1997"},
+ {0x160, "SCC (no version claimed)"},
+ {0x17b, "SCC T10/1047-D revision 06c"},
+ {0x17c, "SCC ANSI INCITS 276-1997"},
+ {0x180, "SBC (no version claimed)"},
+ {0x19b, "SBC T10/0996-D revision 08c"},
+ {0x19c, "SBC ANSI INCITS 306-1998"},
+ {0x1a0, "SMC (no version claimed)"},
+ {0x1bb, "SMC T10/0999-D revision 10a"},
+ {0x1bc, "SMC ANSI INCITS 314-1998"},
+ {0x1be, "SMC ISO/IEC 14776-351"},
+ {0x1c0, "SES (no version claimed)"},
+ {0x1db, "SES T10/1212-D revision 08b"},
+ {0x1dc, "SES ANSI INCITS 305-1998"},
+ {0x1dd, "SES T10/1212-D revision 08b w/ Amendment ANSI "
+ "INCITS.305/AM1:2000"},
+ {0x1de, "SES ANSI INCITS 305-1998 w/ Amendment ANSI "
+ "INCITS.305/AM1:2000"},
+ {0x1e0, "SCC-2 (no version claimed}"},
+ {0x1fb, "SCC-2 T10/1125-D revision 04"},
+ {0x1fc, "SCC-2 ANSI INCITS 318-1998"},
+ {0x200, "SSC (no version claimed)"},
+ {0x201, "SSC T10/0997-D revision 17"},
+ {0x207, "SSC T10/0997-D revision 22"},
+ {0x21c, "SSC ANSI INCITS 335-2000"},
+ {0x220, "RBC (no version claimed)"},
+ {0x238, "RBC T10/1240-D revision 10a"},
+ {0x23c, "RBC ANSI INCITS 330-2000"},
+ {0x240, "MMC-2 (no version claimed)"},
+ {0x255, "MMC-2 T10/1228-D revision 11"},
+ {0x25b, "MMC-2 T10/1228-D revision 11a"},
+ {0x25c, "MMC-2 ANSI INCITS 333-2000"},
+ {0x260, "SPC-2 (no version claimed)"},
+ {0x267, "SPC-2 T10/1236-D revision 12"},
+ {0x269, "SPC-2 T10/1236-D revision 18"},
+ {0x275, "SPC-2 T10/1236-D revision 19"},
+ {0x276, "SPC-2 T10/1236-D revision 20"},
+ {0x277, "SPC-2 ANSI INCITS 351-2001"},
+ {0x278, "SPC-2 ISO/IEC 14776-452"},
+ {0x280, "OCRW (no version claimed)"},
+ {0x29e, "OCRW ISO/IEC 14776-381"},
+ {0x2a0, "MMC-3 (no version claimed)"},
+ {0x2b5, "MMC-3 T10/1363-D revision 9"},
+ {0x2b6, "MMC-3 T10/1363-D revision 10g"},
+ {0x2b8, "MMC-3 ANSI INCITS 360-2002"},
+ {0x2e0, "SMC-2 (no version claimed)"},
+ {0x2f5, "SMC-2 T10/1383-D revision 5"},
+ {0x2fc, "SMC-2 T10/1383-D revision 6"},
+ {0x2fd, "SMC-2 T10/1383-D revision 7"},
+ {0x2fe, "SMC-2 ANSI INCITS 382-2004"},
+ {0x300, "SPC-3 (no version claimed)"},
+ {0x301, "SPC-3 T10/1416-D revision 7"},
+ {0x307, "SPC-3 T10/1416-D revision 21"},
+ {0x30f, "SPC-3 T10/1416-D revision 22"},
+ {0x312, "SPC-3 T10/1416-D revision 23"},
+ {0x314, "SPC-3 ANSI INCITS 408-2005"},
+ {0x316, "SPC-3 ISO/IEC 14776-453"},
+ {0x320, "SBC-2 (no version claimed)"},
+ {0x322, "SBC-2 T10/1417-D revision 5a"},
+ {0x324, "SBC-2 T10/1417-D revision 15"},
+ {0x33b, "SBC-2 T10/1417-D revision 16"},
+ {0x33d, "SBC-2 ANSI INCITS 405-2005"},
+ {0x33e, "SBC-2 ISO/IEC 14776-322"},
+ {0x340, "OSD (no version claimed)"},
+ {0x341, "OSD T10/1355-D revision 0"},
+ {0x342, "OSD T10/1355-D revision 7a"},
+ {0x343, "OSD T10/1355-D revision 8"},
+ {0x344, "OSD T10/1355-D revision 9"},
+ {0x355, "OSD T10/1355-D revision 10"},
+ {0x356, "OSD ANSI INCITS 400-2004"},
+ {0x360, "SSC-2 (no version claimed)"},
+ {0x374, "SSC-2 T10/1434-D revision 7"},
+ {0x375, "SSC-2 T10/1434-D revision 9"},
+ {0x37d, "SSC-2 ANSI INCITS 380-2003"},
+ {0x380, "BCC (no version claimed)"},
+ {0x3a0, "MMC-4 (no version claimed)"},
+ {0x3b0, "MMC-4 T10/1545-D revision 5"}, /* dropped in spc4r09 */
+ {0x3b1, "MMC-4 T10/1545-D revision 5a"},
+ {0x3bd, "MMC-4 T10/1545-D revision 3"},
+ {0x3be, "MMC-4 T10/1545-D revision 3d"},
+ {0x3bf, "MMC-4 ANSI INCITS 401-2005"},
+ {0x3c0, "ADC (no version claimed)"},
+ {0x3d5, "ADC T10/1558-D revision 6"},
+ {0x3d6, "ADC T10/1558-D revision 7"},
+ {0x3d7, "ADC ANSI INCITS 403-2005"},
+ {0x3e0, "SES-2 (no version claimed)"},
+ {0x3e1, "SES-2 T10/1559-D revision 16"},
+ {0x3e7, "SES-2 T10/1559-D revision 19"},
+ {0x3eb, "SES-2 T10/1559-D revision 20"},
+ {0x3f0, "SES-2 ANSI INCITS 448-2008"},
+ {0x3f2, "SES-2 ISO/IEC 14776-372"},
+ {0x400, "SSC-3 (no version claimed)"},
+ {0x403, "SSC-3 T10/1611-D revision 04a"},
+ {0x407, "SSC-3 T10/1611-D revision 05"},
+ {0x409, "SSC-3 ANSI INCITS 467-2011"},
+ {0x40b, "SSC-3 ISO/IEC 14776-333:2013"},
+ {0x420, "MMC-5 (no version claimed)"},
+ {0x42f, "MMC-5 T10/1675-D revision 03"},
+ {0x431, "MMC-5 T10/1675-D revision 03b"},
+ {0x432, "MMC-5 T10/1675-D revision 04"},
+ {0x434, "MMC-5 ANSI INCITS 430-2007"},
+ {0x440, "OSD-2 (no version claimed)"},
+ {0x444, "OSD-2 T10/1729-D revision 4"},
+ {0x446, "OSD-2 T10/1729-D revision 5"},
+ {0x448, "OSD-2 ANSI INCITS 458-2011"},
+ {0x460, "SPC-4 (no version claimed)"},
+ {0x461, "SPC-4 T10/BSR INCITS 513 revision 16"},
+ {0x462, "SPC-4 T10/BSR INCITS 513 revision 18"},
+ {0x463, "SPC-4 T10/BSR INCITS 513 revision 23"},
+ {0x466, "SPC-4 T10/BSR INCITS 513 revision 36"},
+ {0x468, "SPC-4 T10/BSR INCITS 513 revision 37"},
+ {0x469, "SPC-4 T10/BSR INCITS 513 revision 37a"},
+ {0x46c, "SPC-4 ANSI INCITS 513-2015"},
+ {0x480, "SMC-3 (no version claimed)"},
+ {0x482, "SMC-3 T10/1730-D revision 15"},
+ {0x484, "SMC-3 T10/1730-D revision 16"},
+ {0x486, "SMC-3 ANSI INCITS 484-2012"},
+ {0x4a0, "ADC-2 (no version claimed)"},
+ {0x4a7, "ADC-2 T10/1741-D revision 7"},
+ {0x4aa, "ADC-2 T10/1741-D revision 8"},
+ {0x4ac, "ADC-2 ANSI INCITS 441-2008"},
+ {0x4c0, "SBC-3 (no version claimed)"},
+ {0x4c3, "SBC-3 T10/BSR INCITS 514 revision 35"},
+ {0x4c5, "SBC-3 T10/BSR INCITS 514 revision 36"},
+ {0x4c8, "SBC-3 ANSI INCITS 514-2014"},
+ {0x4e0, "MMC-6 (no version claimed)"},
+ {0x4e3, "MMC-6 T10/1836-D revision 2b"},
+ {0x4e5, "MMC-6 T10/1836-D revision 02g"},
+ {0x4e6, "MMC-6 ANSI INCITS 468-2010"},
+ {0x4e7, "MMC-6 ANSI INCITS 468-2010 + MMC-6/AM1 ANSI INCITS "
+ "468-2010/AM 1"},
+ {0x500, "ADC-3 (no version claimed)"},
+ {0x502, "ADC-3 T10/1895-D revision 04"},
+ {0x504, "ADC-3 T10/1895-D revision 05"},
+ {0x506, "ADC-3 T10/1895-D revision 05a"},
+ {0x50a, "ADC-3 ANSI INCITS 497-2012"},
+ {0x520, "SSC-4 (no version claimed)"},
+ {0x523, "SSC-4 T10/BSR INCITS 516 revision 2"},
+ {0x525, "SSC-4 T10/BSR INCITS 516 revision 3"},
+ {0x527, "SSC-4 SSC-4 ANSI INCITS 516-2013"},
+ {0x560, "OSD-3 (no version claimed)"},
+ {0x580, "SES-3 (no version claimed)"},
+ {0x582, "SES-3 T10/BSR INCITS 518 revision 13"},
+ {0x584, "SES-3 T10/BSR INCITS 518 revision 14"},
+ {0x5a0, "SSC-5 (no version claimed)"},
+ {0x5c0, "SPC-5 (no version claimed)"},
+/* SPC-5 is now a standard [ANSI INCITS 502-2020] but no version code */
+/* SPC-6 is now up to draft 06 but still no version code */
+ {0x5e0, "SFSC (no version claimed)"},
+ {0x5e3, "SFSC BSR INCITS 501 revision 01"},
+ {0x5e5, "SFSC BSR INCITS 501 revision 02"},
+ {0x5e8, "SFSC ANSI INCITS 501-2016"},
+ {0x600, "SBC-4 (no version claimed)"},
+ {0x620, "ZBC (no version claimed)"},
+ {0x622, "ZBC BSR INCITS 536 revision 02"},
+ {0x624, "ZBC BSR INCITS 536 revision 05"},
+ {0x640, "ADC-4 (no version claimed)"},
+ {0x660, "ZBC-2 (no version claimed)"},
+ {0x680, "SES-4 (no version claimed)"},
+ {0x820, "SSA-TL2 (no version claimed)"},
+ {0x83b, "SSA-TL2 T10/1147-D revision 05b"},
+ {0x83c, "SSA-TL2 ANSI INCITS 308-1998"},
+ {0x840, "SSA-TL1 (no version claimed)"},
+ {0x85b, "SSA-TL1 T10/0989-D revision 10b"},
+ {0x85c, "SSA-TL1 ANSI INCITS 295-1996"},
+ {0x860, "SSA-S3P (no version claimed)"},
+ {0x87b, "SSA-S3P T10/1051-D revision 05b"},
+ {0x87c, "SSA-S3P ANSI INCITS 309-1998"},
+ {0x880, "SSA-S2P (no version claimed)"},
+ {0x89b, "SSA-S2P T10/1121-D revision 07b"},
+ {0x89c, "SSA-S2P ANSI INCITS 294-1996"},
+ {0x8a0, "SIP (no version claimed)"},
+ {0x8bb, "SIP T10/0856-D revision 10"},
+ {0x8bc, "SIP ANSI INCITS 292-1997"},
+ {0x8c0, "FCP (no version claimed)"},
+ {0x8db, "FCP T10/0856-D revision 12"},
+ {0x8dc, "FCP ANSI INCITS 269-1996"},
+ {0x8e0, "SBP-2 (no version claimed)"},
+ {0x8fb, "SBP-2 T10/1155-D revision 04"},
+ {0x8fc, "SBP-2 ANSI INCITS 325-1999"},
+ {0x900, "FCP-2 (no version claimed)"},
+ {0x901, "FCP-2 T10/1144-D revision 4"},
+ {0x915, "FCP-2 T10/1144-D revision 7"},
+ {0x916, "FCP-2 T10/1144-D revision 7a"},
+ {0x917, "FCP-2 ANSI INCITS 350-2003"},
+ {0x918, "FCP-2 T10/1144-D revision 8"},
+ {0x920, "SST (no version claimed)"},
+ {0x935, "SST T10/1380-D revision 8b"},
+ {0x940, "SRP (no version claimed)"},
+ {0x954, "SRP T10/1415-D revision 10"},
+ {0x955, "SRP T10/1415-D revision 16a"},
+ {0x95c, "SRP ANSI INCITS 365-2002"},
+ {0x960, "iSCSI (no version claimed)"},
+ {0x961, "iSCSI RFC 7143"},
+ {0x962, "iSCSI RFC 7144"},
+ /* 0x960 up to 0x97f for iSCSI use */
+ {0x980, "SBP-3 (no version claimed)"},
+ {0x982, "SBP-3 T10/1467-D revision 1f"},
+ {0x994, "SBP-3 T10/1467-D revision 3"},
+ {0x99a, "SBP-3 T10/1467-D revision 4"},
+ {0x99b, "SBP-3 T10/1467-D revision 5"},
+ {0x99c, "SBP-3 ANSI INCITS 375-2004"},
+ {0x9a0, "SRP-2 (no version claimed)"},
+ {0x9c0, "ADP (no version claimed)"},
+ {0x9e0, "ADT (no version claimed)"},
+ {0x9f9, "ADT T10/1557-D revision 11"},
+ {0x9fa, "ADT T10/1557-D revision 14"},
+ {0x9fd, "ADT ANSI INCITS 406-2005"},
+ {0xa00, "FCP-3 (no version claimed)"},
+ {0xa07, "FCP-3 T10/1560-D revision 3f"},
+ {0xa0f, "FCP-3 T10/1560-D revision 4"},
+ {0xa11, "FCP-3 ANSI INCITS 416-2006"},
+ {0xa1c, "FCP-3 ISO/IEC 14776-223"},
+ {0xa20, "ADT-2 (no version claimed)"},
+ {0xa22, "ADT-2 T10/1742-D revision 06"},
+ {0xa27, "ADT-2 T10/1742-D revision 08"},
+ {0xa28, "ADT-2 T10/1742-D revision 09"},
+ {0xa2b, "ADT-2 ANSI INCITS 472-2011"},
+ {0xa40, "FCP-4 (no version claimed)"},
+ {0xa42, "FCP-4 T10/1828-D revision 01"},
+ {0xa44, "FCP-4 T10/1828-D revision 02"},
+ {0xa45, "FCP-4 T10/1828-D revision 02b"},
+ {0xa46, "FCP-4 ANSI INCITS 481-2012"},
+ {0xa60, "ADT-3 (no version claimed)"},
+ {0xaa0, "SPI (no version claimed)"},
+ {0xab9, "SPI T10/0855-D revision 15a"},
+ {0xaba, "SPI ANSI INCITS 253-1995"},
+ {0xabb, "SPI T10/0855-D revision 15a with SPI Amnd revision 3a"},
+ {0xabc, "SPI ANSI INCITS 253-1995 with SPI Amnd ANSI INCITS "
+ "253/AM1:1998"},
+ {0xac0, "Fast-20 (no version claimed)"},
+ {0xadb, "Fast-20 T10/1071-D revision 06"},
+ {0xadc, "Fast-20 ANSI INCITS 277-1996"},
+ {0xae0, "SPI-2 (no version claimed)"},
+ {0xafb, "SPI-2 T10/1142-D revision 20b"},
+ {0xafc, "SPI-2 ANSI INCITS 302-1999"},
+ {0xb00, "SPI-3 (no version claimed)"},
+ {0xb18, "SPI-3 T10/1302-D revision 10"},
+ {0xb19, "SPI-3 T10/1302-D revision 13a"},
+ {0xb1a, "SPI-3 T10/1302-D revision 14"},
+ {0xb1c, "SPI-3 ANSI INCITS 336-2000"},
+ {0xb20, "EPI (no version claimed)"},
+ {0xb3b, "EPI T10/1134-D revision 16"},
+ {0xb3c, "EPI ANSI INCITS TR-23 1999"},
+ {0xb40, "SPI-4 (no version claimed)"},
+ {0xb54, "SPI-4 T10/1365-D revision 7"},
+ {0xb55, "SPI-4 T10/1365-D revision 9"},
+ {0xb56, "SPI-4 ANSI INCITS 362-2002"},
+ {0xb59, "SPI-4 T10/1365-D revision 10"},
+ {0xb60, "SPI-5 (no version claimed)"},
+ {0xb79, "SPI-5 T10/1525-D revision 3"},
+ {0xb7a, "SPI-5 T10/1525-D revision 5"},
+ {0xb7b, "SPI-5 T10/1525-D revision 6"},
+ {0xb7c, "SPI-5 ANSI INCITS 367-2004"},
+ {0xbe0, "SAS (no version claimed)"},
+ {0xbe1, "SAS T10/1562-D revision 01"},
+ {0xbf5, "SAS T10/1562-D revision 03"},
+ {0xbfa, "SAS T10/1562-D revision 04"},
+ {0xbfb, "SAS T10/1562-D revision 04"},
+ {0xbfc, "SAS T10/1562-D revision 05"},
+ {0xbfd, "SAS ANSI INCITS 376-2003"},
+ {0xc00, "SAS-1.1 (no version claimed)"},
+ {0xc07, "SAS-1.1 T10/1602-D revision 9"},
+ {0xc0f, "SAS-1.1 T10/1602-D revision 10"},
+ {0xc11, "SAS-1.1 ANSI INCITS 417-2006"},
+ {0xc12, "SAS-1.1 ISO/IEC 14776-151"},
+ {0xc20, "SAS-2 (no version claimed)"},
+ {0xc23, "SAS-2 T10/1760-D revision 14"},
+ {0xc27, "SAS-2 T10/1760-D revision 15"},
+ {0xc28, "SAS-2 T10/1760-D revision 16"},
+ {0xc2a, "SAS-2 ANSI INCITS 457-2010"},
+ {0xc40, "SAS-2.1 (no version claimed)"},
+ {0xc48, "SAS-2.1 T10/2125-D revision 04"},
+ {0xc4a, "SAS-2.1 T10/2125-D revision 06"},
+ {0xc4b, "SAS-2.1 T10/2125-D revision 07"},
+ {0xc4e, "SAS-2.1 ANSI INCITS 478-2011"},
+ {0xc4f, "SAS-2.1 ANSI INCITS 478-2011 w/ Amnd 1 ANSI INCITS "
+ "478/AM1-2014"},
+ {0xc52, "SAS-2.1 ISO/IEC 14776-153"},
+ {0xc60, "SAS-3 (no version claimed)"},
+ {0xc63, "SAS-3 T10/BSR INCITS 519 revision 05a"},
+ {0xc65, "SAS-3 T10/BSR INCITS 519 revision 06"},
+ {0xc68, "SAS-3 ANSI INCITS 519-2014"},
+ {0xc80, "SAS-4 (no version claimed)"},
+ {0xc82, "SAS-4 T10/BSR INCITS 534 revision 08a"},
+ {0xd20, "FC-PH (no version claimed)"},
+ {0xd3b, "FC-PH ANSI INCITS 230-1994"},
+ {0xd3c, "FC-PH ANSI INCITS 230-1994 with Amnd 1 ANSI INCITS "
+ "230/AM1:1996"},
+ {0xd40, "FC-AL (no version claimed)"},
+ {0xd5c, "FC-AL ANSI INCITS 272-1996"},
+ {0xd60, "FC-AL-2 (no version claimed)"},
+ {0xd61, "FC-AL-2 T11/1133-D revision 7.0"},
+ {0xd63, "FC-AL-2 ANSI INCITS 332-1999 with AM1-2003 & AM2-2006"},
+ {0xd64, "FC-AL-2 ANSI INCITS 332-1999 with Amnd 2 AM2-2006"},
+ {0xd65, "FC-AL-2 ISO/IEC 14165-122 with AM1 & AM2"},
+ {0xd7c, "FC-AL-2 ANSI INCITS 332-1999"},
+ {0xd7d, "FC-AL-2 ANSI INCITS 332-1999 with Amnd 1 AM1:2002"},
+ {0xd80, "FC-PH-3 (no version claimed)"},
+ {0xd9c, "FC-PH-3 ANSI INCITS 303-1998"},
+ {0xda0, "FC-FS (no version claimed)"},
+ {0xdb7, "FC-FS T11/1331-D revision 1.2"},
+ {0xdb8, "FC-FS T11/1331-D revision 1.7"},
+ {0xdbc, "FC-FS ANSI INCITS 373-2003"},
+ {0xdbd, "FC-FS ISO/IEC 14165-251"},
+ {0xdc0, "FC-PI (no version claimed)"},
+ {0xddc, "FC-PI ANSI INCITS 352-2002"},
+ {0xde0, "FC-PI-2 (no version claimed)"},
+ {0xde2, "FC-PI-2 T11/1506-D revision 5.0"},
+ {0xde4, "FC-PI-2 ANSI INCITS 404-2006"},
+ {0xe00, "FC-FS-2 (no version claimed)"},
+ {0xe02, "FC-FS-2 ANSI INCITS 242-2007"},
+ {0xe03, "FC-FS-2 ANSI INCITS 242-2007 with AM1 ANSI INCITS 242/AM1-2007"},
+ {0xe20, "FC-LS (no version claimed)"},
+ {0xe21, "FC-LS T11/1620-D revision 1.62"},
+ {0xe29, "FC-LS ANSI INCITS 433-2007"},
+ {0xe40, "FC-SP (no version claimed)"},
+ {0xe42, "FC-SP T11/1570-D revision 1.6"},
+ {0xe45, "FC-SP ANSI INCITS 426-2007"},
+ {0xe60, "FC-PI-3 (no version claimed)"},
+ {0xe62, "FC-PI-3 T11/1625-D revision 2.0"},
+ {0xe68, "FC-PI-3 T11/1625-D revision 2.1"},
+ {0xe6a, "FC-PI-3 T11/1625-D revision 4.0"},
+ {0xe6e, "FC-PI-3 ANSI INCITS 460-2011"},
+ {0xe80, "FC-PI-4 (no version claimed)"},
+ {0xe82, "FC-PI-4 T11/1647-D revision 8.0"},
+ {0xe88, "FC-PI-4 ANSI INCITS 450 -2009"},
+ {0xea0, "FC 10GFC (no version claimed)"},
+ {0xea2, "FC 10GFC ANSI INCITS 364-2003"},
+ {0xea3, "FC 10GFC ISO/IEC 14165-116"},
+ {0xea5, "FC 10GFC ISO/IEC 14165-116 with AM1"},
+ {0xea6, "FC 10GFC ANSI INCITS 364-2003 with AM1 ANSI INCITS 364/AM1-2007"},
+ {0xec0, "FC-SP-2 (no version claimed)"},
+ {0xee0, "FC-FS-3 (no version claimed)"},
+ {0xee2, "FC-FS-3 T11/1861-D revision 0.9"},
+ {0xee7, "FC-FS-3 T11/1861-D revision 1.0"},
+ {0xee9, "FC-FS-3 T11/1861-D revision 1.10"},
+ {0xeeb, "FC-FS-3 ANSI INCITS 470-2011"},
+ {0xf00, "FC-LS-2 (no version claimed)"},
+ {0xf03, "FC-LS-2 T11/2103-D revision 2.11"},
+ {0xf05, "FC-LS-2 T11/2103-D revision 2.21"},
+ {0xf07, "FC-LS-2 ANSI INCITS 477-2011"},
+ {0xf20, "FC-PI-5 (no version claimed)"},
+ {0xf27, "FC-PI-5 T11/2118-D revision 2.00"},
+ {0xf28, "FC-PI-5 T11/2118-D revision 3.00"},
+ {0xf2a, "FC-PI-5 T11/2118-D revision 6.00"},
+ {0xf2b, "FC-PI-5 T11/2118-D revision 6.10"},
+ {0xf2e, "FC-PI-5 ANSI INCITS 479-2011"},
+ {0xf40, "FC-PI-6 (no version claimed)"},
+ {0xf60, "FC-FS-4 (no version claimed)"},
+ {0xf80, "FC-LS-3 (no version claimed)"},
+ {0x12a0, "FC-SCM (no version claimed)"},
+ {0x12a3, "FC-SCM T11/1824DT revision 1.0"},
+ {0x12a5, "FC-SCM T11/1824DT revision 1.1"},
+ {0x12a7, "FC-SCM T11/1824DT revision 1.4"},
+ {0x12aa, "FC-SCM INCITS TR-47 2012"},
+ {0x12c0, "FC-DA-2 (no version claimed)"},
+ {0x12c3, "FC-DA-2 T11/1870DT revision 1.04"},
+ {0x12c5, "FC-DA-2 T11/1870DT revision 1.06"},
+ {0x12c9, "FC-DA-2 INCITS TR-49 2012"},
+ {0x12e0, "FC-DA (no version claimed)"},
+ {0x12e2, "FC-DA T11/1513-DT revision 3.1"},
+ {0x12e8, "FC-DA ANSI INCITS TR-36 2004"},
+ {0x12e9, "FC-DA ISO/IEC 14165-341"},
+ {0x1300, "FC-Tape (no version claimed)"},
+ {0x1301, "FC-Tape T11/1315-D revision 1.16"},
+ {0x131b, "FC-Tape T11/1315-D revision 1.17"},
+ {0x131c, "FC-Tape ANSI INCITS TR-24 1999"},
+ {0x1320, "FC-FLA (no version claimed)"},
+ {0x133b, "FC-FLA T11/1235-D revision 7"},
+ {0x133c, "FC-FLA ANSI INCITS TR-20 1998"},
+ {0x1340, "FC-PLDA (no version claimed)"},
+ {0x135b, "FC-PLDA T11/1162-D revision 2.1"},
+ {0x135c, "FC-PLDA ANSI INCITS TR-19 1998"},
+ {0x1360, "SSA-PH2 (no version claimed)"},
+ {0x137b, "SSA-PH2 T10/1145-D revision 09c"},
+ {0x137c, "SSA-PH2 ANSI INCITS 293-1996"},
+ {0x1380, "SSA-PH3 (no version claimed)"},
+ {0x139b, "SSA-PH3 T10/1146-D revision 05b"},
+ {0x139c, "SSA-PH3 ANSI INCITS 307-1998"},
+ {0x14a0, "IEEE 1394 (no version claimed)"},
+ {0x14bd, "ANSI IEEE 1394:1995"},
+ {0x14c0, "IEEE 1394a (no version claimed)"},
+ {0x14e0, "IEEE 1394b (no version claimed)"},
+ {0x15e0, "ATA/ATAPI-6 (no version claimed)"},
+ {0x15fd, "ATA/ATAPI-6 ANSI INCITS 361-2002"},
+ {0x1600, "ATA/ATAPI-7 (no version claimed)"},
+ {0x1602, "ATA/ATAPI-7 T13/1532-D revision 3"},
+ {0x161c, "ATA/ATAPI-7 ANSI INCITS 397-2005"},
+ {0x161e, "ATA/ATAPI-7 ISO/IEC 24739"},
+ {0x1620, "ATA/ATAPI-8 ATA-AAM Architecture model (no version claimed)"},
+ {0x1621, "ATA/ATAPI-8 ATA-PT Parallel transport (no version claimed)"},
+ {0x1622, "ATA/ATAPI-8 ATA-AST Serial transport (no version claimed)"},
+ {0x1623, "ATA/ATAPI-8 ATA-ACS ATA/ATAPI command set (no version "
+ "claimed)"},
+ {0x1628, "ATA/ATAPI-8 ATA-AAM ANSI INCITS 451-2008"},
+ {0x162a, "ATA/ATAPI-8 ATA8-ACS ANSI INCITS 452-2009 w/ Amendment 1"},
+ {0x1728, "Universal Serial Bus Specification, Revision 1.1"},
+ {0x1729, "Universal Serial Bus Specification, Revision 2.0"},
+ {0x1730, "USB Mass Storage Class Bulk-Only Transport, Revision 1.0"},
+ {0x1740, "UAS (no version claimed)"}, /* USB attached SCSI */
+ {0x1743, "UAS T10/2095-D revision 02"},
+ {0x1747, "UAS T10/2095-D revision 04"},
+ {0x1748, "UAS ANSI INCITS 471-2010"},
+ {0x1749, "UAS ISO/IEC 14776-251:2014"},
+ {0x1761, "ACS-2 (no version claimed)"},
+ {0x1762, "ACS-2 ANSI INCITS 482-2013"},
+ {0x1765, "ACS-3 INCITS 522-2014"},
+ {0x1767, "ACS-4 INCITS 529-2018"},
+ {0x1780, "UAS-2 (no version claimed)"},
+ {0x1ea0, "SAT (no version claimed)"},
+ {0x1ea7, "SAT T10/1711-D rev 8"},
+ {0x1eab, "SAT T10/1711-D rev 9"},
+ {0x1ead, "SAT ANSI INCITS 431-2007"},
+ {0x1ec0, "SAT-2 (no version claimed)"},
+ {0x1ec4, "SAT-2 T10/1826-D revision 06"},
+ {0x1ec8, "SAT-2 T10/1826-D revision 09"},
+ {0x1eca, "SAT-2 ANSI INCITS 465-2010"},
+ {0x1ee0, "SAT-3 (no version claimed)"},
+ {0x1ee2, "SAT-3 T10/BSR INCITS 517 revision 4"},
+ {0x1ee4, "SAT-3 T10/BSR INCITS 517 revision 7"},
+ {0x1ee8, "SAT-3 ANSI INCITS 517-2015"},
+ {0x1f00, "SAT-4 (no version claimed)"},
+ {0x1f02, "SAT-4 T10/BSR INCITS 491 revision 5"},
+ {0x1f04, "SAT-4 T10/BSR INCITS 491 revision 6"},
+ {0x20a0, "SPL (no version claimed)"},
+ {0x20a3, "SPL T10/2124-D revision 6a"},
+ {0x20a5, "SPL T10/2124-D revision 7"},
+ {0x20a7, "SPL ANSI INCITS 476-2011"},
+ {0x20a8, "SPL ANSI INCITS 476-2011 + SPL AM1 INCITS 476/AM1 2012"},
+ {0x20aa, "SPL ISO/IEC 14776-261:2012"},
+ {0x20c0, "SPL-2 (no version claimed)"},
+ {0x20c2, "SPL-2 T10/BSR INCITS 505 revision 4"},
+ {0x20c4, "SPL-2 T10/BSR INCITS 505 revision 5"},
+ {0x20c8, "SPL-2 ANSI INCITS 505-2013"},
+ {0x20e0, "SPL-3 (no version claimed)"},
+ {0x20e4, "SPL-3 T10/BSR INCITS 492 revision 6"},
+ {0x20e6, "SPL-3 T10/BSR INCITS 492 revision 7"},
+ {0x20e8, "SPL-3 ANSI INCITS 492-2015"},
+ {0x2100, "SPL-4 (no version claimed)"},
+ {0x2102, "SPL-4 T10/BSR INCITS 538 revision 08a"},
+ {0x2104, "SPL-4 T10/BSR INCITS 538 revision 10"},
+ {0x2105, "SPL-4 T10/BSR INCITS 538 revision 11"},
+ {0x2120, "SPL-5 (no version claimed)"},
+ {0x21e0, "SOP (no version claimed)"},
+ {0x21e4, "SOP T10/BSR INCITS 489 revision 4"},
+ {0x21e6, "SOP T10/BSR INCITS 489 revision 5"},
+ {0x21e8, "SOP ANSI INCITS 489-2014"},
+ {0x2200, "PQI (no version claimed)"},
+ {0x2204, "PQI T10/BSR INCITS 490 revision 6"},
+ {0x2206, "PQI T10/BSR INCITS 490 revision 7"},
+ {0x2208, "PQI ANSI INCITS 490-2014"},
+ {0x2220, "SOP-2 (no draft published)"},
+ {0x2240, "PQI-2 (no version claimed)"},
+ {0x2242, "PQI-2 T10/BSR INCITS 507 revision 01"},
+ {0x2244, "PQI-2 PQI-2 ANSI INCITS 507-2016"},
+ {0xffc0, "IEEE 1667 (no version claimed)"},
+ {0xffc1, "IEEE 1667-2006"},
+ {0xffc2, "IEEE 1667-2009"},
+ {0xffc3, "IEEE 1667-2015"},
+ {0xffc4, "IEEE 1667-2018"},
+ {0xffff, NULL}, /* sentinel, leave at end */
+};
+
+#else
+
+struct sg_lib_simple_value_name_t sg_version_descriptor_arr[] = {
+ {0xffff, NULL}, /* sentinel, leave at end */
+};
+
+#endif
diff --git a/src/sg_logs.c b/src/sg_logs.c
new file mode 100644
index 00000000..ce6a7e98
--- /dev/null
+++ b/src/sg_logs.c
@@ -0,0 +1,9156 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program outputs information provided by a SCSI LOG SENSE command
+ * and in some cases issues a LOG SELECT command.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_lib_names.h"
+#include "sg_cmds_basic.h"
+#ifdef SG_LIB_WIN32
+#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */
+#endif
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "2.08 20221112"; /* spc6r06 + sbc5r03 */
+
+#define MY_NAME "sg_logs"
+
+#define MX_ALLOC_LEN (0xfffc)
+#define MX_INLEN_ALLOC_LEN (0x800000)
+#define DEF_INLEN_ALLOC_LEN (0x40000)
+#define SHORT_RESP_LEN 128
+
+#define SUPP_PAGES_LPAGE 0x0
+#define BUFF_OVER_UNDER_LPAGE 0x1
+#define WRITE_ERR_LPAGE 0x2
+#define READ_ERR_LPAGE 0x3
+#define READ_REV_ERR_LPAGE 0x4
+#define VERIFY_ERR_LPAGE 0x5
+#define NON_MEDIUM_LPAGE 0x6
+#define LAST_N_ERR_LPAGE 0x7
+#define FORMAT_STATUS_LPAGE 0x8
+#define LAST_N_DEFERRED_LPAGE 0xb
+#define LB_PROV_LPAGE 0xc
+#define TEMPERATURE_LPAGE 0xd
+#define START_STOP_LPAGE 0xe
+#define APP_CLIENT_LPAGE 0xf
+#define SELF_TEST_LPAGE 0x10
+#define SOLID_STATE_MEDIA_LPAGE 0x11
+#define REQ_RECOVERY_LPAGE 0x13
+#define DEVICE_STATS_LPAGE 0x14
+#define BACKGROUND_SCAN_LPAGE 0x15
+#define SAT_ATA_RESULTS_LPAGE 0x16
+#define PROTO_SPECIFIC_LPAGE 0x18
+#define STATS_LPAGE 0x19
+#define PCT_LPAGE 0x1a
+#define TAPE_ALERT_LPAGE 0x2e
+#define IE_LPAGE 0x2f
+#define NOT_SPG_SUBPG 0x0 /* any page: no subpages */
+#define SUPP_SPGS_SUBPG 0xff /* all subpages of ... */
+#define PENDING_DEFECTS_SUBPG 0x1 /* page 0x15 */
+#define BACKGROUND_OP_SUBPG 0x2 /* page 0x15 */
+#define CACHE_STATS_SUBPG 0x20 /* page 0x19 */
+#define CMD_DUR_LIMITS_SUBPG 0x21 /* page 0x19 */
+#define ENV_REPORTING_SUBPG 0x1 /* page 0xd */
+#define UTILIZATION_SUBPG 0x1 /* page 0xe */
+#define ENV_LIMITS_SUBPG 0x2 /* page 0xd */
+#define LPS_MISALIGNMENT_SUBPG 0x3 /* page 0x15 */
+#define ZONED_BLOCK_DEV_STATS_SUBPG 0x1 /* page 0x14 */
+#define LAST_N_INQUIRY_DATA_CH_SUBPG 0x1 /* page 0xb */
+#define LAST_N_MODE_PG_DATA_CH_SUBPG 0x2 /* page 0xb */
+
+/* Vendor product numbers/identifiers */
+#define VP_NONE (-1)
+#define VP_SEAG 0
+#define VP_HITA 1
+#define VP_TOSH 2
+#define VP_LTO5 3
+#define VP_LTO6 4
+#define VP_ALL 99
+
+#define MVP_OFFSET 8
+
+/* Vendor product masks
+ * MVP_STD OR-ed with MVP_<vendor> is a T10 defined lpage with vendor
+ * specific parameter codes (e.g. Information Exceptions lpage [0x2f]) */
+#define MVP_STD (1 << (MVP_OFFSET - 1))
+#define MVP_SEAG (1 << (VP_SEAG + MVP_OFFSET))
+#define MVP_HITA (1 << (VP_HITA + MVP_OFFSET))
+#define MVP_TOSH (1 << (VP_TOSH + MVP_OFFSET))
+#define MVP_LTO5 (1 << (VP_LTO5 + MVP_OFFSET))
+#define MVP_LTO6 (1 << (VP_LTO6 + MVP_OFFSET))
+
+#define OVP_LTO (MVP_LTO5 | MVP_LTO6)
+#define OVP_ALL (~0)
+
+
+#define PCB_STR_LEN 128
+
+#define LOG_SENSE_PROBE_ALLOC_LEN 4
+#define LOG_SENSE_DEF_TIMEOUT 64 /* seconds */
+
+static uint8_t * rsp_buff;
+static uint8_t * free_rsp_buff;
+static int rsp_buff_sz = MX_ALLOC_LEN + 4;
+static const int parr_sz = 4096;
+
+static const char * const unknown_s = "unknown";
+static const char * const not_avail = "not available";
+static const char * const param_c = "Parameter code";
+static const char * const param_c_sn = "parameter_code";
+static const char * const as_s_s = "as_string";
+static const char * const rstrict_s = "restricted";
+static const char * const rsv_s = "reserved";
+static const char * const vend_spec = "vendor specific";
+static const char * const not_rep = "not reported";
+static const char * const in_hex = "in hex";
+static const char * const s_key = "sense key";
+
+static struct option long_options[] = {
+ {"All", no_argument, 0, 'A'}, /* equivalent to '-aa' */
+ {"ALL", no_argument, 0, 'A'}, /* equivalent to '-aa' */
+ {"all", no_argument, 0, 'a'},
+ {"brief", no_argument, 0, 'b'},
+ {"control", required_argument, 0, 'c'},
+ {"enumerate", no_argument, 0, 'e'},
+ {"exclude", no_argument, 0, 'E'},
+ {"filter", required_argument, 0, 'f'},
+ {"full", no_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'},
+ {"inhex", required_argument, 0, 'i'},
+ {"json", optional_argument, 0, 'j'},
+ {"list", no_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"name", no_argument, 0, 'n'},
+ {"new", no_argument, 0, 'N'},
+ {"no_inq", no_argument, 0, 'x'},
+ {"no-inq", no_argument, 0, 'x'},
+ {"old", no_argument, 0, 'O'},
+ {"page", required_argument, 0, 'p'},
+ {"paramp", required_argument, 0, 'P'},
+ {"pcb", no_argument, 0, 'q'},
+ {"ppc", no_argument, 0, 'Q'},
+ {"pdt", required_argument, 0, 'D'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'X'},
+ {"reset", no_argument, 0, 'R'},
+ {"sp", no_argument, 0, 's'},
+ {"select", no_argument, 0, 'S'},
+ {"temperature", no_argument, 0, 't'},
+ {"transport", no_argument, 0, 'T'},
+ {"undefined", no_argument, 0, 'u'},
+ {"vendor", required_argument, 0, 'M'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_full;
+ bool do_name;
+ bool do_pcb;
+ bool do_ppc;
+ bool do_raw;
+ bool do_pcreset;
+ bool do_select;
+ bool do_sp;
+ bool do_temperature;
+ bool do_transport;
+ bool exclude_vendor;
+ bool filter_given;
+ bool maxlen_given;
+ bool o_readonly;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_all;
+ int do_brief;
+ int do_enumerate;
+ int do_help;
+ int do_hex;
+ int do_list;
+ int dstrhex_no_ascii; /* value for dStrHex() no_ascii argument */
+ int hex2str_oformat; /* value for hex2str() oformat argument */
+ int vend_prod_num; /* one of the VP_* constants or -1 (def) */
+ int deduced_vpn; /* deduced vendor_prod_num; from INQUIRY, etc */
+ int verbose;
+ int filter;
+ int page_control;
+ int maxlen;
+ int pg_code;
+ int subpg_code;
+ int paramp;
+ int no_inq;
+ int dev_pdt; /* from device or --pdt=DT */
+ int decod_subpg_code;
+ int undefined_hex; /* hex format of undefined/unrecognized fields */
+ const char * device_name;
+ const char * in_fn;
+ const char * pg_arg;
+ const char * vend_prod;
+ const struct log_elem * lep;
+ sgj_state json_st;
+};
+
+
+struct log_elem {
+ int pg_code;
+ int subpg_code; /* only unless subpg_high>0 then this is only */
+ int subpg_high; /* when >0 this is high end of subpage range */
+ int pdt; /* -1 for all */
+ int flags; /* bit mask; or-ed with MVP_* constants */
+ const char * name;
+ const char * acron;
+ bool (*show_pagep)(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+ /* Returns true if done */
+};
+
+struct vp_name_t {
+ int vend_prod_num; /* vendor/product identifier */
+ const char * acron;
+ const char * name;
+ const char * t10_vendorp;
+ const char * t10_productp;
+};
+
+static const char * ls_s = "log_sense: ";
+
+static bool show_supported_pgs_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_supported_pgs_sub_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_buffer_over_under_run_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_error_counter_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_non_medium_error_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_last_n_error_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_format_status_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_last_n_deferred_error_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_last_n_inq_data_ch_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_last_n_mode_pg_data_ch_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_lb_provisioning_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_sequential_access_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_temperature_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_start_stop_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_utilization_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_app_client_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_self_test_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_solid_state_media_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_device_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_media_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_dt_device_status_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_tapealert_response_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_requested_recovery_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_background_scan_results_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_zoned_block_dev_stats(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_pending_defects_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_background_op_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_lps_misalignment_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_element_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_service_buffer_info_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_ata_pt_results_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_tape_diag_data_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_mchanger_diag_data_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_non_volatile_cache_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_volume_stats_pages(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_protocol_specific_port_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_stats_perform_pages(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_cache_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_power_condition_transitions_page(const uint8_t * resp,
+ int len, struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_environmental_reporting_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_environmental_limits_page(const uint8_t * resp, int len,
+ struct opts_t * op,
+ sgj_opaque_p jop);
+static bool show_cmd_dur_limits_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_data_compression_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_tape_alert_ssc_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_ie_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_tape_usage_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_tape_capacity_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_seagate_cache_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_seagate_factory_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_hgst_perf_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+static bool show_hgst_misc_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+
+/* elements in page_number/subpage_number order */
+static struct log_elem log_arr[] = {
+ {SUPP_PAGES_LPAGE, 0, 0, -1, MVP_STD, "Supported log pages", "sp",
+ show_supported_pgs_page}, /* 0, 0 */
+ {SUPP_PAGES_LPAGE, SUPP_SPGS_SUBPG, 0, -1, MVP_STD, "Supported log pages "
+ "and subpages", "ssp", show_supported_pgs_sub_page}, /* 0, 0xff */
+ {BUFF_OVER_UNDER_LPAGE, 0, 0, -1, MVP_STD, "Buffer over-run/under-run",
+ "bou", show_buffer_over_under_run_page}, /* 0x1, 0x0 */
+ {WRITE_ERR_LPAGE, 0, 0, -1, MVP_STD, "Write error counters", "we",
+ show_error_counter_page}, /* 0x2, 0x0 */
+ {READ_ERR_LPAGE, 0, 0, -1, MVP_STD, "Read error counters", "re",
+ show_error_counter_page}, /* 0x3, 0x0 */
+ {READ_REV_ERR_LPAGE, 0, 0, -1, MVP_STD, "Read reverse error counters",
+ "rre", show_error_counter_page}, /* 0x4, 0x0 */
+ {VERIFY_ERR_LPAGE, 0, 0, -1, MVP_STD, "Verify error counters", "ve",
+ show_error_counter_page}, /* 0x5, 0x0 */
+ {NON_MEDIUM_LPAGE, 0, 0, -1, MVP_STD, "Non medium", "nm",
+ show_non_medium_error_page}, /* 0x6, 0x0 */
+ {LAST_N_ERR_LPAGE, 0, 0, -1, MVP_STD, "Last n error", "lne",
+ show_last_n_error_page}, /* 0x7, 0x0 */
+ {FORMAT_STATUS_LPAGE, 0, 0, 0, MVP_STD, "Format status", "fs",
+ show_format_status_page}, /* 0x8, 0x0 SBC */
+ {LAST_N_DEFERRED_LPAGE, 0, 0, -1, MVP_STD, "Last n deferred error", "lnd",
+ show_last_n_deferred_error_page}, /* 0xb, 0x0 */
+ {LAST_N_DEFERRED_LPAGE, LAST_N_INQUIRY_DATA_CH_SUBPG, 0, -1, MVP_STD,
+ "Last n inquiry data changed", "lnic",
+ show_last_n_inq_data_ch_page}, /* 0xb, 0x1 */
+ {LAST_N_DEFERRED_LPAGE, LAST_N_MODE_PG_DATA_CH_SUBPG, 0, -1, MVP_STD,
+ "Last n mode page data changed", "lnmc",
+ show_last_n_mode_pg_data_ch_page}, /* 0xb, 0x2 */
+ {LB_PROV_LPAGE, 0, 0, 0, MVP_STD, "Logical block provisioning", "lbp",
+ show_lb_provisioning_page}, /* 0xc, 0x0 SBC */
+ {0xc, 0, 0, PDT_TAPE, MVP_STD, "Sequential access device", "sad",
+ show_sequential_access_page}, /* 0xc, 0x0 SSC */
+ {TEMPERATURE_LPAGE, 0, 0, -1, MVP_STD, "Temperature", "temp",
+ show_temperature_page}, /* 0xd, 0x0 */
+ {TEMPERATURE_LPAGE, ENV_REPORTING_SUBPG, 0, -1, MVP_STD, /* 0xd, 0x1 */
+ "Environmental reporting", "enr", show_environmental_reporting_page},
+ {TEMPERATURE_LPAGE, ENV_LIMITS_SUBPG, 0, -1, MVP_STD, /* 0xd, 0x2 */
+ "Environmental limits", "enl", show_environmental_limits_page},
+ {START_STOP_LPAGE, 0, 0, -1, MVP_STD, "Start-stop cycle counter", "sscc",
+ show_start_stop_page}, /* 0xe, 0x0 */
+ {START_STOP_LPAGE, UTILIZATION_SUBPG, 0, 0, MVP_STD, "Utilization",
+ "util", show_utilization_page}, /* 0xe, 0x1 SBC */ /* sbc4r04 */
+ {APP_CLIENT_LPAGE, 0, 0, -1, MVP_STD, "Application client", "ac",
+ show_app_client_page}, /* 0xf, 0x0 */
+ {SELF_TEST_LPAGE, 0, 0, -1, MVP_STD, "Self test results", "str",
+ show_self_test_page}, /* 0x10, 0x0 */
+ {SOLID_STATE_MEDIA_LPAGE, 0, 0, 0, MVP_STD, "Solid state media", "ssm",
+ show_solid_state_media_page}, /* 0x11, 0x0 SBC */
+ {0x11, 0, 0, PDT_TAPE, MVP_STD, "DT Device status", "dtds",
+ show_dt_device_status_page}, /* 0x11, 0x0 SSC,ADC */
+ {0x12, 0, 0, PDT_TAPE, MVP_STD, "Tape alert response", "tar",
+ show_tapealert_response_page}, /* 0x12, 0x0 SSC,ADC */
+ {REQ_RECOVERY_LPAGE, 0, 0, PDT_TAPE, MVP_STD, "Requested recovery", "rr",
+ show_requested_recovery_page}, /* 0x13, 0x0 SSC,ADC */
+ {DEVICE_STATS_LPAGE, 0, 0, PDT_TAPE, MVP_STD, "Device statistics", "ds",
+ show_device_stats_page}, /* 0x14, 0x0 SSC,ADC */
+ {DEVICE_STATS_LPAGE, 0, 0, PDT_MCHANGER, MVP_STD, /* 0x14, 0x0 SMC */
+ "Media changer statistics", "mcs", show_media_stats_page},
+ {DEVICE_STATS_LPAGE, ZONED_BLOCK_DEV_STATS_SUBPG, /* 0x14,0x1 zbc2r01 */
+ 0, 0, MVP_STD, "Zoned block device statistics", "zbds",
+ show_zoned_block_dev_stats},
+ {BACKGROUND_SCAN_LPAGE, 0, 0, 0, MVP_STD, "Background scan results",
+ "bsr", show_background_scan_results_page}, /* 0x15, 0x0 SBC */
+ {BACKGROUND_SCAN_LPAGE, BACKGROUND_OP_SUBPG, 0, 0, MVP_STD,
+ "Background operation", "bop", show_background_op_page},
+ /* 0x15, 0x2 SBC */
+ {BACKGROUND_SCAN_LPAGE, LPS_MISALIGNMENT_SUBPG, 0, 0, MVP_STD,
+ "LPS misalignment", "lps", show_lps_misalignment_page},
+ /* 0x15, 0x3 SBC-4 */
+ {0x15, 0, 0, PDT_MCHANGER, MVP_STD, "Element statistics", "els",
+ show_element_stats_page}, /* 0x15, 0x0 SMC */
+ {0x15, 0, 0, PDT_ADC, MVP_STD, "Service buffers information", "sbi",
+ show_service_buffer_info_page}, /* 0x15, 0x0 ADC */
+ {BACKGROUND_SCAN_LPAGE, PENDING_DEFECTS_SUBPG, 0, 0, MVP_STD,
+ "Pending defects", "pd", show_pending_defects_page}, /* 0x15, 0x1 SBC */
+ {SAT_ATA_RESULTS_LPAGE, 0, 0, 0, MVP_STD, "ATA pass-through results",
+ "aptr", show_ata_pt_results_page}, /* 0x16, 0x0 SAT */
+ {0x16, 0, 0, PDT_TAPE, MVP_STD, "Tape diagnostic data", "tdd",
+ show_tape_diag_data_page}, /* 0x16, 0x0 SSC */
+ {0x16, 0, 0, PDT_MCHANGER, MVP_STD, "Media changer diagnostic data",
+ "mcdd", show_mchanger_diag_data_page}, /* 0x16, 0x0 SMC */
+ {0x17, 0, 0, 0, MVP_STD, "Non volatile cache", "nvc",
+ show_non_volatile_cache_page}, /* 0x17, 0x0 SBC */
+ {0x17, 0, 0xf, PDT_TAPE, MVP_STD, "Volume statistics", "vs",
+ show_volume_stats_pages}, /* 0x17, 0x0...0xf SSC */
+ {PROTO_SPECIFIC_LPAGE, 0, 0, -1, MVP_STD, "Protocol specific port",
+ "psp", show_protocol_specific_port_page}, /* 0x18, 0x0 */
+ {STATS_LPAGE, 0, 0, -1, MVP_STD, "General Statistics and Performance",
+ "gsp", show_stats_perform_pages}, /* 0x19, 0x0 */
+ {STATS_LPAGE, 0x1, 0x1f, -1, MVP_STD, "Group Statistics and Performance",
+ "grsp", show_stats_perform_pages}, /* 0x19, 0x1...0x1f */
+ {STATS_LPAGE, CACHE_STATS_SUBPG, 0, -1, MVP_STD, /* 0x19, 0x20 */
+ "Cache memory statistics", "cms", show_cache_stats_page},
+ {STATS_LPAGE, CMD_DUR_LIMITS_SUBPG, 0, -1, MVP_STD, /* 0x19, 0x21 */
+ "Command duration limits statistics", "cdl",
+ show_cmd_dur_limits_page /* spc6r01 */ },
+ {PCT_LPAGE, 0, 0, -1, MVP_STD, "Power condition transitions", "pct",
+ show_power_condition_transitions_page}, /* 0x1a, 0 */
+ {0x1b, 0, 0, PDT_TAPE, MVP_STD, "Data compression", "dc",
+ show_data_compression_page}, /* 0x1b, 0 SSC */
+ {0x2d, 0, 0, PDT_TAPE, MVP_STD, "Current service information", "csi",
+ NULL}, /* 0x2d, 0 SSC */
+ {TAPE_ALERT_LPAGE, 0, 0, PDT_TAPE, MVP_STD, "Tape alert", "ta",
+ show_tape_alert_ssc_page}, /* 0x2e, 0 SSC */
+ {IE_LPAGE, 0, 0, -1, (MVP_STD | MVP_HITA),
+ "Informational exceptions", "ie", show_ie_page}, /* 0x2f, 0 */
+/* vendor specific */
+ {0x30, 0, 0, PDT_DISK, MVP_HITA, "Performance counters (Hitachi)",
+ "pc_hi", show_hgst_perf_page}, /* 0x30, 0 SBC */
+ {0x30, 0, 0, PDT_TAPE, OVP_LTO, "Tape usage (lto-5, 6)", "tu_",
+ show_tape_usage_page}, /* 0x30, 0 SSC */
+ {0x31, 0, 0, PDT_TAPE, OVP_LTO, "Tape capacity (lto-5, 6)",
+ "tc_", show_tape_capacity_page}, /* 0x31, 0 SSC */
+ {0x32, 0, 0, PDT_TAPE, MVP_LTO5, "Data compression (lto-5)",
+ "dc_", show_data_compression_page}, /* 0x32, 0 SSC; redirect to 0x1b */
+ {0x33, 0, 0, PDT_TAPE, MVP_LTO5, "Write errors (lto-5)", "we_",
+ NULL}, /* 0x33, 0 SSC */
+ {0x34, 0, 0, PDT_TAPE, MVP_LTO5, "Read forward errors (lto-5)",
+ "rfe_", NULL}, /* 0x34, 0 SSC */
+ {0x35, 0, 0, PDT_TAPE, OVP_LTO, "DT Device Error (lto-5, 6)",
+ "dtde_", NULL}, /* 0x35, 0 SSC */
+ {0x37, 0, 0, PDT_DISK, MVP_SEAG, "Cache (seagate)", "c_se",
+ show_seagate_cache_page}, /* 0x37, 0 SBC */
+ {0x37, 0, 0, PDT_DISK, MVP_HITA, "Miscellaneous (hitachi)", "mi_hi",
+ show_hgst_misc_page}, /* 0x37, 0 SBC */
+ {0x37, 0, 0, PDT_TAPE, MVP_LTO5, "Performance characteristics "
+ "(lto-5)", "pc_", NULL}, /* 0x37, 0 SSC */
+ {0x38, 0, 0, PDT_TAPE, MVP_LTO5, "Blocks/bytes transferred "
+ "(lto-5)", "bbt_", NULL}, /* 0x38, 0 SSC */
+ {0x39, 0, 0, PDT_TAPE, MVP_LTO5, "Host port 0 interface errors "
+ "(lto-5)", "hp0_", NULL}, /* 0x39, 0 SSC */
+ {0x3a, 0, 0, PDT_TAPE, MVP_LTO5, "Drive control verification "
+ "(lto-5)", "dcv_", NULL}, /* 0x3a, 0 SSC */
+ {0x3b, 0, 0, PDT_TAPE, MVP_LTO5, "Host port 1 interface errors "
+ "(lto-5)", "hp1_", NULL}, /* 0x3b, 0 SSC */
+ {0x3c, 0, 0, PDT_TAPE, MVP_LTO5, "Drive usage information "
+ "(lto-5)", "dui_", NULL}, /* 0x3c, 0 SSC */
+ {0x3d, 0, 0, PDT_TAPE, MVP_LTO5, "Subsystem statistics (lto-5)",
+ "ss_", NULL}, /* 0x3d, 0 SSC */
+ {0x3e, 0, 0, PDT_DISK, MVP_SEAG, "Factory (seagate)", "f_se",
+ show_seagate_factory_page}, /* 0x3e, 0 SBC */
+ {0x3e, 0, 0, PDT_DISK, MVP_HITA, "Factory (hitachi)", "f_hi",
+ NULL}, /* 0x3e, 0 SBC */
+ {0x3e, 0, 0, PDT_TAPE, OVP_LTO, "Device Status (lto-5, 6)",
+ "ds_", NULL}, /* 0x3e, 0 SSC */
+
+ {-1, -1, -1, -1, 0, NULL, "zzzzz", NULL}, /* end sentinel */
+};
+
+/* Supported vendor product codes */
+/* Arrange in alphabetical order by acronym */
+static struct vp_name_t vp_arr[] = {
+ {VP_SEAG, "sea", "Seagate", "SEAGATE", NULL},
+ {VP_HITA, "hit", "Hitachi", "HGST", NULL},
+ {VP_HITA, "wdc", "WDC/Hitachi", "WDC", NULL},
+ {VP_TOSH, "tos", "Toshiba", "TOSHIBA", NULL},
+ {VP_LTO5, "lto5", "LTO-5 (tape drive consortium)", NULL, NULL},
+ {VP_LTO6, "lto6", "LTO-6 (tape drive consortium)", NULL, NULL},
+ {VP_ALL, "all", "enumerate all vendor specific", NULL, NULL},
+ {0, NULL, NULL, NULL, NULL},
+};
+
+static char t10_vendor_str[10];
+static char t10_product_str[18];
+
+#ifdef SG_LIB_WIN32
+static bool win32_spt_init_state = false;
+static bool win32_spt_curr_state = false;
+#endif
+
+
+static void
+usage(int hval)
+{
+ if (1 == hval) {
+ pr2serr(
+ "Usage: sg_logs [-ALL] [--all] [--brief] [--control=PC] "
+ "[--enumerate]\n"
+ " [--exclude] [--filter=FL] [--full] [--help] "
+ "[--hex]\n"
+ " [--in=FN] [--json[=JO]] [--list] [--maxlen=LEN] "
+ "[--name]\n"
+ " [--no_inq] [--page=PG] [--paramp=PP] [--pcb] "
+ "[--ppc]\n"
+ " [--pdt=DT] [--raw] [--readonly] [--reset] "
+ "[--select]\n"
+ " [--sp] [--temperature] [--transport] "
+ "[--undefined]\n"
+ " [--vendor=VP] [--verbose] [--version] DEVICE\n"
+ " where the main options are:\n"
+ " --ALL|-A fetch and decode all log pages and "
+ "subpages\n"
+ " --all|-a fetch and decode all log pages, but not "
+ "subpages; use\n"
+ " twice to fetch and decode all log pages "
+ "and subpages\n"
+ " --brief|-b shorten the output of some log pages\n"
+ " --enumerate|-e enumerate known pages, ignore DEVICE. "
+ "Sort order,\n"
+ " '-e': all by acronym; '-ee': non-vendor "
+ "by acronym;\n"
+ " '-eee': all numerically; '-eeee': "
+ "non-v numerically\n"
+ " --filter=FL|-f FL FL is parameter code to display (def: "
+ "all);\n"
+ " with '-e' then FL>=0 enumerate that "
+ "pdt + spc\n"
+ " FL=-1 all (default), FL=-2 spc only\n"
+ " --full|-F drill down in application client log page\n"
+ " --help|-h print usage message then exit. Use twice "
+ "for more help\n"
+ " --hex|-H output response in hex (default: decode if "
+ "known)\n"
+ " --in=FN|-i FN FN is a filename containing a log page "
+ "in ASCII hex\n"
+ " or binary if --raw also given. --inhex=FN "
+ "also accepted\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable\n"
+ " test. Use --json=? for JSON help\n"
+ " --list|-l list supported log pages; twice: list "
+ "supported log\n"
+ " pages and subpages page; thrice: merge of "
+ "both pages\n"
+ " --page=PG|-p PG PG is either log page acronym, PGN or "
+ "PGN,SPGN\n"
+ " where (S)PGN is a (sub) page number\n");
+ pr2serr(
+ " --raw|-r either output response in binary to stdout "
+ "or, if\n"
+ " '--in=FN' is given, FN is decoded as "
+ "binary\n"
+ " --temperature|-t decode temperature (log page 0xd or "
+ "0x2f)\n"
+ " --transport|-T decode transport (protocol specific port "
+ "0x18) page\n"
+ " --vendor=VP|-M VP vendor/product abbreviation [or "
+ "number]\n"
+ " --verbose|-v increase verbosity\n\n"
+ "Performs a SCSI LOG SENSE (or LOG SELECT) command and decodes "
+ "the response.\nIf only DEVICE is given then '-p sp' (supported "
+ "pages) is assumed. Use\n'-e' to see known pages and their "
+ "acronyms. For more help use '-hh'.\n");
+ } else if (hval > 1) {
+ pr2serr(
+ " where sg_logs' lesser used options are:\n"
+ " --control=PC|-c PC page control(PC) (default: 1)\n"
+ " 0: current threshold, 1: current "
+ "cumulative\n"
+ " 2: default threshold, 3: default "
+ "cumulative\n"
+ " --exclude|-E exclude vendor specific pages and "
+ "parameters\n"
+ " --list|-l list supported log page names (equivalent to "
+ "'-p sp')\n"
+ " use twice to list supported log page and "
+ "subpage names\n"
+ " --maxlen=LEN|-m LEN max response length (def: 0 "
+ "-> everything)\n"
+ " when > 1 will request LEN bytes\n"
+ " --name|-n decode some pages into multiple name=value "
+ "lines\n"
+ " --no_inq|-x no initial INQUIRY output (twice: and no "
+ "INQUIRY call)\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --paramp=PP|-P PP place PP in parameter pointer field in "
+ "cdb (def: 0)\n"
+ " --pcb|-q show parameter control bytes in decoded "
+ "output\n"
+ " --ppc|-Q set the Parameter Pointer Control (PPC) bit "
+ "(def: 0)\n"
+ " --pdt=DT|-D DT DT is peripheral device type to use with "
+ "'--in=FN'\n"
+ " or when '--no_inq' is used\n"
+ " --readonly|-X open DEVICE read-only (def: first "
+ "read-write then if\n"
+ " fails try open again read-only)\n"
+ " --reset|-R reset log parameters (takes PC and SP into "
+ "account)\n"
+ " (uses PCR bit in LOG SELECT)\n"
+ " --select|-S perform LOG SELECT (def: LOG SENSE)\n"
+ " --sp|-s set the Saving Parameters (SP) bit (def: "
+ "0)\n"
+ " --undefined|-u hex format for undefined/unrecognized "
+ "fields,\n"
+ " use one or more times; format as per "
+ "--hex\n"
+ " --version|-V output version string then exit\n\n"
+ "If DEVICE and --select are given, a LOG SELECT command will be "
+ "issued.\nIf DEVICE is not given and '--in=FN' is given then FN "
+ "will decoded as if\nit were a log page. The contents of FN "
+ "generated by either a prior\n'sg_logs -HHH ...' invocation or "
+ "by a text editor.\nLog pages defined in SPC are common "
+ "to all device types.\n");
+ }
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_logs [-a] [-A] [-b] [-c=PC] [-D=DT] [-e] [-E] [-f=FL] "
+ "[-F]\n"
+ " [-h] [-H] [-i=FN] [-l] [-L] [-m=LEN] [-M=VP] "
+ "[-n] [-p=PG]\n"
+ " [-paramp=PP] [-pcb] [-ppc] [-r] [-select] [-sp] "
+ "[-t] [-T]\n"
+ " [-u] [-v] [-V] [-x] [-X] [-?] DEVICE\n"
+ " where:\n"
+ " -a fetch and decode all log pages\n"
+ " -A fetch and decode all log pages and subpages\n"
+ " -b shorten the output of some log pages\n"
+ " -c=PC page control(PC) (default: 1)\n"
+ " 0: current threshold, 1: current cumulative\n"
+ " 2: default threshold, 3: default cumulative\n"
+ " -e enumerate known log pages\n"
+ " -D=DT DT is peripheral device type to use with "
+ "'--in=FN'\n"
+ " -E exclude vendor specific pages and parameters\n"
+ " -f=FL filter match parameter code or pdt\n"
+ " -F drill down in application client log page\n"
+ " -h output in hex (default: decode if known)\n"
+ " -H output in hex (same as '-h')\n"
+ " -i=FN FN is a filename containing a log page "
+ "in ASCII hex.\n"
+ " -l list supported log page names (equivalent to "
+ "'-p=0')\n"
+ " -L list supported log page and subpages names "
+ "(equivalent to\n"
+ " '-p=0,ff')\n"
+ " -m=LEN max response length (decimal) (def: 0 "
+ "-> everything)\n"
+ " -M=VP vendor/product abbreviation [or number]\n"
+ " -n decode some pages into multiple name=value "
+ "lines\n"
+ " -N|--new use new interface\n"
+ " -p=PG PG is an acronym (def: 'sp')\n"
+ " -p=PGN page code in hex (def: 0)\n"
+ " -p=PGN,SPGN page and subpage codes in hex, (defs: 0,0)\n"
+ " -paramp=PP (in hex) (def: 0)\n"
+ " -pcb show parameter control bytes in decoded "
+ "output\n");
+ printf(" -ppc set the Parameter Pointer Control (PPC) bit "
+ "(def: 0)\n"
+ " -r reset log parameters (takes PC and SP into "
+ "account)\n"
+ " (uses PCR bit in LOG SELECT)\n"
+ " -select perform LOG SELECT (def: LOG SENSE)\n"
+ " -sp set the Saving Parameters (SP) bit (def: 0)\n"
+ " -t outputs temperature log page (0xd)\n"
+ " -T outputs transport (protocol specific port) log "
+ "page (0x18)\n"
+ " -u hex format for undefined/unrecognized fields\n"
+ " -v increase verbosity\n"
+ " -V output version string\n"
+ " -x no initial INQUIRY output (twice: no INQUIRY call)\n"
+ " -X open DEVICE read-only (def: first read-write then "
+ "if fails\n"
+ " try open again with read-only)\n"
+ " -? output this usage message\n\n"
+ "Performs a SCSI LOG SENSE (or LOG SELECT) command\n");
+}
+
+/* Return vendor product mask given vendor product number */
+static int
+get_vp_mask(int vpn)
+{
+ if (vpn < 0)
+ return 0;
+ else
+ return (vpn >= (32 - MVP_OFFSET)) ? OVP_ALL :
+ (1 << (vpn + MVP_OFFSET));
+}
+
+static int
+asort_comp(const void * lp, const void * rp)
+{
+ const struct log_elem * const * lepp =
+ (const struct log_elem * const *)lp;
+ const struct log_elem * const * repp =
+ (const struct log_elem * const *)rp;
+
+ return strcmp((*lepp)->acron, (*repp)->acron);
+}
+
+static void
+enumerate_helper(const struct log_elem * lep, bool first,
+ const struct opts_t * op)
+{
+ char b[80];
+ char bb[80];
+ const char * cp;
+ bool vendor_lpage = ! (MVP_STD & lep->flags);
+
+ if (first) {
+ if (1 == op->verbose) {
+ printf("acronym pg[,spg] name\n");
+ printf("===============================================\n");
+ } else if (2 == op->verbose) {
+ printf("acronym pg[,spg] pdt name\n");
+ printf("===================================================\n");
+ }
+ }
+ if ((0 == (op->do_enumerate % 2)) && vendor_lpage)
+ return; /* if do_enumerate is even then skip vendor pages */
+ else if ((! op->filter_given) || (-1 == op->filter))
+ ; /* otherwise enumerate all lpages if no --filter= */
+ else if (-2 == op->filter) { /* skip non-SPC pages */
+ if (lep->pdt >= 0)
+ return;
+ } else if (-10 == op->filter) { /* skip non-disk like pages */
+ if (sg_lib_pdt_decay(lep->pdt) != 0)
+ return;
+ } else if (-11 == op->filter) { /* skip tape like device pages */
+ if (sg_lib_pdt_decay(lep->pdt) != 1)
+ return;
+ } else if ((op->filter >= 0) && (op->filter <= 0x1f)) {
+ if ((lep->pdt >= 0) && (lep->pdt != op->filter) &&
+ (lep->pdt != sg_lib_pdt_decay(op->filter)))
+ return;
+ }
+ if (op->vend_prod_num >= 0) {
+ if (! (lep->flags & get_vp_mask(op->vend_prod_num)))
+ return;
+ }
+ if (op->deduced_vpn >= 0) {
+ if (! (lep->flags & get_vp_mask(op->deduced_vpn)))
+ return;
+ }
+ if (lep->subpg_high > 0)
+ snprintf(b, sizeof(b), "0x%x,0x%x->0x%x", lep->pg_code,
+ lep->subpg_code, lep->subpg_high);
+ else if (lep->subpg_code > 0)
+ snprintf(b, sizeof(b), "0x%x,0x%x", lep->pg_code,
+ lep->subpg_code);
+ else
+ snprintf(b, sizeof(b), "0x%x", lep->pg_code);
+ snprintf(bb, sizeof(bb), "%-16s", b);
+ cp = (op->verbose && (! lep->show_pagep)) ? " [hex only]" : "";
+ if (op->verbose > 1) {
+ if (lep->pdt < 0)
+ printf(" %-8s%s- %s%s\n", lep->acron, bb, lep->name, cp);
+ else
+ printf(" %-8s%s0x%02x %s%s\n", lep->acron, bb, lep->pdt,
+ lep->name, cp);
+ } else
+ printf(" %-8s%s%s%s\n", lep->acron, bb, lep->name, cp);
+}
+
+static void
+enumerate_pages(const struct opts_t * op)
+{
+ int j;
+ struct log_elem * lep;
+ struct log_elem ** lep_arr;
+
+ if (op->do_enumerate < 3) { /* -e, -ee: sort by acronym */
+ int k;
+ struct log_elem ** lepp;
+
+ for (k = 0, lep = log_arr; lep->pg_code >=0; ++lep, ++k)
+ ;
+ ++k;
+ lep_arr = (struct log_elem **)calloc(k, sizeof(struct log_elem *));
+ if (NULL == lep_arr) {
+ pr2serr("%s: out of memory\n", __func__);
+ return;
+ }
+ for (k = 0, lep = log_arr; lep->pg_code >=0; ++lep, ++k)
+ lep_arr[k] = lep;
+ lep_arr[k++] = lep; /* put sentinel on end */
+ qsort(lep_arr, k, sizeof(struct log_elem *), asort_comp);
+ printf("Known log pages in acronym order:\n");
+ for (lepp = lep_arr, j = 0; (*lepp)->pg_code >=0; ++lepp, ++j)
+ enumerate_helper(*lepp, (0 == j), op);
+ free(lep_arr);
+ } else { /* -eee, -eeee numeric sort (as per table) */
+ printf("Known log pages in numerical order:\n");
+ for (lep = log_arr, j = 0; lep->pg_code >=0; ++lep, ++j)
+ enumerate_helper(lep, (0 == j), op);
+ }
+}
+
+static const struct log_elem *
+acron_search(const char * acron)
+{
+ const struct log_elem * lep;
+
+ for (lep = log_arr; lep->pg_code >=0; ++lep) {
+ if (0 == strcmp(acron, lep->acron))
+ return lep;
+ }
+ return NULL;
+}
+
+static int
+find_vpn_by_acron(const char * vp_ap)
+{
+ const struct vp_name_t * vpp;
+
+ for (vpp = vp_arr; vpp->acron; ++vpp) {
+ size_t k;
+ size_t len = strlen(vpp->acron);
+
+ for (k = 0; k < len; ++k) {
+ if (tolower((uint8_t)vp_ap[k]) != (uint8_t)vpp->acron[k])
+ break;
+ }
+ if (k < len)
+ continue;
+ return vpp->vend_prod_num;
+ }
+ return VP_NONE;
+}
+
+/* Find vendor product number using T10 VENDOR and PRODUCT ID fields in a
+ INQUIRY response. */
+static int
+find_vpn_by_inquiry(void)
+{
+ size_t len;
+ size_t t10_v_len = strlen(t10_vendor_str);
+ size_t t10_p_len = strlen(t10_product_str);
+ const struct vp_name_t * vpp;
+
+ if ((0 == t10_v_len) && (0 == t10_p_len))
+ return VP_NONE;
+ for (vpp = vp_arr; vpp->acron; ++vpp) {
+ bool matched = false;
+
+ if (vpp->t10_vendorp && (t10_v_len > 0)) {
+ len = strlen(vpp->t10_vendorp);
+ len = (len > t10_v_len) ? t10_v_len : len;
+ if (strncmp(vpp->t10_vendorp, t10_vendor_str, len))
+ continue;
+ matched = true;
+ }
+ if (vpp->t10_productp && (t10_p_len > 0)) {
+ len = strlen(vpp->t10_productp);
+ len = (len > t10_p_len) ? t10_p_len : len;
+ if (strncmp(vpp->t10_productp, t10_product_str, len))
+ continue;
+ matched = true;
+ }
+ if (matched)
+ return vpp->vend_prod_num;
+ }
+ return VP_NONE;
+}
+
+static void
+enumerate_vp(void)
+{
+ const struct vp_name_t * vpp;
+ bool seen = false;
+
+ for (vpp = vp_arr; vpp->acron; ++vpp) {
+ if (vpp->name) {
+ if (! seen) {
+ printf("\nVendor/product identifiers:\n");
+ seen = true;
+ }
+ printf(" %-10s %d %s\n", vpp->acron,
+ vpp->vend_prod_num, vpp->name);
+ }
+ }
+}
+
+static const struct log_elem *
+pg_subpg_pdt_search(int pg_code, int subpg_code, int pdt, int vpn)
+{
+ const struct log_elem * lep;
+ int d_pdt;
+ int vp_mask = get_vp_mask(vpn);
+
+ d_pdt = sg_lib_pdt_decay(pdt);
+ for (lep = log_arr; lep->pg_code >=0; ++lep) {
+ if (pg_code == lep->pg_code) {
+ if (subpg_code == lep->subpg_code) {
+ if ((MVP_STD & lep->flags) || (0 == vp_mask) ||
+ (vp_mask & lep->flags))
+ ;
+ else
+ continue;
+ if ((lep->pdt < 0) || (pdt == lep->pdt) || (pdt < 0))
+ return lep;
+ else if (d_pdt == lep->pdt)
+ return lep;
+ else if (pdt == sg_lib_pdt_decay(lep->pdt))
+ return lep;
+ } else if ((lep->subpg_high > 0) &&
+ (subpg_code > lep->subpg_code) &&
+ (subpg_code <= lep->subpg_high))
+ return lep;
+ }
+ }
+ return NULL;
+}
+
+static void
+js_snakenv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop,
+ const char * conv2sname, int64_t val_i,
+ bool hex_as_well, const char * str_name,
+ const char * val_s, const char * nex_s)
+{
+
+ if ((NULL == jsp) || (NULL == jop))
+ return;
+ if (sgj_is_snake_name(conv2sname))
+ sgj_js_nv_ihexstr_nex(jsp, jop, conv2sname, val_i, hex_as_well,
+ str_name, val_s, nex_s);
+ else {
+ char b[128];
+
+ sgj_convert_to_snake_name(conv2sname, b, sizeof(b));
+ sgj_js_nv_ihexstr_nex(jsp, jop, b, val_i, hex_as_well, str_name,
+ val_s, nex_s);
+ }
+}
+
+static void
+usage_for(int hval, const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage(hval);
+ else
+ usage_old();
+}
+
+/* Processes command line options according to new option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ while (1) {
+ int c, n;
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aAbc:D:eEf:FhHi:j::lLm:M:nNOp:P:qQrRsStT"
+ "uvVxX", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ ++op->do_all;
+ break;
+ case 'A':
+ op->do_all += 2;
+ break;
+ case 'b':
+ ++op->do_brief;
+ break;
+ case 'c':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 3)) {
+ pr2serr("bad argument to '--control='\n");
+ usage(2);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->page_control = n;
+ break;
+ case 'D':
+ if (0 == memcmp("-1", optarg, 3))
+ op->dev_pdt = -1;
+ else {
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 31)) {
+ pr2serr("bad argument to '--pdt='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->dev_pdt = n;
+ }
+ break;
+ case 'e':
+ ++op->do_enumerate;
+ break;
+ case 'E':
+ op->exclude_vendor = true;
+ break;
+ case 'f':
+ if ('-' == optarg[0]) {
+ n = sg_get_num(optarg + 1);
+ if ((n < 0) || (n > 0x30)) {
+ pr2serr("bad negated argument to '--filter='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->filter = -n;
+ } else {
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("bad argument to '--filter='\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->filter = n;
+ }
+ op->filter_given = true;
+ break;
+ case 'F':
+ op->do_full = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->in_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'l':
+ ++op->do_list;
+ break;
+ case 'L':
+ op->do_list += 2;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (1 == n)) {
+ pr2serr("bad argument to '--maxlen=', from 2 and up "
+ "expected\n");
+ usage(2);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (n < 4) {
+ pr2serr("Warning: setting '--maxlen' to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ op->maxlen_given = true;
+ break;
+ case 'M':
+ if (op->vend_prod) {
+ pr2serr("only one '--vendor=' option permitted\n");
+ usage(2);
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->vend_prod = optarg;
+ break;
+ case 'n':
+ op->do_name = true;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ op->pg_arg = optarg;
+ break;
+ case 'P':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--paramp='\n");
+ usage(2);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->paramp = n;
+ break;
+ case 'q':
+ op->do_pcb = true;
+ break;
+ case 'Q': /* N.B. PPC bit obsoleted in SPC-4 rev 18 */
+ op->do_ppc = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->do_pcreset = true;
+ op->do_select = true;
+ break;
+ case 's':
+ op->do_sp = true;
+ break;
+ case 'S':
+ op->do_select = true;
+ break;
+ case 't':
+ op->do_temperature = true;
+ break;
+ case 'T':
+ op->do_transport = true;
+ break;
+ case 'u':
+ ++op->undefined_hex;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'x':
+ ++op->no_inq;
+ break;
+ case 'X':
+ op->o_readonly = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Processes command line options according to old option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, num, n;
+ unsigned int u, uu;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ int plen;
+
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'a':
+ ++op->do_all;
+ break;
+ case 'A':
+ op->do_all += 2;
+ break;
+ case 'b':
+ ++op->do_brief;
+ break;
+ case 'e':
+ ++op->do_enumerate;
+ break;
+ case 'E':
+ op->exclude_vendor = true;
+ break;
+ case 'F':
+ op->do_full = true;
+ break;
+ case 'h':
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ ++op->do_list;
+ break;
+ case 'L':
+ op->do_list += 2;
+ break;
+ case 'n':
+ op->do_name = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'r':
+ op->do_pcreset = true;
+ op->do_select = true;
+ break;
+ case 't':
+ op->do_temperature = true;
+ break;
+ case 'T':
+ op->do_transport = true;
+ break;
+ case 'u':
+ ++op->undefined_hex;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'x':
+ ++op->no_inq;
+ break;
+ case 'X':
+ op->o_readonly = true;
+ break;
+ case '?':
+ ++op->do_help;
+ break;
+ case '-':
+ ++cp;
+ jmp_out = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("c=", cp, 2)) {
+ num = sscanf(cp + 2, "%6x", &u);
+ if ((1 != num) || (u > 3)) {
+ pr2serr("Bad page control after '-c=' option [0..3]\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->page_control = u;
+ } else if (0 == strncmp("D=", cp, 2)) {
+ n = sg_get_num(cp + 2);
+ if ((n < 0) || (n > 31)) {
+ pr2serr("Bad argument after '-D=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->dev_pdt = n;
+ } else if (0 == strncmp("f=", cp, 2)) {
+ n = sg_get_num(cp + 2);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("Bad argument after '-f=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->filter = n;
+ op->filter_given = true;
+ } else if (0 == strncmp("i=", cp, 2))
+ op->in_fn = cp + 2;
+ else if (0 == strncmp("m=", cp, 2)) {
+ num = sscanf(cp + 2, "%8d", &n);
+ if ((1 != num) || (n < 0)) {
+ pr2serr("Bad maximum response length after '-m=' "
+ "option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->maxlen_given = true;
+ op->maxlen = n;
+ } else if (0 == strncmp("M=", cp, 2)) {
+ if (op->vend_prod) {
+ pr2serr("only one '-M=' option permitted\n");
+ usage(2);
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->vend_prod = cp + 2;
+ } else if (0 == strncmp("p=", cp, 2)) {
+ const char * ccp = cp + 2;
+ const struct log_elem * lep;
+
+ if (isalpha((uint8_t)ccp[0])) {
+ char * xp;
+ char b[80];
+
+ if (strlen(ccp) >= (sizeof(b) - 1)) {
+ pr2serr("argument to '-p=' is too long\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ strcpy(b, ccp);
+ xp = (char *)strchr(b, ',');
+ if (xp)
+ *xp = '\0';
+ lep = acron_search(b);
+ if (NULL == lep) {
+ pr2serr("bad argument to '--page=' no acronyn match "
+ "to '%s'\n", b);
+ pr2serr(" Try using '-e' or'-ee' to see available "
+ "acronyns\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->lep = lep;
+ op->pg_code = lep->pg_code;
+ if (xp) {
+ n = sg_get_num_nomult(xp + 1);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Bad second value in argument to "
+ "'--page='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->subpg_code = n;
+ } else
+ op->subpg_code = lep->subpg_code;
+ } else {
+ /* numeric arg: either 'pg_num' or 'pg_num,subpg_num' */
+ if (NULL == strchr(cp + 2, ',')) {
+ num = sscanf(cp + 2, "%6x", &u);
+ if ((1 != num) || (u > 63)) {
+ pr2serr("Bad page code value after '-p=' "
+ "option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->pg_code = u;
+ } else if (2 == sscanf(cp + 2, "%4x,%4x", &u, &uu)) {
+ if (uu > 255) {
+ pr2serr("Bad sub page code value after '-p=' "
+ "option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->pg_code = u;
+ op->subpg_code = uu;
+ } else {
+ pr2serr("Bad page code, subpage code sequence after "
+ "'-p=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ } else if (0 == strncmp("paramp=", cp, 7)) {
+ num = sscanf(cp + 7, "%8x", &u);
+ if ((1 != num) || (u > 0xffff)) {
+ pr2serr("Bad parameter pointer after '-paramp=' "
+ "option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->paramp = u;
+ } else if (0 == strncmp("pcb", cp, 3))
+ op->do_pcb = true;
+ else if (0 == strncmp("ppc", cp, 3))
+ op->do_ppc = true;
+ else if (0 == strncmp("select", cp, 6))
+ op->do_select = true;
+ else if (0 == strncmp("sp", cp, 2))
+ op->do_sp = true;
+ else if (0 == strncmp("old", cp, 3))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Process command line options. First check using new option format unless
+ * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the
+ * old option format to be checked first. Both new and old format can be
+ * countermanded by a '-O' and '-N' options respectively. As soon as either
+ * of these options is detected (when processing the other format), processing
+ * stops and is restarted using the other format. Clear? */
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (0 == op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Returns 'xp' with "unknown" if all bits set; otherwise decoded (big endian)
+ * number in 'xp'. Number rendered in decimal if pr_in_hex=false otherwise in
+ * hex with leading '0x' prepended. */
+static char *
+num_or_unknown(const uint8_t * xp, int num_bytes /* max is 8 */,
+ bool pr_in_hex, char * b, int blen)
+{
+ if (sg_all_ffs(xp, num_bytes))
+ snprintf(b, blen, "%s", unknown_s);
+ else {
+ uint64_t num = sg_get_unaligned_be(num_bytes, xp);
+
+ if (pr_in_hex)
+ snprintf(b, blen, "0x%" PRIx64, num);
+ else
+ snprintf(b, blen, "%" PRIu64, num);
+ }
+ return b;
+}
+
+/* Call LOG SENSE twice: the first time ask for 4 byte response to determine
+ actual length of response; then a second time requesting the
+ min(actual_len, mx_resp_len) bytes. If the calculated length for the
+ second fetch is odd then it is incremented (perhaps should be made modulo
+ 4 in the future for SAS). Returns 0 if ok, SG_LIB_CAT_INVALID_OP for
+ log_sense not supported, SG_LIB_CAT_ILLEGAL_REQ for bad field in log sense
+ command, SG_LIB_CAT_NOT_READY, SG_LIB_CAT_UNIT_ATTENTION,
+ SG_LIB_CAT_ABORTED_COMMAND and -1 for other errors. */
+static int
+do_logs(int sg_fd, uint8_t * resp, int mx_resp_len,
+ const struct opts_t * op)
+{
+ int calc_len, request_len, res, resid, vb;
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (! win32_spt_init_state) {
+ if (win32_spt_curr_state) {
+ if (mx_resp_len < 16384) {
+ scsi_pt_win32_direct(0);
+ win32_spt_curr_state = false;
+ }
+ } else {
+ if (mx_resp_len >= 16384) {
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT direct */);
+ win32_spt_curr_state = true;
+ }
+ }
+ }
+#endif
+#endif
+ memset(resp, 0, mx_resp_len);
+ vb = op->verbose;
+ if (op->maxlen > 1)
+ request_len = mx_resp_len;
+ else {
+ request_len = LOG_SENSE_PROBE_ALLOC_LEN;
+ if ((res = sg_ll_log_sense_v2(sg_fd, op->do_ppc, op->do_sp,
+ op->page_control, op->pg_code,
+ op->subpg_code, op->paramp,
+ resp, request_len, LOG_SENSE_DEF_TIMEOUT,
+ &resid, true /* noisy */, vb)))
+ return res;
+ if (resid > 0) {
+ res = SG_LIB_WILD_RESID;
+ goto resid_err;
+ }
+ calc_len = sg_get_unaligned_be16(resp + 2) + 4;
+ if ((! op->do_raw) && (vb > 1)) {
+ pr2serr(" Log sense (find length) response:\n");
+ hex2stderr(resp, LOG_SENSE_PROBE_ALLOC_LEN, 1);
+ pr2serr(" hence calculated response length=%d\n", calc_len);
+ }
+ if (op->pg_code != (0x3f & resp[0])) {
+ if (vb)
+ pr2serr("Page code does not appear in first byte of "
+ "response so it's suspect\n");
+ if (calc_len > 0x40) {
+ calc_len = 0x40;
+ if (vb)
+ pr2serr("Trim response length to 64 bytes due to "
+ "suspect response format\n");
+ }
+ }
+ /* Some HBAs don't like odd transfer lengths */
+ if (calc_len % 2)
+ calc_len += 1;
+ if (calc_len > mx_resp_len)
+ calc_len = mx_resp_len;
+ request_len = calc_len;
+ }
+ if ((res = sg_ll_log_sense_v2(sg_fd, op->do_ppc, op->do_sp,
+ op->page_control, op->pg_code,
+ op->subpg_code, op->paramp,
+ resp, request_len,
+ LOG_SENSE_DEF_TIMEOUT, &resid,
+ true /* noisy */, vb)))
+ return res;
+ if (resid > 0) {
+ request_len -= resid;
+ if (request_len < 4) {
+ request_len += resid;
+ res = SG_LIB_WILD_RESID;
+ goto resid_err;
+ }
+ }
+ if ((! op->do_raw) && (vb > 1)) {
+ pr2serr(" Log sense response:\n");
+ hex2stderr(resp, request_len, 1);
+ }
+ return 0;
+resid_err:
+ pr2serr("%s: request_len=%d, resid=%d, problems\n", __func__, request_len,
+ resid);
+ request_len -= resid;
+ if ((request_len > 0) && (! op->do_raw) && (vb > 1)) {
+ pr2serr(" Log sense (resid_err) response:\n");
+ hex2stderr(resp, request_len, 1);
+ }
+ return res;
+}
+
+sgj_opaque_p
+sg_log_js_hdr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const uint8_t * log_hdrp)
+{
+ bool ds = !! (log_hdrp[0] & 0x80);
+ bool spf = !! (log_hdrp[0] & 0x40);
+ int pg = log_hdrp[0] & 0x3f;
+ int subpg = log_hdrp[1];
+ size_t nlen = strlen(name);
+ sgj_opaque_p jo2p;
+ char b[80];
+
+ if ((nlen < 4) || (0 != strcmp("age", name + nlen - 3))) {
+ memcpy(b, name, nlen);
+ memcpy(b + nlen, " log page", 10);
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, b);
+ } else
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, name);
+
+ sgj_js_nv_ihex_nex(jsp, jo2p, "ds", (int)ds, false, "Did not Save");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "spf", (int)spf, NULL, "SubPage Format");
+ sgj_js_nv_ihex(jsp, jo2p, "page_code", pg);
+ sgj_js_nv_ihex(jsp, jo2p, "subpage_code", subpg);
+ return jo2p;
+}
+
+
+
+/* DS made obsolete in spc4r03; TMC and ETC made obsolete in spc5r03. */
+static char *
+get_pcb_str(int pcb, char * outp, int maxoutlen)
+{
+ char buff[PCB_STR_LEN];
+ int n;
+
+ n = sprintf(buff, "du=%d [ds=%d] tsd=%d [etc=%d] ", ((pcb & 0x80) ? 1 : 0),
+ ((pcb & 0x40) ? 1 : 0), ((pcb & 0x20) ? 1 : 0),
+ ((pcb & 0x10) ? 1 : 0));
+ if (pcb & 0x10)
+ n += sprintf(buff + n, "[tmc=%d] ", ((pcb & 0xc) >> 2));
+#if 1
+ n += sprintf(buff + n, "format+linking=%d [0x%.2x]", pcb & 3,
+ pcb);
+#else
+ if (pcb & 0x1)
+ n += sprintf(buff + n, "lbin=%d ", ((pcb & 0x2) >> 1));
+ n += sprintf(buff + n, "lp=%d [0x%.2x]", pcb & 0x1, pcb);
+#endif
+ if (outp && (n < maxoutlen)) {
+ memcpy(outp, buff, n);
+ outp[n] = '\0';
+ } else if (outp && (maxoutlen > 0))
+ outp[0] = '\0';
+ return outp;
+}
+
+static void
+js_pcb(sgj_state * jsp, sgj_opaque_p jop, int pcb)
+{
+ sgj_opaque_p jo2p = sgj_snake_named_subobject_r(jsp, jop,
+ "parameter_control_byte");
+
+ sgj_js_nv_ihex_nex(jsp, jo2p, "du", (pcb & 0x80) ? 1 : 0, false,
+ "Disable Update");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "ds", (pcb & 0x40) ? 1 : 0, false,
+ "Disable Save [obsolete]");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "tsd", (pcb & 0x20) ? 1 : 0, false,
+ "Target Save Disable");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "etc", (pcb & 0x10) ? 1 : 0, false,
+ "Enable Threshold Comparison [obsolete]");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "tmc", (pcb & 0xc) >> 2, false,
+ "Threshold Met Criteria [obsolete]");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "format_and_linking", pcb & 0x3, false,
+ NULL);
+}
+
+/* SUPP_PAGES_LPAGE [0x0,0x0] <sp> */
+static bool
+show_supported_pgs_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, k;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo3p;
+ sgj_opaque_p jap = NULL;
+ char b[64];
+ static const char * slpgs = "Supported log pages";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x0]:\n", slpgs); /* introduced in: SPC-2 */
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if ((op->do_hex > 0) || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp, len);
+ else
+ hex2stdout(resp, len, op->dstrhex_no_ascii);
+ return true;
+ }
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, slpgs, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "supported_pages_list");
+ }
+
+ for (k = 0; k < num; ++k) {
+ int pg_code = bp[k] & 0x3f;
+ const struct log_elem * lep;
+
+ snprintf(b, sizeof(b) - 1, " 0x%02x ", pg_code);
+ lep = pg_subpg_pdt_search(pg_code, 0, op->dev_pdt, -1);
+ if (lep) {
+ if (op->do_brief > 1)
+ sgj_pr_hr(jsp, " %s\n", lep->name);
+ else if (op->do_brief)
+ sgj_pr_hr(jsp, "%s%s\n", b, lep->name);
+ else
+ sgj_pr_hr(jsp, "%s%s [%s]\n", b, lep->name, lep->acron);
+ } else
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jo3p, "page_code", pg_code);
+ sgj_js_nv_s(jsp, jo3p, "name", lep ? lep->name : unknown_s);
+ sgj_js_nv_s(jsp, jo3p, "acronym", lep ? lep->acron : unknown_s);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ }
+ return true;
+}
+
+/* SUPP_PAGES_LPAGE,SUPP_SPGS_SUBPG [0x0,0xff] <ssp> or all subpages of a
+ * given page code: [<pg_code>,0xff] where <pg_code> > 0 */
+static bool
+show_supported_pgs_sub_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, k;
+ const uint8_t * bp;
+ const struct log_elem * lep = NULL;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo3p;
+ sgj_opaque_p jap = NULL;
+ char b[64];
+ static const char * slpass = "Supported log pages and subpages";
+ static const char * sss = "Supported subpages";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (op->pg_code > 0)
+ sgj_pr_hr(jsp, "%s [0x%x, 0xff]:\n", sss, op->pg_code);
+ else
+ sgj_pr_hr(jsp, "%s [0x0, 0xff]:\n", sss);
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if ((op->do_hex > 0) || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp, len);
+ else
+ hex2stdout(resp, len, op->dstrhex_no_ascii);
+ return true;
+ }
+ if (jsp->pr_as_json) {
+ if (op->pg_code > 0) {
+ jo2p = sg_log_js_hdr(jsp, jop, sss, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "supported_subpage_descriptors");
+ } else {
+ jo2p = sg_log_js_hdr(jsp, jop, slpass, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "supported_page_subpage_descriptors");
+ }
+ }
+
+ for (k = 0; k < num; k += 2) {
+ bool pr_name = true;
+ int pg_code = bp[k];
+ int subpg_code = bp[k + 1];
+
+ /* formerly ignored [pg, 0xff] when pg > 0, don't know why */
+ if (NOT_SPG_SUBPG == subpg_code)
+ snprintf(b, sizeof(b) - 1, " 0x%02x ", pg_code);
+ else
+ snprintf(b, sizeof(b) - 1, " 0x%02x,0x%02x ", pg_code,
+ subpg_code);
+ if ((pg_code > 0) && (subpg_code == 0xff)) {
+ sgj_pr_hr(jsp, "%s\n", b);
+ pr_name = false;
+ } else {
+ lep = pg_subpg_pdt_search(pg_code, subpg_code, op->dev_pdt, -1);
+ if (lep) {
+ if (op->do_brief > 1)
+ sgj_pr_hr(jsp, " %s\n", lep->name);
+ else if (op->do_brief)
+ sgj_pr_hr(jsp, "%s%s\n", b, lep->name);
+ else
+ sgj_pr_hr(jsp, "%s%s [%s]\n", b, lep->name, lep->acron);
+ } else
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jo3p, "page_code", pg_code);
+ sgj_js_nv_ihex(jsp, jo3p, "subpage_code", subpg_code);
+ if (pr_name) {
+ sgj_js_nv_s(jsp, jo3p, "name", lep ? lep->name : unknown_s);
+ sgj_js_nv_s(jsp, jo3p, "acronym", lep ? lep->acron :
+ unknown_s);
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ }
+ return true;
+}
+
+/* BUFF_OVER_UNDER_LPAGE [0x1] <bou> introduced: SPC-2 */
+static bool
+show_buffer_over_under_run_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ uint64_t count;
+ const uint8_t * bp;
+ const char * cp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ static const char * bourlp = "Buffer over-run/under-run log page";
+ static const char * orurc = "over_run_under_run_counter";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x1]\n", bourlp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, bourlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "buffer_over_run_under_run_log_parameters");
+ }
+ while (num > 3) {
+ cp = NULL;
+ pl = bp[3] + 4;
+ count = (pl > 4) ? sg_get_unaligned_be(pl - 4, bp + 4) : 0;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 0x0:
+ cp = "under-run";
+ break;
+ case 0x1:
+ cp = "over-run";
+ break;
+ case 0x2:
+ cp = "service delivery subsystem busy, under-run";
+ break;
+ case 0x3:
+ cp = "service delivery subsystem busy, over-run";
+ break;
+ case 0x4:
+ cp = "transfer too slow, under-run";
+ break;
+ case 0x5:
+ cp = "transfer too slow, over-run";
+ break;
+ case 0x20:
+ cp = "command, under-run";
+ break;
+ case 0x21:
+ cp = "command, over-run";
+ break;
+ case 0x22:
+ cp = "command, service delivery subsystem busy, under-run";
+ break;
+ case 0x23:
+ cp = "command, service delivery subsystem busy, over-run";
+ break;
+ case 0x24:
+ cp = "command, transfer too slow, under-run";
+ break;
+ case 0x25:
+ cp = "command, transfer too slow, over-run";
+ break;
+ case 0x40:
+ cp = "I_T nexus, under-run";
+ break;
+ case 0x41:
+ cp = "I_T nexus, over-run";
+ break;
+ case 0x42:
+ cp = "I_T nexus, service delivery subsystem busy, under-run";
+ break;
+ case 0x43:
+ cp = "I_T nexus, service delivery subsystem busy, over-run";
+ break;
+ case 0x44:
+ cp = "I_T nexus, transfer too slow, under-run";
+ break;
+ case 0x45:
+ cp = "I_T nexus, transfer too slow, over-run";
+ break;
+ case 0x80:
+ cp = "time, under-run";
+ break;
+ case 0x81:
+ cp = "time, over-run";
+ break;
+ case 0x82:
+ cp = "time, service delivery subsystem busy, under-run";
+ break;
+ case 0x83:
+ cp = "time, service delivery subsystem busy, over-run";
+ break;
+ case 0x84:
+ cp = "time, transfer too slow, under-run";
+ break;
+ case 0x85:
+ cp = "time, transfer too slow, over-run";
+ break;
+ default:
+ pr2serr(" undefined %s [0x%x], count = %" PRIu64 "\n",
+ param_c, pc, count);
+ break;
+ }
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+ sgj_pr_hr(jsp, " %s=0x%x\n", param_c, pc);
+ if (cp) {
+ sgj_pr_hr(jsp, " %s = %" PRIu64 "\n", cp, count);
+ js_snakenv_ihexstr_nex(jsp, jo3p, param_c, pc, true,
+ NULL, cp, NULL);
+ sgj_js_nv_ihex(jsp, jo3p, orurc, count);
+ } else
+ sgj_pr_hr(jsp, " counter = %" PRIu64 "\n", count);
+
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* WRITE_ERR_LPAGE; READ_ERR_LPAGE; READ_REV_ERR_LPAGE; VERIFY_ERR_LPAGE */
+/* [0x2, 0x3, 0x4, 0x5] <we, re, rre, ve> introduced: SPC-3 */
+static bool
+show_error_counter_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool skip_out = false;
+ bool evsm_output = false;
+ int n, num, pl, pc, pg_code;
+ uint64_t val;
+ const uint8_t * bp;
+ const char * pg_cp = NULL;
+ const char * par_cp = NULL;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[128] SG_C_CPP_ZERO_INIT;
+ char d[128];
+ char e[64];
+ static const char * wec = "Write error counter";
+ static const char * rec = "Read error counter";
+ static const char * rrec = "Read reverse error counter";
+ static const char * vec = "Verify error counter";
+
+ pg_code = resp[0] & 0x3f;
+ switch(pg_code) {
+ case WRITE_ERR_LPAGE:
+ pg_cp = wec;
+ break;
+ case READ_ERR_LPAGE:
+ pg_cp = rec;
+ break;
+ case READ_REV_ERR_LPAGE:
+ pg_cp = rrec;
+ break;
+ case VERIFY_ERR_LPAGE:
+ pg_cp = vec;
+ break;
+ default:
+ pr2serr("expecting error counter page, got page = 0x%x\n",
+ pg_code);
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s log page [0x%x]\n", pg_cp, pg_code);
+ if (jsp->pr_as_json) {
+ n = strlen(pg_cp);
+ memcpy(b, pg_cp, n);
+ memcpy(b + n, " log", 4);
+ n = strlen(b);
+ memcpy(b + n, " page", 5);
+ jo2p = sg_log_js_hdr(jsp, jop, b, resp);
+ memcpy(b + n, " parameters", 11);
+ sgj_convert_to_snake_name(b, d, sizeof(d) - 1);
+ jap = sgj_named_subarray_r(jsp, jo2p, d);
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ par_cp = NULL;
+ switch (pc) {
+ case 0:
+ par_cp = "Errors corrected without substantial delay";
+ break;
+ case 1:
+ par_cp = "Errors corrected with possible delays";
+ break;
+ case 2:
+ par_cp = "Total rewrites or rereads";
+ break;
+ case 3:
+ par_cp = "Total errors corrected";
+ break;
+ case 4:
+ par_cp = "Total times correction algorithm processed";
+ break;
+ case 5:
+ par_cp = "Total bytes processed";
+ break;
+ case 6:
+ par_cp = "Total uncorrected errors";
+ break;
+ default:
+ if (op->exclude_vendor) {
+ skip_out = true;
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ pr2serr(" %s parameter(s) being ignored\n", vend_spec);
+ }
+ } else {
+ if (0x8009 == pc)
+ par_cp = "Track following errors [Hitachi]";
+ else if (0x8015 == pc)
+ par_cp = "Positioning errors [Hitachi]";
+ else {
+ snprintf(e, sizeof(e), "Reserved or %s [0x%x]", vend_spec,
+ pc);
+ par_cp = e;
+ }
+ }
+ break;
+ }
+
+ if (skip_out)
+ skip_out = false;
+ else if (par_cp) {
+ val = sg_get_unaligned_be(pl - 4, bp + 4);
+ if (val > ((uint64_t)1 << 40))
+ snprintf(d, sizeof(d), "%" PRIu64 " [%" PRIu64 " TB]",
+ val, (val / (1000UL * 1000 * 1000 * 1000)));
+ else if (val > ((uint64_t)1 << 30))
+ snprintf(d, sizeof(d), "%" PRIu64 " [%" PRIu64 " GB]",
+ val, (val / (1000UL * 1000 * 1000)));
+ else
+ snprintf(d, sizeof(d), "%" PRIu64, val);
+ sgj_pr_hr(jsp, " %s = %s\n", par_cp, d);
+ if (jsp->pr_as_json) {
+ js_snakenv_ihexstr_nex(jsp, jo3p, param_c, pc, true,
+ NULL, par_cp, NULL);
+ sgj_convert_to_snake_name(pg_cp, e, sizeof(e) - 1);
+ n = strlen(e);
+ memcpy(e + n, "_counter", 9); /* take trailing null */
+ sgj_js_nv_ihexstr(jsp, jo3p, e, val, as_s_s, d);
+ }
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* NON_MEDIUM_LPAGE [0x6] <nm> introduced: SPC-2 */
+static bool
+show_non_medium_error_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool skip_out = false;
+ bool evsm_output = false;
+ int num, pl, pc;
+ uint64_t count;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[128] SG_C_CPP_ZERO_INIT;
+ static const char * nmelp = "Non-medium error log page";
+ static const char * nmec = "Non-medium error count";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x6]\n", nmelp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, nmelp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "non_medium_error_log_parameters");
+ }
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 0:
+ snprintf(b, sizeof(b), "%s", nmec);
+ break;
+ default:
+ if (pc <= 0x7fff)
+ snprintf(b, sizeof(b), " Reserved [0x%x]", pc);
+ else {
+ if (op->exclude_vendor) {
+ skip_out = true;
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ pr2serr(" %s parameter(s) being ignored\n",
+ vend_spec);
+ }
+ } else
+ snprintf(b, sizeof(b), "%s [0x%x]", vend_spec, pc);
+ }
+ break;
+ }
+ if (skip_out)
+ skip_out = false;
+ else {
+ count = sg_get_unaligned_be(pl - 4, bp + 4);
+ sgj_pr_hr(jsp, " %s = %" PRIu64 "\n", b, count);
+ js_snakenv_ihexstr_nex(jsp, jo3p, param_c, pc, true,
+ NULL, b, NULL);
+ js_snakenv_ihexstr_nex(jsp, jo3p, nmec, count, true, NULL, NULL,
+ NULL);
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* PCT_LPAGE [0x1a] <pct> introduced: SPC-4 */
+static bool
+show_power_condition_transitions_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool partial;
+ int num, pl, pc;
+ uint64_t count;
+ const uint8_t * bp;
+ const char * cp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[128];
+ char bb[64];
+ static const char * pctlp = "Power condition transitions log page";
+ static const char * att = "Accumulated transitions to";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x1a]\n", pctlp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, pctlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "power_condition_transition_log_parameters");
+ }
+
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ cp = NULL;
+ partial = true;
+ switch (pc) {
+ case 1:
+ cp = "active";
+ break;
+ case 2:
+ cp = "idle_a";
+ break;
+ case 3:
+ cp = "idle_b";
+ break;
+ case 4:
+ cp = "idle_c";
+ break;
+ case 8:
+ cp = "standby_z";
+ break;
+ case 9:
+ cp = "standby_y";
+ break;
+ default:
+ snprintf(bb, sizeof(bb), "Reserved [0x%x]", pc);
+ cp = bb;
+ partial = false;
+ break;
+ }
+ if (partial) {
+ snprintf(b, sizeof(b), "%s %s", att, cp);
+ cp = b;
+ }
+ count = sg_get_unaligned_be(pl - 4, bp + 4);
+ sgj_pr_hr(jsp, " %s = %" PRIu64 "\n", cp, count);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json) {
+ js_snakenv_ihexstr_nex(jsp, jo3p, cp, count, true,
+ NULL, NULL, "saturating counter");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+static char *
+temperature_str(int8_t t, bool reporting, char * b, int blen)
+{
+ if (-128 == t) {
+ if (reporting)
+ snprintf(b, blen, "%s", not_avail);
+ else
+ snprintf(b, blen, "no limit");
+ } else
+ snprintf(b, blen, "%d C", t);
+ return b;
+}
+
+static char *
+humidity_str(uint8_t h, bool reporting, char * b, int blen)
+{
+ if (255 == h) {
+ if (reporting)
+ snprintf(b, blen, "%s", not_avail);
+ else
+ snprintf(b, blen, "no limit");
+ } else if (h <= 100)
+ snprintf(b, blen, "%u %%", h);
+ else
+ snprintf(b, blen, "%s value [%u]", rsv_s, h);
+ return b;
+}
+
+/* ENV_REPORTING_SUBPG [0xd,0x1] <env> introduced: SPC-5 (rev 02). "mounted"
+ * changed to "other" in spc5r11 */
+static bool
+show_environmental_reporting_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc, blen;
+ bool other_valid;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[32];
+ static const char * erlp = "Environmental reporting log page";
+ static const char * temp = "Temperature";
+ static const char * lmaxt = "Lifetime maximum temperature";
+ static const char * lmint = "Lifetime minimum temperature";
+ static const char * maxtspo = "Maximum temperature since power on";
+ static const char * mintspo = "Minimum temperature since power on";
+ static const char * maxot = "Maximum other temperature";
+ static const char * minot = "Minimum other temperature";
+ static const char * relhum = "Relative humidity";
+ static const char * lmaxrh = "Lifetime maximum relative humidity";
+ static const char * lminrh = "Lifetime minimum relative humidity";
+ static const char * maxrhspo = "Maximum relative humidity since power on";
+ static const char * minrhspo = "Minimum relative humidity since power on";
+ static const char * maxorh = "Maximum other relative humidity";
+ static const char * minorh = "Minimum other relative humidity";
+
+ blen = sizeof(b);
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xd,0x1]\n", erlp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, erlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "environmental_reporting_log_parameters");
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ other_valid = !!(bp[4] & 1);
+ if (pc < 0x100) {
+ if (pl < 12) {
+ pr2serr(" <<expect parameter 0x%x to be at least 12 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto inner;
+ }
+ sgj_pr_hr(jsp, " %s=0x%x\n", param_c, pc);
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+ sgj_pr_hr(jsp, " OTV=%d\n", (int)other_valid);
+ sgj_js_nv_ihex_nex(jsp, jo3p, "otv", (int)other_valid,
+ false, "Other Temperature Valid");
+
+ temperature_str(bp[5], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", temp, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, temp, bp[5], false,
+ NULL, b, "current [Celsius]");
+ temperature_str(bp[6], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lmaxt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lmaxt, bp[6], false,
+ NULL, b, NULL);
+ temperature_str(bp[7], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lmint, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lmint, bp[7], false,
+ NULL, b, NULL);
+ temperature_str(bp[8], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", maxtspo, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, maxtspo, bp[8], false,
+ NULL, b, NULL);
+ temperature_str(bp[9], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", mintspo, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, mintspo, bp[9], false,
+ NULL, b, NULL);
+ if (other_valid) {
+ temperature_str(bp[10], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", maxot, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, maxot, bp[10], false,
+ NULL, b, NULL);
+ temperature_str(bp[11], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", minot, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, minot, bp[11], false,
+ NULL, b, NULL);
+ }
+ } else if (pc < 0x200) {
+ if (pl < 12) {
+ pr2serr(" <<expect parameter 0x%x to be at least 12 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto inner;
+ }
+ sgj_pr_hr(jsp, " %s=0x%x\n", param_c, pc);
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+ sgj_pr_hr(jsp, " ORHV=%d\n", (int)other_valid);
+ sgj_js_nv_ihex_nex(jsp, jo3p, "orhv", (int)other_valid,
+ false, "Other Relative Humidity Valid");
+
+ humidity_str(bp[5], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", relhum, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, relhum, bp[5], false,
+ NULL, b, NULL);
+ humidity_str(bp[6], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lmaxrh, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lmaxrh, bp[6], false,
+ NULL, b, NULL);
+ humidity_str(bp[7], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lminrh, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lminrh, bp[7], false,
+ NULL, b, NULL);
+ humidity_str(bp[8], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", maxrhspo, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, maxrhspo, bp[8], false,
+ NULL, b, NULL);
+ humidity_str(bp[9], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", minrhspo, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, minrhspo, bp[9], false,
+ NULL, b, NULL);
+ if (other_valid) {
+ humidity_str(bp[10], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", maxorh, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, maxorh, bp[10], false,
+ NULL, b, NULL);
+ humidity_str(bp[11], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", minorh, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, minorh, bp[11], false,
+ NULL, b, NULL);
+ }
+ } else
+ sgj_pr_hr(jsp, " <<unexpected %s 0x%x\n", param_c, pc);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+inner:
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* ENV_LIMITS_SUBPG [0xd,0x2] <enl> introduced: SPC-5 (rev 02) */
+static bool
+show_environmental_limits_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc, blen;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[32];
+ static const char * ellp = "Environmental limits log page";
+ static const char * hctlt = "High critical temperature limit trigger";
+ static const char * hctlr = "High critical temperature limit reset";
+ static const char * lctlr = "High critical temperature limit reset";
+ static const char * lctlt = "High critical temperature limit trigger";
+ static const char * hotlt = "High operating temperature limit trigger";
+ static const char * hotlr = "High operating temperature limit reset";
+ static const char * lotlr = "High operating temperature limit reset";
+ static const char * lotlt = "High operating temperature limit trigger";
+ static const char * hcrhlt =
+ "High critical relative humidity limit trigger";
+ static const char * hcrhlr =
+ "High critical relative humidity limit reset";
+ static const char * lcrhlr =
+ "High critical relative humidity limit reset";
+ static const char * lcrhlt =
+ "High critical relative humidity limit trigger";
+ static const char * horhlt =
+ "High operating relative humidity limit trigger";
+ static const char * horhlr =
+ "High operating relative humidity limit reset";
+ static const char * lorhlr =
+ "High operating relative humidity limit reset";
+ static const char * lorhlt =
+ "High operating relative humidity limit trigger";
+
+ blen = sizeof(b);
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xd,0x2]\n", ellp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, ellp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "environmental_limits_log_parameters");
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ if (pc < 0x100) {
+ if (pl < 12) {
+ pr2serr(" <<expect parameter 0x%x to be at least 12 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto inner;
+ }
+ sgj_pr_hr(jsp, " %s=0x%x\n", param_c, pc);
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+
+ temperature_str(bp[4], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hctlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hctlt, bp[4], false,
+ NULL, b, "[Celsius]");
+ temperature_str(bp[5], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hctlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hctlr, bp[5], false,
+ NULL, b, NULL);
+ temperature_str(bp[6], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lctlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lctlr, bp[6], false,
+ NULL, b, NULL);
+ temperature_str(bp[7], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lctlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lctlt, bp[7], false,
+ NULL, b, NULL);
+ temperature_str(bp[8], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hotlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hotlt, bp[8], false,
+ NULL, b, NULL);
+ temperature_str(bp[9], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hotlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hotlr, bp[9], false,
+ NULL, b, NULL);
+ temperature_str(bp[10], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lotlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lotlr, bp[10], false,
+ NULL, b, NULL);
+ temperature_str(bp[11], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lotlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lotlt, bp[11], false,
+ NULL, b, NULL);
+ } else if (pc < 0x200) {
+ if (pl < 12) {
+ pr2serr(" <<expect parameter 0x%x to be at least 12 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto inner;
+ }
+ sgj_pr_hr(jsp, " %s=0x%x\n", param_c, pc);
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+
+ humidity_str(bp[4], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hcrhlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hcrhlt, bp[4], false,
+ NULL, b, "[percentage]");
+ humidity_str(bp[5], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", hcrhlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, hcrhlr, bp[5], false,
+ NULL, b, NULL);
+ humidity_str(bp[6], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lcrhlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lcrhlr, bp[6], false,
+ NULL, b, NULL);
+ humidity_str(bp[7], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lcrhlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lcrhlt, bp[7], false,
+ NULL, b, NULL);
+ humidity_str(bp[8], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", horhlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, horhlt, bp[8], false,
+ NULL, b, NULL);
+ humidity_str(bp[9], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", horhlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, horhlr, bp[9], false,
+ NULL, b, NULL);
+ humidity_str(bp[10], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lorhlr, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lorhlr, bp[10], false,
+ NULL, b, NULL);
+ humidity_str(bp[11], true, b, blen);
+ sgj_pr_hr(jsp, " %s: %s\n", lorhlt, b);
+ js_snakenv_ihexstr_nex(jsp, jo3p, lorhlt, bp[11], false,
+ NULL, b, NULL);
+ } else
+ sgj_pr_hr(jsp, " <<unexpected %s 0x%x\n", param_c, pc);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+inner:
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* CMD_DUR_LIMITS_SUBPG [0x19,0x21] <cdl>
+ * introduced: SPC-6 rev 1, significantly changed rev 6 */
+static bool
+show_cmd_dur_limits_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ uint32_t count, noitmc_v, noatmc_v, noitatmc_v, noc_v;
+ const uint8_t * bp;
+ const char * cp;
+ const char * thp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[144];
+ static const char * cdllp = "Command duration limits statistics log page";
+ static const char * t2cdld = "T2 command duration limit descriptor";
+ static const char * cdlt2amp = "CDL T2A mode page";
+ static const char * cdlt2bmp = "CDL T2B mode page";
+ static const char * first_7[] = {"First", "Second", "Third", "Fourth",
+ "Fifth", "Sixth", "Seventh"};
+ static const char * noitmc = "Number of inactive target miss commands";
+ static const char * noatmc = "Number of active target miss commands";
+ static const char * noitatmc =
+ "Number of inactive target and active target miss commands";
+ static const char * noc = "Number of commands";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x19,0x21]\n", cdllp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, cdllp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "command_duration_limits_statistcs_log_parameters");
+ }
+
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4; /* parameter length */
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 0x1:
+ /* spc6r06: table 349 name "Number of READ commands" seems to
+ * be wrong. Use what surrounding text and table 347 suggest */
+ cp = "Achievable latency target";
+ count = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %" PRIu32 "\n", cp, count);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, cp);
+ js_snakenv_ihexstr_nex(jsp, jop, cp, count, true, NULL, NULL,
+ "unit: microsecond");
+ }
+ break;
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ sgj_pr_hr(jsp, " %s code 0x%x restricted\n", param_c, pc);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, rstrict_s);
+ break;
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ thp = first_7[pc - 0x21];
+ sgj_pr_hr(jsp, " %s %s for %s [pc=0x%x]:\n", thp, t2cdld,
+ cdlt2amp, pc);
+ noitmc_v = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", noitmc, noitmc_v);
+ noatmc_v = sg_get_unaligned_be32(bp + 8);
+ sgj_pr_hr(jsp, " %s = %u\n", noatmc, noatmc_v);
+ noitatmc_v = sg_get_unaligned_be32(bp + 12);
+ sgj_pr_hr(jsp, " %s = %u\n", noitatmc, noitatmc_v);
+ noc_v = sg_get_unaligned_be32(bp + 16);
+ sgj_pr_hr(jsp, " %s = %u\n", noc, noc_v);
+ if (jsp->pr_as_json) {
+ snprintf(b, sizeof(b), "%s %s for %s", thp, t2cdld, cdlt2amp);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, b);
+
+ js_snakenv_ihexstr_nex(jsp, jop, noitmc, noitmc_v, true, NULL,
+ NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noatmc, noatmc_v, true, NULL,
+ NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noitatmc, noitatmc_v, true,
+ NULL, NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noc, noc_v, true, NULL,
+ NULL, NULL);
+ }
+ break;
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ sgj_pr_hr(jsp, " %s 0x%x restricted\n", param_c, pc);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, rstrict_s);
+ break;
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ /* This short form introduced in draft spc6r06 */
+ thp = first_7[pc - 0x41];
+ sgj_pr_hr(jsp, " %s %s for %s [pc=0x%x]:\n", thp, t2cdld,
+ cdlt2bmp, pc);
+ noitmc_v = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", noitmc, noitmc_v);
+ noatmc_v = sg_get_unaligned_be32(bp + 8);
+ sgj_pr_hr(jsp, " %s = %u\n", noatmc, noatmc_v);
+ noitatmc_v = sg_get_unaligned_be32(bp + 12);
+ sgj_pr_hr(jsp, " %s = %u\n", noitatmc, noitatmc_v);
+ noc_v = sg_get_unaligned_be32(bp + 16);
+ sgj_pr_hr(jsp, " %s = %u\n", noc, noc_v);
+ if (jsp->pr_as_json) {
+ snprintf(b, sizeof(b), "%s %s for %s", thp, t2cdld, cdlt2amp);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, b);
+
+ js_snakenv_ihexstr_nex(jsp, jop, noitmc, noitmc_v, true, NULL,
+ NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noatmc, noatmc_v, true, NULL,
+ NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noitatmc, noitatmc_v, true,
+ NULL, NULL, NULL);
+ js_snakenv_ihexstr_nex(jsp, jop, noc, noc_v, true, NULL,
+ NULL, NULL);
+ }
+
+ break;
+ default:
+ sgj_pr_hr(jsp, " <<unexpected %s 0x%x\n", param_c, pc);
+ break;
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Tape usage: Vendor specific (LTO-5 and LTO-6): 0x30 */
+static bool
+show_tape_usage_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int k, num, extra;
+ unsigned int n;
+ uint64_t ull;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed tape usage page\n");
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Tape usage page (LTO-5 and LTO-6 specific) [0x30]\n");
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ extra = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ break;
+ }
+ ull = n = 0;
+ switch (bp[3]) {
+ case 2:
+ n = sg_get_unaligned_be16(bp + 4);
+ break;
+ case 4:
+ n = sg_get_unaligned_be32(bp + 4);
+ break;
+ case 8:
+ ull = sg_get_unaligned_be64(bp + 4);
+ break;
+ }
+ switch (pc) {
+ case 0x01:
+ if (extra == 8)
+ printf(" Thread count: %u", n);
+ break;
+ case 0x02:
+ if (extra == 12)
+ printf(" Total data sets written: %" PRIu64, ull);
+ break;
+ case 0x03:
+ if (extra == 8)
+ printf(" Total write retries: %u", n);
+ break;
+ case 0x04:
+ if (extra == 6)
+ printf(" Total unrecovered write errors: %u", n);
+ break;
+ case 0x05:
+ if (extra == 6)
+ printf(" Total suspended writes: %u", n);
+ break;
+ case 0x06:
+ if (extra == 6)
+ printf(" Total fatal suspended writes: %u", n);
+ break;
+ case 0x07:
+ if (extra == 12)
+ printf(" Total data sets read: %" PRIu64, ull);
+ break;
+ case 0x08:
+ if (extra == 8)
+ printf(" Total read retries: %u", n);
+ break;
+ case 0x09:
+ if (extra == 6)
+ printf(" Total unrecovered read errors: %u", n);
+ break;
+ case 0x0a:
+ if (extra == 6)
+ printf(" Total suspended reads: %u", n);
+ break;
+ case 0x0b:
+ if (extra == 6)
+ printf(" Total fatal suspended reads: %u", n);
+ break;
+ default:
+ printf(" unknown %s = 0x%x, contents in hex:\n", param_c, pc);
+ hex2stdout(bp, extra, 1);
+ break;
+ }
+ printf("\n");
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* 0x30 */
+static bool
+show_hgst_perf_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool valid = false;
+ int num, pl;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("HGST/WDC performance counters page [0x30]\n");
+ num = len - 4;
+ if (num < 0x30) {
+ printf("HGST/WDC performance counters page too short (%d) < 48\n",
+ num);
+ return valid;
+ }
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ switch (pc) {
+ case 0:
+ valid = true;
+ printf(" Zero Seeks = %u\n", sg_get_unaligned_be16(bp + 4));
+ printf(" Seeks >= 2/3 = %u\n", sg_get_unaligned_be16(bp + 6));
+ printf(" Seeks >= 1/3 and < 2/3 = %u\n",
+ sg_get_unaligned_be16(bp + 8));
+ printf(" Seeks >= 1/6 and < 1/3 = %u\n",
+ sg_get_unaligned_be16(bp + 10));
+ printf(" Seeks >= 1/12 and < 1/6 = %u\n",
+ sg_get_unaligned_be16(bp + 12));
+ printf(" Seeks > 0 and < 1/12 = %u\n",
+ sg_get_unaligned_be16(bp + 14));
+ printf(" Overrun Counter = %u\n",
+ sg_get_unaligned_be16(bp + 20));
+ printf(" Underrun Counter = %u\n",
+ sg_get_unaligned_be16(bp + 22));
+ printf(" Device Cache Full Read Hits = %u\n",
+ sg_get_unaligned_be32(bp + 24));
+ printf(" Device Cache Partial Read Hits = %u\n",
+ sg_get_unaligned_be32(bp + 28));
+ printf(" Device Cache Write Hits = %u\n",
+ sg_get_unaligned_be32(bp + 32));
+ printf(" Device Cache Fast Writes = %u\n",
+ sg_get_unaligned_be32(bp + 36));
+ printf(" Device Cache Read Misses = %u\n",
+ sg_get_unaligned_be32(bp + 40));
+ break;
+ default:
+ valid = false;
+ printf(" Unknown HGST/WDC %s = 0x%x", param_c, pc);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return valid;
+}
+
+/* Tape capacity: vendor specific (LTO-5 and LTO-6 ?): 0x31 */
+static bool
+show_tape_capacity_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, num, extra;
+ unsigned int n;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed tape capacity page\n");
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Tape capacity page (LTO-5 and LTO-6 specific) [0x31]\n");
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ extra = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ break;
+ }
+ if (extra != 8)
+ continue;
+ n = sg_get_unaligned_be32(bp + 4);
+ switch (pc) {
+ case 0x01:
+ printf(" Main partition remaining capacity (in MiB): %u", n);
+ break;
+ case 0x02:
+ printf(" Alternate partition remaining capacity (in MiB): %u", n);
+ break;
+ case 0x03:
+ printf(" Main partition maximum capacity (in MiB): %u", n);
+ break;
+ case 0x04:
+ printf(" Alternate partition maximum capacity (in MiB): %u", n);
+ break;
+ default:
+ printf(" unknown %s = 0x%x, contents in hex:\n", param_c, pc);
+ hex2stdout(bp, extra, 1);
+ break;
+ }
+ printf("\n");
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* Data compression: originally vendor specific 0x32 (LTO-5), then
+ * ssc-4 standardizes it at 0x1b <dc> */
+static bool
+show_data_compression_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, j, pl, num, extra, pc, pg_code;
+ uint64_t n;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ pg_code = resp[0] & 0x3f;
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed data compression page\n");
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (0x1b == pg_code)
+ printf("Data compression page (ssc-4) [0x1b]\n");
+ else
+ printf("Data compression page (LTO-5 specific) [0x%x]\n",
+ pg_code);
+ }
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3];
+ extra = pl + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ break;
+ }
+ if ((0 == pl) || (pl > 8)) {
+ printf("badly formed data compression log parameter\n");
+ printf(" %s = 0x%x, contents in hex:\n", param_c, pc);
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip_para;
+ }
+ /* variable length integer, max length 8 bytes */
+ for (j = 0, n = 0; j < pl; ++j) {
+ if (j > 0)
+ n <<= 8;
+ n |= bp[4 + j];
+ }
+ switch (pc) {
+ case 0x00:
+ printf(" Read compression ratio x100: %" PRIu64 , n);
+ break;
+ case 0x01:
+ printf(" Write compression ratio x100: %" PRIu64 , n);
+ break;
+ case 0x02:
+ printf(" Megabytes transferred to server: %" PRIu64 , n);
+ break;
+ case 0x03:
+ printf(" Bytes transferred to server: %" PRIu64 , n);
+ break;
+ case 0x04:
+ printf(" Megabytes read from tape: %" PRIu64 , n);
+ break;
+ case 0x05:
+ printf(" Bytes read from tape: %" PRIu64 , n);
+ break;
+ case 0x06:
+ printf(" Megabytes transferred from server: %" PRIu64 , n);
+ break;
+ case 0x07:
+ printf(" Bytes transferred from server: %" PRIu64 , n);
+ break;
+ case 0x08:
+ printf(" Megabytes written to tape: %" PRIu64 , n);
+ break;
+ case 0x09:
+ printf(" Bytes written to tape: %" PRIu64 , n);
+ break;
+ case 0x100:
+ printf(" Data compression enabled: 0x%" PRIx64, n);
+ break;
+ default:
+ printf(" unknown %s = 0x%x, contents in hex:\n", param_c, pc);
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ break;
+ }
+skip_para:
+ printf("\n");
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* LAST_N_ERR_LPAGE [0x7] <lne> introduced: SPC-2 */
+static bool
+show_last_n_error_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, num, pl;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[256];
+ static const char * lneelp = "Last n error events log page";
+ static const char * eed = "error_event_data";
+
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ sgj_pr_hr(jsp, "No error events logged\n");
+ return true;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x7]\n", lneelp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, lneelp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "error_event_log_parameters");
+ }
+
+ for (k = num; k > 0; k -= pl, bp += pl) {
+ uint16_t pc;
+
+ if (k < 3) {
+ pr2serr("short %s\n", lneelp);
+ return false;
+ }
+ pl = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, NULL);
+ }
+
+ sgj_pr_hr(jsp, " Error event %u [0x%x]:\n", pc, pc);
+ if (pl > 4) {
+ if ((bp[2] & 0x1) && (bp[2] & 0x2)) {
+ sgj_pr_hr(jsp, " [binary]:\n");
+ hex2str(bp + 4, pl - 4, " ", op->hex2str_oformat,
+ sizeof(b), b);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_hex_bytes(jsp, jo3p, eed, bp + 4, pl - 4);
+ } else if (0x01 == (bp[2] & 0x3)) { /* ASCII */
+ sgj_pr_hr(jsp, " %.*s\n", pl - 4, (const char *)(bp + 4));
+ if (jsp->pr_as_json)
+ sgj_js_nv_s_len(jsp, jo3p, eed,
+ (const char *)(bp + 4), pl - 4);
+ } else {
+ sgj_pr_hr(jsp, " [data counter?? (LP bit should be "
+ "set)]:\n");
+ hex2str(bp + 4, pl - 4, " ", op->hex2str_oformat,
+ sizeof(b), b);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_hex_bytes(jsp, jo3p, eed, bp + 4, pl - 4);
+ }
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* LAST_N_DEFERRED_LPAGE [0xb] <lnd> introduced: SPC-2 */
+static bool
+show_last_n_deferred_error_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, n, num, pl;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo4p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[512];
+ static const char * lndeoaelp =
+ "Last n deferred errors or asynchronous events log page";
+ static const char * deoae = "Deferred error or asynchronous event";
+ static const char * sd = "sense_data";
+
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("No deferred errors logged\n");
+ return true;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xb]\n", lndeoaelp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, lndeoaelp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "deferred_error_or_asynchronous_event_log_parameters");
+ }
+
+ for (k = num; k > 0; k -= pl, bp += pl) {
+ int pc;
+
+ if (k < 3) {
+ pr2serr("short %s\n", lndeoaelp);
+ return false;
+ }
+ pl = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, deoae);
+ }
+ sgj_pr_hr(jsp, " %s [0x%x]:\n", deoae, pc);
+ if (op->do_brief > 0) {
+ hex2stdout(bp + 4, pl - 4, op->dstrhex_no_ascii);
+ hex2str(bp + 4, pl - 4, " ", op->hex2str_oformat,
+ sizeof(b), b);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_hex_bytes(jsp, jo3p, sd, bp + 4, pl - 4);
+ } else {
+
+ n = sg_get_sense_str(" ", bp + 4, pl - 4, false, sizeof(b),
+ b);
+ sgj_pr_hr(jsp, "%.*s\n", n, b);
+ if (jsp->pr_as_json) {
+ jo4p = sgj_named_subobject_r(jsp, jo3p, sd);
+ sgj_js_sense(jsp, jo4p, bp + 4, pl - 4);
+ }
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+static const char * clgc = "Change list generation code";
+static const char * cgn = "Changed generation number";
+
+/* LAST_N_INQUIRY_DATA_CH_SUBPG [0xb,0x1] <lnic> introduced: SPC-5 (rev 17) */
+static bool
+show_last_n_inq_data_ch_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool vpd;
+ int j, num, pl, vpd_pg;
+ uint32_t k, n;
+ const uint8_t * bp;
+ const char * vpd_pg_name = NULL;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo4p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p ja2p;
+ char str[PCB_STR_LEN];
+ char b[128];
+ static const char * lnidclp = "Last n inquiry data changed log page";
+ static const char * idci = "Inquiry data changed indicator";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xb,0x1]\n", lnidclp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, lnidclp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "inquiry_data_changed_log_parameters");
+ }
+
+ while (num > 3) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ 0 == pc ? clgc : idci);
+ }
+ if (0 == pc) {
+ if (pl < 8) {
+ pr2serr(" <<expect parameter 0x%x to be at least 8 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto skip;
+ }
+ sgj_pr_hr(jsp, " %s [pc=0x0]:\n", clgc);
+ for (j = 4, k = 1; j < pl; j +=4, ++k) {
+ n = sg_get_unaligned_be32(bp + j);
+ sgj_pr_hr(jsp, " %s [0x%x]: %u\n", cgn, k, n);
+ }
+ if (jsp->pr_as_json) {
+ ja2p = sgj_named_subarray_r(jsp, jo3p,
+ "changed_generation_numbers");
+ for (j = 4, k = 1; j < pl; j +=4, ++k) {
+ jo4p = sgj_new_unattached_object_r(jsp);
+ n = sg_get_unaligned_be32(bp + j);
+ js_snakenv_ihexstr_nex(jsp, jo4p, cgn, n, true, NULL,
+ NULL, NULL);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo4p);
+ }
+ }
+ } else { /* pc > 0x0 */
+ int m;
+ const int nn = sg_lib_names_mode_len;
+ struct sg_lib_simple_value_name_t * nvp = sg_lib_names_vpd_arr;
+
+ snprintf(b, sizeof(b), " %s 0x%x, ", param_c, pc);
+ vpd = !! (1 & *(bp + 4));
+ vpd_pg = *(bp + 5);
+ if (vpd) {
+ for (m = 0; m < nn; ++m, ++nvp) {
+ if (nvp->value == vpd_pg)
+ break;
+ }
+ vpd_pg_name = (m < nn) ? nvp->name : NULL;
+ } else
+ vpd_pg_name = "Standard INQUIRY";
+
+ if (jsp->pr_as_json) {
+ sgj_js_nv_i(jsp, jo3p, "vpd", (int)vpd);
+ sgj_js_nv_ihex(jsp, jo3p, "changed_page_code", vpd_pg);
+ if (vpd_pg_name)
+ sgj_js_nv_s(jsp, jo3p, "changed_page_name", vpd_pg_name);
+ }
+ if (vpd) {
+ sgj_pr_hr(jsp, "%sVPD page 0x%x changed\n", b, vpd_pg);
+ if (0 == op->do_brief) {
+ if (vpd_pg_name)
+ sgj_pr_hr(jsp, " name: %s\n", vpd_pg_name);
+ }
+ } else
+ sgj_pr_hr(jsp, "%sStandard INQUIRY data changed\n", b);
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+skip:
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* LAST_N_MODE_PG_DATA_CH_SUBPG [0xb,0x2] <lnmc> introduced: SPC-5 (rev 17) */
+static bool
+show_last_n_mode_pg_data_ch_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool spf;
+ int j, k, num, pl, pg_code, spg_code;
+ uint32_t n;
+ const uint8_t * bp;
+ const char * mode_pg_name = NULL;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo4p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p ja2p;
+ char str[PCB_STR_LEN];
+ char b[128];
+ static const char * lnmpdclp = "Last n mode page data changed log page";
+ static const char * mpdci = "Mode page data changed indicator";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xb,0x2]\n", lnmpdclp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, lnmpdclp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "mode_page_data_changed_log_parameters");
+ }
+
+ while (num > 3) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ 0 == pc ? clgc : mpdci);
+ }
+ if (0 == pc) { /* Same as LAST_N_INQUIRY_DATA_CH_SUBPG [0xb,0x1] */
+ if (pl < 8) {
+ pr2serr(" <<expect parameter 0x%x to be at least 8 bytes "
+ "long, got %d, skip>>\n", pc, pl);
+ goto skip;
+ }
+ sgj_pr_hr(jsp, " %s [pc=0x0]:\n", clgc);
+ for (j = 4, k = 1; j < pl; j +=4, ++k) {
+ n = sg_get_unaligned_be32(bp + j);
+ sgj_pr_hr(jsp, " %s [0x%x]: %u\n", cgn, k, n);
+ }
+ if (jsp->pr_as_json) {
+ ja2p = sgj_named_subarray_r(jsp, jo3p,
+ "changed_generation_numbers");
+ for (j = 4, k = 1; j < pl; j +=4, ++k) {
+ jo4p = sgj_new_unattached_object_r(jsp);
+ n = sg_get_unaligned_be32(bp + j);
+ js_snakenv_ihexstr_nex(jsp, jo4p, cgn, n, true, NULL,
+ NULL, NULL);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo4p);
+ }
+ }
+ } else { /* pc > 0x0 */
+ int k, val;
+ const int nn = sg_lib_names_mode_len;
+ struct sg_lib_simple_value_name_t * nmp = sg_lib_names_mode_arr;
+
+ snprintf(b, sizeof(b), " %s 0x%x, ", param_c, pc);
+ spf = !! (0x40 & *(bp + 4));
+ pg_code = 0x3f & *(bp + 4);
+ spg_code = *(bp + 5);
+ if (spf) /* SPF bit set */
+ sgj_pr_hr(jsp, "%smode page 0x%x,0%x changed\n", b, pg_code,
+ spg_code);
+ else
+ sgj_pr_hr(jsp, "%smode page 0x%x changed\n", b, pg_code);
+
+ val = (pg_code << 8) | spg_code;
+ for (k = 0; k < nn; ++k, ++nmp) {
+ if (nmp->value == val)
+ break;
+ }
+ mode_pg_name = (k < nn) ? nmp->name : NULL;
+ if ((0 == op->do_brief) && mode_pg_name)
+ sgj_pr_hr(jsp, " name: %s\n", nmp->name);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_i(jsp, jo3p, "spf", (int)spf);
+ sgj_js_nv_ihex(jsp, jo3p, "mode_page_code", pg_code);
+ sgj_js_nv_ihex(jsp, jo3p, "subpage_code", spg_code);
+ if (mode_pg_name)
+ sgj_js_nv_s(jsp, jo3p, "mode_page_name", mode_pg_name);
+ }
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+skip:
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->filter_given)
+ break;
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+static const char * self_test_code[] = {
+ "default", "background short", "background extended", rsv_s,
+ "aborted background", "foreground short", "foreground extended",
+ rsv_s};
+
+static const char * self_test_result[] = {
+ "completed without error",
+ "aborted by SEND DIAGNOSTIC",
+ "aborted other than by SEND DIAGNOSTIC",
+ "unknown error, unable to complete",
+ "self test completed with failure in test segment (which one unknown)",
+ "first segment in self test failed",
+ "second segment in self test failed",
+ "another segment in self test failed",
+ rsv_s, rsv_s, rsv_s, rsv_s, rsv_s, rsv_s,
+ rsv_s,
+ "self test in progress"};
+
+/* SELF_TEST_LPAGE [0x10] <str> introduced: SPC-3 */
+static bool
+show_self_test_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool addr_all_ffs;
+ int k, num, res, st_c;
+ unsigned int v;
+ uint32_t n;
+ uint64_t ull;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[80];
+ static const char * strlp = "Self-test results log page";
+ static const char * stc_s = "Self-test code";
+ static const char * str_s = "Self-test result";
+ static const char * stn_s = "Self-test number";
+ static const char * apoh = "Accumulated power on hours";
+
+ num = len - 4;
+ if (num < 0x190) {
+ pr2serr("short %s [length 0x%x rather than 0x190 bytes]\n", strlp,
+ num);
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x10]\n", strlp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, strlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "self_test_results_log_parameters");
+ }
+
+ for (k = 0, bp = resp + 4; k < 20; ++k, bp += 20 ) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+ int pl = bp[3] + 4;
+
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ break;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ "Self-test results");
+ }
+ n = sg_get_unaligned_be16(bp + 6);
+ if ((0 == n) && (0 == bp[4])) {
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ break;
+ }
+ sgj_pr_hr(jsp, " %s = %d, accumulated power-on hours = %d\n",
+ param_c, pc, n);
+ st_c = (bp[4] >> 5) & 0x7;
+ sgj_pr_hr(jsp, " %s: %s [%d]\n", stc_s, self_test_code[st_c],
+ st_c);
+ res = bp[4] & 0xf;
+ sgj_pr_hr(jsp, " %s: %s [%d]\n", str_s, self_test_result[res],
+ res);
+ if (bp[5])
+ sgj_pr_hr(jsp, " %s = %d\n", stn_s, (int)bp[5]);
+ ull = sg_get_unaligned_be64(bp + 8);
+
+ addr_all_ffs = sg_all_ffs(bp + 8, 8);
+ if (! addr_all_ffs) {
+ addr_all_ffs = false;
+ if ((res > 0) && ( res < 0xf))
+ sgj_pr_hr(jsp, " address of first error = 0x%" PRIx64 "\n",
+ ull);
+ }
+ addr_all_ffs = false;
+ v = bp[16] & 0xf;
+ if (v) {
+ if (op->do_brief)
+ sgj_pr_hr(jsp, " %s = 0x%x , asc = 0x%x, ascq = 0x%x\n",
+ s_key, v, bp[17], bp[18]);
+ else {
+ sgj_pr_hr(jsp, " %s = 0x%x [%s]\n", s_key, v,
+ sg_get_sense_key_str(v, sizeof(b), b));
+
+ sgj_pr_hr(jsp, " asc = 0x%x, ascq = 0x%x [%s]\n",
+ bp[17], bp[18], sg_get_asc_ascq_str(bp[17], bp[18],
+ sizeof(b), b));
+ }
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2],
+ str, sizeof(str)));
+ if (jsp->pr_as_json) {
+ js_snakenv_ihexstr_nex(jsp, jo3p, stc_s, st_c, true, NULL,
+ self_test_code[st_c], NULL);
+ js_snakenv_ihexstr_nex(jsp, jo3p, str_s, res, true, NULL,
+ self_test_result[res], NULL);
+ js_snakenv_ihexstr_nex(jsp, jo3p, stn_s, bp[5], false, NULL,
+ NULL, "segment number that failed");
+ js_snakenv_ihexstr_nex(jsp, jo3p, apoh, n, true, NULL,
+ (0xffff == n ? "65535 hours or more" : NULL), NULL);
+ sgj_js_nv_ihexstr(jsp, jo3p, "address_of_first_failure", pc, NULL,
+ addr_all_ffs ? "no errors detected" : NULL);
+ sgj_js_nv_ihexstr(jsp, jo3p, "sense_key", v, NULL,
+ sg_get_sense_key_str(v, sizeof(b), b));
+ sgj_js_nv_ihexstr(jsp, jo3p, "additional_sense_code", bp[17],
+ NULL, NULL);
+ sgj_js_nv_ihexstr(jsp, jo3p, "additional_sense_code_qualifier",
+ bp[18], NULL, sg_get_asc_ascq_str(bp[17],
+ bp[18], sizeof(b), b));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* TEMPERATURE_LPAGE [0xd] <temp> introduced: SPC-3
+ * N.B. The ENV_REPORTING_SUBPG [0xd,0x1] and the ENV_LIMITS_SUBPG [0xd,0x2]
+ * (both added SPC-5) are a superset of this page. */
+static bool
+show_temperature_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int k, num, extra;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ static const char * tlp = "Temperature log page";
+ static const char * ctemp = "Current temperature";
+ static const char * rtemp = "Reference temperature";
+
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed Temperature page\n");
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (! op->do_temperature)
+ sgj_pr_hr(jsp, "%s [0xd]\n", tlp);
+ }
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, tlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "temperature_log_parameters");
+ }
+
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc;
+
+ if (k < 3) {
+ pr2serr("short Temperature page\n");
+ return true;
+ }
+ extra = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ sgj_js_nv_ihex(jsp, jo3p, param_c_sn, pc);
+ }
+
+ switch (pc) {
+ case 0:
+ if ((extra > 5) && (k > 5)) {
+ if (0 == bp[5])
+ sgj_pr_hr(jsp, " %s = 0 C (or less)\n", ctemp);
+ else if (bp[5] < 0xff)
+ sgj_pr_hr(jsp, " %s = %d C\n", ctemp, bp[5]);
+ else
+ sgj_pr_hr(jsp, " %s = <%s>\n", ctemp, not_avail);
+ if (jsp->pr_as_json) {
+ const char * cp = NULL;
+
+ if (0 == bp[5])
+ cp = "0 or less Celsius";
+ else if (0xff == bp[5])
+ cp = "temperature not available";
+ js_snakenv_ihexstr_nex(jsp, jo3p, "temperature", bp[5],
+ false, NULL, cp,
+ "current [unit: celsius]");
+ }
+ }
+ break;
+ case 1:
+ if ((extra > 5) && (k > 5)) {
+ if (bp[5] < 0xff)
+ sgj_pr_hr(jsp, " %s = %d C\n", rtemp, bp[5]);
+ else
+ sgj_pr_hr(jsp, " %s = <%s>\n", rtemp, not_avail);
+ if (jsp->pr_as_json) {
+ const char * cp;
+
+ if (0 == bp[5])
+ cp = "in C (or less)";
+ else if (0xff == bp[5])
+ cp = not_avail;
+ else
+ cp = "in C";
+ sgj_js_nv_ihex_nex(jsp, jo3p, "reference_temperature",
+ bp[5], true, cp);
+ }
+ }
+ break;
+ default:
+ if (! op->do_temperature) {
+ sgj_pr_hr(jsp, " unknown %s = 0x%x, contents in hex:\n",
+ param_c, pc);
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ } else {
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ continue;
+ }
+ break;
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+skip:
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* START_STOP_LPAGE [0xe] <sscc> introduced: SPC-3 */
+static bool
+show_start_stop_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int k, num, extra;
+ uint32_t val;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[256];
+ static const char * sscclp = "Start-stop cycle counter log page";
+ static const char * dom = "Date of manufacture";
+ static const char * ad = "Accounting date";
+ static const char * sccodl = "Specified cycle count over device lifetime";
+ static const char * assc = "Accumulated start-stop cycles";
+ static const char * slucodl =
+ "Specified load-unload count over device lifetime";
+ static const char * aluc = "Accumulated load-unload cycles";
+
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed Start-stop cycle counter page\n");
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0xe]\n", sscclp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, sscclp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "start_stop_cycle_log_parameters");
+ }
+
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc;
+
+ if (k < 3) {
+ pr2serr("short %s\n", sscclp);
+ return false;
+ }
+ extra = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 1:
+ if (10 == extra) {
+ sgj_pr_hr(jsp, " %s, year: %.4s, week: %.2s\n", dom,
+ bp + 4, bp + 8);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ "Date of manufacture");
+ sgj_js_nv_s_len(jsp, jo3p, "year_of_manufacture",
+ (const char *)(bp + 4), 4);
+ sgj_js_nv_s_len(jsp, jo3p, "week_of_manufacture",
+ (const char *)(bp + 8), 2);
+ }
+ } else if (op->verbose) {
+ pr2serr("%s parameter length strange: %d\n", dom, extra - 4);
+ hex2stderr(bp, extra, 1);
+ }
+ break;
+ case 2:
+ if (10 == extra) {
+ sgj_pr_hr(jsp, " %s, year: %.4s, week: %.2s\n", ad, bp + 4,
+ bp + 8);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ "Accounting date");
+ sgj_js_nv_s_len(jsp, jo3p, "year_of_manufacture",
+ (const char *)(bp + 4), 4);
+ sgj_js_nv_s_len(jsp, jo3p, "week_of_manufacture",
+ (const char *)(bp + 8), 2);
+ }
+ } else if (op->verbose) {
+ pr2serr("%s parameter length strange: %d\n", ad, extra - 4);
+ hex2stderr(bp, extra, 1);
+ }
+ break;
+ case 3:
+ if (extra > 7) {
+ val = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", sccodl, val);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ sccodl);
+ js_snakenv_ihexstr_nex(jsp, jo3p, sccodl, val, false,
+ NULL, NULL, NULL);
+ }
+ }
+ break;
+ case 4:
+ if (extra > 7) {
+ val = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", assc, val);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ assc);
+ js_snakenv_ihexstr_nex(jsp, jo3p, assc, val, false,
+ NULL, NULL, NULL);
+ }
+ }
+ break;
+ case 5:
+ if (extra > 7) {
+ val = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", slucodl, val);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ slucodl);
+ js_snakenv_ihexstr_nex(jsp, jo3p, slucodl, val, false,
+ NULL, NULL, NULL);
+ }
+ }
+ break;
+ case 6:
+ if (extra > 7) {
+ val = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, " %s = %u\n", aluc, val);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, aluc);
+ js_snakenv_ihexstr_nex(jsp, jo3p, aluc, val, false,
+ NULL, NULL, NULL);
+ }
+ }
+ break;
+ default:
+ sgj_pr_hr(jsp, " unknown %s = 0x%x, contents in hex:\n",
+ param_c, pc);
+ hex2str(bp, extra, " ", op->hex2str_oformat, sizeof(b), b);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, unknown_s);
+ sgj_js_nv_hex_bytes(jsp, jo3p, in_hex, bp, extra);
+ }
+ break;
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+skip:
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* APP_CLIENT_LPAGE [0xf] <ac> introduced: SPC-3 */
+static bool
+show_app_client_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int k, n, num, extra;
+ char * mp;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ static const char * aclp = "Application Client log page";
+ static const char * guac = "General Usage Application Client";
+
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed %s\n", aclp);
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (op->do_hex == 0)))
+ sgj_pr_hr(jsp, "%s [0xf]\n", aclp);
+ if (jsp->pr_as_json)
+ jo2p = sg_log_js_hdr(jsp, jop, aclp, resp);
+ if ((0 == op->filter_given) && (! op->do_full)) {
+ if ((len > 128) && (0 == op->do_hex) && (0 == op->undefined_hex)) {
+ char d[256];
+
+ hex2str(resp, 64, " ", op->hex2str_oformat, sizeof(d), d);
+ sgj_pr_hr(jsp, "%s", d);
+ sgj_pr_hr(jsp, " ..... [truncated after 64 of %d bytes (use "
+ "'-H' to see the rest)]\n", len);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihex(jsp, jo2p, "actual_length", len);
+ sgj_js_nv_ihex(jsp, jo2p, "truncated_length", 64);
+ sgj_js_nv_hex_bytes(jsp, jo2p, in_hex, resp, 64);
+ }
+ } else {
+ n = len * 4 + 32;
+ mp = malloc(n);
+ if (mp) {
+ hex2str(resp, len, " ", op->hex2str_oformat, n, mp);
+ sgj_pr_hr(jsp, "%s", mp);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihex(jsp, jo2p, "length", len);
+ sgj_js_nv_hex_bytes(jsp, jo2p, in_hex, resp, len);
+ }
+ free(mp);
+ }
+ }
+ return true;
+ }
+ if (jsp->pr_as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "application_client_log_parameters");
+
+ /* here if filter_given set or --full given */
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc;
+ char d[1024];
+
+ if (k < 3) {
+ pr2serr("short %s\n", aclp);
+ return true;
+ }
+ extra = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ sgj_pr_hr(jsp, " %s = %d [0x%x] %s\n", param_c, pc, pc,
+ (pc <= 0xfff) ? guac : "");
+ hex2str(bp, extra, " ", op->hex2str_oformat, sizeof(d), d);
+ sgj_pr_hr(jsp, "%s", d);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ (pc <= 0xfff) ? guac : NULL);
+ sgj_js_nv_hex_bytes(jsp, jo3p, in_hex, bp, extra);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* IE_LPAGE [0x2f] <ie> "Informational Exceptions" introduced: SPC-3
+ * Previously known as "SMART Status and Temperature Reading" lpage. */
+static bool
+show_ie_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool skip = false;
+ int k, num, param_len;
+ const uint8_t * bp;
+ const char * cp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[512];
+ char bb[64];
+ bool full, decoded;
+ static const char * ielp = "Informational exceptions log page";
+ static const char * ieasc =
+ "informational_exceptions_additional_sense_code";
+ static const char * ct = "Current temperature";
+ static const char * tt = "Threshold temperature";
+ static const char * mt = "Maximum temperature";
+ static const char * ce = "common extension";
+
+ full = ! op->do_temperature;
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (num < 4) {
+ pr2serr("badly formed %s\n", ielp);
+ return false;
+ }
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (full)
+ sgj_pr_hr(jsp, "%s [0x2f]\n", ielp);
+ }
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, ielp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "informational_exceptions_log_parameters");
+ }
+
+ for (k = num; k > 0; k -= param_len, bp += param_len) {
+ int pc;
+
+ if (k < 3) {
+ pr2serr("short %s\n", ielp);
+ return false;
+ }
+ param_len = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, param_len);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, param_len, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ decoded = true;
+ cp = NULL;
+
+ switch (pc) {
+ case 0x0:
+ if (param_len > 5) {
+ bool na;
+ uint8_t t;
+
+ if (full) {
+ sgj_pr_hr(jsp, " IE asc = 0x%x, ascq = 0x%x\n", bp[4],
+ bp[5]);
+ if (bp[4] || bp[5])
+ if(sg_get_asc_ascq_str(bp[4], bp[5], sizeof(b), b))
+ sgj_pr_hr(jsp, " [%s]\n", b);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ "Informational exceptions general");
+ sgj_js_nv_ihexstr(jsp, jo3p, ieasc, bp[4], NULL,
+ NULL);
+ snprintf(b, sizeof(b), "%s_qualifier", ieasc);
+ sgj_js_nv_ihexstr(jsp, jo3p, b, bp[5], NULL,
+ sg_get_asc_ascq_str(bp[4], bp[5],
+ sizeof(bb), bb));
+ }
+ }
+ if (param_len <= 6)
+ break;
+ t = bp[6];
+ na = (0xff == t);
+ if (na)
+ snprintf(b, sizeof(b), "%u C", t);
+ else
+ snprintf(b, sizeof(b), "<%s>", unknown_s);
+ sgj_pr_hr(jsp, " %s = %s\n", ct, b);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, ct, t, true,
+ NULL, na ? unknown_s : NULL,
+ "[unit: celsius]");
+ if (param_len > 7) {
+ t = bp[7];
+ na = (0xff == t);
+ if (na)
+ snprintf(b, sizeof(b), "%u C", t);
+ else
+ snprintf(b, sizeof(b), "<%s>", unknown_s);
+ sgj_pr_hr(jsp, " %s = %s [%s]\n", tt, b, ce);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, tt, t, true, NULL,
+ na ? unknown_s : NULL, ce);
+ t = bp[8];
+ if ((param_len > 8) && (t >= bp[6])) {
+ na = (0xff == t);
+ if (na)
+ snprintf(b, sizeof(b), "%u C", t);
+ else
+ snprintf(b, sizeof(b), "<%s>", unknown_s);
+ sgj_pr_hr(jsp, " %s = %s [%s]\n", mt, b, ce);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, mt, t, true,
+ NULL,
+ na ? unknown_s : NULL, ce);
+ }
+ }
+ }
+ decoded = true;
+ break;
+ default:
+ if (op->do_brief > 0) {
+ cp = NULL;
+ skip = true;
+ break;
+ }
+ if (VP_HITA == op->vend_prod_num) {
+ switch (pc) {
+ case 0x1:
+ cp = "Remaining reserve 1";
+ break;
+ case 0x2:
+ cp = "Remaining reserve XOR";
+ break;
+ case 0x3:
+ cp = "XOR depletion";
+ break;
+ case 0x4:
+ cp = "Volatile memory backup failure";
+ break;
+ case 0x5:
+ cp = "Wear indicator";
+ break;
+ case 0x6:
+ cp = "System area wear indicator";
+ break;
+ case 0x7:
+ cp = "Channel hangs";
+ break;
+ case 0x8:
+ cp = "Flash scan failure";
+ break;
+ default:
+ decoded = false;
+ break;
+ }
+ if (cp) {
+ sgj_pr_hr(jsp, " %s:\n", cp);
+ sgj_pr_hr(jsp, " SMART sense_code=0x%x sense_qualifier"
+ "=0x%x threshold=%d%% trip=%d\n", bp[4], bp[5],
+ bp[6], bp[7]);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ cp);
+ sgj_js_nv_ihex(jsp, jo3p, "smart_sense_code", bp[4]);
+ sgj_js_nv_ihex(jsp, jo3p, "smart_sense_qualifier",
+ bp[5]);
+ sgj_js_nv_ihex(jsp, jo3p, "smart_threshold", bp[6]);
+ sgj_js_nv_ihex(jsp, jo3p, "smart_trip", bp[7]);
+ }
+ }
+ } else {
+ decoded = false;
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ unknown_s);
+ }
+ break;
+ } /* end of switch statement */
+ if (skip)
+ skip = false;
+ else if ((! decoded) && full) {
+ hex2str(bp, param_len, " ", op->hex2str_oformat, sizeof(b), b);
+ sgj_pr_hr(jsp, " %s = 0x%x, contents in hex:\n%s", param_c, pc,
+ b);
+ }
+
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+skip:
+ if (op->filter_given)
+ break;
+ } /* end of for loop */
+ return true;
+}
+
+/* called for SAS port of PROTO_SPECIFIC_LPAGE [0x18] */
+static const char *
+show_sas_phy_event_info(int pes, unsigned int val, unsigned int thresh_val,
+ char * b, int blen)
+{
+ int n = 0;
+ unsigned int u;
+ const char * cp = "";
+ static const char * pvdt = "Peak value detector threshold";
+
+ switch (pes) {
+ case 0:
+ cp = "No event";
+ snprintf(b, blen, "%s", cp);
+ break;
+ case 0x1:
+ cp = "Invalid word count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x2:
+ cp = "Running disparity error count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x3:
+ cp = "Loss of dword synchronization count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x4:
+ cp = "Phy reset problem count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x5:
+ cp = "Elasticity buffer overflow count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x6:
+ cp = "Received ERROR count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x7:
+ cp = "Invalid SPL packet count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x8:
+ cp = "Loss of SPL packet synchronization count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x20:
+ cp = "Received address frame error count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x21:
+ cp = "Transmitted abandon-class OPEN_REJECT count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x22:
+ cp = "Received abandon-class OPEN_REJECT count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x23:
+ cp = "Transmitted retry-class OPEN_REJECT count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x24:
+ cp = "Received retry-class OPEN_REJECT count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x25:
+ cp = "Received AIP (WAITING ON PARTIAL) count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x26:
+ cp = "Received AIP (WAITING ON CONNECTION) count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x27:
+ cp = "Transmitted BREAK count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x28:
+ cp = "Received BREAK count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x29:
+ cp = "Break timeout count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x2a:
+ cp = "Connection count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x2b:
+ cp = "Peak transmitted pathway blocked count";
+ n = sg_scnpr(b, blen, "%s: %u", cp, val & 0xff);
+ sg_scnpr(b + n, blen - n, "\t%s: %u", pvdt, thresh_val & 0xff);
+ break;
+ case 0x2c:
+ cp = "Peak transmitted arbitration wait time";
+ u = val & 0xffff;
+ if (u < 0x8000)
+ n = sg_scnpr(b, blen, "%s (us): %u", cp, u);
+ else
+ n = sg_scnpr(b, blen, "%s (ms): %u", cp, 33 + (u - 0x8000));
+ u = thresh_val & 0xffff;
+ if (u < 0x8000)
+ sg_scnpr(b + n, blen - n, "\t%s (us): %u", pvdt, u);
+ else
+ sg_scnpr(b + n, blen - n, "\t%s (ms): %u", pvdt,
+ 33 + (u - 0x8000));
+ break;
+ case 0x2d:
+ cp = "Peak arbitration time";
+ n = sg_scnpr(b, blen, "%s (us): %u", cp, val);
+ sg_scnpr(b + n, blen - n, "\t%s: %u", pvdt, thresh_val);
+ break;
+ case 0x2e:
+ cp = "Peak connection time";
+ n = sg_scnpr(b, blen, "%s (us): %u", cp, val);
+ sg_scnpr(b + n, blen - n, "\t%s: %u", pvdt, thresh_val);
+ break;
+ case 0x2f:
+ cp = "Persistent connection count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x40:
+ cp = "Transmitted SSP frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x41:
+ cp = "Received SSP frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x42:
+ cp = "Transmitted SSP frame error count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x43:
+ cp = "Received SSP frame error count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x44:
+ cp = "Transmitted CREDIT_BLOCKED count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x45:
+ cp = "Received CREDIT_BLOCKED count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x50:
+ cp = "Transmitted SATA frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x51:
+ cp = "Received SATA frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x52:
+ cp = "SATA flow control buffer overflow count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x60:
+ cp = "Transmitted SMP frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x61:
+ cp = "Received SMP frame count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ case 0x63:
+ cp = "Received SMP frame error count";
+ snprintf(b, blen, "%s: %u", cp, val);
+ break;
+ default:
+ cp = "";
+ snprintf(b, blen, "Unknown phy event source: %d, val=%u, "
+ "thresh_val=%u", pes, val, thresh_val);
+ break;
+ }
+ return cp;
+}
+
+static const char * sas_link_rate_arr[16] = {
+ "phy enabled; unknown rate",
+ "phy disabled",
+ "phy enabled; speed negotiation failed",
+ "phy enabled; SATA spinup hold state",
+ "phy enabled; port selector",
+ "phy enabled; reset in progress",
+ "phy enabled; unsupported phy attached",
+ "reserved [0x7]",
+ "1.5 Gbps", /* 0x8 */
+ "3 Gbps",
+ "6 Gbps",
+ "12 Gbps",
+ "22.5 Gbps",
+ "reserved [0xd]",
+ "reserved [0xe]",
+ "reserved [0xf]",
+};
+
+static char *
+sas_negot_link_rate(int lrate, char * b, int blen)
+{
+ int mask = 0xf;
+
+ if (~mask & lrate)
+ snprintf(b, blen, "bad link_rate value=0x%x\n", lrate);
+ else
+ snprintf(b, blen, "%s", sas_link_rate_arr[lrate]);
+ return b;
+}
+
+/* helper for SAS port of PROTO_SPECIFIC_LPAGE [0x18] */
+static void
+show_sas_port_param(const uint8_t * bp, int param_len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int j, m, nphys, t, spld_len, pi;
+ uint64_t ull;
+ unsigned int ui, ui2, ui3, ui4;
+ char * cp;
+ const char * ccp;
+ const char * cc2p;
+ const char * cc3p;
+ const char * cc4p;
+ const uint8_t * vcp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p ja2p = NULL;
+ char b[160];
+ char s[80];
+ static char * rtpi = "Relative target port identifier";
+ static char * psplpfstp =
+ "Protocol Specific Port log parameter for SAS target port";
+ static char * at = "attached";
+ static char * ip = "initiator_port";
+ static char * tp = "target_port";
+ static char * pvdt = "peak_value_detector_threshold";
+ static const int sz = sizeof(s);
+ static const int blen = sizeof(b);
+
+ t = sg_get_unaligned_be16(bp + 0);
+ if (op->do_name)
+ sgj_pr_hr(jsp, " rel_target_port=%d\n", t);
+ else
+ sgj_pr_hr(jsp, " %s = %d\n", rtpi, t);
+ if (op->do_name)
+ sgj_pr_hr(jsp, " gen_code=%d\n", bp[6]);
+ else
+ sgj_pr_hr(jsp, " generation code = %d\n", bp[6]);
+ nphys = bp[7];
+ if (op->do_name)
+ sgj_pr_hr(jsp, " num_phys=%d\n", nphys);
+ else
+ sgj_pr_hr(jsp, " number of phys = %d\n", nphys);
+ if (jsp->pr_as_json) {
+ js_snakenv_ihexstr_nex(jsp, jop, param_c , t, true,
+ NULL, psplpfstp, rtpi);
+ pi = 0xf & bp[4];
+ sgj_js_nv_ihexstr(jsp, jop, "protocol_identifier", pi, NULL,
+ sg_get_trans_proto_str(pi, blen, b));
+ sgj_js_nv_ihex(jsp, jop, "generation_code", bp[6]);
+ sgj_js_nv_ihex(jsp, jop, "number_of_phys", bp[7]);
+ jap = sgj_named_subarray_r(jsp, jop, "sas_phy_log_descriptor_list");
+ }
+
+ for (j = 0, vcp = bp + 8; j < (param_len - 8);
+ vcp += spld_len, j += spld_len) {
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo2p, vcp[2]);
+ }
+ if (op->do_name)
+ sgj_pr_hr(jsp, " phy_id=%d\n", vcp[1]);
+ else
+ sgj_haj_vi(jsp, jo2p, 2, "phy identifier", SGJ_SEP_EQUAL_1_SPACE,
+ vcp[1], true);
+ spld_len = vcp[3];
+ if (spld_len < 44)
+ spld_len = 48; /* in SAS-1 and SAS-1.1 vcp[3]==0 */
+ else
+ spld_len += 4;
+ if (op->do_name) {
+ t = ((0x70 & vcp[4]) >> 4);
+ sgj_pr_hr(jsp, " att_dev_type=%d\n", t);
+ sgj_pr_hr(jsp, " att_iport_mask=0x%x\n", vcp[6]);
+ sgj_pr_hr(jsp, " att_phy_id=%d\n", vcp[24]);
+ sgj_pr_hr(jsp, " att_reason=0x%x\n", (vcp[4] & 0xf));
+ ull = sg_get_unaligned_be64(vcp + 16);
+ sgj_pr_hr(jsp, " att_sas_addr=0x%" PRIx64 "\n", ull);
+ sgj_pr_hr(jsp, " att_tport_mask=0x%x\n", vcp[7]);
+ ui = sg_get_unaligned_be32(vcp + 32);
+ sgj_pr_hr(jsp, " inv_dwords=%u\n", ui);
+ ui = sg_get_unaligned_be32(vcp + 40);
+ sgj_pr_hr(jsp, " loss_dword_sync=%u\n", ui);
+ sgj_pr_hr(jsp, " neg_log_lrate=%d\n", 0xf & vcp[5]);
+ ui = sg_get_unaligned_be32(vcp + 44);
+ sgj_pr_hr(jsp, " phy_reset_probs=%u\n", ui);
+ ui = sg_get_unaligned_be32(vcp + 36);
+ sgj_pr_hr(jsp, " running_disparity=%u\n", ui);
+ sgj_pr_hr(jsp, " reason=0x%x\n", (vcp[5] & 0xf0) >> 4);
+ ull = sg_get_unaligned_be64(vcp + 8);
+ sgj_pr_hr(jsp, " sas_addr=0x%" PRIx64 "\n", ull);
+ } else {
+ t = ((0x70 & vcp[4]) >> 4);
+ /* attached SAS device type. In SAS-1.1 case 2 was an edge
+ * expander; in SAS-2 case 3 is marked as obsolete. */
+ switch (t) {
+ case 0: snprintf(s, sz, "no device %s", at); break;
+ case 1: snprintf(s, sz, "SAS or SATA device"); break;
+ case 2: snprintf(s, sz, "expander device"); break;
+ case 3: snprintf(s, sz, "expander device (fanout)"); break;
+ default: snprintf(s, sz, "%s [%d]", rsv_s, t); break;
+ }
+ /* the word 'SAS' in following added in spl4r01 */
+ sgj_pr_hr(jsp, " %s SAS device type: %s\n", at, s);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo2p, "attached_sas_device_type", t,
+ NULL, s);
+ t = 0xf & vcp[4];
+ switch (t) {
+ case 0: snprintf(s, sz, "%s", unknown_s); break;
+ case 1: snprintf(s, sz, "power on"); break;
+ case 2: snprintf(s, sz, "hard reset"); break;
+ case 3: snprintf(s, sz, "SMP phy control function"); break;
+ case 4: snprintf(s, sz, "loss of dword synchronization"); break;
+ case 5: snprintf(s, sz, "mux mix up"); break;
+ case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA");
+ break;
+ case 7: snprintf(s, sz, "break timeout timer expired"); break;
+ case 8: snprintf(s, sz, "phy test function stopped"); break;
+ case 9: snprintf(s, sz, "expander device reduced functionality");
+ break;
+ default: snprintf(s, sz, "%s [0x%x]", rsv_s, t); break;
+ }
+ sgj_pr_hr(jsp, " %s reason: %s\n", at, s);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo2p, "attached_reason", t, NULL, s);
+ t = (vcp[5] & 0xf0) >> 4;
+ switch (t) {
+ case 0: snprintf(s, sz, "%s", unknown_s); break;
+ case 1: snprintf(s, sz, "power on"); break;
+ case 2: snprintf(s, sz, "hard reset"); break;
+ case 3: snprintf(s, sz, "SMP phy control function"); break;
+ case 4: snprintf(s, sz, "loss of dword synchronization"); break;
+ case 5: snprintf(s, sz, "mux mix up"); break;
+ case 6: snprintf(s, sz, "I_T nexus loss timeout for STP/SATA");
+ break;
+ case 7: snprintf(s, sz, "break timeout timer expired"); break;
+ case 8: snprintf(s, sz, "phy test function stopped"); break;
+ case 9: snprintf(s, sz, "expander device reduced functionality");
+ break;
+ default: snprintf(s, sz, "%s [0x%x]", rsv_s, t); break;
+ }
+ sgj_pr_hr(jsp, " reason: %s\n", s);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo2p, "reason", t, NULL, s);
+ t = (0xf & vcp[5]);
+ ccp = "negotiated logical link rate";
+ cc2p = sas_negot_link_rate(t, s, sz);
+ sgj_pr_hr(jsp, " %s: %s\n", ccp, cc2p);
+ if (jsp->pr_as_json) {
+ sgj_convert_to_snake_name(ccp, b, blen);
+ sgj_js_nv_ihexstr(jsp, jo2p, b, t, NULL, cc2p);
+ }
+
+ sgj_pr_hr(jsp, " %s initiator port: ssp=%d stp=%d smp=%d\n",
+ at, !! (vcp[6] & 8), !! (vcp[6] & 4), !! (vcp[6] & 2));
+ if (jsp->pr_as_json) {
+ snprintf(b, blen, "%s_ssp_%s", at, ip);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[6] & 8));
+ snprintf(b, blen, "%s_stp_%s", at, ip);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[6] & 4));
+ snprintf(b, blen, "%s_smp_%s", at, ip);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[6] & 2));
+ }
+ sgj_pr_hr(jsp, " %s target port: ssp=%d stp=%d smp=%d\n", at,
+ !! (vcp[7] & 8), !! (vcp[7] & 4), !! (vcp[7] & 2));
+ if (jsp->pr_as_json) {
+ snprintf(b, blen, "%s_ssp_%s", at, tp);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[7] & 8));
+ snprintf(b, blen, "%s_stp_%s", at, tp);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[7] & 4));
+ snprintf(b, blen, "%s_smp_%s", at, tp);
+ sgj_js_nv_i(jsp, jo2p, b, !! (vcp[7] & 2));
+ }
+ ull = sg_get_unaligned_be64(vcp + 8);
+ sgj_pr_hr(jsp, " SAS address = 0x%" PRIx64 "\n", ull);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihex(jsp, jo2p, "sas_address", ull);
+ ull = sg_get_unaligned_be64(vcp + 16);
+ sgj_pr_hr(jsp, " %s SAS address = 0x%" PRIx64 "\n", at, ull);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihex(jsp, jo2p, "attached_sas_address", ull);
+ ccp = "attached phy identifier";
+ sgj_haj_vi(jsp, jo2p, 4, ccp, SGJ_SEP_EQUAL_1_SPACE, vcp[24],
+ true);
+ ccp = "Invalid DWORD count";
+ ui = sg_get_unaligned_be32(vcp + 32);
+ cc2p = "Running disparity error count";
+ ui2 = sg_get_unaligned_be32(vcp + 36);
+ cc3p = "Loss of DWORD synchronization count";
+ ui3 = sg_get_unaligned_be32(vcp + 40);
+ cc4p = "Phy reset problem count";
+ ui4 = sg_get_unaligned_be32(vcp + 44);
+ if (jsp->pr_as_json) {
+ sgj_convert_to_snake_name(ccp, b, blen);
+ sgj_js_nv_ihex(jsp, jo2p, b, ui);
+ sgj_convert_to_snake_name(cc2p, b, blen);
+ sgj_js_nv_ihex(jsp, jo2p, b, ui2);
+ sgj_convert_to_snake_name(cc3p, b, blen);
+ sgj_js_nv_ihex(jsp, jo2p, b, ui3);
+ sgj_convert_to_snake_name(cc4p, b, blen);
+ sgj_js_nv_ihex(jsp, jo2p, b, ui4);
+ } else {
+ if (0 == op->do_brief) {
+ sgj_pr_hr(jsp, " %s = %u\n", ccp, ui);
+ sgj_pr_hr(jsp, " %s = %u\n", cc2p, ui2);
+ sgj_pr_hr(jsp, " %s = %u\n", cc3p, ui3);
+ sgj_pr_hr(jsp, " %s = %u\n", cc4p, ui4);
+ }
+ }
+ }
+ if (op->do_brief > 0)
+ goto skip;
+ if (spld_len > 51) {
+ int num_ped;
+ const uint8_t * xcp;
+
+ num_ped = vcp[51];
+ if (op->verbose > 1)
+ sgj_pr_hr(jsp, " <<Phy event descriptors: %d, spld_len: "
+ "%d, calc_ped: %d>>\n", num_ped, spld_len,
+ (spld_len - 52) / 12);
+ if (num_ped > 0) {
+ if (op->do_name) {
+ sgj_pr_hr(jsp, " phy_event_desc_num=%d\n", num_ped);
+ return; /* don't decode at this stage */
+ } else
+ sgj_pr_hr(jsp, " Phy event descriptors:\n");
+ }
+ if (jsp->pr_as_json) {
+ sgj_js_nv_i(jsp, jo2p, "number_of_phy_event_descriptors",
+ num_ped);
+ if (num_ped > 0)
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "phy_event_descriptor_list");
+ }
+ xcp = vcp + 52;
+ for (m = 0; m < (num_ped * 12); m += 12, xcp += 12) {
+ int pes = xcp[3];
+ unsigned int pvdt_v;
+
+ if (jsp->pr_as_json)
+ jo3p = sgj_new_unattached_object_r(jsp);
+ ui = sg_get_unaligned_be32(xcp + 4);
+ pvdt_v = sg_get_unaligned_be32(xcp + 8);
+ ccp = show_sas_phy_event_info(pes, ui, pvdt_v, b, blen);
+ if (0 == strlen(ccp)) {
+ sgj_pr_hr(jsp, " %s\n", b); /* unknown pvdt_v */
+ if (jsp->pr_as_json) {
+ int n;
+
+ snprintf(s, sz, "%s_pes_0x%x", unknown_s, pes);
+ sgj_js_nv_ihex(jsp, jo3p, s, ui);
+ n = strlen(s);
+ sg_scnpr(s + n, sz - n, "_%s", "threshold");
+ sgj_js_nv_ihex(jsp, jo3p, s, pvdt_v);
+ }
+ } else {
+ if (jsp->pr_as_json) {
+ sgj_convert_to_snake_name(ccp, s, sz);
+ sgj_js_nv_ihex(jsp, jo3p, s, ui);
+ if (0x2b == pes)
+ sgj_js_nv_ihex(jsp, jo3p, pvdt, pvdt_v);
+ else if (0x2c == pes)
+ sgj_js_nv_ihex(jsp, jo3p, pvdt, pvdt_v);
+ else if (0x2d == pes)
+ sgj_js_nv_ihex(jsp, jo3p, pvdt, pvdt_v);
+ else if (0x2e == pes)
+ sgj_js_nv_ihex(jsp, jo3p, pvdt, pvdt_v);
+ } else {
+ cp = strchr(b, '\t');
+ if (cp) {
+ *cp = '\0';
+ sgj_pr_hr(jsp, " %s\n", b);
+ sgj_pr_hr(jsp, " %s\n", cp + 1);
+ } else
+ sgj_pr_hr(jsp, " %s\n", b);
+ }
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ } else if (op->verbose)
+ printf(" <<No phy event descriptors>>\n");
+skip:
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ } /* end of for loop over phys with this relative port */
+}
+
+/* PROTO_SPECIFIC_LPAGE [0x18] <psp> */
+static bool
+show_protocol_specific_port_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, num, pl, pid;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char b[128];
+ static const char * psplp = "Protocol specific port log page";
+ static const char * fss = "for SAS SSP";
+
+ num = len - 4;
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (op->do_name)
+ sgj_pr_hr(jsp, "log_page=0x%x\n", PROTO_SPECIFIC_LPAGE);
+ else
+ sgj_pr_hr(jsp, "%s [0x18]\n", psplp);
+ }
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, psplp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "protocol_specific_port_log_parameter_list");
+ }
+
+ for (k = 0, bp = resp + 4; k < num; ) {
+ int pc = sg_get_unaligned_be16(bp + 0);
+
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ pid = 0xf & bp[4];
+ if (6 != pid) {
+ pr2serr("Protocol identifier: %d, only support SAS (SPL) which "
+ "is 6\n", pid);
+ return false; /* only decode SAS log page */
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ if ((0 == k) && (! op->do_name))
+ sgj_pr_hr(jsp, "%s %s [0x18]\n", psplp, fss);
+ /* call helper */
+ show_sas_port_param(bp, pl, op, jo3p);
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if ((op->do_pcb) && (! op->do_name))
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], b,
+ sizeof(b)));
+ if (op->filter_given)
+ break;
+skip:
+ k += pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Returns true if processed page, false otherwise */
+/* STATS_LPAGE [0x19], subpages: 0x0 to 0x1f <gsp,grsp> introduced: SPC-4 */
+static bool
+show_stats_perform_pages(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool nam, spf;
+ int k, num, param_len, param_code, subpg_code, extra;
+ uint64_t ull;
+ const uint8_t * bp;
+ const char * ccp;
+ const char * pg_name;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ static const char * gsaplp =
+ "General statistics and performance log page";
+ static const char * gr_saplp =
+ "Group statistics and performance log page";
+
+// yyyyyyyyyyyyyyyyyy
+ nam = op->do_name;
+ num = len - 4;
+ bp = resp + 4;
+ spf = !!(resp[0] & 0x40);
+ subpg_code = spf ? resp[1] : NOT_SPG_SUBPG;
+ if (0 == subpg_code)
+ pg_name = gsaplp;
+ else if (subpg_code < 0x20)
+ pg_name = gr_saplp;
+ else
+ pg_name = "Unknown subpage";
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (nam) {
+ sgj_pr_hr(jsp, "log_page=0x%x\n", STATS_LPAGE);
+ if (subpg_code > 0)
+ sgj_pr_hr(jsp, "log_subpage=0x%x\n", subpg_code);
+ } else {
+ if (0 == subpg_code)
+ sgj_pr_hr(jsp, "%s [0x19]\n", gsaplp);
+ else if (subpg_code < 0x20)
+ sgj_pr_hr(jsp, "%s (%d) [0x19,0x%x]\n", gr_saplp, subpg_code,
+ subpg_code);
+ else
+ sgj_pr_hr(jsp, "%s: %d [0x19,0x%x]\n", pg_name, subpg_code,
+ subpg_code);
+ }
+ }
+ if (jsp->pr_as_json)
+ jo2p = sg_log_js_hdr(jsp, jop, pg_name, resp);
+ if (subpg_code > 31)
+ return false;
+ if (jsp->pr_as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p, 0 == subpg_code ?
+ "general_statistics_and_performance_log_parameters" :
+ "group_statistics_and_performance_log_parameters");
+ if (0 == subpg_code) { /* General statistics and performance log page */
+ if (num < 0x5c)
+ return false;
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ unsigned int ui;
+
+ if (k < 3)
+ return false;
+ param_len = bp[3];
+ extra = param_len + 4;
+ param_code = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (param_code != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (param_code) {
+ case 1: /* Statistics and performance log parameter */
+ ccp = nam ? "parameter_code=1" : "Statistics and performance "
+ "log parameter";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "read_commands=" : "number of read commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 12);
+ ccp = nam ? "write_commands=" : "number of write commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 20);
+ ccp = nam ? "lb_received="
+ : "number of logical blocks received = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 28);
+ ccp = nam ? "lb_transmitted="
+ : "number of logical blocks transmitted = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 36);
+ ccp = nam ? "read_proc_intervals="
+ : "read command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 44);
+ ccp = nam ? "write_proc_intervals="
+ : "write command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 52);
+ ccp = nam ? "weight_rw_commands=" : "weighted number of "
+ "read commands plus write commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 60);
+ ccp = nam ? "weight_rw_processing=" : "weighted read command "
+ "processing plus write command processing = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 2: /* Idle time log parameter */
+ ccp = nam ? "parameter_code=2" : "Idle time log parameter";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "idle_time_intervals=" : "idle time "
+ "intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 3: /* Time interval log parameter for general stats */
+ ccp = nam ? "parameter_code=3" : "Time interval log "
+ "parameter for general stats";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ui = sg_get_unaligned_be32(bp + 4);
+ ccp = nam ? "time_interval_neg_exp=" : "time interval "
+ "negative exponent = ";
+ sgj_pr_hr(jsp, " %s%u\n", ccp, ui);
+ ui = sg_get_unaligned_be32(bp + 8);
+ ccp = nam ? "time_interval_int=" : "time interval "
+ "integer = ";
+ sgj_pr_hr(jsp, " %s%u\n", ccp, ui);
+ break;
+ case 4: /* FUA statistics and performance log parameter */
+ ccp = nam ? "parameter_code=4" : "Force unit access "
+ "statistics and performance log parameter ";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "read_fua_commands=" : "number of read FUA "
+ "commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 12);
+ ccp = nam ? "write_fua_commands=" : "number of write FUA "
+ "commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 20);
+ ccp = nam ? "read_fua_nv_commands="
+ : "number of read FUA_NV commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 28);
+ ccp = nam ? "write_fua_nv_commands="
+ : "number of write FUA_NV commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 36);
+ ccp = nam ? "read_fua_proc_intervals="
+ : "read FUA command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 44);
+ ccp = nam ? "write_fua_proc_intervals="
+ : "write FUA command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 52);
+ ccp = nam ? "read_fua_nv_proc_intervals="
+ : "read FUA_NV command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 60);
+ ccp = nam ? "write_fua_nv_proc_intervals="
+ : "write FUA_NV command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 6: /* Time interval log parameter for cache stats */
+ ccp = nam ? "parameter_code=6" : "Time interval log "
+ "parameter for cache stats";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "time_interval_neg_exp=" : "time interval "
+ "negative exponent = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 8);
+ ccp = nam ? "time_interval_int=" : "time interval "
+ "integer = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ default:
+ if (nam) {
+ sgj_pr_hr(jsp, "parameter_code=%d\n", param_code);
+ sgj_pr_hr(jsp, " unknown=1\n");
+ } else
+ pr2serr("show_performance... unknown %s %d\n", param_c,
+ param_code);
+ if (op->verbose)
+ hex2stderr(bp, extra, 1);
+ break;
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if ((op->do_pcb) && (! op->do_name))
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+skip:
+ if (op->filter_given)
+ break;
+ }
+ } else { /* Group statistics and performance (n) log page */
+ if (num < 0x34)
+ return false;
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ if (k < 3)
+ return false;
+ param_len = bp[3];
+ extra = param_len + 4;
+ param_code = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (param_code != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ goto skip2;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip2;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (param_code) {
+ case 1: /* Group n Statistics and performance log parameter */
+ if (nam)
+ sgj_pr_hr(jsp, "parameter_code=1\n");
+ else
+ sgj_pr_hr(jsp, "Group %d Statistics and performance log "
+ "parameter\n", subpg_code);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "gn_read_commands=" : "group n number of read "
+ "commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 12);
+ ccp = nam ? "gn_write_commands=" : "group n number of write "
+ "commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 20);
+ ccp = nam ? "gn_lb_received="
+ : "group n number of logical blocks received = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 28);
+ ccp = nam ? "gn_lb_transmitted="
+ : "group n number of logical blocks transmitted = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 36);
+ ccp = nam ? "gn_read_proc_intervals="
+ : "group n read command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 44);
+ ccp = nam ? "gn_write_proc_intervals="
+ : "group n write command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 4: /* Group n FUA statistics and performance log parameter */
+ ccp = nam ? "parameter_code=4" : "Group n force unit access "
+ "statistics and performance log parameter";
+ sgj_pr_hr(jsp, "%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "gn_read_fua_commands="
+ : "group n number of read FUA commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 12);
+ ccp = nam ? "gn_write_fua_commands="
+ : "group n number of write FUA commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 20);
+ ccp = nam ? "gn_read_fua_nv_commands="
+ : "group n number of read FUA_NV commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 28);
+ ccp = nam ? "gn_write_fua_nv_commands="
+ : "group n number of write FUA_NV commands = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 36);
+ ccp = nam ? "gn_read_fua_proc_intervals="
+ : "group n read FUA command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 44);
+ ccp = nam ? "gn_write_fua_proc_intervals=" : "group n write "
+ "FUA command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 52);
+ ccp = nam ? "gn_read_fua_nv_proc_intervals=" : "group n "
+ "read FUA_NV command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ ull = sg_get_unaligned_be64(bp + 60);
+ ccp = nam ? "gn_write_fua_nv_proc_intervals=" : "group n "
+ "write FUA_NV command processing intervals = ";
+ sgj_pr_hr(jsp, " %s%" PRIu64 "\n", ccp, ull);
+ break;
+ default:
+ if (nam) {
+ sgj_pr_hr(jsp, "parameter_code=%d\n", param_code);
+ sgj_pr_hr(jsp, " unknown=1\n");
+ } else
+ pr2serr("show_performance... unknown %s %d\n", param_c,
+ param_code);
+ if (op->verbose)
+ hex2stderr(bp, extra, 1);
+ break;
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if ((op->do_pcb) && (! op->do_name))
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+skip2:
+ if (op->filter_given)
+ break;
+ }
+ }
+ return true;
+}
+
+/* Returns true if processed page, false otherwise */
+/* STATS_LPAGE [0x19], CACHE_STATS_SUBPG [0x20] <cms> introduced: SPC-4 */
+static bool
+show_cache_stats_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int k, num, subpg_code, extra;
+ bool nam, spf;
+ unsigned int ui;
+ const uint8_t * bp;
+ const char * ccp;
+ uint64_t ull;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ nam = op->do_name;
+ num = len - 4;
+ bp = resp + 4;
+ if (num < 4) {
+ pr2serr("badly formed Cache memory statistics page\n");
+ return false;
+ }
+ spf = !!(resp[0] & 0x40);
+ subpg_code = spf ? resp[1] : NOT_SPG_SUBPG;
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (nam) {
+ printf("log_page=0x%x\n", STATS_LPAGE);
+ if (subpg_code > 0)
+ printf("log_subpage=0x%x\n", subpg_code);
+ } else
+ printf("Cache memory statistics page [0x19,0x20]\n");
+ }
+
+ for (k = num; k > 0; k -= extra, bp += extra) {
+ int pc;
+
+ if (k < 3) {
+ pr2serr("short Cache memory statistics page\n");
+ return false;
+ }
+ if (8 != bp[3]) {
+ printf("Cache memory statistics page parameter length not "
+ "8\n");
+ return false;
+ }
+ extra = bp[3] + 4;
+ pc = sg_get_unaligned_be16(bp + 0);
+ if (op->filter_given) {
+ if (pc != op->filter)
+ continue;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, extra);
+ goto skip;
+ } else if (op->do_hex) {
+ hex2stdout(bp, extra, op->dstrhex_no_ascii);
+ goto skip;
+ }
+ switch (pc) {
+ case 1: /* Read cache memory hits log parameter */
+ ccp = nam ? "parameter_code=1" :
+ "Read cache memory hits log parameter";
+ printf("%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "read_cache_memory_hits=" :
+ "read cache memory hits = ";
+ printf(" %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 2: /* Reads to cache memory log parameter */
+ ccp = nam ? "parameter_code=2" :
+ "Reads to cache memory log parameter";
+ printf("%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "reads_to_cache_memory=" :
+ "reads to cache memory = ";
+ printf(" %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 3: /* Write cache memory hits log parameter */
+ ccp = nam ? "parameter_code=3" :
+ "Write cache memory hits log parameter";
+ printf("%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "write_cache_memory_hits=" :
+ "write cache memory hits = ";
+ printf(" %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 4: /* Writes from cache memory log parameter */
+ ccp = nam ? "parameter_code=4" :
+ "Writes from cache memory log parameter";
+ printf("%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "writes_from_cache_memory=" :
+ "writes from cache memory = ";
+ printf(" %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 5: /* Time from last hard reset log parameter */
+ ccp = nam ? "parameter_code=5" :
+ "Time from last hard reset log parameter";
+ printf("%s\n", ccp);
+ ull = sg_get_unaligned_be64(bp + 4);
+ ccp = nam ? "time_from_last_hard_reset=" :
+ "time from last hard reset = ";
+ printf(" %s%" PRIu64 "\n", ccp, ull);
+ break;
+ case 6: /* Time interval log parameter for cache stats */
+ ccp = nam ? "parameter_code=6" :
+ "Time interval log parameter";
+ printf("%s\n", ccp);
+ ui = sg_get_unaligned_be32(bp + 4);
+ ccp = nam ? "time_interval_neg_exp=" : "time interval "
+ "negative exponent = ";
+ printf(" %s%u\n", ccp, ui);
+ ui = sg_get_unaligned_be32(bp + 8);
+ ccp = nam ? "time_interval_int=" : "time interval "
+ "integer = ";
+ printf(" %s%u\n", ccp, ui);
+ break;
+ default:
+ if (nam) {
+ printf("parameter_code=%d\n", pc);
+ printf(" unknown=1\n");
+ } else
+ pr2serr("show_performance... unknown %s %d\n", param_c,
+ pc);
+ if (op->verbose)
+ hex2stderr(bp, extra, 1);
+ break;
+ }
+ if ((op->do_pcb) && (! op->do_name))
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+skip:
+ if (op->filter_given)
+ break;
+ }
+ return true;
+}
+
+/* FORMAT_STATUS_LPAGE [0x8] <fs> introduced: SBC-2 */
+static bool
+show_format_status_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool is_count, is_not_avail;
+ int k, num, pl, pc;
+ uint64_t ull;
+ const char * cp = "";
+ const uint8_t * bp;
+ const uint8_t * xp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[512];
+ static const char * fslp = "Format status log page";
+ static const char * fso = "Format status out";
+ static const char * fso_sn = "format_status_out";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x8]\n", fslp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, fslp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "format_status_log_parameters");
+ }
+
+
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+ is_count = true;
+
+ switch (pc) {
+ case 0:
+ is_not_avail = false;
+ if (pl < 5)
+ sgj_pr_hr(jsp, " %s: <empty>\n", fso);
+ else {
+ if (sg_all_ffs(bp + 4, pl - 4)) {
+ sgj_pr_hr(jsp, " %s: <%s>\n", fso, not_avail);
+ is_not_avail = true;
+ } else {
+ hex2str(bp + 4, pl - 4, " ", op->hex2str_oformat,
+ sizeof(b), b);
+ sgj_pr_hr(jsp, " %s:\n%s", fso, b);
+
+
+ }
+ }
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, fso);
+ if (is_not_avail)
+ sgj_js_nv_ihexstr(jsp, jo3p, fso_sn, 0, NULL, not_avail);
+ else
+ sgj_js_nv_hex_bytes(jsp, jo3p, fso_sn, bp + 4, pl - 4);
+ }
+ is_count = false;
+ break;
+ case 1:
+ cp = "Grown defects during certification";
+ break;
+ case 2:
+ cp = "Total blocks reassigned during format";
+ break;
+ case 3:
+ cp = "Total new blocks reassigned";
+ break;
+ case 4:
+ cp = "Power on minutes since format";
+ break;
+ default:
+ sgj_pr_hr(jsp, " Unknown Format %s = 0x%x\n", param_c, pc);
+ is_count = false;
+ hex2fp(bp, pl, " ", op->hex2str_oformat, stdout);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, unknown_s);
+ sgj_js_nv_hex_bytes(jsp, jo3p, in_hex, bp, pl);
+ }
+ break;
+ }
+ if (is_count) {
+ k = pl - 4;
+ xp = bp + 4;
+ is_not_avail = false;
+ ull = 0;
+ if (sg_all_ffs(xp, k)) {
+ sgj_pr_hr(jsp, " %s: <%s>\n", cp, not_avail);
+ is_not_avail = true;
+ } else {
+ if (k > (int)sizeof(ull)) {
+ xp += (k - sizeof(ull));
+ k = sizeof(ull);
+ }
+ ull = sg_get_unaligned_be(k, xp);
+ sgj_pr_hr(jsp, " %s = %" PRIu64 "\n", cp, ull);
+ }
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, cp);
+ sgj_convert_to_snake_name(cp, b, sizeof(b));
+ if (is_not_avail)
+ sgj_js_nv_ihexstr(jsp, jo3p, b, 0, NULL, not_avail);
+ else
+ sgj_js_nv_ihex(jsp, jo3p, b, ull);
+ }
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if ((op->do_pcb) && (! op->do_name))
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Non-volatile cache page [0x17] <nvc> introduced: SBC-2
+ * Standard vacillates between "non-volatile" and "nonvolatile" */
+static bool
+show_non_volatile_cache_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int j, num, pl, pc;
+ const char * cp;
+ const char * c2p;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[96];
+ static const char * nvclp = "Non-volatile cache log page";
+ static const char * ziinv = "0 (i.e. it is now volatile)";
+ static const char * indef = "indefinite";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x17]\n", nvclp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, nvclp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "nonvolatile_cache_log_parameters");
+ }
+
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ cp = NULL;
+ switch (pc) {
+ case 0:
+ cp = "Remaining nonvolatile time";
+ c2p = NULL;
+ j = sg_get_unaligned_be24(bp + 5);
+ switch (j) {
+ case 0:
+ c2p = ziinv;
+ sgj_pr_hr(jsp, " %s: %s\n", cp, c2p);
+ break;
+ case 1:
+ c2p = unknown_s;
+ sgj_pr_hr(jsp, " %s: <%s>\n", cp, c2p);
+ break;
+ case 0xffffff:
+ c2p = indef;
+ sgj_pr_hr(jsp, " %s: <%s>\n", cp, c2p);
+ break;
+ default:
+ snprintf(b, sizeof(b), "%d minutes [%d:%d]", j, (j / 60),
+ (j % 60));
+ c2p = b;
+ sgj_pr_hr(jsp, " %s: %s\n", cp, c2p);
+ break;
+ }
+ break;
+ case 1:
+ cp = "Maximum non-volatile time";
+ c2p = NULL;
+ j = sg_get_unaligned_be24(bp + 5);
+ switch (j) {
+ case 0:
+ c2p = ziinv;
+ sgj_pr_hr(jsp, " %s: %s\n", cp, c2p);
+ break;
+ case 1:
+ c2p = rsv_s;
+ sgj_pr_hr(jsp, " %s: <%s>\n", cp, c2p);
+ break;
+ case 0xffffff:
+ c2p = indef;
+ sgj_pr_hr(jsp, " %s: <%s>\n", cp, c2p);
+ break;
+ default:
+ snprintf(b, sizeof(b), "%d minutes [%d:%d]", j, (j / 60),
+ (j % 60));
+ c2p = b;
+ sgj_pr_hr(jsp, " %s: %s\n", cp, c2p);
+ break;
+ }
+ break;
+ default:
+ sgj_pr_hr(jsp, " Unknown %s = 0x%x\n", param_c, pc);
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ cp ? cp : unknown_s);
+ if (cp)
+ js_snakenv_ihexstr_nex(jsp, jo3p, cp , j, true,
+ NULL, c2p, NULL);
+ else if (pl > 4)
+ sgj_js_nv_hex_bytes(jsp, jo3p, in_hex, bp + 4, pl - 4);
+
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ }
+ if ((op->do_pcb) && (! op->do_name))
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* LB_PROV_LPAGE [0xc] <lbp> introduced: SBC-3 */
+static bool
+show_lb_provisioning_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ const char * cp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Logical block provisioning page [0xc]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x1:
+ cp = " Available LBA mapping threshold";
+ break;
+ case 0x2:
+ cp = " Used LBA mapping threshold";
+ break;
+ case 0x3:
+ cp = " Available provisioning resource percentage";
+ break;
+ case 0x100:
+ cp = " De-duplicated LBA";
+ break;
+ case 0x101:
+ cp = " Compressed LBA";
+ break;
+ case 0x102:
+ cp = " Total efficiency LBA";
+ break;
+ default:
+ cp = NULL;
+ break;
+ }
+ if (cp) {
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ pr2serr("\n truncated by response length, expected at "
+ "least 8 bytes\n");
+ else
+ pr2serr("\n parameter length >= 8 expected, got %d\n",
+ pl);
+ break;
+ }
+ if (0x3 == pc) /* resource percentage log parameter */
+ printf(" %s: %u %%\n", cp, sg_get_unaligned_be16(bp + 4));
+ else /* resource count log parameters */
+ printf(" %s resource count: %u\n", cp,
+ sg_get_unaligned_be32(bp + 4));
+ if (pl > 8) {
+ switch (bp[8] & 0x3) { /* SCOPE field */
+ case 0: cp = not_rep; break;
+ case 1: cp = "dedicated to lu"; break;
+ case 2: cp = "not dedicated to lu"; break;
+ case 3: cp = rsv_s; break;
+ }
+ printf(" Scope: %s\n", cp);
+ }
+ } else if ((pc >= 0xfff0) && (pc <= 0xffff)) {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" %s parameter(s) being ignored\n", vend_spec);
+ }
+ } else {
+ printf(" %s [0x%x]:", vend_spec, pc);
+ hex2stdout(bp, ((pl < num) ? pl : num), op->dstrhex_no_ascii);
+ }
+ } else {
+ printf(" Reserved [%s=0x%x]:\n", param_c_sn, pc);
+ hex2stdout(bp, ((pl < num) ? pl : num), op->dstrhex_no_ascii);
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* UTILIZATION_SUBPG [0xe,0x1] <util> introduced: SBC-4 */
+static bool
+show_utilization_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int num, pl, pc, k;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Utilization page [0xe,0x1]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" Workload utilization:");
+ if ((pl < 6) || (num < 6)) {
+ if (num < 6)
+ pr2serr("\n truncated by response length, expected "
+ "at least 6 bytes\n");
+ else
+ pr2serr("\n parameter length >= 6 expected, got %d\n",
+ pl);
+ break;
+ }
+ k = sg_get_unaligned_be16(bp + 4);
+ printf(" %d.%02d %%\n", k / 100, k % 100);
+ break;
+ case 0x1:
+ printf(" Utilization usage rate based on date and time:");
+ if ((pl < 6) || (num < 6)) {
+ if (num < 6)
+ pr2serr("\n truncated by response length, expected "
+ "at least 6 bytes\n");
+ else
+ pr2serr("\n parameter length >= 6 expected, got %d\n",
+ pl);
+ break;
+ }
+ printf(" %d %%\n", bp[4]);
+ break;
+ default:
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2stdout(bp, ((pl < num) ? pl : num), op->dstrhex_no_ascii);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* SOLID_STATE_MEDIA_LPAGE [0x11] <ssm> introduced: SBC-3 */
+static bool
+show_solid_state_media_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ static const char * ssmlp = "Solid state media log page";
+ static const char * puei = "Percentage used endurance indicator";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x11]\n", ssmlp);
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, ssmlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "solid_state_media_log_parameters");
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 0x1:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ pr2serr("\n truncated by response length, expected "
+ "at least 8 bytes\n");
+ else
+ pr2serr("\n parameter length >= 8 expected, got %d\n",
+ pl);
+ break;
+ }
+ sgj_pr_hr(jsp, " %s: %u %%\n", puei, bp[7]);
+ if (jsp->pr_as_json) {
+ js_snakenv_ihexstr_nex(jsp, jo3p, param_c, pc, true,
+ NULL, puei, NULL);
+ js_snakenv_ihexstr_nex(jsp, jo3p, puei, bp[7], false,
+ NULL, NULL, NULL);
+ }
+ break;
+ default:
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2stdout(bp, ((pl < num) ? pl : num), op->dstrhex_no_ascii);
+ break;
+ }
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+static const char * dt_dev_activity[] = {
+ "No DT device activity",
+ "Cleaning operation in progress",
+ "Volume is being loaded",
+ "Volume is being unloaded",
+ "Other medium activity",
+ "Reading from medium",
+ "Writing to medium",
+ "Locating medium",
+ "Rewinding medium", /* 8 */
+ "Erasing volume",
+ "Formatting volume",
+ "Calibrating",
+ "Other DT device activity",
+ "Microcode update in progress",
+ "Reading encrypted from medium",
+ "Writing encrypted to medium",
+ "Diagnostic operation in progress", /* 10 */
+};
+
+/* DT device status [0x11] <dtds> (ssc, adc) */
+static bool
+show_dt_device_status_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc, j;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+ char b[512];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("DT device status page (ssc-3, adc-3) [0x11]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" Very high frequency data:\n");
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ pr2serr(" truncated by response length, expected at "
+ "least 8 bytes\n");
+ else
+ pr2serr(" parameter length >= 8 expected, got %d\n",
+ pl);
+ break;
+ }
+ printf(" PAMR=%d HUI=%d MACC=%d CMPR=%d ", !!(0x80 & bp[4]),
+ !!(0x40 & bp[4]), !!(0x20 & bp[4]), !!(0x10 & bp[4]));
+ printf("WRTP=%d CRQST=%d CRQRD=%d DINIT=%d\n", !!(0x8 & bp[4]),
+ !!(0x4 & bp[4]), !!(0x2 & bp[4]), !!(0x1 & bp[4]));
+ printf(" INXTN=%d RAA=%d MPRSNT=%d ", !!(0x80 & bp[5]),
+ !!(0x20 & bp[5]), !!(0x10 & bp[5]));
+ printf("MSTD=%d MTHRD=%d MOUNTED=%d\n",
+ !!(0x4 & bp[5]), !!(0x2 & bp[5]), !!(0x1 & bp[5]));
+ printf(" DT device activity: ");
+ j = bp[6];
+ if (j < (int)SG_ARRAY_SIZE(dt_dev_activity))
+ printf("%s\n", dt_dev_activity[j]);
+ else if (j < 0x80)
+ printf("Reserved [0x%x]\n", j);
+ else
+ printf("%s [0x%x]\n", vend_spec, j);
+ printf(" VS=%d TDDEC=%d EPP=%d ", !!(0x80 & bp[7]),
+ !!(0x20 & bp[7]), !!(0x10 & bp[7]));
+ printf("ESR=%d RRQST=%d INTFC=%d TAFC=%d\n", !!(0x8 & bp[7]),
+ !!(0x4 & bp[7]), !!(0x2 & bp[7]), !!(0x1 & bp[7]));
+ break;
+ case 0x1:
+ printf(" Very high frequency polling delay: ");
+ if ((pl < 6) || (num < 6)) {
+ if (num < 6)
+ pr2serr("\n truncated by response length, expected at "
+ "least 6 bytes\n");
+ else
+ pr2serr("\n parameter length >= 6 expected, got %d\n",
+ pl);
+ break;
+ }
+ printf(" %d milliseconds\n", sg_get_unaligned_be16(bp + 4));
+ break;
+ case 0x2:
+ printf(" DT device ADC data encryption control status (hex "
+ "only now):\n");
+ if ((pl < 12) || (num < 12)) {
+ if (num < 12)
+ pr2serr(" truncated by response length, expected at "
+ "least 12 bytes\n");
+ else
+ pr2serr(" parameter length >= 12 expected, got %d\n",
+ pl);
+ break;
+ }
+ hex2fp(bp + 4, 8, " ", op->hex2str_oformat, stdout);
+ break;
+ case 0x3:
+ printf(" Key management error data (hex only now):\n");
+ if ((pl < 16) || (num < 16)) {
+ if (num < 16)
+ pr2serr(" truncated by response length, expected at "
+ "least 16 bytes\n");
+ else
+ pr2serr(" parameter length >= 16 expected, got %d\n",
+ pl);
+ break;
+ }
+ hex2fp(bp + 4, 12, " ", op->hex2str_oformat, stdout);
+ break;
+ default:
+ if ((pc >= 0x101) && (pc <= 0x1ff)) {
+ printf(" Primary port %d status:\n", pc - 0x100);
+ if (12 == bp[3]) { /* if length of desc is 12, assume SAS */
+ printf(" SAS: negotiated physical link rate: %s\n",
+ sas_negot_link_rate((0xf & (bp[4] >> 4)), b,
+ sizeof(b)));
+ printf(" signal=%d, pic=%d, ", !!(0x2 & bp[4]),
+ !!(0x1 & bp[4]));
+ printf("hashed SAS addr: 0x%u\n",
+ sg_get_unaligned_be24(bp + 5));
+ printf(" SAS addr: 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(bp + 8));
+ } else {
+ printf(" non-SAS transport, in hex:\n");
+ hex2fp(bp + 4, ((pl < num) ? pl : num) - 4, " ",
+ op->hex2str_oformat, stdout);
+ }
+ } else if (pc >= 0x8000) {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" %s parameter(s) being ignored\n",
+ vend_spec);
+ }
+ } else {
+ printf(" %s [%s=0x%x]:\n", vend_spec, param_c_sn, pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ } else {
+ printf(" Reserved [%s=0x%x]:\n", param_c_sn, pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* TapeAlert response [0x12] <tar> (adc,ssc) */
+static bool
+show_tapealert_response_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc, k, mod, div;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("TapeAlert response page (ssc-3, adc-3) [0x12]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ if (pl < 12) {
+
+ }
+ for (k = 1; k < 0x41; ++k) {
+ mod = ((k - 1) % 8);
+ div = (k - 1) / 8;
+ if (0 == mod) {
+ if (div > 0)
+ printf("\n");
+ printf(" Flag%02Xh: %d", k, !! (bp[4 + div] & 0x80));
+ } else
+ printf(" %02Xh: %d", k,
+ !! (bp[4 + div] & (1 << (7 - mod))));
+ }
+ printf("\n");
+ break;
+ default:
+ if (pc <= 0x8000) {
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ } else {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" %s parameter(s) being ignored\n",
+ vend_spec);
+ }
+ } else {
+ printf(" %s [%s=0x%x]:\n", vend_spec, param_c_sn, pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ }
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+#define NUM_REQ_REC_ARR_ELEMS 16
+static const char * req_rec_arr[NUM_REQ_REC_ARR_ELEMS] = {
+ "Recovery not requested",
+ "Recovery requested, no recovery procedure defined",
+ "Instruct operator to push volume",
+ "Instruct operator to remove and re-insert volume",
+ "Issue UNLOAD command. Instruct operator to remove and re-insert volume",
+ "Instruct operator to power cycle target device",
+ "Issue LOAD command",
+ "Issue UNLOAD command",
+ "Issue LOGICAL UNIT RESET task management function", /* 0x8 */
+ "No recovery procedure defined. Contact service organization",
+ "Issue UNLOAD command. Instruct operator to remove and quarantine "
+ "volume",
+ "Instruct operator to not insert a volume. Contact service organization",
+ "Issue UNLOAD command. Instruct operator to remove volume. Contact "
+ "service organization",
+ "Request creation of target device error log",
+ "Retrieve a target device error log",
+ "Modify configuration to all microcode update and instruct operator to "
+ "re-insert volume", /* 0xf */
+};
+
+/* REQ_RECOVERY_LPAGE Requested recovery [0x13] <rr> (ssc) */
+static bool
+show_requested_recovery_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc, j, k;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Requested recovery page (ssc-3) [0x13]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" Recovery procedures:\n");
+ for (k = 4; k < pl; ++ k) {
+ j = bp[k];
+ if (j < NUM_REQ_REC_ARR_ELEMS)
+ printf(" %s\n", req_rec_arr[j]);
+ else if (j < 0x80)
+ printf(" Reserved [0x%x]\n", j);
+ else
+ printf(" Vendor specific [0x%x]\n", j);
+ }
+ break;
+ default:
+ if (pc <= 0x8000) {
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ } else {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" Vendor specific parameter(s) being "
+ "ignored\n");
+ }
+ } else {
+ printf(" Vendor specific [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ }
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* SAT_ATA_RESULTS_LPAGE (SAT-2) [0x16] <aptr> */
+static bool
+show_ata_pt_results_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ const uint8_t * bp;
+ const uint8_t * dp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("ATA pass-through results page (sat-2) [0x16]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if ((pc < 0xf) && (pl > 17)) {
+ int extend, count;
+
+ printf(" Log_index=0x%x (parameter_code=0x%x)\n", pc + 1, pc);
+ dp = bp + 4; /* dp is start of ATA Return descriptor
+ * which is 14 bytes long */
+ extend = dp[2] & 1;
+ count = dp[5] + (extend ? (dp[4] << 8) : 0);
+ printf(" extend=%d error=0x%x count=0x%x\n", extend,
+ dp[3], count);
+ if (extend)
+ printf(" lba=0x%02x%02x%02x%02x%02x%02x\n", dp[10], dp[8],
+ dp[6], dp[11], dp[9], dp[7]);
+ else
+ printf(" lba=0x%02x%02x%02x\n", dp[11], dp[9], dp[7]);
+ printf(" device=0x%x status=0x%x\n", dp[12], dp[13]);
+ } else if (pl > 17) {
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ } else {
+ printf(" short parameter length: %d [parameter_code=0x%x]:\n",
+ pl, pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+static const char * bms_status[] = {
+ "no background scans active",
+ "background medium scan is active",
+ "background pre-scan is active",
+ "background scan halted due to fatal error",
+ "background scan halted due to a vendor specific pattern of error",
+ "background scan halted due to medium formatted without P-List",
+ "background scan halted - vendor specific cause",
+ "background scan halted due to temperature out of range",
+ ("background scan enabled, none active (waiting for BMS interval timer "
+ "to expire)"), /* clang warns about this, add parens to quieten */
+ "background scan halted - scan results list full",
+ "background scan halted - pre-scan time limit timer expired" /* 10 */,
+};
+
+static const char * reassign_status[] = {
+ "Reserved [0x0]",
+ "Reassignment pending receipt of Reassign or Write command",
+ "Logical block successfully reassigned by device server",
+ "Reserved [0x3]",
+ "Reassignment by device server failed",
+ "Logical block recovered by device server via rewrite",
+ "Logical block reassigned by application client, has valid data",
+ "Logical block reassigned by application client, contains no valid data",
+ "Logical block unsuccessfully reassigned by application client", /* 8 */
+};
+
+/* Background scan results [0x15,0] <bsr> for disk introduced: SBC-3 */
+static bool
+show_background_scan_results_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool skip_out = false;
+ bool evsm_output = false;
+ bool ok;
+ int j, m, n, num, pl, pc;
+ const uint8_t * bp;
+ double d;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ sgj_opaque_p jo3p = NULL;
+ sgj_opaque_p jap = NULL;
+ char str[PCB_STR_LEN];
+ char b[144];
+ char e[80];
+ static const int blen = sizeof(b);
+ static const int elen = sizeof(e);
+ static const char * bsrlp = "Background scan results log page";
+ static const char * bss = "Background scan status";
+ static const char * bms = "Background medium scan";
+ static const char * bsr = "Background scan results";
+ static const char * bs = "background scan";
+ static const char * ms = "Medium scan";
+ static const char * apom = "Accumulated power on minutes";
+ static const char * rs = "Reassign status";
+
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ sgj_pr_hr(jsp, "%s [0x15]\n", bsrlp);
+ num = len - 4;
+ bp = &resp[0] + 4;
+ if (jsp->pr_as_json) {
+ jo2p = sg_log_js_hdr(jsp, jop, bsrlp, resp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "background_scan_parameters");
+ }
+
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (jsp->pr_as_json) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ if (op->do_pcb)
+ js_pcb(jsp, jo3p, bp[2]);
+ }
+
+ switch (pc) {
+ case 0:
+ sgj_pr_hr(jsp, " Status parameters:\n");
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, bss);
+ if ((pl < 16) || (num < 16)) {
+ if (num < 16)
+ pr2serr(" truncated by response length, expected at "
+ "least 16 bytes\n");
+ else
+ pr2serr(" parameter length >= 16 expected, got %d\n",
+ pl);
+ break;
+ }
+ sg_scnpr(b, blen, " %s: ", apom);
+ j = sg_get_unaligned_be32(bp + 4);
+ sgj_pr_hr(jsp, "%s%d [h:m %d:%d]\n", b, j, (j / 60), (j % 60));
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, apom, j, false, NULL, NULL,
+ NULL);
+ sg_scnpr(b, blen, " Status: ");
+ j = bp[9];
+ ok = (j < (int)SG_ARRAY_SIZE(bms_status));
+ if (ok)
+ sgj_pr_hr(jsp, "%s%s\n", b, bms_status[j]);
+ else
+ sgj_pr_hr(jsp, "%sunknown [0x%x] %s value\n", b, j, bss);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, bss, j, true, NULL,
+ ok ? bms_status[j] : unknown_s, NULL);
+ j = sg_get_unaligned_be16(bp + 10);
+ snprintf(b, blen, "Number of %ss performed", bs);
+ sgj_pr_hr(jsp, " %s: %d\n", b, j);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, b, j, true, NULL, NULL,
+ NULL);
+ j = sg_get_unaligned_be16(bp + 12);
+ snprintf(b, blen, "%s progress", bms);
+ d = (100.0 * j / 65536.0);
+#ifdef SG_LIB_MINGW
+ snprintf(e, elen, "%g %%", d);
+#else
+ snprintf(e, elen, "%.2f %%", d);
+#endif
+ sgj_pr_hr(jsp, " %s: %s\n", b, e);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, b, j, true, NULL, e,
+ NULL);
+ j = sg_get_unaligned_be16(bp + 14);
+ snprintf(b, blen, "Number of %ss performed", bms);
+
+ ok = (j > 0);
+ if (ok)
+ sgj_pr_hr(jsp, " %s: %d\n", b, j);
+ else
+ sgj_pr_hr(jsp, " %s: 0 [%s]\n", b, not_rep);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, b, j, true, NULL,
+ ok ? NULL : not_rep, NULL);
+ break;
+ default:
+ if (pc > 0x800) {
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL,
+ (pc >= 0x8000) ? vend_spec : NULL);
+ if ((pc >= 0x8000) && (pc <= 0xafff)) {
+ if (op->exclude_vendor) {
+ skip_out = true;
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ sgj_pr_hr(jsp, " %s parameter(s) being "
+ "ignored\n", vend_spec);
+ }
+ } else
+ sgj_pr_hr(jsp, " %s parameter # %d [0x%x], %s\n",
+ ms, pc, pc, vend_spec);
+ } else
+ sgj_pr_hr(jsp, " %s parameter # %d [0x%x], %s\n", ms, pc,
+ pc, rsv_s);
+ if (skip_out)
+ skip_out = false;
+ else
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ break;
+ } else {
+ sgj_pr_hr(jsp, " %s parameter # %d [0x%x]\n", ms, pc, pc);
+ if (jsp->pr_as_json)
+ sgj_js_nv_ihexstr(jsp, jo3p, param_c_sn, pc, NULL, bsr);
+ }
+ if ((pl < 24) || (num < 24)) {
+ if (num < 24)
+ pr2serr(" truncated by response length, expected at "
+ "least 24 bytes\n");
+ else
+ pr2serr(" parameter length >= 24 expected, got %d\n",
+ pl);
+ break;
+ }
+ j = sg_get_unaligned_be32(bp + 4);
+ n = (j % 60);
+ sgj_pr_hr(jsp, " %s when error detected: %d [%d:%d]\n", apom,
+ j, (j / 60), n);
+ if (jsp->pr_as_json) {
+ snprintf(b, blen, "%d hours, %d minute%s", (j / 60), n,
+ n != 1 ? "s" : "");
+ js_snakenv_ihexstr_nex(jsp, jo3p, apom, j, true, NULL, b,
+ "when error detected [unit: minute]");
+ }
+ j = (bp[8] >> 4) & 0xf;
+ ok = (j < (int)SG_ARRAY_SIZE(reassign_status));
+ if (ok)
+ sgj_pr_hr(jsp, " %s: %s\n", rs, reassign_status[j]);
+ else
+ sgj_pr_hr(jsp, " %s: %s [0x%x]\n", rs, rsv_s, j);
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, rs, j, true, NULL,
+ ok ? reassign_status[j] : NULL, NULL);
+ n = 0xf & b[8];
+ sgj_pr_hr(jsp, " %s: %s [sk,asc,ascq: 0x%x,0x%x,0x%x]\n",
+ s_key, sg_get_sense_key_str(n, blen, b), n, bp[9],
+ bp[10]);
+ if (bp[9] || bp[10])
+ sgj_pr_hr(jsp, " %s\n",
+ sg_get_asc_ascq_str(bp[9], bp[10], blen, b));
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihexstr(jsp, jo3p, "sense_key", n, NULL,
+ sg_get_sense_key_str(n, blen, b));
+ sgj_js_nv_ihexstr(jsp, jo3p, "additional_sense_code", bp[9],
+ NULL, NULL);
+ sgj_js_nv_ihexstr(jsp, jo3p, "additional_sense_code_qualifier",
+ bp[10], NULL, sg_get_asc_ascq_str(bp[9],
+ bp[10], blen, b));
+ }
+ if (op->verbose) {
+ n = sg_scnpr(b, blen, " vendor bytes [11 -> 15]: ");
+ for (m = 0; m < 5; ++m)
+ n += sg_scnpr(b + n, blen - n, "0x%02x ", bp[11 + m]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ n = sg_scnpr(b, blen, " LBA (associated with medium error): "
+ "0x");
+ if (sg_all_zeros(bp + 16, 8))
+ sgj_pr_hr(jsp, "%s0\n", b);
+ else {
+ for (m = 0; m < 8; ++m)
+ n += sg_scnpr(b + n, blen - n, "%02x", bp[16 + m]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ if (jsp->pr_as_json)
+ js_snakenv_ihexstr_nex(jsp, jo3p, "logical_block_address",
+ sg_get_unaligned_be64(bp + 16), true,
+ NULL, NULL, "of medium error");
+ break;
+ } /* end of switch statement block */
+ if (jsp->pr_as_json)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo3p);
+ if (op->do_pcb)
+ sgj_pr_hr(jsp, " <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* ZONED_BLOCK_DEV_STATS_SUBPG [0x14,0x1] <zbds> introduced: zbc2r01 */
+static bool
+show_zoned_block_dev_stats(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool trunc, bad_pl;
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Zoned block device statistics page [0x14,0x1]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ trunc = false;
+ bad_pl = false;
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (4 == pl) /* DC HC560 has empty descriptors */
+ goto skip;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Maximum open zones: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x1:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Maximum explicitly open zones: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x2:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Maximum implicitly open zones: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x3:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Minimum empty zones: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x4:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Maximum non-sequential zones: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x5:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Zones emptied: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x6:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Suboptimal write commands: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x7:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Commands exceeding optimal limit: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x8:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Failed explicit opens: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0x9:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Read rule violations: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0xa:
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Write rule violations: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ break;
+ case 0xb: /* added zbc2r04 */
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ trunc = true;
+ else
+ bad_pl = true;
+ } else
+ printf(" Maximum implicitly open or before required zones: "
+ "%" PRIu32 "\n", sg_get_unaligned_be32(bp + 8));
+ break;
+ default:
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ break;
+ }
+ if (trunc)
+ pr2serr(" truncated by response length, expected at least "
+ "8 bytes\n");
+ if (bad_pl)
+ pr2serr(" parameter length >= 8 expected, got %d\n", pl);
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* PENDING_DEFECTS_SUBPG [0x15,0x1] <pd> introduced: SBC-4 */
+static bool
+show_pending_defects_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ uint32_t count;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Pending defects page [0x15,0x1]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" Pending defect count: ");
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ pr2serr("\n truncated by response length, expected "
+ "at least 8 bytes\n");
+ else
+ pr2serr("\n parameter length >= 8 expected, got %d\n",
+ pl);
+ break;
+ }
+ count = sg_get_unaligned_be32(bp + 4);
+ if (0 == count) {
+ printf("0\n");
+ break;
+ }
+ printf("%3u | LBA Accumulated power_on\n", count);
+ printf("-----------------------------|---------------");
+ printf("-----------hours---------\n");
+ break;
+ default:
+ printf(" Pending defect %4d: ", pc);
+ if ((pl < 16) || (num < 16)) {
+ if (num < 16)
+ pr2serr("\n truncated by response length, expected "
+ "at least 16 bytes\n");
+ else
+ pr2serr("\n parameter length >= 16 expected, got %d\n",
+ pl);
+ break;
+ }
+ printf(" 0x%-16" PRIx64 " %5u\n",
+ sg_get_unaligned_be64(bp + 8),
+ sg_get_unaligned_be32(bp + 4));
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* BACKGROUND_OP_SUBPG [0x15,0x2] <bop> introduced: SBC-4 rev 7 */
+static bool
+show_background_op_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Background operation page [0x15,0x2]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" Background operation:");
+ if ((pl < 8) || (num < 8)) {
+ if (num < 8)
+ pr2serr("\n truncated by response length, expected "
+ "at least 8 bytes\n");
+ else
+ pr2serr("\n parameter length >= 8 expected, got %d\n",
+ pl);
+ break;
+ }
+ printf(" BO_STATUS=%d\n", bp[4]);
+ break;
+ default:
+ printf(" Reserved [parameter_code=0x%x]:\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* LPS misalignment page [0x15,0x3] <lps> introduced: SBC-4 rev 10
+ LPS: "Long Physical Sector" a term from an ATA feature set */
+static bool
+show_lps_misalignment_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("LPS misalignment page [0x15,0x3]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0x0:
+ printf(" LPS misalignment count: ");
+ if (4 == bp[3])
+ printf("max lpsm: %" PRIu16 ", count=%" PRIu16 "\n",
+ sg_get_unaligned_be16(bp + 4),
+ sg_get_unaligned_be16(bp + 6));
+ else
+ printf("<unexpected pc=0 parameter length=%d>\n", bp[4]);
+ break;
+ default:
+ if (pc <= 0xf000) { /* parameter codes 0x1 to 0xf000 */
+ if (8 == bp[3])
+ printf(" LBA of misaligned block: 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(bp + 4));
+ else
+ printf("<unexpected pc=0x%x parameter length=%d>\n",
+ pc, bp[4]);
+ } else {
+ printf("<unexpected pc=0x%x>\n", pc);
+ hex2fp(bp, ((pl < num) ? pl : num), " ",
+ op->hex2str_oformat, stdout);
+ }
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Service buffer information [0x15] <sbi> (adc) */
+static bool
+show_service_buffer_info_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Service buffer information page (adc-3) [0x15]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (pc < 0x100) {
+ printf(" Service buffer identifier: 0x%x\n", pc);
+ printf(" Buffer id: 0x%x, tu=%d, nmp=%d, nmm=%d, "
+ "offline=%d\n", bp[4], !!(0x10 & bp[5]),
+ !!(0x8 & bp[5]), !!(0x4 & bp[5]), !!(0x2 & bp[5]));
+ printf(" pd=%d, code_set: %s, Service buffer title:\n",
+ !!(0x1 & bp[5]), sg_get_desig_code_set_str(0xf & bp[6]));
+ printf(" %.*s\n", pl - 8, bp + 8);
+ } else if (pc < 0x8000) {
+ printf(" parameter_code=0x%x, Reserved, parameter in hex:\n",
+ pc);
+ hex2fp(bp + 4, pl - 4, " ", op->hex2str_oformat, stdout);
+ } else {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" Vendor specific parameter(s) being "
+ "ignored\n");
+ }
+ } else {
+ printf(" parameter_code=0x%x, Vendor-specific, parameter in "
+ "hex:\n", pc);
+ hex2fp(bp + 4, pl - 4, " ", op->hex2str_oformat, stdout);
+ }
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Sequential access device page [0xc] <sad> for tape */
+static bool
+show_sequential_access_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ uint64_t ull, gbytes;
+ bool all_set;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Sequential access device page (ssc-3)\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ ull = sg_get_unaligned_be(pl - 4, bp + 4);
+ all_set = sg_all_ffs(bp + 4, pl - 4);
+ gbytes = ull / 1000000000;
+ switch (pc) {
+ case 0:
+ printf(" Data bytes received with WRITE commands: %" PRIu64
+ " GB", gbytes);
+ if (op->verbose)
+ printf(" [%" PRIu64 " bytes]", ull);
+ printf("\n");
+ break;
+ case 1:
+ printf(" Data bytes written to media by WRITE commands: %" PRIu64
+ " GB", gbytes);
+ if (op->verbose)
+ printf(" [%" PRIu64 " bytes]", ull);
+ printf("\n");
+ break;
+ case 2:
+ printf(" Data bytes read from media by READ commands: %" PRIu64
+ " GB", gbytes);
+ if (op->verbose)
+ printf(" [%" PRIu64 " bytes]", ull);
+ printf("\n");
+ break;
+ case 3:
+ printf(" Data bytes transferred by READ commands: %" PRIu64
+ " GB", gbytes);
+ if (op->verbose)
+ printf(" [%" PRIu64 " bytes]", ull);
+ printf("\n");
+ break;
+ case 4:
+ if (! all_set)
+ printf(" Native capacity from BOP to EOD: %" PRIu64 " MB\n",
+ ull);
+ break;
+ case 5:
+ if (! all_set)
+ printf(" Native capacity from BOP to EW of current "
+ "partition: %" PRIu64 " MB\n", ull);
+ break;
+ case 6:
+ if (! all_set)
+ printf(" Minimum native capacity from EW to EOP of current "
+ "partition: %" PRIu64 " MB\n", ull);
+ break;
+ case 7:
+ if (! all_set)
+ printf(" Native capacity from BOP to current position: %"
+ PRIu64 " MB\n", ull);
+ break;
+ case 8:
+ if (! all_set)
+ printf(" Maximum native capacity in device object buffer: %"
+ PRIu64 " MB\n", ull);
+ break;
+ case 0x100:
+ if (ull > 0)
+ printf(" Cleaning action required\n");
+ else
+ printf(" Cleaning action not required (or completed)\n");
+ if (op->verbose)
+ printf(" cleaning value: %" PRIu64 "\n", ull);
+ break;
+ default:
+ if (pc >= 0x8000) {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" Vendor specific parameter(s) being "
+ "ignored\n");
+ }
+ } else
+ printf(" Vendor specific parameter [0x%x] value: %"
+ PRIu64 "\n", pc, ull);
+ } else
+ printf(" Reserved parameter [0x%x] value: %" PRIu64 "\n",
+ pc, ull);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Device statistics 0x14 <ds> for tape and ADC */
+static bool
+show_device_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool evsm_output = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Device statistics page (ssc-3 and adc)\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ if (pc < 0x1000) {
+ bool vl_num = true;
+
+ switch (pc) {
+ case 0:
+ printf(" Lifetime media loads:");
+ break;
+ case 1:
+ printf(" Lifetime cleaning operations:");
+ break;
+ case 2:
+ printf(" Lifetime power on hours:");
+ break;
+ case 3:
+ printf(" Lifetime media motion (head) hours:");
+ break;
+ case 4:
+ printf(" Lifetime metres of tape processed:");
+ break;
+ case 5:
+ printf(" Lifetime media motion (head) hours when "
+ "incompatible media last loaded:");
+ break;
+ case 6:
+ printf(" Lifetime power on hours when last temperature "
+ "condition occurred:");
+ break;
+ case 7:
+ printf(" Lifetime power on hours when last power "
+ "consumption condition occurred:");
+ break;
+ case 8:
+ printf(" Media motion (head) hours since last successful "
+ "cleaning operation:");
+ break;
+ case 9:
+ printf(" Media motion (head) hours since 2nd to last "
+ "successful cleaning:");
+ break;
+ case 0xa:
+ printf(" Media motion (head) hours since 3rd to last "
+ "successful cleaning:");
+ break;
+ case 0xb:
+ printf(" Lifetime power on hours when last operator "
+ "initiated forced reset\n and/or emergency "
+ "eject occurred:");
+ break;
+ case 0xc:
+ printf(" Lifetime power cycles:");
+ break;
+ case 0xd:
+ printf(" Volume loads since last parameter reset:");
+ break;
+ case 0xe:
+ printf(" Hard write errors:");
+ break;
+ case 0xf:
+ printf(" Hard read errors:");
+ break;
+ case 0x10:
+ printf(" Duty cycle sample time (ms):");
+ break;
+ case 0x11:
+ printf(" Read duty cycle:");
+ break;
+ case 0x12:
+ printf(" Write duty cycle:");
+ break;
+ case 0x13:
+ printf(" Activity duty cycle:");
+ break;
+ case 0x14:
+ printf(" Volume not present duty cycle:");
+ break;
+ case 0x15:
+ printf(" Ready duty cycle:");
+ break;
+ case 0x16:
+ printf(" MBs transferred from app client in duty cycle "
+ "sample time:");
+ break;
+ case 0x17:
+ printf(" MBs transferred to app client in duty cycle "
+ "sample time:");
+ break;
+ case 0x40:
+ printf(" Drive manufacturer's serial number:");
+ break;
+ case 0x41:
+ printf(" Drive serial number:");
+ break;
+ case 0x42: /* added ssc5r02b */
+ vl_num = false;
+ printf(" Manufacturing date (yyyymmdd): %.*s\n", pl - 4,
+ bp + 4);
+ break;
+ case 0x43: /* added ssc5r02b */
+ vl_num = false;
+ printf(" Manufacturing date (yyyyww): %.*s\n", pl - 4,
+ bp + 4);
+ break;
+ case 0x80:
+ printf(" Medium removal prevented:");
+ break;
+ case 0x81:
+ printf(" Maximum recommended mechanism temperature "
+ "exceeded:");
+ break;
+ default:
+ vl_num = false;
+ printf(" Reserved %s [0x%x] data in hex:\n", param_c, pc);
+ hex2fp(bp + 4, pl - 4, " ", op->hex2str_oformat, stdout);
+ break;
+ }
+ if (vl_num)
+ printf(" %" PRIu64 "\n", sg_get_unaligned_be(pl - 4, bp + 4));
+ } else { /* parameter_code >= 0x1000 */
+ int k;
+ const uint8_t * p = bp + 4;
+
+ switch (pc) {
+ case 0x1000:
+ printf(" Media motion (head) hours for each medium type:\n");
+ for (k = 0; ((pl - 4) - k) >= 8; k += 8, p += 8)
+ printf(" [%d] Density code: %u, Medium type: 0x%x, "
+ "hours: %u\n", ((k / 8) + 1), p[2], p[3],
+ sg_get_unaligned_be32(p + 4));
+ break;
+ default:
+ if (pc >= 0x8000) {
+ if (op->exclude_vendor) {
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" Vendor specific parameter(s) being "
+ "ignored\n");
+ }
+ } else
+ printf(" Vendor specific parameter [0x%x], dump in "
+ "hex:\n", pc);
+ } else {
+ printf(" Reserved parameter [0x%x], dump in hex:\n", pc);
+ hex2fp(bp + 4, pl - 4, " ", op->hex2str_oformat,
+ stdout);
+ }
+ break;
+ }
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Media changer statistics 0x14 <mcs> for media changer */
+static bool
+show_media_stats_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ const uint8_t * bp;
+ uint64_t ull;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Media statistics page (smc-3)\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ ull = sg_get_unaligned_be(pl - 4, bp + 4);
+ switch (pc) {
+ case 0:
+ printf(" Number of moves: %" PRIu64 "\n", ull);
+ break;
+ case 1:
+ printf(" Number of picks: %" PRIu64 "\n", ull);
+ break;
+ case 2:
+ printf(" Number of pick retries: %" PRIu64 "\n", ull);
+ break;
+ case 3:
+ printf(" Number of places: %" PRIu64 "\n", ull);
+ break;
+ case 4:
+ printf(" Number of place retries: %" PRIu64 "\n", ull);
+ break;
+ case 5:
+ printf(" Number of volume tags read by volume "
+ "tag reader: %" PRIu64 "\n", ull);
+ break;
+ case 6:
+ printf(" Number of invalid volume tags returned by "
+ "volume tag reader: %" PRIu64 "\n", ull);
+ break;
+ case 7:
+ printf(" Number of library door opens: %" PRIu64 "\n", ull);
+ break;
+ case 8:
+ printf(" Number of import/export door opens: %" PRIu64 "\n",
+ ull);
+ break;
+ case 9:
+ printf(" Number of physical inventory scans: %" PRIu64 "\n",
+ ull);
+ break;
+ case 0xa:
+ printf(" Number of medium transport unrecovered errors: "
+ "%" PRIu64 "\n", ull);
+ break;
+ case 0xb:
+ printf(" Number of medium transport recovered errors: "
+ "%" PRIu64 "\n", ull);
+ break;
+ case 0xc:
+ printf(" Number of medium transport X axis translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0xd:
+ printf(" Number of medium transport X axis translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0xe:
+ printf(" Number of medium transport Y axis translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0xf:
+ printf(" Number of medium transport Y axis translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x10:
+ printf(" Number of medium transport Z axis translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x11:
+ printf(" Number of medium transport Z axis translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x12:
+ printf(" Number of medium transport rotational translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x13:
+ printf(" Number of medium transport rotational translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x14:
+ printf(" Number of medium transport inversion translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x15:
+ printf(" Number of medium transport inversion translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x16:
+ printf(" Number of medium transport auxiliary translation "
+ "unrecovered errors: %" PRIu64 "\n", ull);
+ break;
+ case 0x17:
+ printf(" Number of medium transport auxiliary translation "
+ "recovered errors: %" PRIu64 "\n", ull);
+ break;
+ default:
+ printf(" Reserved parameter [0x%x] value: %" PRIu64 "\n",
+ pc, ull);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Element statistics page, 0x15 <els> for SMC */
+static bool
+show_element_stats_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ unsigned int v;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Element statistics page (smc-3) [0x15]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ printf(" Element address: %d\n", pc);
+ v = sg_get_unaligned_be32(bp + 4);
+ printf(" Number of places: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 8);
+ printf(" Number of place retries: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 12);
+ printf(" Number of picks: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 16);
+ printf(" Number of pick retries: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 20);
+ printf(" Number of determined volume identifiers: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 24);
+ printf(" Number of unreadable volume identifiers: %u\n", v);
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Tape diagnostic data [0x16] <tdd> for tape */
+static bool
+show_tape_diag_data_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, n, num, pl, pc;
+ unsigned int v;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+ char b[512];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Tape diagnostics data page (ssc-3) [0x16]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ printf(" %s: %d\n", param_c, pc);
+ printf(" Density code: 0x%x\n", bp[6]);
+ printf(" Medium type: 0x%x\n", bp[7]);
+ v = sg_get_unaligned_be32(bp + 8);
+ printf(" Lifetime media motion hours: %u\n", v);
+ printf(" Repeat: %d\n", !!(bp[13] & 0x80));
+ v = bp[13] & 0xf;
+ printf(" Sense key: 0x%x [%s]\n", v,
+ sg_get_sense_key_str(v, sizeof(b), b));
+ printf(" Additional sense code: 0x%x\n", bp[14]);
+ printf(" Additional sense code qualifier: 0x%x\n", bp[15]);
+ if (bp[14] || bp[15])
+ printf(" [%s]\n", sg_get_asc_ascq_str(bp[14], bp[15],
+ sizeof(b), b));
+ v = sg_get_unaligned_be32(bp + 16);
+ printf(" Vendor specific code qualifier: 0x%x\n", v);
+ v = sg_get_unaligned_be32(bp + 20);
+ printf(" Product revision level: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 24);
+ printf(" Hours since last clean: %u\n", v);
+ printf(" Operation code: 0x%x\n", bp[28]);
+ printf(" Service action: 0x%x\n", bp[29] & 0xf);
+ // Check Medium id number for all zeros
+ // ssc4r03.pdf does not define this field, why? xxxxxx
+ if (sg_all_zeros(bp + 32, 32))
+ printf(" Medium id number is 32 bytes of zero\n");
+ else {
+ hex2str(bp + 32, 32, " ", 0 /* with ASCII */, sizeof(b), b);
+ printf(" Medium id number (in hex):\n%s", b);
+ }
+ printf(" Timestamp origin: 0x%x\n", bp[64] & 0xf);
+ // Check Timestamp for all zeros
+ if (sg_all_zeros(bp + 66, 6))
+ printf(" Timestamp is all zeros:\n");
+ else {
+ hex2str(bp + 66, 6, NULL, op->hex2str_oformat, sizeof(b), b);
+ printf(" Timestamp: %s", b);
+ }
+ if (pl > 72) {
+ n = pl - 72;
+ k = hex2str(bp + 72, n, " ", op->hex2str_oformat,
+ sizeof(b), b);
+ printf(" Vendor specific:\n");
+ printf("%s", b);
+ if (k >= (int)sizeof(b) - 1)
+ printf(" <truncated>\n");
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Media changer diagnostic data [0x16] <mcdd> for media changer */
+static bool
+show_mchanger_diag_data_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc;
+ unsigned int v;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+ char b[512];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Media changer diagnostics data page (smc-3) [0x16]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ printf(" %s: %d\n", param_c, pc);
+ printf(" Repeat: %d\n", !!(bp[5] & 0x80));
+ v = bp[5] & 0xf;
+ printf(" Sense key: 0x%x [%s]\n", v,
+ sg_get_sense_key_str(v, sizeof(b), b));
+ printf(" Additional sense code: 0x%x\n", bp[6]);
+ printf(" Additional sense code qualifier: 0x%x\n", bp[7]);
+ if (bp[6] || bp[7])
+ printf(" [%s]\n", sg_get_asc_ascq_str(bp[6], bp[7],
+ sizeof(b), b));
+ v = sg_get_unaligned_be32(bp + 8);
+ printf(" Vendor specific code qualifier: 0x%x\n", v);
+ v = sg_get_unaligned_be32(bp + 12);
+ printf(" Product revision level: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 16);
+ printf(" Number of moves: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 20);
+ printf(" Number of pick: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 24);
+ printf(" Number of pick retries: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 28);
+ printf(" Number of places: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 32);
+ printf(" Number of place retries: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 36);
+ printf(" Number of determined volume identifiers: %u\n", v);
+ v = sg_get_unaligned_be32(bp + 40);
+ printf(" Number of unreadable volume identifiers: %u\n", v);
+ printf(" Operation code: 0x%x\n", bp[44]);
+ printf(" Service action: 0x%x\n", bp[45] & 0xf);
+ printf(" Media changer error type: 0x%x\n", bp[46]);
+ printf(" MTAV: %d\n", !!(bp[47] & 0x8));
+ printf(" IAV: %d\n", !!(bp[47] & 0x4));
+ printf(" LSAV: %d\n", !!(bp[47] & 0x2));
+ printf(" DAV: %d\n", !!(bp[47] & 0x1));
+ v = sg_get_unaligned_be16(bp + 48);
+ printf(" Medium transport address: 0x%x\n", v);
+ v = sg_get_unaligned_be16(bp + 50);
+ printf(" Initial address: 0x%x\n", v);
+ v = sg_get_unaligned_be16(bp + 52);
+ printf(" Last successful address: 0x%x\n", v);
+ v = sg_get_unaligned_be16(bp + 54);
+ printf(" Destination address: 0x%x\n", v);
+ if (pl > 91) {
+ printf(" Volume tag information:\n");
+ hex2fp(bp + 56, 36, " ", op->hex2str_oformat, stdout);
+ }
+ if (pl > 99) {
+ printf(" Timestamp origin: 0x%x\n", bp[92] & 0xf);
+ printf(" Timestamp:\n");
+ hex2fp(bp + 94, 6, " ", op->hex2str_oformat, stdout);
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* Helper for show_volume_stats_pages() */
+static void
+volume_stats_partition(const uint8_t * xp, int len, bool pr_in_hex)
+{
+ uint64_t ull;
+
+ while (len > 3) {
+ bool all_ffs, ffs_last_fe;
+ int dl, pn;
+
+ dl = xp[0] + 1;
+ if (dl < 3)
+ return;
+ pn = sg_get_unaligned_be16(xp + 2);
+ ffs_last_fe = false;
+ all_ffs = false;
+ if (sg_all_ffs(xp + 4, dl - 3)) {
+ switch (xp[4 + dl - 3]) {
+ case 0xff:
+ all_ffs = true;
+ break;
+ case 0xfe:
+ ffs_last_fe = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (! (all_ffs || ffs_last_fe)) {
+ ull = sg_get_unaligned_be(dl - 4, xp + 4);
+ if (pr_in_hex)
+ printf(" partition number: %d, partition record data "
+ "counter: 0x%" PRIx64 "\n", pn, ull);
+ else
+ printf(" partition number: %d, partition record data "
+ "counter: %" PRIu64 "\n", pn, ull);
+ } else if (all_ffs)
+ printf(" partition number: %d, partition record data "
+ "counter is all 0xFFs\n", pn);
+ else /* ffs_last_fe is true */
+ printf(" partition number: %d, partition record data "
+ "counter is all 0xFFs apart\n from a trailing "
+ "0xFE\n", pn);
+ xp += dl;
+ len -= dl;
+ }
+}
+
+/* Helper for show_volume_stats_pages() */
+static void
+volume_stats_history(const uint8_t * xp, int len)
+{
+ while (len > 3) {
+ int dl, mhi;
+
+ dl = xp[0] + 1;
+ if (dl < 4)
+ return;
+ mhi = sg_get_unaligned_be16(xp + 2);
+ if (dl < 12)
+ printf(" index: %d\n", mhi);
+ else if (12 == dl)
+ printf(" index: %d, vendor: %.8s\n", mhi, xp + 4);
+ else
+ printf(" index: %d, vendor: %.8s, unit serial number: %.*s\n",
+ mhi, xp + 4, dl - 12, xp + 12);
+ xp += dl;
+ len -= dl;
+ }
+}
+
+/* Volume Statistics log page and subpages (ssc-4) [0x17, 0x0-0xf] <vs> */
+static bool
+show_volume_stats_pages(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool skip_out = false;
+ bool evsm_output = false;
+ int num, pl, pc, subpg_code;
+ bool spf;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+ char b[512];
+
+if (jop) { };
+ spf = !!(resp[0] & 0x40);
+ subpg_code = spf ? resp[1] : NOT_SPG_SUBPG;
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (subpg_code < 0x10)
+ printf("Volume statistics page (ssc-4), subpage=%d\n",
+ subpg_code);
+ else {
+ printf("Volume statistics page (ssc-4), subpage=%d; Reserved, "
+ "skip\n", subpg_code);
+ return false;
+ }
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+
+ switch (pc) {
+ case 0:
+ printf(" Page valid: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 1:
+ printf(" Thread count: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 2:
+ printf(" Total data sets written: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 3:
+ printf(" Total write retries: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 4:
+ printf(" Total unrecovered write errors: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 5:
+ printf(" Total suspended writes: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 6:
+ printf(" Total fatal suspended writes: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 7:
+ printf(" Total data sets read: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 8:
+ printf(" Total read retries: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 9:
+ printf(" Total unrecovered read errors: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xa:
+ printf(" Total suspended reads: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xb:
+ printf(" Total fatal suspended reads: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xc:
+ printf(" Last mount unrecovered write errors: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xd:
+ printf(" Last mount unrecovered read errors: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xe:
+ printf(" Last mount megabytes written: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0xf:
+ printf(" Last mount megabytes read: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x10:
+ printf(" Lifetime megabytes written: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x11:
+ printf(" Lifetime megabytes read: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x12:
+ printf(" Last load write compression ratio: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x13:
+ printf(" Last load read compression ratio: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x14:
+ printf(" Medium mount time: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x15:
+ printf(" Medium ready time: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x16:
+ printf(" Total native capacity [MB]: %s\n",
+ num_or_unknown(bp + 4, pl - 4, false, b, sizeof(b)));
+ break;
+ case 0x17:
+ printf(" Total used native capacity [MB]: %s\n",
+ num_or_unknown(bp + 4, pl - 4, false, b, sizeof(b)));
+ break;
+ case 0x1a:
+ printf(" Volume stop writes of forward wraps: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x1b:
+ printf(" Volume stop writes of backward wraps: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x40:
+ printf(" Volume serial number: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x41:
+ printf(" Tape lot identifier: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x42:
+ printf(" Volume barcode: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x43:
+ printf(" Volume manufacturer: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x44:
+ printf(" Volume license code: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x45:
+ printf(" Volume personality: %.*s\n", pl - 4, bp + 4);
+ break;
+ case 0x80:
+ printf(" Write protect: %s\n",
+ num_or_unknown(bp + 4, pl - 4, false, b, sizeof(b)));
+ break;
+ case 0x81:
+ printf(" WORM: %s\n",
+ num_or_unknown(bp + 4, pl - 4, false, b, sizeof(b)));
+ break;
+ case 0x82:
+ printf(" Maximum recommended tape path temperature exceeded: "
+ "%s\n", num_or_unknown(bp + 4, pl - 4, false, b,
+ sizeof(b)));
+ break;
+ case 0x100:
+ printf(" Volume write mounts: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x101:
+ printf(" Beginning of medium passes: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x102:
+ printf(" Middle of medium passes: %" PRIu64 "\n",
+ sg_get_unaligned_be(pl - 4, bp + 4));
+ break;
+ case 0x200:
+ printf(" Logical position of first encrypted logical object:\n");
+ volume_stats_partition(bp + 4, pl - 4, true);
+ break;
+ case 0x201:
+ printf(" Logical position of first unencrypted logical object "
+ "after first\n encrypted logical object:\n");
+ volume_stats_partition(bp + 4, pl - 4, true);
+ break;
+ case 0x202:
+ printf(" Native capacity partition(s) [MB]:\n");
+ volume_stats_partition(bp + 4, pl - 4, false);
+ break;
+ case 0x203:
+ printf(" Used native capacity partition(s) [MB]:\n");
+ volume_stats_partition(bp + 4, pl - 4, false);
+ break;
+ case 0x204:
+ printf(" Remaining native capacity partition(s) [MB]:\n");
+ volume_stats_partition(bp + 4, pl - 4, false);
+ break;
+ case 0x300:
+ printf(" Mount history:\n");
+ volume_stats_history(bp + 4, pl - 4);
+ break;
+
+ default:
+ if (pc >= 0xf000) {
+ if (op->exclude_vendor) {
+ skip_out = true;
+ if ((op->verbose > 0) && (0 == op->do_brief) &&
+ (! evsm_output)) {
+ evsm_output = true;
+ printf(" Vendor specific parameter(s) being "
+ "ignored\n");
+ }
+ } else
+ printf(" Vendor specific %s (0x%x), payload in hex\n",
+ param_c, pc);
+ } else
+ printf(" Reserved %s (0x%x), payload in hex\n", param_c, pc);
+ if (skip_out)
+ skip_out = false;
+ else
+ hex2fp(bp + 4, pl - 4, " ", op->hex2str_oformat, stdout);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* TAPE_ALERT_LPAGE [0x2e] <ta> */
+static bool
+show_tape_alert_ssc_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ int num, pl, pc, flag;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ /* N.B. the Tape alert log page for smc-3 is different */
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Tape alert page (ssc-3) [0x2e]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ flag = bp[4] & 1;
+ if (op->verbose && (0 == op->do_brief) && flag)
+ printf(" >>>> ");
+ if ((0 == op->do_brief) || op->verbose || flag) {
+ if (NULL == sg_lib_tapealert_strs[0])
+ printf(" No string available for code 0x%x, flag: %d\n",
+ pc, flag);
+ else if (pc <= 0x40)
+ printf(" %s: %d\n", sg_lib_tapealert_strs[pc], flag);
+ else
+ printf(" Reserved %s 0x%x, flag: %d\n", param_c, pc, flag);
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* 0x37 */
+static bool
+show_seagate_cache_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool skip = false;
+ int num, pl, pc;
+ int bsti = 0;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) {
+ if (resp[1] > 0) {
+ printf("Suspicious page 0x37, SPF=0 but subpage=0x%x\n", resp[1]);
+ if (op->verbose)
+ printf("... try vendor=wdc\n");
+ if (op->do_brief > 0)
+ return true;
+ } else
+ printf("Seagate cache page [0x37]\n");
+ }
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0:
+ ++bsti;
+ if (bsti < 2)
+ printf(" Blocks sent to initiator");
+ else
+ skip = true;
+ break;
+ case 1:
+ printf(" Blocks received from initiator");
+ break;
+ case 2:
+ printf(" Blocks read from cache and sent to initiator");
+ break;
+ case 3:
+ printf(" Number of read and write commands whose size "
+ "<= segment size");
+ break;
+ case 4:
+ printf(" Number of read and write commands whose size "
+ "> segment size");
+ break;
+ default:
+ printf(" Unknown Seagate %s = 0x%x", param_c, pc);
+ break;
+ }
+ if (skip)
+ skip = false;
+ else {
+ printf(" = %" PRIu64 "", sg_get_unaligned_be(pl - 4, bp + 4));
+ printf("\n");
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str,
+ sizeof(str)));
+ }
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+/* 0x37 */
+static bool
+show_hgst_misc_page(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool valid = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("HGST/WDC miscellaneous page [0x37, 0x%x]\n",
+ op->decod_subpg_code);
+ num = len - 4;
+ if (num < 0x30) {
+ printf("HGST/WDC miscellaneous page too short (%d) < 48\n", num);
+ return valid;
+ }
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ switch (pc) {
+ case 0:
+ valid = true;
+ printf(" Power on hours = %u\n", sg_get_unaligned_be32(bp + 4));
+ printf(" Total Bytes Read = %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 8));
+ printf(" Total Bytes Written = %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 16));
+ printf(" Max Drive Temp (Celsius) = %u\n", bp[24]);
+ printf(" GList Size = %u\n", sg_get_unaligned_be16(bp + 25));
+ printf(" Number of Information Exceptions = %u\n", bp[27]);
+ printf(" MED EXC = %u\n", !! (0x80 & bp[28]));
+ printf(" HDW EXC = %u\n", !! (0x40 & bp[28]));
+ printf(" Total Read Commands = %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 29));
+ printf(" Total Write Commands = %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 37));
+ printf(" Flash Correction Count = %u\n",
+ sg_get_unaligned_be16(bp + 46));
+ break;
+ default:
+ valid = false;
+ printf(" Unknown HGST/WDC %s = 0x%x", param_c, pc);
+ break;
+ }
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return valid;
+}
+
+/* 0x3e */
+static bool
+show_seagate_factory_page(const uint8_t * resp, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool valid = false;
+ int num, pl, pc;
+ const uint8_t * bp;
+ uint64_t ull;
+ char str[PCB_STR_LEN];
+
+if (jop) { };
+ if (op->verbose || ((! op->do_raw) && (0 == op->do_hex)))
+ printf("Seagate/Hitachi factory page [0x3e]\n");
+ num = len - 4;
+ bp = &resp[0] + 4;
+ while (num > 3) {
+ pc = sg_get_unaligned_be16(bp + 0);
+ pl = bp[3] + 4;
+ if (op->filter_given) {
+ if (pc != op->filter)
+ goto skip;
+ }
+ if (op->do_raw) {
+ dStrRaw(bp, pl);
+ goto filter_chk;
+ } else if (op->do_hex) {
+ hex2stdout(bp, pl, op->dstrhex_no_ascii);
+ goto filter_chk;
+ }
+ valid = true;
+ switch (pc) {
+ case 0:
+ printf(" number of hours powered up");
+ break;
+ case 8:
+ printf(" number of minutes until next internal SMART test");
+ break;
+ default:
+ valid = false;
+ printf(" Unknown Seagate/Hitachi %s = 0x%x", param_c, pc);
+ break;
+ }
+ if (valid) {
+ ull = sg_get_unaligned_be(pl - 4, bp + 4);
+ if (0 == pc)
+ printf(" = %.2f", ((double)ull) / 60.0 );
+ else
+ printf(" = %" PRIu64 "", ull);
+ }
+ printf("\n");
+ if (op->do_pcb)
+ printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str)));
+filter_chk:
+ if (op->filter_given)
+ break;
+skip:
+ num -= pl;
+ bp += pl;
+ }
+ return true;
+}
+
+static void
+decode_page_contents(const uint8_t * resp, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int pg_code, subpg_code, vpn;
+ bool spf;
+ bool done = false;
+ const struct log_elem * lep;
+
+ if (len < 3) {
+ pr2serr("%s: response has bad length: %d\n", __func__, len);
+ return;
+ }
+ spf = !!(resp[0] & 0x40);
+ pg_code = resp[0] & 0x3f;
+ if ((VP_HITA == op->vend_prod_num) && (pg_code >= 0x30))
+ subpg_code = resp[1]; /* Hitachi don't set SPF on VS pages */
+ else
+ subpg_code = spf ? resp[1] : NOT_SPG_SUBPG;
+ op->decod_subpg_code = subpg_code;
+ if ((SUPP_SPGS_SUBPG == subpg_code) && (SUPP_PAGES_LPAGE != pg_code)) {
+ done = show_supported_pgs_sub_page(resp, len, op, jop);
+ if (done)
+ return;
+ }
+ vpn = (op->vend_prod_num >= 0) ? op->vend_prod_num : op->deduced_vpn;
+ lep = pg_subpg_pdt_search(pg_code, subpg_code, op->dev_pdt, vpn);
+
+ /* Below is the indirect function call to all the show_* functions */
+ if (lep && lep->show_pagep)
+ done = (*lep->show_pagep)(resp, len, op, jop);
+
+ if (! done) {
+ if (0 == op->do_hex) {
+ static const char * unable_s = "Unable to decode page = 0x";
+
+ if (subpg_code > 0)
+ printf("%s%x, subpage = 0x%x, here is hex:\n", unable_s,
+ pg_code, subpg_code);
+ else
+ printf("%s%x, here is hex:\n", unable_s, pg_code);
+ }
+ if ((len > 128) && (0 == op->do_hex)) {
+ hex2fp(resp, 64, " ", op->hex2str_oformat, stdout);
+ printf(" ..... [truncated after 64 of %d bytes (use '-H' to "
+ "see the rest)]\n", len);
+ } else {
+ if (0 == op->do_hex)
+ hex2fp(resp, len, " ", op->hex2str_oformat, stdout);
+ else
+ hex2stdout(resp, len, op->dstrhex_no_ascii);
+ }
+ }
+}
+
+/* Tries to fetch the TEMPERATURE_LPAGE [0xd] page first. If that fails
+ * tries to get the Informational Exceptions (IE_LPAGE) page. */
+static int
+fetchTemperature(int sg_fd, uint8_t * resp, int max_len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int len;
+ int res = 0;
+
+ op->pg_code = TEMPERATURE_LPAGE;
+ op->subpg_code = NOT_SPG_SUBPG;
+ res = do_logs(sg_fd, resp, max_len, op);
+ if (0 == res) {
+ len = sg_get_unaligned_be16(resp + 2) + 4;
+ if (op->do_raw)
+ dStrRaw(resp, len);
+ else if (op->do_hex)
+ hex2stdout(resp, len, op->dstrhex_no_ascii);
+ else
+ show_temperature_page(resp, len, op, jop);
+ } else if (SG_LIB_CAT_NOT_READY == res)
+ pr2serr("Device not ready\n");
+ else {
+ op->pg_code = IE_LPAGE;
+ res = do_logs(sg_fd, resp, max_len, op);
+ if (0 == res) {
+ len = sg_get_unaligned_be16(resp + 2) + 4;
+ if (op->do_raw)
+ dStrRaw(resp, len);
+ else if (op->do_hex)
+ hex2stdout(resp, len, op->dstrhex_no_ascii);
+ else
+ show_ie_page(resp, len, op, jop);
+ } else
+ pr2serr("Unable to find temperature in either Temperature or "
+ "IE log page\n");
+ }
+ sg_cmds_close_device(sg_fd);
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+}
+
+/* Returns 0 if successful else SG_LIB_SYNTAX_ERROR. */
+static int
+decode_pg_arg(struct opts_t * op)
+{
+ int nn;
+ const struct log_elem * lep;
+ char * cp;
+
+ if (isalpha((uint8_t)op->pg_arg[0])) {
+ char b[80];
+
+ if (strlen(op->pg_arg) >= (sizeof(b) - 1)) {
+ pr2serr("argument to '--page=' is too long\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ strcpy(b, op->pg_arg);
+ cp = (char *)strchr(b, ',');
+ if (cp)
+ *cp = '\0';
+ lep = acron_search(b);
+ if (NULL == lep) {
+ pr2serr("bad argument to '--page=' no acronyn match to "
+ "'%s'\n", b);
+ pr2serr(" Try using '-e' or'-ee' to see available "
+ "acronyns\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->lep = lep;
+ op->pg_code = lep->pg_code;
+ if (cp) {
+ nn = sg_get_num_nomult(cp + 1);
+ if ((nn < 0) || (nn > 255)) {
+ pr2serr("Bad second value in argument to "
+ "'--page='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->subpg_code = nn;
+ } else
+ op->subpg_code = lep->subpg_code;
+ } else { /* numeric arg: either 'pg_num' or 'pg_num,subpg_num' */
+ int n;
+
+ cp = (char *)strchr(op->pg_arg, ',');
+ n = sg_get_num_nomult(op->pg_arg);
+ if ((n < 0) || (n > 63)) {
+ pr2serr("Bad argument to '--page='\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (cp) {
+ nn = sg_get_num_nomult(cp + 1);
+ if ((nn < 0) || (nn > 255)) {
+ pr2serr("Bad second value in argument to "
+ "'--page='\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else
+ nn = 0;
+ op->pg_code = n;
+ op->subpg_code = nn;
+ }
+ return 0;
+}
+
+/* Since the Supported subpages page is sitting in the rsp_buff which is
+ * MX_ALLOC_LEN bytes long (~ 64 KB) then move it (from rsp_buff+0 to
+ * rsp_buff+pg_len-1) to the top end of that buffer. Then there is room
+ * to merge supp_pgs_rsp with the supported subpages with the result back
+ * at the bottom of rsp_buff. The new length of the merged subpages page
+ * (excluding its 4 byte header) is returned.
+ * Assumes both pages are in ascending order (as required by SPC-4). */
+static int
+merge_both_supported(const uint8_t * supp_pgs_p, int su_p_pg_len, int pg_len)
+{
+ uint8_t pg;
+ int k, kp, ks;
+ int max_blen = (2 * su_p_pg_len) + pg_len;
+ uint8_t * m_buff = rsp_buff + (rsp_buff_sz - pg_len);
+ uint8_t * r_buff = rsp_buff + 4;
+
+ if (pg_len > 0)
+ memmove(m_buff, rsp_buff + 4, pg_len);
+ for (k = 0, kp = 0, ks = 0; k < max_blen; k += 2) {
+ if (kp < su_p_pg_len)
+ pg = supp_pgs_p[kp];
+ else
+ pg = 0xff;
+ if (ks < pg_len) {
+ if (m_buff[ks] < pg) {
+ r_buff[k] = m_buff[ks];
+ r_buff[k + 1] = m_buff[ks + 1];
+ ks += 2;
+ } else if ((m_buff[ks] == pg) && (m_buff[ks + 1] == 0)) {
+ r_buff[k] = m_buff[ks];
+ r_buff[k + 1] = m_buff[ks + 1];
+ ks += 2;
+ ++kp;
+ } else {
+ r_buff[k] = pg;
+ r_buff[k + 1] = 0;
+ ++kp;
+ }
+ } else {
+ if (0xff == pg)
+ break;
+ r_buff[k] = pg;
+ r_buff[k + 1] = 0;
+ ++kp;
+ }
+ }
+ sg_put_unaligned_be16(k, rsp_buff + 2);
+ return k;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json;
+ int k, nn, pg_len, res, vb;
+ int resp_len = 0;
+ int su_p_pg_len = 0;
+ int in_len = -1;
+ int sg_fd = -1;
+ int ret = 0;
+ uint8_t * parr;
+ uint8_t * free_parr = NULL;
+ struct opts_t * op;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ struct sg_simple_inquiry_resp inq_out;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ uint8_t supp_pgs_rsp[256];
+ char b[128];
+ static const int blen = sizeof(b);
+
+ op = &opts;
+ /* N.B. some disks only give data for current cumulative */
+ op->page_control = 1;
+ op->dev_pdt = -1;
+ op->vend_prod_num = VP_NONE;
+ op->deduced_vpn = VP_NONE;
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ usage_for(op->do_help, op);
+ return 0;
+ }
+ jsp = &op->json_st;
+ as_json = jsp->pr_as_json;
+ if (as_json) {
+ if (op->do_name) {
+ pr2serr(">>> The --json option is superior to the --name "
+ "option.\n");
+ pr2serr(">>> Ignoring the --name option.\n");
+ op->do_name = false;
+ }
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+ if (op->do_hex > 0) {
+ if (op->do_hex > 2) {
+ op->dstrhex_no_ascii = -1;
+ op->hex2str_oformat = 1;
+ } else {
+ op->dstrhex_no_ascii = (1 == op->do_hex);
+ op->hex2str_oformat = (1 == op->do_hex);
+ }
+ } else {
+ if (op->undefined_hex > 0) {
+ if (op->undefined_hex > 2) {
+ op->dstrhex_no_ascii = -1;
+ op->hex2str_oformat = 1;
+ } else {
+ op->dstrhex_no_ascii = (1 == op->undefined_hex);
+ op->hex2str_oformat = (1 == op->undefined_hex);
+ }
+ } else { /* default when no --hex nor --undefined */
+ op->dstrhex_no_ascii = -1;
+ op->hex2str_oformat = 1;
+ }
+ }
+ vb = op->verbose;
+ if (op->vend_prod) {
+ if (0 == memcmp("-1", op->vend_prod,3))
+ k = VP_NONE;
+ else if (isdigit((uint8_t)op->vend_prod[0]))
+ k = sg_get_num_nomult(op->vend_prod);
+ else
+ k = find_vpn_by_acron(op->vend_prod);
+ op->vend_prod_num = k;
+ if (VP_ALL == k)
+ ;
+ else if ((k < 0) || (k > (32 - MVP_OFFSET))) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ " ('-M ') option\n");
+ enumerate_vp();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->do_enumerate > 0) {
+ if (op->device_name && vb)
+ pr2serr("Warning: device: %s is being ignored\n",
+ op->device_name);
+ enumerate_pages(op);
+ return 0;
+ }
+ if (op->in_fn) {
+ if (op->maxlen_given) {
+ if (op->maxlen > MX_INLEN_ALLOC_LEN) {
+ pr2serr("bad argument to '--maxlen=' when --in= given, from "
+ "2 to %d (inclusive) expected\n", MX_INLEN_ALLOC_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ rsp_buff_sz = op->maxlen;
+ } else
+ rsp_buff_sz = DEF_INLEN_ALLOC_LEN;
+ } else {
+ if (op->maxlen_given) {
+ if (op->maxlen > MX_ALLOC_LEN) {
+ pr2serr("bad argument to '--maxlen=', from 2 to 65535 "
+ "(inclusive) expected\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ rsp_buff_sz = op->maxlen;
+ }
+ }
+ rsp_buff = sg_memalign(rsp_buff_sz, 0 /* page aligned */, &free_rsp_buff,
+ false);
+ if (NULL == rsp_buff) {
+ pr2serr("Unable to allocate %d bytes on the heap\n", rsp_buff_sz);
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ if (NULL == op->device_name) {
+ if (op->in_fn) {
+ bool found = false;
+ bool r_spf = false;
+ uint16_t u;
+ int pg_code, subpg_code, pdt, n;
+ const struct log_elem * lep;
+ const uint8_t * bp;
+
+ if ((ret = sg_f2hex_arr(op->in_fn, op->do_raw, false, rsp_buff,
+ &in_len, rsp_buff_sz)))
+ goto err_out;
+ if (vb > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (op->do_raw)
+ op->do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n",
+ op->in_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->pg_arg) {
+ char b[144];
+ char * cp;
+
+ strcpy(b, op->pg_arg);
+ cp = (char *)strchr(b, ',');
+ if (cp)
+ *cp = '\0';
+ lep = acron_search(b);
+ if (NULL == lep) {
+ pr2serr("bad argument to '--page=' no acronyn match to "
+ "'%s'\n", b);
+ pr2serr(" Try using '-e' or'-ee' to see available "
+ "acronyns\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->lep = lep;
+ op->pg_code = lep->pg_code;
+ op->subpg_code = lep->subpg_code;
+ if (op->subpg_code > 0)
+ r_spf = true;
+ }
+
+ for (bp = rsp_buff, k = 0; k < in_len; bp += n, k += n) {
+ bool spf = !! (bp[0] & 0x40);
+
+ pg_code = bp[0] & 0x3f;
+ subpg_code = spf ? bp[1] : NOT_SPG_SUBPG;
+ u = sg_get_unaligned_be16(bp + 2);
+ n = u + 4;
+ if (n > (in_len - k)) {
+ pr2serr("bytes decoded remaining (%d) less than lpage "
+ "length (%d), try decoding anyway\n", in_len - k,
+ n);
+ n = in_len - k;
+ }
+ if (op->pg_arg) {
+ if ((NOT_SPG_SUBPG == op->subpg_code) && spf) {
+ continue;
+ } else if ((! spf) && (! r_spf)) {
+ if (pg_code != op->pg_code)
+ continue;
+ } else if ((SUPP_SPGS_SUBPG == op->subpg_code) &&
+ (SUPP_PAGES_LPAGE != op->pg_code)) {
+ if (pg_code != op->pg_code)
+ continue;
+ } else if ((SUPP_SPGS_SUBPG != op->subpg_code) &&
+ (SUPP_PAGES_LPAGE == op->pg_code)) {
+ if (subpg_code != op->subpg_code)
+ continue;
+ } else if ((SUPP_SPGS_SUBPG != op->subpg_code) &&
+ (SUPP_PAGES_LPAGE != op->pg_code)) {
+ if ((pg_code != op->pg_code) ||
+ (subpg_code != op->subpg_code))
+ continue;
+ }
+ }
+ if (op->exclude_vendor && (pg_code >= 0x30))
+ continue;
+ found = true;
+ if (op->do_hex > 2) {
+ hex2fp(bp, n, NULL, op->hex2str_oformat, stdout);
+ continue;
+ }
+ pdt = op->dev_pdt;
+ lep = pg_subpg_pdt_search(pg_code, subpg_code, pdt,
+ op->vend_prod_num);
+ if (lep) {
+ /* Below is the indirect function call to all the
+ * show_* functions */
+ if (lep->show_pagep)
+ (*lep->show_pagep)(bp, n, op, jop);
+ else
+ sgj_pr_hr(jsp, "Unable to decode %s [%s]\n",
+ lep->name, lep->acron);
+ } else {
+ nn = sg_scnpr(b, blen, "Unable to decode page=0x%x",
+ pg_code);
+ if (subpg_code > 0)
+ sg_scnpr(b + nn, blen - nn, ", subpage=0x%x",
+ subpg_code);
+ if (pdt >= 0)
+ sg_scnpr(b + nn, blen - nn, ", pdt=0x%x\n", pdt);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ } /* end of page/subpage search loop */
+ if (op->pg_arg && (! found)) {
+ nn = sg_scnpr(b, blen, "Unable to find page=0x%x",
+ op->pg_code);
+ if (op->subpg_code > 0)
+ sg_scnpr(b + nn, blen - nn, ", subpage=0x%x",
+ op->subpg_code);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_i(jsp, jop, "page_not_found", 1);
+ }
+ ret = 0;
+ goto err_out;
+ }
+ if (op->pg_arg) { /* do this for 'sg_logs -p xxx' */
+ ret = decode_pg_arg(op);
+ if (ret)
+ goto err_out;
+ }
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(1, op);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ if (op->do_select) {
+ if (op->do_temperature) {
+ pr2serr("--select cannot be used with --temperature\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_transport) {
+ pr2serr("--select cannot be used with --transport\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ } else if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+ if (op->do_all) {
+ if (op->do_select) {
+ pr2serr("--all conflicts with --select\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ }
+ if (op->in_fn) {
+ if (! op->do_select) {
+ pr2serr("--in=FN can only be used with --select when DEVICE "
+ "given\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if ((ret = sg_f2hex_arr(op->in_fn, op->do_raw, false, rsp_buff,
+ &in_len, rsp_buff_sz)))
+ goto err_out;
+ if (vb > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n", in_len,
+ in_len);
+ }
+ if (op->pg_arg) {
+ if (op->do_all) {
+ if (0 == op->do_brief)
+ pr2serr(">>> warning: --page=%s ignored when --all given\n",
+ op->pg_arg);
+ } else {
+ ret = decode_pg_arg(op);
+ if (ret)
+ goto err_out;
+ }
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ win32_spt_init_state = !! scsi_pt_win32_spt_state();
+ if (vb > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ win32_spt_init_state ? "direct" : "indirect");
+#endif
+#endif
+ sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly, vb);
+ if ((sg_fd < 0) && (! op->o_readonly))
+ sg_fd = sg_cmds_open_device(op->device_name, true /* ro */, vb);
+ if (sg_fd < 0) {
+ pr2serr("error opening file: %s: %s \n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+ if (op->do_list || op->do_all) {
+ op->pg_code = SUPP_PAGES_LPAGE;
+ if ((op->do_list > 1) || (op->do_all > 1))
+ op->subpg_code = SUPP_SPGS_SUBPG;
+ }
+ if (op->do_transport) {
+ if ((op->pg_code > 0) || (op->subpg_code > 0) ||
+ op->do_temperature) {
+ pr2serr("'-T' should not be mixed with options implying other "
+ "pages\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ op->pg_code = PROTO_SPECIFIC_LPAGE;
+ }
+
+ memset(&inq_out, 0, sizeof(inq_out));
+ if (op->no_inq < 2) {
+ if (sg_simple_inquiry(sg_fd, &inq_out, true, vb)) {
+ pr2serr("%s doesn't respond to a SCSI INQUIRY\n",
+ op->device_name);
+ ret = SG_LIB_CAT_OTHER;
+ goto err_out;
+ }
+ op->dev_pdt = inq_out.peripheral_type;
+ if ((! op->do_raw) && (0 == op->do_hex) && (! op->do_name) &&
+ (0 == op->no_inq) && (0 == op->do_brief))
+ sgj_pr_hr(jsp, " %.8s %.16s %.4s\n", inq_out.vendor,
+ inq_out.product, inq_out.revision);
+ memcpy(t10_vendor_str, inq_out.vendor, 8);
+ memcpy(t10_product_str, inq_out.product, 16);
+ if (VP_NONE == op->vend_prod_num)
+ op->deduced_vpn = find_vpn_by_inquiry();
+ }
+
+ if (op->do_temperature) {
+ ret = fetchTemperature(sg_fd, rsp_buff, SHORT_RESP_LEN, op, jop);
+ goto err_out;
+ }
+ if (op->do_select) {
+ k = sg_ll_log_select(sg_fd, op->do_pcreset, op->do_sp,
+ op->page_control, op->pg_code, op->subpg_code,
+ rsp_buff, ((in_len > 0) ? in_len : 0), true, vb);
+ if (k) {
+ if (SG_LIB_CAT_NOT_READY == k)
+ pr2serr("log_select: device not ready\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == k)
+ pr2serr("log_select: field in cdb illegal\n");
+ else if (SG_LIB_CAT_INVALID_OP == k)
+ pr2serr("log_select: not supported\n");
+ else if (SG_LIB_CAT_UNIT_ATTENTION == k)
+ pr2serr("log_select: unit attention\n");
+ else if (SG_LIB_CAT_ABORTED_COMMAND == k)
+ pr2serr("log_select: aborted command\n");
+ else
+ pr2serr("log_select: failed (%d), try '-v' for more "
+ "information\n", k);
+ }
+ ret = (k >= 0) ? k : SG_LIB_CAT_OTHER;
+ goto err_out;
+ }
+ if (op->do_list > 2) {
+ const int supp_pgs_blen = sizeof(supp_pgs_rsp);
+
+ op->subpg_code = NOT_SPG_SUBPG;
+ res = do_logs(sg_fd, supp_pgs_rsp, supp_pgs_blen, op);
+ if (res != 0)
+ goto bad;
+ su_p_pg_len = sg_get_unaligned_be16(supp_pgs_rsp + 2);
+ if ((su_p_pg_len + 4) > supp_pgs_blen) {
+ pr2serr("Supported log pages log page is too long [%d], exit\n",
+ su_p_pg_len);
+ res = SG_LIB_CAT_OTHER;
+ goto bad;
+ }
+ op->subpg_code = SUPP_SPGS_SUBPG;
+ }
+ resp_len = (op->maxlen > 0) ? op->maxlen : MX_ALLOC_LEN;
+ res = do_logs(sg_fd, rsp_buff, resp_len, op);
+ if (0 == res) {
+ pg_len = sg_get_unaligned_be16(rsp_buff + 2);
+ if ((pg_len + 4) > resp_len) {
+ pr2serr("Only fetched %d bytes of response (available: %d "
+ "bytes)\n truncate output\n",
+ resp_len, pg_len + 4);
+ pg_len = resp_len - 4;
+ }
+ goto good;
+ }
+bad:
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%snot supported\n", ls_s);
+ else if (SG_LIB_CAT_NOT_READY == res)
+ pr2serr("%sdevice not ready\n", ls_s);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
+ if ((op->do_list > 2) && (SUPP_SPGS_SUBPG == op->subpg_code)) {
+ rsp_buff[0] = 0x40;
+ rsp_buff[1] = SUPP_SPGS_SUBPG;
+ pg_len = 0;
+ res = 0;
+ if (op->verbose)
+ pr2serr("%sfield in cdb illegal in [0,0xff], "
+ "continue with merge\n", ls_s);
+ goto good;
+ } else
+ pr2serr("%sfield in cdb illegal\n", ls_s);
+ } else if (SG_LIB_CAT_UNIT_ATTENTION == res)
+ pr2serr("%sunit attention\n", ls_s);
+ else if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("%saborted command\n", ls_s);
+ else if (SG_LIB_TRANSPORT_ERROR == res)
+ pr2serr("%stransport error\n", ls_s);
+ else
+ pr2serr("%sother error [%d]\n", ls_s, res);
+ ret = res;
+ goto err_out;
+
+good:
+ if (op->do_list > 2)
+ pg_len = merge_both_supported(supp_pgs_rsp + 4, su_p_pg_len, pg_len);
+
+ if (0 == op->do_all) {
+ if (op->filter_given) {
+ if (op->do_hex > 2)
+ hex2stdout(rsp_buff, pg_len + 4, op->dstrhex_no_ascii);
+ else
+ decode_page_contents(rsp_buff, pg_len + 4, op, jop);
+ } else if (op->do_raw)
+ dStrRaw(rsp_buff, pg_len + 4);
+ else if (op->do_hex > 1)
+ hex2stdout(rsp_buff, pg_len + 4, op->dstrhex_no_ascii);
+ else if (pg_len > 1) {
+ if (op->do_hex) {
+ if (rsp_buff[0] & 0x40)
+ printf("Log page code=0x%x,0x%x, DS=%d, SPF=1, "
+ "page_len=0x%x\n", rsp_buff[0] & 0x3f, rsp_buff[1],
+ !!(rsp_buff[0] & 0x80), pg_len);
+ else
+ printf("Log page code=0x%x, DS=%d, SPF=0, page_len=0x%x\n",
+ rsp_buff[0] & 0x3f, !!(rsp_buff[0] & 0x80), pg_len);
+ hex2stdout(rsp_buff, pg_len + 4, op->dstrhex_no_ascii);
+ }
+ else
+ decode_page_contents(rsp_buff, pg_len + 4, op, jop);
+ }
+ }
+ ret = res;
+
+ if (op->do_all && (pg_len > 1)) {
+ int my_len = pg_len;
+ bool spf;
+
+ parr = sg_memalign(parr_sz, 0, &free_parr, false);
+ if (NULL == parr) {
+ pr2serr("Unable to allocate heap for parr\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ spf = !!(rsp_buff[0] & 0x40);
+ if (my_len > parr_sz) {
+ pr2serr("Unexpectedly large page_len=%d, trim to %d\n", my_len,
+ parr_sz);
+ my_len = parr_sz;
+ }
+ memcpy(parr, rsp_buff + 4, my_len);
+ for (k = 0; k < my_len; ++k) {
+ op->pg_code = parr[k] & 0x3f;
+ if (spf)
+ op->subpg_code = parr[++k];
+ else
+ op->subpg_code = NOT_SPG_SUBPG;
+
+ /* Some devices include [pg_code, 0xff] for all pg_code > 0 */
+ if ((op->pg_code > 0) && (SUPP_SPGS_SUBPG == op->subpg_code))
+ continue; /* skip since no new information */
+ if ((op->pg_code >= 0x30) && op->exclude_vendor)
+ continue;
+ if (! op->do_raw)
+ sgj_pr_hr(jsp, "\n");
+ res = do_logs(sg_fd, rsp_buff, resp_len, op);
+ if (0 == res) {
+ pg_len = sg_get_unaligned_be16(rsp_buff + 2);
+ if ((pg_len + 4) > resp_len) {
+ pr2serr("Only fetched %d bytes of response, truncate "
+ "output\n", resp_len);
+ pg_len = resp_len - 4;
+ }
+ if (op->do_raw && (! op->filter_given))
+ dStrRaw(rsp_buff, pg_len + 4);
+ else if (op->do_hex > 4)
+ decode_page_contents(rsp_buff, pg_len + 4, op, jop);
+ else if (op->do_hex > 1)
+ hex2stdout(rsp_buff, pg_len + 4, op->dstrhex_no_ascii);
+ else if (1 == op->do_hex) {
+ if (0 == op->do_brief) {
+ if (rsp_buff[0] & 0x40)
+ printf("Log page code=0x%x,0x%x, DS=%d, SPF=1, "
+ "page_len=0x%x\n", rsp_buff[0] & 0x3f,
+ rsp_buff[1], !!(rsp_buff[0] & 0x80),
+ pg_len);
+ else
+ printf("Log page code=0x%x, DS=%d, SPF=0, "
+ "page_len=0x%x\n", rsp_buff[0] & 0x3f,
+ !!(rsp_buff[0] & 0x80), pg_len);
+ }
+ hex2stdout(rsp_buff, pg_len + 4, op->dstrhex_no_ascii);
+ }
+ else
+ decode_page_contents(rsp_buff, pg_len + 4, op, jop);
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%spage=0x%x,0x%x not supported\n", ls_s,
+ op->pg_code, op->subpg_code);
+ else if (SG_LIB_CAT_NOT_READY == res)
+ pr2serr("%sdevice not ready\n", ls_s);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("%sfield in cdb illegal [page=0x%x,0x%x]\n", ls_s,
+ op->pg_code, op->subpg_code);
+ else if (SG_LIB_CAT_UNIT_ATTENTION == res)
+ pr2serr("%sunit attention\n", ls_s);
+ else if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("%saborted command\n", ls_s);
+ else
+ pr2serr("%sfailed, try '-v' for more information\n", ls_s);
+ }
+ }
+err_out:
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if (free_parr)
+ free(free_parr);
+ if (sg_fd >= 0)
+ sg_cmds_close_device(sg_fd);
+ if (0 == vb) {
+ if (! sg_if_can2stderr("sg_logs failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_luns.c b/src/sg_luns.c
new file mode 100644
index 00000000..15d367c7
--- /dev/null
+++ b/src/sg_luns.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright (c) 2004-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI REPORT LUNS command to the given SCSI device
+ * and decodes the response.
+ */
+
+static const char * version_str = "1.48 20210804"; /* spc6r05 */
+
+#define MAX_RLUNS_BUFF_LEN (1024 * 1024)
+#define DEF_RLUNS_BUFF_LEN (1024 * 8)
+
+
+static struct option long_options[] = {
+ {"decode", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+#ifdef SG_LIB_LINUX
+ {"linux", no_argument, 0, 'l'},
+#endif
+ {"lu_cong", no_argument, 0, 'L'},
+ {"lu-cong", no_argument, 0, 'L'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"quiet", no_argument, 0, 'q'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"select", required_argument, 0, 's'},
+ {"test", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+#ifdef SG_LIB_LINUX
+ pr2serr("Usage: sg_luns [--decode] [--help] [--hex] [--linux] "
+ "[--lu_cong]\n"
+ " [--maxlen=LEN] [--quiet] [--raw] "
+ "[--readonly]\n"
+ " [--select=SR] [--verbose] [--version] "
+ "DEVICE\n");
+#else
+ pr2serr("Usage: sg_luns [--decode] [--help] [--hex] [--lu_cong] "
+ "[--maxlen=LEN]\n"
+ " [--quiet] [--raw] [--readonly] "
+ "[--select=SR]\n"
+ " [--verbose] [--version] DEVICE\n");
+#endif
+ pr2serr(" or\n"
+ " sg_luns --test=ALUN [--decode] [--hex] [--lu_cong] "
+ "[--verbose]\n"
+ " where:\n"
+ " --decode|-d decode all luns into component parts\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output response in hexadecimal; used "
+ "twice\n"
+ " shows decoded values in hex\n");
+#ifdef SG_LIB_LINUX
+ pr2serr(" --linux|-l show Linux integer lun after T10 "
+ "representation\n");
+#endif
+ pr2serr(" --lu_cong|-L decode as if LU_CONG is set; used "
+ "twice:\n"
+ " decode as if LU_CONG is clear\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> %d bytes)\n"
+ " --quiet|-q output only ASCII hex lun values\n"
+ " --raw|-r output response in binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --select=SR|-s SR select report SR (def: 0)\n"
+ " 0 -> luns apart from 'well "
+ "known' lus\n"
+ " 1 -> only 'well known' "
+ "logical unit numbers\n"
+ " 2 -> all luns\n"
+ " 0x10 -> administrative luns\n"
+ " 0x11 -> admin luns + "
+ "non-conglomerate luns\n"
+ " 0x12 -> admin lun + its "
+ "subsidiary luns\n"
+ " --test=ALUN|-t ALUN decode ALUN and ignore most other "
+ "options\n"
+ " and DEVICE (apart from '-H')\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REPORT LUNS command or decodes the given ALUN. "
+ "When SR is\n0x10 or 0x11 DEVICE must be LUN 0 or REPORT LUNS "
+ "well known logical unit;\nwhen SR is 0x12 DEVICE must be an "
+ "administrative logical unit. When the\n--test=ALUN option is "
+ "given, decodes ALUN rather than sending a REPORT\nLUNS "
+ "command.\n", DEF_RLUNS_BUFF_LEN );
+}
+
+/* Decoded according to SAM-5 rev 10. Note that one draft: BCC rev 0,
+ * defines its own "bridge addressing method" in place of the SAM-3
+ * "logical addressing method". */
+static void
+decode_lun(const char * leadin, const uint8_t * lunp, bool lu_cong,
+ int do_hex, int verbose)
+{
+ bool next_level, admin_lu_cong;
+ int k, x, a_method, bus_id, target, lun, len_fld, e_a_method;
+ uint64_t ull;
+ char l_leadin[128];
+ char b[256];
+
+ if (0xff == lunp[0]) {
+ printf("%sLogical unit _not_ specified\n", leadin);
+ return;
+ }
+ admin_lu_cong = lu_cong;
+ memset(l_leadin, 0, sizeof(l_leadin));
+ for (k = 0; k < 4; ++k, lunp += 2) {
+ next_level = false;
+ strncpy(l_leadin, leadin, sizeof(l_leadin) - 3);
+ if (k > 0) {
+ if (lu_cong) {
+ admin_lu_cong = false;
+ if ((0 == lunp[0]) && (0 == lunp[1])) {
+ printf("%s>>>> Administrative LU\n", l_leadin);
+ if (do_hex || verbose)
+ printf(" since Subsidiary element is "
+ "0x0000\n");
+ break;
+ } else
+ printf("%s>>Subsidiary element:\n", l_leadin);
+ } else
+ printf("%s>>%s level addressing:\n", l_leadin, ((1 == k) ?
+ "Second" : ((2 == k) ? "Third" : "Fourth")));
+ strcat(l_leadin, " ");
+ } else if (lu_cong) {
+ printf("%s>>Administrative element:\n", l_leadin);
+ strcat(l_leadin, " ");
+ }
+ a_method = (lunp[0] >> 6) & 0x3;
+ switch (a_method) {
+ case 0: /* peripheral device addressing method */
+ if (lu_cong) {
+ snprintf(b, sizeof(b), "%sSimple lu addressing: ",
+ l_leadin);
+ x = 0x3fff & sg_get_unaligned_be16(lunp + 0);
+ if (do_hex)
+ printf("%s0x%04x\n", b, x);
+ else
+ printf("%s%d\n", b, x);
+ if (admin_lu_cong)
+ next_level = true;
+ } else {
+ bus_id = lunp[0] & 0x3f;
+ snprintf(b, sizeof(b), "%sPeripheral device addressing: ",
+ l_leadin);
+ if ((0 == bus_id) && (0 == verbose)) {
+ if (do_hex)
+ printf("%slun=0x%02x\n", b, lunp[1]);
+ else
+ printf("%slun=%d\n", b, lunp[1]);
+ } else {
+ if (do_hex)
+ printf("%sbus_id=0x%02x, %s=0x%02x\n", b, bus_id,
+ (bus_id ? "target" : "lun"), lunp[1]);
+ else
+ printf("%sbus_id=%d, %s=%d\n", b, bus_id,
+ (bus_id ? "target" : "lun"), lunp[1]);
+ }
+ if (bus_id)
+ next_level = true;
+ }
+ break;
+ case 1: /* flat space addressing method */
+ lun = 0x3fff & sg_get_unaligned_be16(lunp + 0);
+ if (lu_cong) {
+ printf("%sSince LU_CONG=1, unexpected Flat space "
+ "addressing: lun=0x%04x\n", l_leadin, lun);
+ break;
+ }
+ if (do_hex)
+ printf("%sFlat space addressing: lun=0x%04x\n", l_leadin,
+ lun);
+ else
+ printf("%sFlat space addressing: lun=%d\n", l_leadin, lun);
+ break;
+ case 2: /* logical unit addressing method */
+ target = (lunp[0] & 0x3f);
+ bus_id = (lunp[1] >> 5) & 0x7;
+ lun = lunp[1] & 0x1f;
+ if (lu_cong) {
+ printf("%sSince LU_CONG=1, unexpected lu addressing: "
+ "bus_id=0x%x, target=0x%02x, lun=0x%02x\n", l_leadin,
+ bus_id, target, lun);
+ break;
+ }
+ if (do_hex)
+ printf("%sLogical unit addressing: bus_id=0x%x, "
+ "target=0x%02x, lun=0x%02x\n", l_leadin, bus_id,
+ target, lun);
+ else
+ printf("%sLogical unit addressing: bus_id=%d, target=%d, "
+ "lun=%d\n", l_leadin, bus_id, target, lun);
+ break;
+ case 3: /* extended logical unit + flat space addressing */
+ len_fld = (lunp[0] & 0x30) >> 4;
+ e_a_method = lunp[0] & 0xf;
+ x = lunp[1];
+ if ((0 == len_fld) && (1 == e_a_method)) {
+ snprintf(b, sizeof(b), "well known logical unit");
+ switch (x) {
+ case 1:
+ printf("%sREPORT LUNS %s\n", l_leadin, b);
+ break;
+ case 2: /* obsolete in spc5r01 */
+ printf("%sACCESS CONTROLS %s\n", l_leadin, b);
+ break;
+ case 3:
+ printf("%sTARGET LOG PAGES %s\n", l_leadin, b);
+ break;
+ case 4:
+ printf("%sSECURITY PROTOCOL %s\n", l_leadin, b);
+ break;
+ case 5:
+ printf("%sMANAGEMENT PROTOCOL %s\n", l_leadin, b);
+ break;
+ case 6:
+ printf("%sTARGET COMMANDS %s\n", l_leadin, b);
+ break;
+ default:
+ if (do_hex)
+ printf("%s%s 0x%02x\n", l_leadin, b, x);
+ else
+ printf("%s%s %d\n", l_leadin, b, x);
+ break;
+ }
+ } else if ((1 == len_fld) && (2 == e_a_method)) {
+ x = sg_get_unaligned_be24(lunp + 1);
+ if (do_hex)
+ printf("%sExtended flat space addressing: lun=0x%06x\n",
+ l_leadin, x);
+ else
+ printf("%sExtended flat space addressing: lun=%d\n",
+ l_leadin, x);
+ } else if ((2 == len_fld) && (2 == e_a_method)) {
+ ull = sg_get_unaligned_be(5, lunp + 1);
+ if (do_hex)
+ printf("%sLong extended flat space addressing: "
+ "lun=0x%010" PRIx64 "\n", l_leadin, ull);
+ else
+ printf("%sLong extended flat space addressing: "
+ "lun=%" PRIu64 "\n", l_leadin, ull);
+ } else if ((3 == len_fld) && (0xf == e_a_method))
+ printf("%sLogical unit _not_ specified addressing\n",
+ l_leadin);
+ else {
+ if (len_fld < 2) {
+ if (1 == len_fld)
+ x = sg_get_unaligned_be24(lunp + 1);
+ if (do_hex)
+ printf("%sExtended logical unit addressing: "
+ "length=%d, e.a. method=%d, value=0x%06x\n",
+ l_leadin, len_fld, e_a_method, x);
+ else
+ printf("%sExtended logical unit addressing: "
+ "length=%d, e.a. method=%d, value=%d\n",
+ l_leadin, len_fld, e_a_method, x);
+ } else {
+ ull = sg_get_unaligned_be(((2 == len_fld) ? 5 : 7),
+ lunp + 1);
+ if (do_hex) {
+ printf("%sExtended logical unit addressing: "
+ "length=%d, e. a. method=%d, ", l_leadin,
+ len_fld, e_a_method);
+ if (5 == len_fld)
+ printf("value=0x%010" PRIx64 "\n", ull);
+ else
+ printf("value=0x%014" PRIx64 "\n", ull);
+ } else
+ printf("%sExtended logical unit addressing: "
+ "length=%d, e. a. method=%d, value=%" PRIu64
+ "\n", l_leadin, len_fld, e_a_method, ull);
+ }
+ }
+ break;
+ }
+ if (next_level)
+ continue;
+ if ((2 == a_method) && (k < 3) && (lunp[2] || lunp[3]))
+ printf("%s<<unexpected data at next level, continue>>\n",
+ l_leadin);
+ break;
+ }
+}
+
+#ifdef SG_LIB_LINUX
+static void
+linux2t10_lun(uint64_t linux_lun, uint8_t t10_lun[])
+{
+ int k;
+
+ for (k = 0; k < 8; k += 2, linux_lun >>= 16)
+ sg_put_unaligned_be16((uint16_t)linux_lun, t10_lun + k);
+}
+
+static uint64_t
+t10_2linux_lun(const uint8_t t10_lun[])
+{
+ int k;
+ const uint8_t * cp;
+ uint64_t res;
+
+ res = sg_get_unaligned_be16(t10_lun + 6);
+ for (cp = t10_lun + 4, k = 0; k < 3; ++k, cp -= 2)
+ res = (res << 16) + sg_get_unaligned_be16(cp);
+ return res;
+}
+#endif /* SG_LIB_LINUX */
+
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+int
+main(int argc, char * argv[])
+{
+#ifdef SG_LIB_LINUX
+ bool do_linux = false;
+#endif
+ bool do_quiet = false;
+ bool do_raw = false;
+ bool lu_cong_arg_given = false;
+ bool o_readonly = false;
+#ifdef SG_LIB_LINUX
+ bool test_linux_in = false;
+ bool test_linux_out = false;
+#endif
+ bool trunc;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, k, m, off, res, c, list_len, len_cap, luns;
+ int decode_arg = 0;
+ int do_hex = 0;
+ int lu_cong_arg = 0;
+ int maxlen = 0;
+ int ret = 0;
+ int select_rep = 0;
+ int verbose = 0;
+ unsigned int h;
+ const char * test_arg = NULL;
+ const char * device_name = NULL;
+ const char * cp;
+ uint8_t * reportLunsBuff = NULL;
+ uint8_t * free_reportLunsBuff = NULL;
+ uint8_t lun_arr[8];
+ struct sg_simple_inquiry_resp sir;
+
+ while (1) {
+ int option_index = 0;
+
+#ifdef SG_LIB_LINUX
+ c = getopt_long(argc, argv, "dhHlLm:qrRs:t:vV", long_options,
+ &option_index);
+#else
+ c = getopt_long(argc, argv, "dhHLm:qrRs:t:vV", long_options,
+ &option_index);
+#endif
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ ++decode_arg;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+#ifdef SG_LIB_LINUX
+ case 'l':
+ do_linux = false;
+ break;
+#endif
+ case 'L':
+ ++lu_cong_arg;
+ lu_cong_arg_given = true;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_RLUNS_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_RLUNS_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (maxlen < 4) {
+ pr2serr("Warning: setting '--maxlen' to 4\n");
+ maxlen = 4;
+ }
+ break;
+ case 'q':
+ do_quiet = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 's':
+ select_rep = sg_get_num(optarg);
+ if ((select_rep < 0) || (select_rep > 255)) {
+ pr2serr("bad argument to '--select', expect 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 't':
+ test_arg = optarg;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (test_arg) {
+ memset(lun_arr, 0, sizeof(lun_arr));
+ cp = test_arg;
+ /* check for leading 'L' */
+#ifdef SG_LIB_LINUX
+ if ('L' == toupper(cp[0])) {
+ uint64_t ull;
+
+ if (('0' == cp[1]) && ('X' == toupper((uint8_t)cp[2])))
+ k = sscanf(cp + 3, " %" SCNx64, &ull);
+ else
+ k = sscanf(cp + 1, " %" SCNu64, &ull);
+ if (1 != k) {
+ pr2serr("Unable to read Linux style LUN integer given to "
+ "--test=\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ linux2t10_lun(ull, lun_arr);
+ test_linux_in = true;
+ } else
+#endif
+ {
+ /* Check if trailing 'L' */
+#ifdef SG_LIB_LINUX
+ m = strlen(cp); /* must be at least 1 char in test_arg */
+ if ('L' == toupper(cp[m - 1]))
+ test_linux_out = true;
+#endif
+ if (('0' == cp[0]) && ('X' == toupper(cp[1])))
+ cp += 2;
+ if (strchr(cp, ' ') || strchr(cp, '\t') || strchr(cp, '-')) {
+ for (k = 0; k < 8; ++k, cp += 2) {
+ c = *cp;
+ if ('\0' == c)
+ break;
+ else if (! isxdigit(c))
+ ++cp;
+ if (1 != sscanf(cp, "%2x", &h))
+ break;
+ lun_arr[k] = h & 0xff;
+ }
+ } else {
+ for (k = 0; k < 8; ++k, cp += 2) {
+ if (1 != sscanf(cp, "%2x", &h))
+ break;
+ lun_arr[k] = h & 0xff;
+ }
+ }
+ if (0 == k) {
+ pr2serr("expected a hex number, optionally prefixed by "
+ "'0x'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef SG_LIB_LINUX
+ if (verbose || test_linux_in || decode_arg)
+#else
+ if (verbose || decode_arg)
+#endif
+ {
+ if (decode_arg > 1) {
+ printf("64 bit LUN in T10 (hex, dashed) format: ");
+ for (k = 0; k < 8; k += 2)
+ printf("%c%02x%02x", (k ? '-' : ' '), lun_arr[k],
+ lun_arr[k + 1]);
+ } else {
+ printf("64 bit LUN in T10 preferred (hex) format: ");
+ for (k = 0; k < 8; ++k)
+ printf(" %02x", lun_arr[k]);
+ }
+ printf("\n");
+ }
+#ifdef SG_LIB_LINUX
+ if (test_linux_out) {
+ if (do_hex > 1)
+ printf("Linux 'word flipped' integer LUN representation: "
+ "0x%016" PRIx64 "\n", t10_2linux_lun(lun_arr));
+ else if (do_hex)
+ printf("Linux 'word flipped' integer LUN representation: 0x%"
+ PRIx64 "\n", t10_2linux_lun(lun_arr));
+ else
+ printf("Linux 'word flipped' integer LUN representation: %"
+ PRIu64 "\n", t10_2linux_lun(lun_arr));
+ }
+#endif
+ printf("Decoded LUN:\n");
+ decode_lun(" ", lun_arr, (lu_cong_arg % 2), do_hex, verbose);
+ return 0;
+ }
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ int err = -sg_fd;
+
+ pr2serr("open error: %s: %s\n", device_name, safe_strerror(err));
+ if ((! o_readonly) && ((err == EACCES) || (err == EROFS)))
+ pr2serr("Perhaps try again with --readonly option or with root "
+ "permissions\n");
+ return sg_convert_errno(-sg_fd);
+ }
+ if (decode_arg && (! lu_cong_arg_given)) {
+ if (verbose > 1)
+ pr2serr("in order to decode LUN and since --lu_cong not given, "
+ "do standard\nINQUIRY to find LU_CONG bit\n");
+ /* check if LU_CONG set in standard INQUIRY response */
+ res = sg_simple_inquiry(sg_fd, &sir, false, verbose);
+ ret = res;
+ if (res) {
+ pr2serr("fetching standard INQUIRY response failed\n");
+ goto the_end;
+ }
+ lu_cong_arg = !!(0x40 & sir.byte_1);
+ if (verbose && lu_cong_arg)
+ pr2serr("LU_CONG bit set in standard INQUIRY response\n");
+ }
+
+ if (0 == maxlen)
+ maxlen = DEF_RLUNS_BUFF_LEN;
+ reportLunsBuff = (uint8_t *)sg_memalign(maxlen, 0, &free_reportLunsBuff,
+ verbose > 3);
+ if (NULL == reportLunsBuff) {
+ pr2serr("unable to sg_memalign %d bytes\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ trunc = false;
+
+ res = sg_ll_report_luns(sg_fd, select_rep, reportLunsBuff, maxlen, true,
+ verbose);
+ ret = res;
+ if (0 == res) {
+ list_len = sg_get_unaligned_be32(reportLunsBuff + 0);
+ len_cap = list_len + 8;
+ if (len_cap > maxlen)
+ len_cap = maxlen;
+ if (do_raw) {
+ dStrRaw((const char *)reportLunsBuff, len_cap);
+ goto the_end;
+ }
+ if (1 == do_hex) {
+ hex2stdout(reportLunsBuff, len_cap, 1);
+ goto the_end;
+ }
+ luns = (list_len / 8);
+ if (! do_quiet)
+ printf("Lun list length = %d which imples %d lun entr%s\n",
+ list_len, luns, ((1 == luns) ? "y" : "ies"));
+ if ((list_len + 8) > maxlen) {
+ luns = ((maxlen - 8) / 8);
+ trunc = true;
+ pr2serr(" <<too many luns for internal buffer, will show %d "
+ "lun%s>>\n", luns, ((1 == luns) ? "" : "s"));
+ }
+ if (verbose > 1) {
+ pr2serr("\nOutput response in hex\n");
+ hex2stderr(reportLunsBuff, (trunc ? maxlen : list_len + 8), 1);
+ }
+ for (k = 0, off = 8; k < luns; ++k, off += 8) {
+ if (! do_quiet) {
+ if (0 == k)
+ printf("Report luns [select_report=0x%x]:\n", select_rep);
+ printf(" ");
+ }
+ for (m = 0; m < 8; ++m)
+ printf("%02x", reportLunsBuff[off + m]);
+#ifdef SG_LIB_LINUX
+ if (do_linux) {
+ uint64_t lin_lun;
+
+ lin_lun = t10_2linux_lun(reportLunsBuff + off);
+ if (do_hex > 1)
+ printf(" [0x%" PRIx64 "]", lin_lun);
+ else
+ printf(" [%" PRIu64 "]", lin_lun);
+ }
+#endif
+ printf("\n");
+ if (decode_arg)
+ decode_lun(" ", reportLunsBuff + off,
+ (bool)(lu_cong_arg % 2), do_hex, verbose);
+ }
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Report Luns command not supported (support mandatory in "
+ "SPC-3)\n");
+ else if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("Report Luns, aborted command\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("Report Luns command has bad field in cdb\n");
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Report Luns command: %s\n", b);
+ }
+
+the_end:
+ if (free_reportLunsBuff)
+ free(free_reportLunsBuff);
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ return sg_convert_errno(-res);
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_luns failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_map.c b/src/sg_map.c
new file mode 100644
index 00000000..ebf2c2e4
--- /dev/null
+++ b/src/sg_map.c
@@ -0,0 +1,508 @@
+/*
+ * Utility program for the Linux OS SCSI generic ("sg") device driver.
+ * Copyright (C) 2000-2017 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ This shows the mapping from "sg" devices to other scsi devices
+ (i.e. sd, scd or st) if any.
+
+ Note: This program requires sg version 2 or better.
+
+ Version 0.19 20041203
+
+ Version 1.02 20050511
+ - allow for sparse disk name with up to 3 letter SCSI
+ disk device node names (e.g. /dev/sdaaa)
+ [Nate Dailey < Nate dot Dailey at stratus dot com >]
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+
+
+static const char * version_str = "1.12 20171010";
+
+static const char * devfs_id = "/dev/.devfsd";
+
+#define NUMERIC_SCAN_DEF true /* set to false to make alpha scan default */
+
+#define INQUIRY_RESP_INITIAL_LEN 36
+#define MAX_SG_DEVS 4096
+#define PRESENT_ARRAY_SIZE MAX_SG_DEVS
+
+static const char * sysfs_sg_dir = "/sys/class/scsi_generic";
+static char gen_index_arr[PRESENT_ARRAY_SIZE];
+static int has_sysfs_sg = 0;
+
+
+typedef struct my_map_info
+{
+ int active;
+ int lin_dev_type;
+ int oth_dev_num;
+ struct sg_scsi_id sg_dat;
+ char vendor[8];
+ char product[16];
+ char revision[4];
+} my_map_info_t;
+
+
+#define MAX_SD_DEVS (26 + 26*26 + 26*26*26) /* sdX, sdXX, sdXXX */
+ /* (26 + 676 + 17576) = 18278 */
+#define MAX_SR_DEVS 128
+#define MAX_ST_DEVS 128
+#define MAX_OSST_DEVS 128
+#define MAX_ERRORS 5
+
+static my_map_info_t map_arr[MAX_SG_DEVS];
+
+#define LIN_DEV_TYPE_UNKNOWN 0
+#define LIN_DEV_TYPE_SD 1
+#define LIN_DEV_TYPE_SR 2
+#define LIN_DEV_TYPE_ST 3
+#define LIN_DEV_TYPE_SCD 4
+#define LIN_DEV_TYPE_OSST 5
+
+
+typedef struct my_scsi_idlun {
+/* why can't userland see this structure ??? */
+ int dev_id;
+ int host_unique_id;
+} My_scsi_idlun;
+
+
+#define EBUFF_SZ 256
+static char ebuff[EBUFF_SZ];
+
+static void scan_dev_type(const char * leadin, int max_dev, bool do_numeric,
+ int lin_dev_type, int last_sg_ind);
+
+static void usage()
+{
+ printf("Usage: sg_map [-a] [-h] [-i] [-n] [-sd] [-scd or -sr] [-st] "
+ "[-V] [-x]\n");
+ printf(" where:\n");
+ printf(" -a do alphabetic scan (ie sga, sgb, sgc)\n");
+ printf(" -h or -? show this usage message then exit\n");
+ printf(" -i also show device INQUIRY strings\n");
+ printf(" -n do numeric scan (i.e. sg0, sg1, sg2) "
+ "(default)\n");
+ printf(" -sd show mapping to disks\n");
+ printf(" -scd show mapping to cdroms (look for /dev/scd<n>\n");
+ printf(" -sr show mapping to cdroms (look for /dev/sr<n>\n");
+ printf(" -st show mapping to tapes (st and osst devices)\n");
+ printf(" -V print version string then exit\n");
+ printf(" -x also show bus,chan,id,lun and type\n\n");
+ printf("If no '-s*' arguments given then show all mappings. This "
+ "utility\nis DEPRECATED, do not use in Linux 2.6 series or "
+ "later.\n");
+}
+
+static int scandir_select(const struct dirent * s)
+{
+ int k;
+
+ if (1 == sscanf(s->d_name, "sg%d", &k)) {
+ if ((k >= 0) && (k < PRESENT_ARRAY_SIZE)) {
+ gen_index_arr[k] = 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int sysfs_sg_scan(const char * dir_name)
+{
+ struct dirent ** namelist;
+ int num, k;
+
+ num = scandir(dir_name, &namelist, scandir_select, NULL);
+ if (num < 0)
+ return -errno;
+ for (k = 0; k < num; ++k)
+ free(namelist[k]);
+ free(namelist);
+ return num;
+}
+
+static void make_dev_name(char * fname, const char * leadin, int k,
+ bool do_numeric)
+{
+ char buff[64];
+ int ones,tens,hundreds; /* for lack of a better name */
+ int buff_idx;
+
+ strcpy(fname, leadin ? leadin : "/dev/sg");
+ if (do_numeric) {
+ sprintf(buff, "%d", k);
+ strcat(fname, buff);
+ }
+ else if (k >= (26 + 26*26 + 26*26*26)) {
+ strcat(fname, "xxxx");
+ }
+ else {
+ ones = k % 26;
+
+ if ((k - 26) >= 0)
+ tens = ((k-26)/26) % 26;
+ else tens = -1;
+
+ if ((k - (26 + 26*26)) >= 0)
+ hundreds = ((k - (26 + 26*26))/(26*26)) % 26;
+ else hundreds = -1;
+
+ buff_idx = 0;
+ if (hundreds >= 0) buff[buff_idx++] = 'a' + (char)hundreds;
+ if (tens >= 0) buff[buff_idx++] = 'a' + (char)tens;
+ buff[buff_idx++] = 'a' + (char)ones;
+ buff[buff_idx] = '\0';
+ strcat(fname, buff);
+ }
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool do_all_s = true;
+ bool do_extra = false;
+ bool do_inquiry = false;
+ bool do_numeric = NUMERIC_SCAN_DEF;
+ bool do_osst = false;
+ bool do_scd = false;
+ bool do_sd = false;
+ bool do_sr = false;
+ bool do_st = false;
+ bool eacces_err = false;
+ int sg_fd, res, k;
+ int num_errors = 0;
+ int num_silent = 0;
+ int last_sg_ind = -1;
+ char fname[64];
+ struct stat a_stat;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == strcmp("-n", argv[k]))
+ do_numeric = true;
+ else if (0 == strcmp("-a", argv[k]))
+ do_numeric = false;
+ else if (0 == strcmp("-x", argv[k]))
+ do_extra = true;
+ else if (0 == strcmp("-i", argv[k]))
+ do_inquiry = true;
+ else if (0 == strcmp("-sd", argv[k])) {
+ do_sd = true;
+ do_all_s = false;
+ } else if (0 == strcmp("-st", argv[k])) {
+ do_st = true;
+ do_osst = true;
+ do_all_s = false;
+ } else if (0 == strcmp("-sr", argv[k])) {
+ do_sr = true;
+ do_all_s = false;
+ } else if (0 == strcmp("-scd", argv[k])) {
+ do_scd = true;
+ do_all_s = false;
+ } else if (0 == strcmp("-V", argv[k])) {
+ fprintf(stderr, "Version string: %s\n", version_str);
+ exit(0);
+ } else if ((0 == strcmp("-?", argv[k])) ||
+ (0 == strncmp("-h", argv[k], 2))) {
+ printf(
+ "Show mapping from sg devices to other scsi device names\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (*argv[k] == '-') {
+ printf("Unknown switch: %s\n", argv[k]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (*argv[k] != '-') {
+ printf("Unknown argument\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if ((stat(sysfs_sg_dir, &a_stat) >= 0) && (S_ISDIR(a_stat.st_mode)))
+ has_sysfs_sg = sysfs_sg_scan(sysfs_sg_dir);
+
+ if (stat(devfs_id, &a_stat) == 0)
+ printf("# Note: the devfs pseudo file system is present\n");
+
+ for (k = 0, res = 0; (k < MAX_SG_DEVS) && (num_errors < MAX_ERRORS);
+ ++k, res = (sg_fd >= 0) ? close(sg_fd) : 0) {
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, "Error closing %s ", fname);
+ perror("sg_map: close error");
+ return SG_LIB_FILE_ERROR;
+ }
+ if (has_sysfs_sg) {
+ if (0 == gen_index_arr[k]) {
+ sg_fd = -1;
+ continue;
+ }
+ make_dev_name(fname, "/dev/sg", k, true);
+ } else
+ make_dev_name(fname, "/dev/sg", k, do_numeric);
+
+ sg_fd = open(fname, O_RDONLY | O_NONBLOCK);
+ if (sg_fd < 0) {
+ if (EBUSY == errno) {
+ map_arr[k].active = -2;
+ continue;
+ }
+ else if ((ENODEV == errno) || (ENOENT == errno) ||
+ (ENXIO == errno)) {
+ ++num_errors;
+ ++num_silent;
+ map_arr[k].active = -1;
+ continue;
+ }
+ else {
+ if (EACCES == errno)
+ eacces_err = true;
+ snprintf(ebuff, EBUFF_SZ, "Error opening %s ", fname);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ }
+ res = ioctl(sg_fd, SG_GET_SCSI_ID, &map_arr[k].sg_dat);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "device %s failed on sg ioctl, skip", fname);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ if (do_inquiry) {
+ char buff[INQUIRY_RESP_INITIAL_LEN];
+
+ if (0 == sg_ll_inquiry(sg_fd, false, false, 0, buff, sizeof(buff),
+ true, 0)) {
+ memcpy(map_arr[k].vendor, &buff[8], 8);
+ memcpy(map_arr[k].product, &buff[16], 16);
+ memcpy(map_arr[k].revision, &buff[32], 4);
+ }
+ }
+ map_arr[k].active = 1;
+ map_arr[k].oth_dev_num = -1;
+ last_sg_ind = k;
+ }
+ if ((num_errors >= MAX_ERRORS) && (num_silent < num_errors)) {
+ printf("Stopping because there are too many error\n");
+ if (eacces_err)
+ printf(" root access may be required\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ if (last_sg_ind < 0) {
+ printf("Stopping because no sg devices found\n");
+ }
+
+ if (do_all_s || do_sd)
+ scan_dev_type("/dev/sd", MAX_SD_DEVS, 0, LIN_DEV_TYPE_SD, last_sg_ind);
+ if (do_all_s || do_sr)
+ scan_dev_type("/dev/sr", MAX_SR_DEVS, 1, LIN_DEV_TYPE_SR, last_sg_ind);
+ if (do_all_s || do_scd)
+ scan_dev_type("/dev/scd", MAX_SR_DEVS, 1, LIN_DEV_TYPE_SCD,
+ last_sg_ind);
+ if (do_all_s || do_st)
+ scan_dev_type("/dev/nst", MAX_ST_DEVS, 1, LIN_DEV_TYPE_ST,
+ last_sg_ind);
+ if (do_all_s || do_osst)
+ scan_dev_type("/dev/osst", MAX_OSST_DEVS, 1, LIN_DEV_TYPE_OSST,
+ last_sg_ind);
+
+ for (k = 0; k <= last_sg_ind; ++k) {
+ if (has_sysfs_sg) {
+ if (0 == gen_index_arr[k]) {
+ continue;
+ }
+ make_dev_name(fname, "/dev/sg", k, true);
+ } else
+ make_dev_name(fname, "/dev/sg", k, do_numeric);
+ printf("%s", fname);
+ switch (map_arr[k].active)
+ {
+ case -2:
+ printf(do_extra ? " -2 -2 -2 -2 -2" : " busy");
+ break;
+ case -1:
+ printf(do_extra ? " -1 -1 -1 -1 -1" : " not present");
+ break;
+ case 0:
+ printf(do_extra ? " -3 -3 -3 -3 -3" : " some error");
+ break;
+ case 1:
+ if (do_extra)
+ printf(" %d %d %d %d %d", map_arr[k].sg_dat.host_no,
+ map_arr[k].sg_dat.channel, map_arr[k].sg_dat.scsi_id,
+ map_arr[k].sg_dat.lun, map_arr[k].sg_dat.scsi_type);
+ switch (map_arr[k].lin_dev_type)
+ {
+ case LIN_DEV_TYPE_SD:
+ make_dev_name(fname, "/dev/sd" , map_arr[k].oth_dev_num, 0);
+ printf(" %s", fname);
+ break;
+ case LIN_DEV_TYPE_ST:
+ make_dev_name(fname, "/dev/nst" , map_arr[k].oth_dev_num, 1);
+ printf(" %s", fname);
+ break;
+ case LIN_DEV_TYPE_OSST:
+ make_dev_name(fname, "/dev/osst" , map_arr[k].oth_dev_num, 1);
+ printf(" %s", fname);
+ break;
+ case LIN_DEV_TYPE_SR:
+ make_dev_name(fname, "/dev/sr" , map_arr[k].oth_dev_num, 1);
+ printf(" %s", fname);
+ break;
+ case LIN_DEV_TYPE_SCD:
+ make_dev_name(fname, "/dev/scd" , map_arr[k].oth_dev_num, 1);
+ printf(" %s", fname);
+ break;
+ default:
+ break;
+ }
+ if (do_inquiry)
+ printf(" %.8s %.16s %.4s", map_arr[k].vendor,
+ map_arr[k].product, map_arr[k].revision);
+ break;
+ default:
+ printf(" bad logic\n");
+ break;
+ }
+ printf("\n");
+ }
+ return 0;
+}
+
+static int find_dev_in_sg_arr(My_scsi_idlun * my_idlun, int host_no,
+ int last_sg_ind)
+{
+ int k;
+ struct sg_scsi_id * sidp;
+
+ for (k = 0; k <= last_sg_ind; ++k) {
+ sidp = &(map_arr[k].sg_dat);
+ if ((host_no == sidp->host_no) &&
+ ((my_idlun->dev_id & 0xff) == sidp->scsi_id) &&
+ (((my_idlun->dev_id >> 8) & 0xff) == sidp->lun) &&
+ (((my_idlun->dev_id >> 16) & 0xff) == sidp->channel))
+ return k;
+ }
+ return -1;
+}
+
+static void scan_dev_type(const char * leadin, int max_dev, bool do_numeric,
+ int lin_dev_type, int last_sg_ind)
+{
+ int k, res, ind, sg_fd = 0;
+ int num_errors = 0;
+ int num_silent = 0;
+ int host_no = -1;
+ My_scsi_idlun my_idlun;
+ char fname[64];
+
+ for (k = 0, res = 0; (k < max_dev) && (num_errors < MAX_ERRORS);
+ ++k, res = (sg_fd >= 0) ? close(sg_fd) : 0) {
+
+/* ignore close() errors */
+#if 0
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, "Error closing %s ", fname);
+ perror("sg_map: close error");
+#ifndef IGN_CLOSE_ERR
+ return;
+#else
+ ++num_errors;
+ sg_fd = 0;
+#endif
+ }
+#endif
+ make_dev_name(fname, leadin, k, do_numeric);
+#ifdef DEBUG
+ printf ("Trying %s: ", fname);
+#endif
+
+ sg_fd = open(fname, O_RDONLY | O_NONBLOCK);
+ if (sg_fd < 0) {
+#ifdef DEBUG
+ printf ("ERROR %i\n", errno);
+#endif
+ if (EBUSY == errno) {
+ printf("Device %s is busy\n", fname);
+ ++num_errors;
+ } else if ((ENODEV == errno) || (ENXIO == errno)) {
+ ++num_errors;
+ ++num_silent;
+ } else if (ENOENT != errno) { /* ignore ENOENT for sparse names */
+ snprintf(ebuff, EBUFF_SZ, "Error opening %s ", fname);
+ perror(ebuff);
+ ++num_errors;
+ }
+ continue;
+ }
+
+ res = ioctl(sg_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "device %s failed on scsi ioctl(idlun), skip", fname);
+ perror(ebuff);
+ ++num_errors;
+#ifdef DEBUG
+ printf ("Couldn't get IDLUN!\n");
+#endif
+ continue;
+ }
+ res = ioctl(sg_fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "device %s failed on scsi ioctl(bus_number), skip", fname);
+ perror(ebuff);
+ ++num_errors;
+#ifdef DEBUG
+ printf ("Couldn't get BUS!\n");
+#endif
+ continue;
+ }
+#ifdef DEBUG
+ printf ("%i(%x) %i %i %i %i\n", host_no, my_idlun.host_unique_id,
+ (my_idlun.dev_id>>24)&0xff, (my_idlun.dev_id>>16)&0xff,
+ (my_idlun.dev_id>>8)&0xff, my_idlun.dev_id&0xff);
+#endif
+ ind = find_dev_in_sg_arr(&my_idlun, host_no, last_sg_ind);
+ if (ind >= 0) {
+ map_arr[ind].oth_dev_num = k;
+ map_arr[ind].lin_dev_type = lin_dev_type;
+ }
+ else
+ printf("Strange, could not find device %s mapped to sg device??\n",
+ fname);
+ }
+}
diff --git a/src/sg_map26.c b/src/sg_map26.c
new file mode 100644
index 00000000..2ea8d691
--- /dev/null
+++ b/src/sg_map26.c
@@ -0,0 +1,1285 @@
+/*
+ * Copyright (c) 2005-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program maps a primary SCSI device node name to the corresponding
+ * SCSI generic device node name (or vice versa). Targets Linux
+ * kernel 2.6, 3 and 4 series. Sysfs device names can also be mapped.
+ */
+
+/* #define _XOPEN_SOURCE 500 */
+/* needed to see DT_REG and friends when compiled with: c99 pedantic */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h> /* new location for major + minor */
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <linux/major.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+
+static const char * version_str = "1.19 20220117";
+
+#define ME "sg_map26: "
+
+#define NT_NO_MATCH 0
+#define NT_SD 1
+#define NT_SR 2
+#define NT_HD 3
+#define NT_ST 4
+#define NT_OSST 5
+#define NT_SG 6
+#define NT_CH 7
+#define NT_REG 8
+#define NT_DIR 9
+
+#define NAME_LEN_MAX 256
+#define D_NAME_LEN_MAX 520
+
+#ifndef SCSI_CHANGER_MAJOR
+#define SCSI_CHANGER_MAJOR 86
+#endif
+#ifndef OSST_MAJOR
+#define OSST_MAJOR 206
+#endif
+
+/* scandir() and stat() categories */
+#define FT_OTHER 0
+#define FT_REGULAR 1
+#define FT_BLOCK 2
+#define FT_CHAR 3
+#define FT_DIR 4
+
+/* older major.h headers may not have these */
+#ifndef SCSI_DISK8_MAJOR
+#define SCSI_DISK8_MAJOR 128
+#define SCSI_DISK9_MAJOR 129
+#define SCSI_DISK10_MAJOR 130
+#define SCSI_DISK11_MAJOR 131
+#define SCSI_DISK12_MAJOR 132
+#define SCSI_DISK13_MAJOR 133
+#define SCSI_DISK14_MAJOR 134
+#define SCSI_DISK15_MAJOR 135
+#endif
+
+/* st minor decodes from Kai Makisara 20081008 */
+#define ST_NBR_MODE_BITS 2
+#define ST_MODE_SHIFT (7 - ST_NBR_MODE_BITS)
+#define TAPE_NR(minor) ( (((minor) & ~255) >> (ST_NBR_MODE_BITS + 1)) | \
+ ((minor) & ~(UINT_MAX << ST_MODE_SHIFT)) )
+
+static const char * sys_sg_dir = "/sys/class/scsi_generic/";
+static const char * sys_sd_dir = "/sys/block/";
+static const char * sys_sr_dir = "/sys/block/";
+static const char * sys_hd_dir = "/sys/block/";
+static const char * sys_st_dir = "/sys/class/scsi_tape/";
+static const char * sys_sch_dir = "/sys/class/scsi_changer/";
+static const char * sys_osst_dir = "/sys/class/onstream_tape/";
+static const char * def_dev_dir = "/dev";
+
+
+static struct option long_options[] = {
+ {"dev_dir", required_argument, 0, 'd'},
+ {"given_is", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"result", required_argument, 0, 'r'},
+ {"symlink", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static const char * nt_names[] = {
+ "No matching",
+ "disk",
+ "cd/dvd",
+ "hd",
+ "tape",
+ "tape (osst)",
+ "generic (sg)",
+ "changer",
+ "regular file",
+ "directory",
+};
+
+#if defined(__GNUC__) || defined(__clang__)
+static int pr2serr(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#else
+static int pr2serr(const char * fmt, ...);
+#endif
+
+
+static int
+pr2serr(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_map26 [--dev_dir=DIR] [--given_is=0...1] [--help] "
+ "[--result=0...3]\n"
+ " [--symlink] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --dev_dir=DIR | -d DIR search in DIR for "
+ "resulting special\n"
+ " (def: directory of DEVICE "
+ "or '/dev')\n"
+ " --given_is=0...1 | -g 0...1 variety of given "
+ "DEVICE\n"
+ " 0->block or char special "
+ "(or symlink to)\n"
+ " 1->sysfs device, 'dev' or "
+ "parent\n"
+ " --help | -h print out usage message\n"
+ " --result=0...3 | -r 0...3 variety of file(s) to "
+ "find\n"
+ " 0->mapped block or char "
+ "special(def)\n"
+ " 1->mapped sysfs path\n"
+ " 2->matching block or "
+ "char special\n"
+ " 3->matching sysfs "
+ "path\n"
+ " --symlink | -s symlinks to special included in "
+ "result\n"
+ " --verbose | -v increase verbosity of output\n"
+ " --version | -V print version string and exit\n\n"
+ "Maps SCSI device node to corresponding generic node (and "
+ "vv)\n"
+ );
+}
+
+
+/* ssafe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
+ Allows for situation in which strerror() is given a wild value (or the
+ C library is incomplete) and returns NULL. Still not thread safe.
+ */
+
+static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
+ 'e', 'r', 'r', 'n', 'o', ':', ' ', 0};
+
+static char *
+ssafe_strerror(int errnum)
+{
+ size_t len;
+ char * errstr;
+
+ errstr = strerror(errnum);
+ if (NULL == errstr) {
+ len = strlen(safe_errbuf);
+ snprintf(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i",
+ errnum);
+ safe_errbuf[sizeof(safe_errbuf) - 1] = '\0'; /* bombproof */
+ return safe_errbuf;
+ }
+ return errstr;
+}
+
+static int
+nt_typ_from_filename(const char * filename, int * majj, int * minn)
+{
+ struct stat st;
+ int ma, mi;
+
+ if (stat(filename, &st) < 0)
+ return -errno;
+ ma = major(st.st_rdev);
+ mi = minor(st.st_rdev);
+ if (majj)
+ *majj = ma;
+ if (minn)
+ *minn = mi;
+ if (S_ISCHR(st.st_mode)) {
+ switch(ma) {
+ case OSST_MAJOR:
+ return NT_OSST;
+ case SCSI_GENERIC_MAJOR:
+ return NT_SG;
+ case SCSI_TAPE_MAJOR:
+ return NT_ST;
+ case SCSI_CHANGER_MAJOR:
+ return NT_CH;
+ default:
+ return NT_NO_MATCH;
+ }
+ } else if (S_ISBLK(st.st_mode)) {
+ switch(ma) {
+ case SCSI_DISK0_MAJOR: case SCSI_DISK1_MAJOR:
+ case SCSI_DISK2_MAJOR: case SCSI_DISK3_MAJOR:
+ case SCSI_DISK4_MAJOR: case SCSI_DISK5_MAJOR:
+ case SCSI_DISK6_MAJOR: case SCSI_DISK7_MAJOR:
+ case SCSI_DISK8_MAJOR: case SCSI_DISK9_MAJOR:
+ case SCSI_DISK10_MAJOR: case SCSI_DISK11_MAJOR:
+ case SCSI_DISK12_MAJOR: case SCSI_DISK13_MAJOR:
+ case SCSI_DISK14_MAJOR: case SCSI_DISK15_MAJOR:
+ return NT_SD;
+ case SCSI_CDROM_MAJOR:
+ return NT_SR;
+ case IDE0_MAJOR: case IDE1_MAJOR:
+ case IDE2_MAJOR: case IDE3_MAJOR:
+ case IDE4_MAJOR: case IDE5_MAJOR:
+ case IDE6_MAJOR: case IDE7_MAJOR:
+ case IDE8_MAJOR: case IDE9_MAJOR:
+ return NT_HD;
+ default:
+ return NT_NO_MATCH;
+ }
+ } else if (S_ISREG(st.st_mode))
+ return NT_REG;
+ else if (S_ISDIR(st.st_mode))
+ return NT_DIR;
+ return NT_NO_MATCH;
+}
+
+static int
+nt_typ_from_major(int ma)
+{
+ switch(ma) {
+ case SCSI_DISK0_MAJOR: case SCSI_DISK1_MAJOR:
+ case SCSI_DISK2_MAJOR: case SCSI_DISK3_MAJOR:
+ case SCSI_DISK4_MAJOR: case SCSI_DISK5_MAJOR:
+ case SCSI_DISK6_MAJOR: case SCSI_DISK7_MAJOR:
+ case SCSI_DISK8_MAJOR: case SCSI_DISK9_MAJOR:
+ case SCSI_DISK10_MAJOR: case SCSI_DISK11_MAJOR:
+ case SCSI_DISK12_MAJOR: case SCSI_DISK13_MAJOR:
+ case SCSI_DISK14_MAJOR: case SCSI_DISK15_MAJOR:
+ return NT_SD;
+ case SCSI_CDROM_MAJOR:
+ return NT_SR;
+ case IDE0_MAJOR: case IDE1_MAJOR:
+ case IDE2_MAJOR: case IDE3_MAJOR:
+ case IDE4_MAJOR: case IDE5_MAJOR:
+ case IDE6_MAJOR: case IDE7_MAJOR:
+ case IDE8_MAJOR: case IDE9_MAJOR:
+ return NT_HD;
+ case OSST_MAJOR:
+ return NT_OSST;
+ case SCSI_GENERIC_MAJOR:
+ return NT_SG;
+ case SCSI_TAPE_MAJOR:
+ return NT_ST;
+ case SCSI_CHANGER_MAJOR:
+ return NT_CH;
+ default:
+ return NT_NO_MATCH;
+ }
+ return NT_NO_MATCH;
+}
+
+
+struct node_match_item {
+ bool follow_symlink;
+ int file_type;
+ int majj;
+ int minn;
+ char dir_name[D_NAME_LEN_MAX];
+};
+
+static struct node_match_item nd_match;
+
+static int
+nd_match_scandir_select(const struct dirent * s)
+{
+ bool symlnk = false;
+ struct stat st;
+ char name[D_NAME_LEN_MAX];
+
+ switch (s->d_type) {
+ case DT_BLK:
+ if (FT_BLOCK != nd_match.file_type)
+ return 0;
+ break;
+ case DT_CHR:
+ if (FT_CHAR != nd_match.file_type)
+ return 0;
+ break;
+ case DT_DIR:
+ return (FT_DIR == nd_match.file_type) ? 1 : 0;
+ case DT_REG:
+ return (FT_REGULAR == nd_match.file_type) ? 1 : 0;
+ case DT_LNK: /* follow symlinks */
+ if (! nd_match.follow_symlink)
+ return 0;
+ symlnk = true;
+ break;
+ default:
+ return 0;
+ }
+ if ((! symlnk) && (-1 == nd_match.majj) && (-1 == nd_match.minn))
+ return 1;
+ snprintf(name, sizeof(name), "%.*s/%.*s", NAME_LEN_MAX,
+ nd_match.dir_name, NAME_LEN_MAX, s->d_name);
+ memset(&st, 0, sizeof(st));
+ if (stat(name, &st) < 0)
+ return 0;
+ if (symlnk) {
+ if (S_ISCHR(st.st_mode)) {
+ if (FT_CHAR != nd_match.file_type)
+ return 0;
+ } else if (S_ISBLK(st.st_mode)) {
+ if (FT_BLOCK != nd_match.file_type)
+ return 0;
+ } else
+ return 0;
+ }
+ return (((-1 == nd_match.majj) ||
+ ((unsigned)major(st.st_rdev) == (unsigned)nd_match.majj)) &&
+ ((-1 == nd_match.minn) ||
+ ((unsigned)minor(st.st_rdev) == (unsigned)nd_match.minn)))
+ ? 1 : 0;
+}
+
+static int
+list_matching_nodes(const char * dir_name, int file_type, int majj, int minn,
+ bool follow_symlink, int verbose)
+{
+ struct dirent ** namelist;
+ int num, k;
+
+ strncpy(nd_match.dir_name, dir_name, D_NAME_LEN_MAX - 1);
+ nd_match.file_type = file_type;
+ nd_match.majj = majj;
+ nd_match.minn = minn;
+ nd_match.follow_symlink = follow_symlink;
+ num = scandir(dir_name, &namelist, nd_match_scandir_select, NULL);
+ if (num < 0) {
+ if (verbose)
+ pr2serr("scandir: %s %s\n", dir_name,
+ ssafe_strerror(errno));
+ return -errno;
+ }
+ for (k = 0; k < num; ++k) {
+ printf("%s/%s\n", dir_name, namelist[k]->d_name);
+ free(namelist[k]);
+ }
+ free(namelist);
+ return num;
+}
+
+struct sg_item_t {
+ char name[NAME_LEN_MAX + 2];
+ int ft;
+ int nt;
+ int d_type;
+};
+
+static struct sg_item_t for_first;
+
+static int
+first_scandir_select(const struct dirent * s)
+{
+ if (FT_OTHER != for_first.ft)
+ return 0;
+ if ((DT_LNK != s->d_type) &&
+ ((DT_DIR != s->d_type) || ('.' == s->d_name[0])))
+ return 0;
+ strncpy(for_first.name, s->d_name, NAME_LEN_MAX);
+ for_first.ft = FT_CHAR; /* dummy */
+ for_first.d_type = s->d_type;
+ return 1;
+}
+
+/* scan for directory entry that is either a symlink or a directory */
+static int
+scan_for_first(const char * dir_name, int verbose)
+{
+ char name[NAME_LEN_MAX];
+ struct dirent ** namelist;
+ int num, k;
+
+ for_first.ft = FT_OTHER;
+ num = scandir(dir_name, &namelist, first_scandir_select, NULL);
+ if (num < 0) {
+ if (verbose > 0) {
+ snprintf(name, NAME_LEN_MAX, "scandir: %s", dir_name);
+ perror(name);
+ }
+ return -1;
+ }
+ for (k = 0; k < num; ++k)
+ free(namelist[k]);
+ free(namelist);
+ return num;
+}
+
+static struct sg_item_t from_sg;
+
+static int
+from_sg_scandir_select(const struct dirent * s)
+{
+ int len;
+
+ if (FT_OTHER != from_sg.ft)
+ return 0;
+ if ((DT_LNK != s->d_type) &&
+ ((DT_DIR != s->d_type) || ('.' == s->d_name[0])))
+ return 0;
+ from_sg.d_type = s->d_type;
+ if (0 == strncmp("scsi_changer", s->d_name, 12)) {
+ strncpy(from_sg.name, s->d_name, NAME_LEN_MAX);
+ from_sg.ft = FT_CHAR;
+ from_sg.nt = NT_CH;
+ return 1;
+ } else if (0 == strncmp("block", s->d_name, 5)) {
+ strncpy(from_sg.name, s->d_name, NAME_LEN_MAX);
+ from_sg.ft = FT_BLOCK;
+ return 1;
+ } else if (0 == strcmp("tape", s->d_name)) {
+ strcpy(from_sg.name, s->d_name);
+ from_sg.ft = FT_CHAR;
+ from_sg.nt = NT_ST;
+ return 1;
+ } else if (0 == strncmp("scsi_tape:st", s->d_name, 12)) {
+ len = strlen(s->d_name);
+ if (isdigit(s->d_name[len - 1])) {
+ /* want 'st<num>' symlink only */
+ strcpy(from_sg.name, s->d_name);
+ from_sg.ft = FT_CHAR;
+ from_sg.nt = NT_ST;
+ return 1;
+ } else
+ return 0;
+ } else if (0 == strncmp("onstream_tape:os", s->d_name, 16)) {
+ strcpy(from_sg.name, s->d_name);
+ from_sg.ft = FT_CHAR;
+ from_sg.nt = NT_OSST;
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+from_sg_scan(const char * dir_name, int verbose)
+{
+ struct dirent ** namelist;
+ int num, k;
+
+ from_sg.ft = FT_OTHER;
+ from_sg.nt = NT_NO_MATCH;
+ num = scandir(dir_name, &namelist, from_sg_scandir_select, NULL);
+ if (num < 0) {
+ if (verbose)
+ pr2serr("scandir: %s %s\n", dir_name,
+ ssafe_strerror(errno));
+ return -errno;
+ }
+ if (verbose) {
+ for (k = 0; k < num; ++k)
+ pr2serr(" %s/%s\n", dir_name,
+ namelist[k]->d_name);
+ }
+ for (k = 0; k < num; ++k)
+ free(namelist[k]);
+ free(namelist);
+ return num;
+}
+
+static struct sg_item_t to_sg;
+
+static int
+to_sg_scandir_select(const struct dirent * s)
+{
+ if (FT_OTHER != to_sg.ft)
+ return 0;
+ if (DT_LNK != s->d_type)
+ return 0;
+ if (0 == strncmp("scsi_generic", s->d_name, 12)) {
+ strncpy(to_sg.name, s->d_name, NAME_LEN_MAX);
+ to_sg.ft = FT_CHAR;
+ to_sg.nt = NT_SG;
+ return 1;
+ } else
+ return 0;
+}
+
+static int
+to_sg_scan(const char * dir_name)
+{
+ struct dirent ** namelist;
+ int num, k;
+
+ to_sg.ft = FT_OTHER;
+ to_sg.nt = NT_NO_MATCH;
+ num = scandir(dir_name, &namelist, to_sg_scandir_select, NULL);
+ if (num < 0)
+ return -errno;
+ for (k = 0; k < num; ++k)
+ free(namelist[k]);
+ free(namelist);
+ return num;
+}
+
+/* Return 1 if directory, else 0 */
+static int
+if_directory_chdir(const char * dir_name, const char * base_name)
+{
+ char buff[D_NAME_LEN_MAX];
+ struct stat a_stat;
+
+ strcpy(buff, dir_name);
+ strcat(buff, "/");
+ strcat(buff, base_name);
+ if (stat(buff, &a_stat) < 0)
+ return 0;
+ if (S_ISDIR(a_stat.st_mode)) {
+ if (chdir(buff) < 0)
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* Return 1 if directory, else 0 */
+static int
+if_directory_ch2generic(const char * dir_name)
+{
+ char buff[NAME_LEN_MAX];
+ struct stat a_stat;
+ const char * old_name = "generic";
+
+ strcpy(buff, dir_name);
+ strcat(buff, "/");
+ strcat(buff, old_name);
+ if ((stat(buff, &a_stat) >= 0) && S_ISDIR(a_stat.st_mode)) {
+ if (chdir(buff) < 0)
+ return 0;
+ return 1;
+ }
+ /* No "generic", so now look for "scsi_generic:sg<n>" */
+ if (1 != to_sg_scan(dir_name))
+ return 0;
+ strcpy(buff, dir_name);
+ strcat(buff, "/");
+ strcat(buff, to_sg.name);
+ if (stat(buff, &a_stat) < 0)
+ return 0;
+ if (S_ISDIR(a_stat.st_mode)) {
+ if (chdir(buff) < 0)
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+/* Return 1 if found, else 0 if problems */
+static int
+get_value(const char * dir_name, const char * base_name, char * value,
+ int max_value_len)
+{
+ char buff[D_NAME_LEN_MAX];
+ FILE * f;
+ int len;
+
+ if ((NULL == dir_name) && (NULL == base_name))
+ return 0;
+ if (dir_name) {
+ strcpy(buff, dir_name);
+ if (base_name && (strlen(base_name) > 0)) {
+ strcat(buff, "/");
+ strcat(buff, base_name);
+ }
+ } else
+ strcpy(buff, base_name);
+ if (NULL == (f = fopen(buff, "r"))) {
+ return 0;
+ }
+ if (NULL == fgets(value, max_value_len, f)) {
+ fclose(f);
+ return 0;
+ }
+ len = strlen(value);
+ if ((len > 0) && (value[len - 1] == '\n'))
+ value[len - 1] = '\0';
+ fclose(f);
+ return 1;
+}
+
+static int
+map_hd(const char * device_dir, int ma, int mi, int result,
+ bool follow_symlink, int verbose)
+{
+ char c, num;
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_BLOCK,
+ ma, mi, follow_symlink,
+ verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ switch (ma) {
+ case IDE0_MAJOR: c = 'a'; break;
+ case IDE1_MAJOR: c = 'c'; break;
+ case IDE2_MAJOR: c = 'e'; break;
+ case IDE3_MAJOR: c = 'g'; break;
+ case IDE4_MAJOR: c = 'i'; break;
+ case IDE5_MAJOR: c = 'k'; break;
+ case IDE6_MAJOR: c = 'm'; break;
+ case IDE7_MAJOR: c = 'o'; break;
+ case IDE8_MAJOR: c = 'q'; break;
+ case IDE9_MAJOR: c = 's'; break;
+ default: c = '?'; break;
+ }
+ if (mi > 63)
+ ++c;
+ printf("%shd%c\n", sys_hd_dir, c);
+ return 0;
+}
+
+static int
+map_sd(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int index, m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_BLOCK, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ if (SCSI_DISK0_MAJOR == ma)
+ index = mi / 16;
+ else if (ma >= SCSI_DISK8_MAJOR)
+ index = (mi / 16) + 128 +
+ ((ma - SCSI_DISK8_MAJOR) * 16);
+ else
+ index = (mi / 16) + 16 +
+ ((ma - SCSI_DISK1_MAJOR) * 16);
+ if (index < 26)
+ snprintf(name, sizeof(name), "%ssd%c",
+ sys_sd_dir, 'a' + index % 26);
+ else if (index < (26 + 1) * 26)
+ snprintf(name, sizeof(name), "%ssd%c%c",
+ sys_sd_dir,
+ 'a' + index / 26 - 1,'a' + index % 26);
+ else {
+ const unsigned int m1 = (index / 26 - 1) / 26 - 1;
+ const unsigned int m2 = (index / 26 - 1) % 26;
+ const unsigned int m3 = index % 26;
+
+ snprintf(name, sizeof(name), "%ssd%c%c%c",
+ sys_sd_dir, 'a' + m1, 'a' + m2, 'a' + m3);
+ }
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs sd dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if (if_directory_ch2generic(".")) {
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs generic dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("sd device: %s does not match any SCSI generic "
+ "device\n", device_name);
+ pr2serr(" perhaps sg module is not loaded\n");
+ return 1;
+ }
+}
+
+static int
+map_sr(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_BLOCK, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ snprintf(name, sizeof(name), "%ssr%d", sys_sr_dir, mi);
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs sr dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if (if_directory_ch2generic(".")) {
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs generic dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, FT_BLOCK, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("sr device: %s does not match any SCSI generic "
+ "device\n", device_name);
+ pr2serr(" perhaps sg module is not loaded\n");
+ return 1;
+ }
+}
+
+static int
+map_st(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ snprintf(name, sizeof(name), "%sst%d", sys_st_dir,
+ TAPE_NR(mi));
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs st dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if (if_directory_ch2generic(".")) {
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs generic dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("st device: %s does not match any SCSI generic "
+ "device\n", device_name);
+ pr2serr(" perhaps sg module is not loaded\n");
+ return 1;
+ }
+}
+
+static int
+map_osst(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ snprintf(name, sizeof(name), "%sosst%d", sys_osst_dir,
+ TAPE_NR(mi));
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs osst dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if (if_directory_ch2generic(".")) {
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs generic dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("osst device: %s does not match any SCSI generic "
+ "device\n", device_name);
+ pr2serr(" perhaps sg module is not loaded\n");
+ return 1;
+ }
+}
+
+static int
+map_ch(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ snprintf(name, sizeof(name), "%ssch%d", sys_sch_dir, mi);
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs sch dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if (if_directory_ch2generic(".")) {
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs generic dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, FT_CHAR, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("sch device: %s does not match any SCSI generic "
+ "device\n", device_name);
+ pr2serr(" perhaps sg module is not loaded\n");
+ return 1;
+ }
+}
+
+static int
+map_sg(const char * device_name, const char * device_dir, int ma, int mi,
+ int result, bool follow_symlink, int verbose)
+{
+ int m_mi, m_ma, num;
+ char value[D_NAME_LEN_MAX];
+ char name[D_NAME_LEN_MAX];
+
+ if (2 == result) {
+ num = list_matching_nodes(device_dir, FT_CHAR, ma, mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ }
+ snprintf(name, sizeof(name), "%ssg%d", sys_sg_dir, mi);
+ if (3 == result) {
+ printf("%s\n", name);
+ return 0;
+ }
+ if (! get_value(name, "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs match for device: %s\n",
+ device_name);
+ return 1;
+ }
+ if (verbose)
+ pr2serr("sysfs sg dev: %s\n", value);
+ if (! if_directory_chdir(name, "device")) {
+ pr2serr("sysfs problem with device: %s\n", device_name);
+ return 1;
+ }
+ if ((1 == from_sg_scan(".", verbose)) &&
+ (if_directory_chdir(".", from_sg.name))) {
+ if (DT_DIR == from_sg.d_type) {
+ if ((1 == scan_for_first(".", verbose)) &&
+ (if_directory_chdir(".", for_first.name))) {
+ ;
+ } else {
+ pr2serr("unexpected scan_for_first error\n");
+ }
+ }
+ if (1 == result) {
+ if (NULL == getcwd(value, sizeof(value)))
+ value[0] = '\0';
+ printf("%s\n", value);
+ return 0;
+ }
+ if (! get_value(".", "dev", value, sizeof(value))) {
+ pr2serr("Couldn't find sysfs block dev\n");
+ return 1;
+ }
+ if (verbose)
+ printf("matching dev: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &m_ma, &m_mi)) {
+ pr2serr("Couldn't decode mapped dev\n");
+ return 1;
+ }
+ num = list_matching_nodes(device_dir, from_sg.ft, m_ma, m_mi,
+ follow_symlink, verbose);
+ return (num > 0) ? 0 : 1;
+ } else {
+ pr2serr("sg device: %s does not match any other SCSI "
+ "device\n", device_name);
+ return 1;
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool cont;
+ int c, num, tt, res;
+ int given_is = -1;
+ int result = 0;
+ int verbose = 0;
+ int ret = 1;
+ int ma, mi;
+ bool do_dev_dir = false;
+ bool follow_symlink = false;
+ char device_name[D_NAME_LEN_MAX];
+ char device_dir[D_NAME_LEN_MAX];
+ char value[D_NAME_LEN_MAX];
+
+ memset(device_name, 0, sizeof(device_name));
+ memset(device_dir, 0, sizeof(device_dir));
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "d:hg:r:svV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ strncpy(device_dir, optarg, sizeof(device_dir) - 1);
+ do_dev_dir = true;
+ break;
+ case 'g':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && ((0 == res) || (1 == res)))
+ given_is = res;
+ else {
+ pr2serr("value for '--given_to=' must be 0 "
+ "or 1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'r':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && (res >= 0) && (res < 4))
+ result = res;
+ else {
+ pr2serr("value for '--result=' must be "
+ "0..3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ follow_symlink = true;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if ('\0' == device_name[0]) {
+ strncpy(device_name, argv[optind],
+ sizeof(device_name) - 1);
+ device_name[sizeof(device_name) - 1] = '\0';
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (0 == device_name[0]) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ ma = 0;
+ mi = 0;
+ if (do_dev_dir) {
+ if (if_directory_chdir(".", device_dir)) {
+ if (getcwd(device_dir, sizeof(device_dir)))
+ device_dir[sizeof(device_dir) - 1] = '\0';
+ else
+ device_dir[0] = '\0';
+ if (verbose > 1)
+ pr2serr("Absolute path to dev_dir: %s\n",
+ device_dir);
+ } else {
+ pr2serr("dev_dir: %s invalid\n", device_dir);
+ return SG_LIB_FILE_ERROR;
+ }
+ } else {
+ strcpy(device_dir, device_name);
+ dirname(device_dir);
+ if (0 == strcmp(device_dir, device_name)) {
+ if (NULL == getcwd(device_dir, sizeof(device_dir)))
+ device_dir[0] = '\0';
+ }
+ }
+ ret = nt_typ_from_filename(device_name, &ma, &mi);
+ if (ret < 0) {
+ pr2serr("stat failed on %s: %s\n", device_name,
+ ssafe_strerror(-ret));
+ return SG_LIB_FILE_ERROR;
+ }
+ if (verbose)
+ pr2serr(" %s: %s device [maj=%d, min=%d]\n", device_name,
+ nt_names[ret], ma, mi);
+ res = 0;
+ switch (ret) {
+ case NT_SD:
+ case NT_SR:
+ case NT_HD:
+ if (given_is > 0) {
+ pr2serr("block special but '--given_is=' suggested "
+ "sysfs device\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ break;
+ case NT_ST:
+ case NT_OSST:
+ case NT_CH:
+ case NT_SG:
+ if (given_is > 0) {
+ pr2serr("character special but '--given_is=' "
+ "suggested sysfs device\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ break;
+ case NT_REG:
+ if (0 == given_is) {
+ pr2serr("regular file but '--given_is=' suggested "
+ "block or char special\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ strcpy(device_dir, def_dev_dir);
+ break;
+ case NT_DIR:
+ if (0 == given_is) {
+ pr2serr("directory but '--given_is=' suggested "
+ "block or char special\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ strcpy(device_dir, def_dev_dir);
+ break;
+ default:
+ break;
+ }
+
+ tt = NT_NO_MATCH;
+ do {
+ cont = false;
+ switch (ret) {
+ case NT_NO_MATCH:
+ res = 1;
+ break;
+ case NT_SD:
+ res = map_sd(device_name, device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_SR:
+ res = map_sr(device_name, device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_HD:
+ if (result < 2) {
+ pr2serr("a hd device does not map to a sg "
+ "device\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ res = map_hd(device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_ST:
+ res = map_st(device_name, device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_OSST:
+ res = map_osst(device_name, device_dir, ma, mi,
+ result, follow_symlink, verbose);
+ break;
+ case NT_CH:
+ res = map_ch(device_name, device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_SG:
+ res = map_sg(device_name, device_dir, ma, mi, result,
+ follow_symlink, verbose);
+ break;
+ case NT_REG:
+ if (! get_value(NULL, device_name, value,
+ sizeof(value))) {
+ pr2serr("Couldn't fetch value from: %s\n",
+ device_name);
+ return SG_LIB_FILE_ERROR;
+ }
+ if (verbose)
+ pr2serr("value: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &ma, &mi)) {
+ pr2serr("Couldn't decode value\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ tt = nt_typ_from_major(ma);
+ cont = true;
+ break;
+ case NT_DIR:
+ if (! get_value(device_name, "dev", value,
+ sizeof(value))) {
+ pr2serr("Couldn't fetch value from: %s/dev\n",
+ device_name);
+ return SG_LIB_FILE_ERROR;
+ }
+ if (verbose)
+ pr2serr("value: %s\n", value);
+ if (2 != sscanf(value, "%d:%d", &ma, &mi)) {
+ pr2serr("Couldn't decode value\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ tt = nt_typ_from_major(ma);
+ cont = true;
+ break;
+ default:
+ break;
+ }
+ ret = tt;
+ } while (cont);
+ return res;
+}
diff --git a/src/sg_modes.c b/src/sg_modes.c
new file mode 100644
index 00000000..0be40ee4
--- /dev/null
+++ b/src/sg_modes.c
@@ -0,0 +1,1579 @@
+/*
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program outputs information provided by a SCSI MODE SENSE command.
+ * Does 10 byte MODE SENSE commands by default, Trent Piepho added a "-6"
+ * switch for force 6 byte mode sense commands.
+ * This utility cannot modify mode pages. See the sdparm utility for that.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.75 20220202";
+
+#define DEF_ALLOC_LEN (1024 * 4)
+#define DEF_6_ALLOC_LEN 252
+#define UNLIKELY_ABOVE_LEN 512
+#define PG_CODE_ALL 0x3f
+#define PG_CODE_MASK 0x3f
+#define PG_CODE_MAX 0x3f
+#define SPG_CODE_ALL 0xff
+#define PROTO_SPECIFIC_1 0x18
+#define PROTO_SPECIFIC_2 0x19
+
+#define EBUFF_SZ 256
+
+
+struct opts_t {
+ bool do_dbd;
+ bool do_dbout;
+ bool do_examine;
+ bool do_flexible;
+ bool do_list;
+ bool do_llbaa;
+ bool do_six;
+ bool o_readwrite;
+ bool subpg_code_given;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_all;
+ int do_help;
+ int do_hex;
+ int maxlen;
+ int do_raw;
+ int verbose;
+ int page_control;
+ int pg_code;
+ int subpg_code;
+ const char * device_name;
+ const char * page_acron;
+};
+
+struct page_code_desc {
+ int page_code;
+ int subpage_code;
+ const char * acron;
+ const char * desc;
+};
+
+struct pc_desc_group {
+ struct page_code_desc * pcdp;
+ const char * group_name;
+};
+
+static struct option long_options[] = {
+ {"all", no_argument, 0, 'a'},
+ {"control", required_argument, 0, 'c'},
+ {"dbd", no_argument, 0, 'd'},
+ {"dbout", no_argument, 0, 'D'},
+ {"examine", no_argument, 0, 'e'},
+ {"flexible", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"list", no_argument, 0, 'l'},
+ {"llbaa", no_argument, 0, 'L'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"page", required_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"read-write", no_argument, 0, 'w'},
+ {"read_write", no_argument, 0, 'w'},
+ {"readwrite", no_argument, 0, 'w'},
+ {"six", no_argument, 0, '6'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+/* Common to all SCSI devices (found in SPCx). In numerical order */
+static struct page_code_desc pc_desc_common[] = {
+ {0x0, 0x0, "ua", "Unit Attention condition [vendor specific format]"},
+ {0x2, 0x0, "dr", "Disconnect-Reconnect"},
+ {0x9, 0x0, "pd", "Peripheral device (obsolete)"},
+ {0xa, 0x0, "co", "Control"},
+ {0xa, 0x1, "coe", "Control extension"},
+ {0xa, 0x3, "cdla", "Command duration limit A"},
+ {0xa, 0x4, "cdlb", "Command duration limit B"},
+ {0xa, 0x7, "cdt2a", "Command duration limit T2A"}, /* spc6r01 */
+ {0xa, 0x8, "cdt2b", "Command duration limit T2B"}, /* spc6r01 */
+ {0x15, 0x0, "ext_", "Extended"},
+ {0x16, 0x0, "edts", "Extended device-type specific"},
+ {0x18, 0x0, "pslu", "Protocol specific lu"},
+ {0x19, 0x0, "pspo", "Protocol specific port"},
+ {0x1a, 0x0, "po", "Power condition"},
+ {0x1a, 0x1, "ps", "Power consumption"},
+ {0x1c, 0x0, "ie", "Informational exceptions control"},
+ {PG_CODE_ALL, 0x0, "asmp", "[yields all supported pages]"},
+ {PG_CODE_ALL, SPG_CODE_ALL,"asmsp",
+ "[yields all supported pages and subpages]"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_disk[] = {
+ {0x1, 0x0, "rw", "Read-Write error recovery"},
+ {0x3, 0x0, "fo", "Format (obsolete)"},
+ {0x4, 0x0, "rd", "Rigid disk geometry (obsolete)"},
+ {0x5, 0x0, "fd", "Flexible disk (obsolete)"},
+ {0x7, 0x0, "ve", "Verify error recovery"},
+ {0x8, 0x0, "ca", "Caching"},
+ {0xa, 0x2, "atag", "Application tag"},
+ {0xa, 0x5, "ioad", "IO advice hints grouping"}, /* added sbc4r06 */
+ {0xa, 0x6, "bop", "Background operation control"}, /* added sbc4r07 */
+ {0xa, 0xf1, "pat", "Parallel ATA control (SAT)"},
+ {0xa, 0xf2, "afc", "ATA feature control (SAT)"}, /* added 20-085r2 */
+ {0xb, 0x0, "mts", "Medium types supported (obsolete)"},
+ {0xc, 0x0, "not", "Notch and partition (obsolete)"},
+ {0xd, 0x0, "pco", "Power condition (obsolete, moved to 0x1a)"},
+ {0x10, 0x0, "xo", "XOR control"}, /* obsolete in sbc3r32 */
+ {0x1a, 0xf1, "apo", "ATA Power condition"},
+ {0x1c, 0x1, "bc", "Background control"},
+ {0x1c, 0x2, "lbp", "Logical block provisioning"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_tape[] = {
+ {0x1, 0x0, "rw", "Read-Write error recovery"},
+ {0xa, 0xf0, "cdp", "Control data protection"},
+ {0xf, 0x0, "dac", "Data Compression"},
+ {0x10, 0x0, "dc", "Device configuration"},
+ {0x10, 0x1, "dcs", "Device configuration extension"},
+ {0x11, 0x0, "mpa", "Medium Partition [1]"},
+ {0x12, 0x0, "mpa2", "Medium Partition [2]"},
+ {0x13, 0x0, "mpa3", "Medium Partition [3]"},
+ {0x14, 0x0, "mpar", "Medium Partition [4]"},
+ {0x1c, 0x0, "ie", "Informational exceptions control (tape version)"},
+ {0x1d, 0x0, "mco", "Medium configuration"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_cddvd[] = {
+ {0x1, 0x0, "rw", "Read-Write error recovery"},
+ {0x3, 0x0, "mrw", "Mount Rainer rewritable"},
+ {0x5, 0x0, "wp", "Write parameters"},
+ {0x7, 0x0, "ve", "Verify error recovery"},
+ {0x8, 0x0, "ca", "Caching"},
+ {0xd, 0x0, "cddp", "CD device parameters (obsolete)"},
+ {0xe, 0x0, "cda", "CD audio"},
+ {0x1a, 0x0, "po", "Power condition (mmc)"},
+ {0x1c, 0x0, "ffrc", "Fault/failure reporting control (mmc)"},
+ {0x1d, 0x0, "tp", "Timeout and protect"},
+ {0x2a, 0x0, "cms", "MM capabilities and mechanical status (obsolete)"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_smc[] = {
+ {0x1d, 0x0, "eaa", "Element address assignment"},
+ {0x1e, 0x0, "tgp", "Transport geometry parameters"},
+ {0x1f, 0x0, "dcs", "Device capabilities"},
+ {0x1f, 0x41, "edc", "Extended device capabilities"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_scc[] = {
+ {0x1b, 0x0, "sslm", "LUN mapping"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_ses[] = {
+ {0x14, 0x0, "esm", "Enclosure services management"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_rbc[] = {
+ {0x6, 0x0, "rbc", "RBC device parameters"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_adc[] = {
+ /* {0xe, 0x0, "ADC device configuration"}, */
+ {0xe, 0x1, "adtd", "Target device"},
+ {0xe, 0x2, "addp", "DT device primary port"},
+ {0xe, 0x3, "adlu", "Logical unit"},
+ {0xe, 0x4, "adts", "Target device serial number"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+
+/* Transport reated mode pages */
+static struct page_code_desc pc_desc_t_fcp[] = {
+ {0x18, 0x0, "pl", "LU control"},
+ {0x19, 0x0, "pp", "Port control"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_t_spi4[] = {
+ {0x18, 0x0, "luc", "LU control"},
+ {0x19, 0x0, "pp", "Port control short format"},
+ {0x19, 0x1, "mc", "Margin control"},
+ {0x19, 0x2, "stc", "Saved training configuration value"},
+ {0x19, 0x3, "ns", "Negotiated settings"},
+ {0x19, 0x4, "rtc", "Report transfer capabilities"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+/* SAS protocol layers now in SPL standards */
+static struct page_code_desc pc_desc_t_sas[] = {
+ {0x18, 0x0, "pslu", "Protocol specific logical unit (SPL)"},
+ {0x19, 0x0, "pspo", "Protocol specific port (SPL)"},
+ {0x19, 0x1, "pcd", "Phy control and discover (SPL)"},
+ {0x19, 0x2, "spc", "Shared port control (SPL)"},
+ {0x19, 0x3, "sep", "Enhanced phy control (SPL)"},
+ {0x19, 0x4, "oobm", "Out of band management control (SPL)"}, /* spl5r01 */
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_t_adc[] = {
+ {0xe, 0x1, "addt", "Target device"},
+ {0xe, 0x2, "addp", "DT device primary port"},
+ {0xe, 0x3, "adlu", "Logical unit"},
+ {0x18, 0x0, "pslu", "Protocol specific lu"},
+ {0x19, 0x0, "pspo", "Protocol specific port"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+static struct page_code_desc pc_desc_zbc[] = {
+ {0x1, 0x0, "rw", "Read-Write error recovery"},
+ {0x7, 0x0, "ve", "Verify error recovery"},
+ {0x8, 0x0, "ca", "Caching"},
+ {0xa, 0x2, "atag", "Application tag"},
+ {0xa, 0xf, "zbdct", "Zoned block device control"}, /* zbc2r04a */
+ {0x1c, 0x1, "bc", "Background control"},
+ {0x0, 0x0, NULL, NULL},
+};
+
+struct pc_desc_group pcd_gr_arr[] = {
+ {pc_desc_common, "common"},
+ {pc_desc_disk, "disk"},
+ {pc_desc_tape, "tape"},
+ {pc_desc_cddvd, "cd/dvd"},
+ {pc_desc_smc, "media changer"},
+ {pc_desc_scc, "scsi controller"},
+ {pc_desc_ses, "enclosure"},
+ {pc_desc_rbc, "reduced block"},
+ {pc_desc_adc, "adc"},
+ {pc_desc_zbc, "zbc"},
+ {pc_desc_t_fcp, "transport: FCP"},
+ {pc_desc_t_spi4, "transport: SPI"},
+ {pc_desc_t_sas, "transport: SAS"},
+ {pc_desc_t_adc, "transport: ADC"},
+
+ {NULL, NULL},
+};
+
+
+
+static void
+usage()
+{
+ printf("Usage: sg_modes [--all] [--control=PC] [--dbd] [--dbout] "
+ "[--examine]\n"
+ " [--flexible] [--help] [--hex] [--list] "
+ "[--llbaa]\n"
+ " [--maxlen=LEN] [--page=PG[,SPG]] [--raw] [-R] "
+ "[--readwrite]\n"
+ " [--six] [--verbose] [--version] [DEVICE]\n"
+ " where:\n"
+ " --all|-a get all mode pages supported by device\n"
+ " use twice to get all mode pages and subpages\n"
+ " --control=PC|-c PC page control (default: 0)\n"
+ " 0: current, 1: changeable,\n"
+ " 2: (manufacturer's) defaults, 3: saved\n"
+ " --dbd|-d disable block descriptors (DBD field in cdb)\n"
+ " --dbout|-D disable block descriptor output\n"
+ " --examine|-e examine pages # 0 through to 0x3e, note if "
+ "found\n"
+ " --flexible|-f be flexible, cope with MODE SENSE 6/10 "
+ "response mixup\n");
+ printf(" --help|-h print usage message then exit\n"
+ " --hex|-H output full response in hex\n"
+ " use twice to output page number and header "
+ "in hex\n"
+ " --list|-l list common page codes for device peripheral "
+ "type,\n"
+ " if no device given then assume disk type\n"
+ " --llbaa|-L set Long LBA Accepted (LLBAA field in mode "
+ "sense (10) cdb)\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> 4096 or 252 (for MODE "
+ "SENSE 6) bytes)\n"
+ " --page=PG|-p PG page code to fetch (def: 63). May be "
+ "acronym\n"
+ " --page=PG,SPG|-p PG,SPG\n"
+ " page code and subpage code to fetch "
+ "(defs: 63,0)\n"
+ " --raw|-r output response in binary to stdout\n"
+ " -R mode page response to stdout, a byte per "
+ "line in ASCII\n"
+ " hex (same result as '--raw --raw')\n"
+ " --readwrite|-w open DEVICE read-write (def: open "
+ "read-only)\n"
+ " --six|-6|-s use MODE SENSE(6), by default uses MODE "
+ "SENSE(10)\n"
+ " --verbose|-v increase verbosity\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V output version string then exit\n\n"
+ "Performs a SCSI MODE SENSE (10 or 6) command. To access and "
+ "possibly change\nmode page fields see the sdparm utility.\n");
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_modes [-a] [-A] [-c=PC] [-d] [-D] [-e] [-f] [-h] "
+ "[-H] [-l] [-L]\n"
+ " [-m=LEN] [-p=PG[,SPG]] [-r] [-subp=SPG] [-v] "
+ "[-V] [-6]\n"
+ " [DEVICE]\n"
+ " where:\n"
+ " -a get all mode pages supported by device\n"
+ " -A get all mode pages and subpages supported by device\n"
+ " -c=PC page control (def: 0 [current],"
+ " 1 [changeable],\n"
+ " 2 [default], 3 [saved])\n"
+ " -d disable block descriptors (DBD field in cdb)\n"
+ " -D disable block descriptor output\n"
+ " -e examine pages # 0 through to 0x3e, note if found\n"
+ " -f be flexible, cope with MODE SENSE 6/10 response "
+ "mixup\n");
+ printf(" -h output page number and header in hex\n"
+ " -H output page number and header in hex (same as '-h')\n"
+ " -l list common page codes for device peripheral type,\n"
+ " if no device given then assume disk type\n"
+ " -L set Long LBA Accepted (LLBAA field in mode sense "
+ "10 cdb)\n"
+ " -m=LEN max response length (allocation length in cdb)\n"
+ " (def: 0 -> 4096 or 252 (for MODE SENSE 6) bytes)\n"
+ " -p=PG page code in hex (def: 3f). No acronym allowed\n"
+ " -p=PG,SPG both in hex, (defs: 3f,0)\n"
+ " -r mode page output to stdout, a byte per line in "
+ "ASCII hex\n"
+ " -subp=SPG sub page code in hex (def: 0)\n"
+ " -v verbose\n"
+ " -V output version string\n"
+ " -6 Use MODE SENSE(6), by default uses MODE SENSE(10)\n"
+ " -N|--new use new interface\n"
+ " -? output this usage message\n\n"
+ "Performs a SCSI MODE SENSE (10 or 6) command\n");
+}
+
+static void
+enum_pc_desc(void)
+{
+ bool first = true;
+ const struct pc_desc_group * pcd_grp = pcd_gr_arr;
+ char b[128];
+
+ for ( ; pcd_grp->pcdp; ++pcd_grp) {
+ const struct page_code_desc * pcdp = pcd_grp->pcdp;
+
+ if (first)
+ first = false;
+ else
+ printf("\n");
+ printf("Mode pages group: %s:\n", pcd_grp->group_name);
+ for ( ; pcdp->acron; ++pcdp) {
+ if (pcdp->subpage_code > 0)
+ snprintf(b, sizeof(b), "[0x%x,0x%x]", pcdp->page_code,
+ pcdp->subpage_code);
+ else
+ snprintf(b, sizeof(b), "[0x%x]", pcdp->page_code);
+ printf(" %s: %s %s\n", pcdp->acron, pcdp->desc, b);
+ }
+ }
+}
+
+static const struct page_code_desc *
+find_pc_desc(const char * acron)
+{
+ const struct pc_desc_group * pcd_grp = pcd_gr_arr;
+
+ for ( ; pcd_grp->pcdp; ++pcd_grp) {
+ const struct page_code_desc * pcdp = pcd_grp->pcdp;
+
+ for ( ; pcdp->acron; ++pcdp) {
+ if (0 == strcmp(acron, pcdp->acron))
+ return pcdp;
+ }
+ }
+ return NULL;
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+/* Processes command line options according to new option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n, nn;
+ char * cp;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "6aAc:dDefhHlLm:NOp:rRsvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '6':
+ op->do_six = true;
+ break;
+ case 'a':
+ ++op->do_all;
+ break;
+ case 'A':
+ op->do_all += 2;
+ break;
+ case 'c':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 3)) {
+ pr2serr("bad argument to '--control='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->page_control = n;
+ break;
+ case 'd':
+ op->do_dbd = true;
+ break;
+ case 'D':
+ op->do_dbout = true;
+ break;
+ case 'e':
+ op->do_examine = true;
+ break;
+ case 'f':
+ op->do_flexible = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ op->do_list = true;
+ break;
+ case 'L':
+ op->do_llbaa = true;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 65535)) {
+ pr2serr("bad argument to '--maxlen='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '--maxlen=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ if (isalpha((uint8_t)optarg[0])) {
+ const struct page_code_desc * pcdp;
+
+ op->page_acron = optarg;
+ if (0 == memcmp("xxx", optarg, 3)) {
+ enum_pc_desc();
+ return SG_LIB_OK_FALSE; /* for quick exit */
+ }
+ pcdp = find_pc_desc(optarg);
+ if (pcdp) {
+ if (pcdp->subpage_code > 0) {
+ op->subpg_code = pcdp->subpage_code;
+ op->subpg_code_given = true;
+ }
+ op->pg_code = pcdp->page_code;
+ } else {
+ pr2serr(" Couldn't match acronym '%s', try '-p xxx' for "
+ "list\n", optarg);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ cp = strchr(optarg, ',');
+ n = sg_get_num_nomult(optarg);
+ if ((n < 0) || (n > 63)) {
+ pr2serr("Bad argument to '--page='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (cp) {
+ nn = sg_get_num_nomult(cp + 1);
+ if ((nn < 0) || (nn > 255)) {
+ pr2serr("Bad second value in argument to "
+ "'--page='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->subpg_code = nn;
+ op->subpg_code_given = true;
+ }
+ op->pg_code = n;
+ }
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 'R':
+ op->do_raw += 2;
+ break;
+ case 's':
+ op->do_six = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->o_readwrite = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Processes command line options according to old option format. Returns
+ * 0 is ok, else SG_LIB_SYNTAX_ERROR is returned. */
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num, n;
+ char pc1;
+ unsigned int u, uu;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case '6':
+ op->do_six = true;
+ break;
+ case 'a':
+ ++op->do_all;
+ break;
+ case 'A':
+ op->do_all += 2;
+ break;
+ case 'd':
+ op->do_dbd = true;
+ break;
+ case 'D':
+ op->do_dbout = true;
+ break;
+ case 'e':
+ op->do_examine = true;
+ break;
+ case 'f':
+ op->do_flexible = true;
+ break;
+ case 'h':
+ case 'H':
+ op->do_hex += 2;
+ break;
+ case 'l':
+ op->do_list = true;
+ break;
+ case 'L':
+ op->do_llbaa = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'r':
+ op->do_raw += 2;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case '?':
+ ++op->do_help;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("c=", cp, 2)) {
+ num = sscanf(cp + 2, "%x", &u);
+ if ((1 != num) || (u > 3)) {
+ pr2serr("Bad page control after 'c=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->page_control = u;
+ } else if (0 == strncmp("m=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 0) || (n > 65535)) {
+ pr2serr("Bad argument after 'm=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((n > 0) && (n < 4)) {
+ pr2serr("Changing that '-m=' value to 4\n");
+ n = 4;
+ }
+ op->maxlen = n;
+ } else if (0 == strncmp("p=", cp, 2)) {
+ pc1 = *(cp + 2);
+ if (isalpha(pc1) && ((islower(pc1) && (pc1 > 'f')) ||
+ (isupper(pc1) && (pc1 > 'F')))) {
+ pr2serr("Old format doesn't accept mode page acronyms: "
+ "%s\n", cp + 2);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (NULL == strchr(cp + 2, ',')) {
+ num = sscanf(cp + 2, "%x", &u);
+ if ((1 != num) || (u > 63)) {
+ pr2serr("Bad page code value after 'p=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->pg_code = u;
+ } else if (2 == sscanf(cp + 2, "%x,%x", &u, &uu)) {
+ if (uu > 255) {
+ pr2serr("Bad subpage code value after 'p=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->pg_code = u;
+ op->subpg_code = uu;
+ op->subpg_code_given = true;
+ } else {
+ pr2serr("Bad page code, subpage code sequence after 'p=' "
+ "option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp("subp=", cp, 5)) {
+ num = sscanf(cp + 5, "%x", &u);
+ if ((1 != num) || (u > 255)) {
+ pr2serr("Bad sub page code after 'subp=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->subpg_code = u;
+ op->subpg_code_given = true;
+ if (-1 == op->pg_code)
+ op->pg_code = 0;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Process command line options. First check using new option format unless
+ * the SG3_UTILS_OLD_OPTS environment variable is defined which causes the
+ * old option format to be checked first. Both new and old format can be
+ * countermanded by a '-O' and '-N' options respectively. As soon as either
+ * of these options is detected (when processing the other format), processing
+ * stops and is restarted using the other format. Clear? */
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Note to coverity: this function is safe as long as the page_code_desc
+ * objects pointed to by pcdp have a sentinel object at the end of each
+ * array. And they do by design.*/
+static int
+count_desc_elems(const struct page_code_desc * pcdp)
+{
+ int k;
+
+ for (k = 0; k < 1024; ++k, ++pcdp) {
+ if (NULL == pcdp->acron)
+ return k;
+ }
+ pr2serr("%s: sanity check trip, invalid pc_desc table\n", __func__);
+ return k;
+}
+
+/* Returns pointer to base of table for scsi_ptype or pointer to common
+ * table if scsi_ptype is -1. Yields numbers of elements in returned
+ * table via pointer sizep. If scsi_ptype not known then returns NULL
+ * with *sizep set to zero. */
+static struct page_code_desc *
+get_mpage_tbl_size(int scsi_ptype, int * sizep)
+{
+ switch (scsi_ptype)
+ {
+ case -1: /* common list */
+ *sizep = count_desc_elems(pc_desc_common);
+ return &pc_desc_common[0];
+ case PDT_DISK: /* disk (direct access) type devices */
+ case PDT_WO:
+ case PDT_OPTICAL:
+ *sizep = count_desc_elems(pc_desc_disk);
+ return &pc_desc_disk[0];
+ case PDT_TAPE: /* tape devices */
+ case PDT_PRINTER:
+ *sizep = count_desc_elems(pc_desc_tape);
+ return &pc_desc_tape[0];
+ case PDT_MMC: /* cd/dvd/bd devices */
+ *sizep = count_desc_elems(pc_desc_cddvd);
+ return &pc_desc_cddvd[0];
+ case PDT_MCHANGER: /* medium changer devices */
+ *sizep = count_desc_elems(pc_desc_smc);
+ return &pc_desc_smc[0];
+ case PDT_SAC: /* storage array devices */
+ *sizep = count_desc_elems(pc_desc_scc);
+ return &pc_desc_scc[0];
+ case PDT_SES: /* enclosure services devices */
+ *sizep = count_desc_elems(pc_desc_ses);
+ return &pc_desc_ses[0];
+ case PDT_RBC: /* simplified direct access device */
+ *sizep = count_desc_elems(pc_desc_rbc);
+ return &pc_desc_rbc[0];
+ case PDT_ADC: /* automation device/interface */
+ *sizep = count_desc_elems(pc_desc_adc);
+ return &pc_desc_adc[0];
+ case PDT_ZBC:
+ *sizep = count_desc_elems(pc_desc_zbc);
+ return &pc_desc_zbc[0];
+ }
+ *sizep = 0;
+ return NULL;
+}
+
+
+static struct page_code_desc *
+get_mpage_trans_tbl_size(int t_proto, int * sizep)
+{
+ switch (t_proto)
+ {
+ case TPROTO_FCP:
+ *sizep = count_desc_elems(pc_desc_t_fcp);
+ return &pc_desc_t_fcp[0];
+ case TPROTO_SPI:
+ *sizep = count_desc_elems(pc_desc_t_spi4);
+ return &pc_desc_t_spi4[0];
+ case TPROTO_SAS:
+ *sizep = count_desc_elems(pc_desc_t_sas);
+ return &pc_desc_t_sas[0];
+ case TPROTO_ADT:
+ *sizep = count_desc_elems(pc_desc_t_adc);
+ return &pc_desc_t_adc[0];
+ }
+ *sizep = 0;
+ return NULL;
+}
+
+static const char *
+find_page_code_desc(int page_num, int subpage_num, int scsi_ptype,
+ bool encserv, bool mchngr, int t_proto)
+{
+ int k, num, decayed_pdt;
+ const struct page_code_desc * pcdp;
+
+ if (t_proto >= 0) {
+ pcdp = get_mpage_trans_tbl_size(t_proto, &num);
+ if (pcdp) {
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if ((page_num == pcdp->page_code) &&
+ (subpage_num == pcdp->subpage_code))
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ break;
+ }
+ }
+ }
+try_again:
+ pcdp = get_mpage_tbl_size(scsi_ptype, &num);
+ if (pcdp) {
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if ((page_num == pcdp->page_code) &&
+ (subpage_num == pcdp->subpage_code))
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ break;
+ }
+ }
+ decayed_pdt = sg_lib_pdt_decay(scsi_ptype);
+ if (decayed_pdt != scsi_ptype) {
+ scsi_ptype = decayed_pdt;
+ goto try_again;
+ }
+ if ((0xd != scsi_ptype) && encserv) {
+ /* check for attached enclosure services processor */
+ pcdp = get_mpage_tbl_size(0xd, &num);
+ if (pcdp) {
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if ((page_num == pcdp->page_code) &&
+ (subpage_num == pcdp->subpage_code))
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ break;
+ }
+ }
+ }
+ if ((0x8 != scsi_ptype) && mchngr) {
+ /* check for attached medium changer device */
+ pcdp = get_mpage_tbl_size(0x8, &num);
+ if (pcdp) {
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if ((page_num == pcdp->page_code) &&
+ (subpage_num == pcdp->subpage_code))
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ break;
+ }
+ }
+ }
+ pcdp = get_mpage_tbl_size(-1, &num);
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if ((page_num == pcdp->page_code) &&
+ (subpage_num == pcdp->subpage_code))
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ break;
+ }
+ return NULL;
+}
+
+static void
+list_page_codes(int scsi_ptype, bool encserv, bool mchngr, int t_proto)
+{
+ int num, num_ptype, pg, spg, c, d;
+ bool valid_transport;
+ const struct page_code_desc * dp;
+ const struct page_code_desc * pe_dp;
+ char b[64];
+
+ valid_transport = ((t_proto >= 0) && (t_proto <= 0xf));
+ printf("Page[,subpage] Name\n");
+ printf("=====================\n");
+ dp = get_mpage_tbl_size(-1, &num);
+ pe_dp = get_mpage_tbl_size(scsi_ptype, &num_ptype);
+ while (1) {
+ pg = dp ? dp->page_code : PG_CODE_ALL + 1;
+ spg = dp ? dp->subpage_code : SPG_CODE_ALL;
+ c = (pg << 8) + spg;
+ pg = pe_dp ? pe_dp->page_code : PG_CODE_ALL + 1;
+ spg = pe_dp ? pe_dp->subpage_code : SPG_CODE_ALL;
+ d = (pg << 8) + spg;
+ if (valid_transport &&
+ ((PROTO_SPECIFIC_1 == c) || (PROTO_SPECIFIC_2 == c)))
+ dp = (--num <= 0) ? NULL : (dp + 1); /* skip protocol specific */
+ else if (c == d) {
+ if (pe_dp) {
+ if (pe_dp->subpage_code)
+ printf(" 0x%02x,0x%02x * %s\n", pe_dp->page_code,
+ pe_dp->subpage_code, pe_dp->desc);
+ else
+ printf(" 0x%02x * %s\n", pe_dp->page_code,
+ pe_dp->desc);
+ pe_dp = (--num_ptype <= 0) ? NULL : (pe_dp + 1);
+ }
+ if (dp)
+ dp = (--num <= 0) ? NULL : (dp + 1);
+ } else if (c < d) {
+ if (dp) {
+ if (dp->subpage_code)
+ printf(" 0x%02x,0x%02x %s\n", dp->page_code,
+ dp->subpage_code, dp->desc);
+ else
+ printf(" 0x%02x %s\n", dp->page_code,
+ dp->desc);
+ dp = (--num <= 0) ? NULL : (dp + 1);
+ }
+ } else {
+ if (pe_dp) {
+ if (pe_dp->subpage_code)
+ printf(" 0x%02x,0x%02x %s\n", pe_dp->page_code,
+ pe_dp->subpage_code, pe_dp->desc);
+ else
+ printf(" 0x%02x %s\n", pe_dp->page_code,
+ pe_dp->desc);
+ pe_dp = (--num_ptype <= 0) ? NULL : (pe_dp + 1);
+ }
+ }
+ if ((NULL == dp) && (NULL == pe_dp))
+ break;
+ }
+ if ((0xd != scsi_ptype) && encserv) {
+ /* check for attached enclosure services processor */
+ printf("\n Attached enclosure services processor\n");
+ dp = get_mpage_tbl_size(0xd, &num);
+ while (dp) {
+ if (dp->subpage_code)
+ printf(" 0x%02x,0x%02x %s\n", dp->page_code,
+ dp->subpage_code, dp->desc);
+ else
+ printf(" 0x%02x %s\n", dp->page_code,
+ dp->desc);
+ dp = (--num <= 0) ? NULL : (dp + 1);
+ }
+ }
+ if ((0x8 != scsi_ptype) && mchngr) {
+ /* check for attached medium changer device */
+ printf("\n Attached medium changer device\n");
+ dp = get_mpage_tbl_size(0x8, &num);
+ while (dp) {
+ if (dp->subpage_code)
+ printf(" 0x%02x,0x%02x %s\n", dp->page_code,
+ dp->subpage_code, dp->desc);
+ else
+ printf(" 0x%02x %s\n", dp->page_code,
+ dp->desc);
+ dp = (--num <= 0) ? NULL : (dp + 1);
+ }
+ }
+ if (valid_transport) {
+ printf("\n Transport protocol: %s\n",
+ sg_get_trans_proto_str(t_proto, sizeof(b), b));
+ dp = get_mpage_trans_tbl_size(t_proto, &num);
+ while (dp) {
+ if (dp->subpage_code)
+ printf(" 0x%02x,0x%02x %s\n", dp->page_code,
+ dp->subpage_code, dp->desc);
+ else
+ printf(" 0x%02x %s\n", dp->page_code,
+ dp->desc);
+ dp = (--num <= 0) ? NULL : (dp + 1);
+ }
+ }
+}
+
+/* Returns 0 for ok, else error value */
+static int
+examine_pages(int sg_fd, int inq_pdt, bool encserv, bool mchngr,
+ const struct opts_t * op)
+{
+ bool header_printed;
+ int k, mresp_len, len, resid;
+ int res = 0;
+ const int mx_len = op->do_six ? DEF_6_ALLOC_LEN : DEF_ALLOC_LEN;
+ const char * cp;
+ uint8_t * rbuf;
+ uint8_t * free_rbuf = NULL;
+
+ rbuf = sg_memalign(mx_len, 0, &free_rbuf, false);
+ if (NULL == rbuf) {
+ pr2serr("%s: out of heap\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ mresp_len = (op->do_raw || op->do_hex) ? mx_len : 4;
+ for (header_printed = false, k = 0; k < PG_CODE_MAX; ++k) {
+ resid = 0;
+ if (op->do_six) {
+ res = sg_ll_mode_sense6(sg_fd, 0, 0, k, 0, rbuf, mresp_len,
+ true, op->verbose);
+ if (SG_LIB_CAT_INVALID_OP == res) {
+ pr2serr(">>>>>> try again without the '-6' switch for a 10 "
+ "byte MODE SENSE command\n");
+ goto out;
+ } else if (SG_LIB_CAT_NOT_READY == res) {
+ pr2serr("MODE SENSE (6) failed, device not ready\n");
+ goto out;
+ }
+ } else {
+ res = sg_ll_mode_sense10_v2(sg_fd, 0, 0, 0, k, 0, rbuf, mresp_len,
+ 0, &resid, true, op->verbose);
+ if (SG_LIB_CAT_INVALID_OP == res) {
+ pr2serr(">>>>>> try again with a '-6' switch for a 6 byte "
+ "MODE SENSE command\n");
+ goto out;
+ } else if (SG_LIB_CAT_NOT_READY == res) {
+ pr2serr("MODE SENSE (10) failed, device not ready\n");
+ goto out;
+ }
+ }
+ if (0 == res) {
+ len = sg_msense_calc_length(rbuf, mresp_len, op->do_six, NULL);
+ if (resid > 0) {
+ mresp_len -= resid;
+ if (mresp_len < 0) {
+ pr2serr("%s: MS(10) resid=%d implies negative response "
+ "length (%d)\n", __func__, resid, mresp_len);
+ res = SG_LIB_WILD_RESID;
+ goto out;
+ }
+ }
+ if (len > mresp_len)
+ len = mresp_len;
+ if (op->do_raw) {
+ dStrRaw(rbuf, len);
+ continue;
+ }
+ if (op->do_hex > 2) {
+ hex2stdout(rbuf, len, -1);
+ continue;
+ }
+ if (! header_printed) {
+ printf("Discovered mode pages:\n");
+ header_printed = true;
+ }
+ cp = find_page_code_desc(k, 0, inq_pdt, encserv, mchngr, -1);
+ if (cp)
+ printf(" %s\n", cp);
+ else
+ printf(" [0x%x]\n", k);
+ if (op->do_hex)
+ hex2stdout(rbuf, len, 1);
+ } else if (op->verbose) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose - 1);
+ pr2serr("MODE SENSE (%s) failed: %s\n", (op->do_six ? "6" : "10"),
+ b);
+ }
+ }
+out:
+ if (free_rbuf)
+ free(free_rbuf);
+ return res;
+}
+
+static const char * pg_control_str_arr[] = {
+ "current",
+ "changeable",
+ "default",
+ "saved",
+};
+
+
+int
+main(int argc, char * argv[])
+{
+ bool resp_mode6, longlba, spf;
+ bool encserv = false;
+ bool mchngr = false;
+ uint8_t uc;
+ int k, num, len, res, md_len, bd_len, page_num, resid;
+ int density_code_off, t_proto, inq_pdt, num_ua_pages, vb;
+ int sg_fd = -1;
+ int ret = 0;
+ int rsp_buff_sz = DEF_ALLOC_LEN;
+ const char * descp;
+ struct opts_t * op;
+ uint8_t * rsp_buff = NULL;
+ uint8_t * free_rsp_buff = NULL;
+ uint8_t * bp;
+ const char * cdbLenStr;
+ struct sg_simple_inquiry_resp inq_out;
+ struct opts_t opts;
+ char b[80];
+ char ebuff[EBUFF_SZ];
+ char pdt_name[64];
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->pg_code = -1;
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return (SG_LIB_OK_FALSE == res) ? 0 : res;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+ vb = op->verbose;
+ if (vb && op->page_acron) {
+ pr2serr("page acronynm: '%s' maps to page_code=0x%x",
+ op->page_acron, op->pg_code);
+ if (op->subpg_code > 0)
+ pr2serr(", subpage_code=0x%x\n", op->subpg_code);
+ else
+ pr2serr("\n");
+ }
+
+ if (NULL == op->device_name) {
+ if (op->do_list) {
+ if ((op->pg_code < 0) || (op->pg_code > PG_CODE_MAX)) {
+ printf(" Assume peripheral device type: disk\n");
+ list_page_codes(0, false, false, -1);
+ } else {
+ printf(" peripheral device type: %s\n",
+ sg_get_pdt_str(op->pg_code, sizeof(pdt_name),
+ pdt_name));
+ if (op->subpg_code_given)
+ list_page_codes(op->pg_code, false, false,
+ op->subpg_code);
+ else
+ list_page_codes(op->pg_code, false, false, -1);
+ }
+ return 0;
+ }
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->do_examine && (op->pg_code >= 0)) {
+ pr2serr("can't give '-e' and a page number\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+ if (op->do_six && op->do_llbaa) {
+ pr2serr("LLBAA not defined for MODE SENSE 6, try without '-L'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->maxlen > 0) {
+ if (op->do_six && (op->maxlen > 255)) {
+ pr2serr("For Mode Sense (6) maxlen cannot exceed 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ rsp_buff = sg_memalign(op->maxlen, 0, &free_rsp_buff, false);
+ rsp_buff_sz = op->maxlen;
+ } else { /* maxlen == 0 */
+ rsp_buff = sg_memalign(rsp_buff_sz, 0, &free_rsp_buff, false);
+ if (op->do_six)
+ rsp_buff_sz = DEF_6_ALLOC_LEN;
+ }
+ if (NULL == rsp_buff) { /* check for both sg_memalign()s */
+ pr2serr("Unable to allocate %d bytes on heap\n", rsp_buff_sz);
+ return sg_convert_errno(ENOMEM);
+ }
+ /* If no pages or list selected than treat as 'a' */
+ if (! ((op->pg_code >= 0) || op->do_all || op->do_list || op->do_examine))
+ op->do_all = 1;
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ }
+
+ if ((sg_fd = sg_cmds_open_device(op->device_name, ! op->o_readwrite,
+ vb)) < 0) {
+ pr2serr("error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if ((res = sg_simple_inquiry(sg_fd, &inq_out, true, vb))) {
+ pr2serr("%s doesn't respond to a SCSI INQUIRY\n", op->device_name);
+ ret = (res > 0) ? res : sg_convert_errno(-res);
+ goto fini;
+ }
+ inq_pdt = inq_out.peripheral_type;
+ encserv = !! (0x40 & inq_out.byte_6);
+ mchngr = !! (0x8 & inq_out.byte_6);
+ if ((0 == op->do_raw) && (op->do_hex < 3))
+ printf(" %.8s %.16s %.4s peripheral_type: %s [0x%x]\n",
+ inq_out.vendor, inq_out.product, inq_out.revision,
+ sg_get_pdt_str(inq_pdt, sizeof(pdt_name), pdt_name), inq_pdt);
+ if (op->do_list) {
+ if (op->subpg_code_given)
+ list_page_codes(inq_pdt, encserv, mchngr, op->subpg_code);
+ else
+ list_page_codes(inq_pdt, encserv, mchngr, -1);
+ goto fini;
+ }
+ if (op->do_examine) {
+ ret = examine_pages(sg_fd, inq_pdt, encserv, mchngr, op);
+ goto fini;
+ }
+ if (PG_CODE_ALL == op->pg_code) {
+ if (0 == op->do_all)
+ ++op->do_all;
+ } else if (op->do_all)
+ op->pg_code = PG_CODE_ALL;
+ if (op->do_all > 1)
+ op->subpg_code = SPG_CODE_ALL;
+
+ if (op->do_raw > 1) {
+ if (op->do_all) {
+ if (op->opt_new)
+ pr2serr("'-R' requires a specific (sub)page, not all\n");
+ else
+ pr2serr("'-r' requires a specific (sub)page, not all\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ }
+
+ resid = 0;
+ if (op->do_six) {
+ res = sg_ll_mode_sense6(sg_fd, op->do_dbd, op->page_control,
+ op->pg_code, op->subpg_code, rsp_buff,
+ rsp_buff_sz, true, vb);
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr(">>>>>> try again without the '-6' switch for a 10 byte "
+ "MODE SENSE command\n");
+ } else {
+ res = sg_ll_mode_sense10_v2(sg_fd, op->do_llbaa, op->do_dbd,
+ op->page_control, op->pg_code,
+ op->subpg_code, rsp_buff, rsp_buff_sz,
+ 0, &resid, true, vb);
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr(">>>>>> try again with a '-6' switch for a 6 byte MODE "
+ "SENSE command\n");
+ }
+ if (SG_LIB_CAT_ILLEGAL_REQ == res) {
+ if (op->subpg_code > 0)
+ pr2serr("invalid field in cdb (perhaps subpages not "
+ "supported)\n");
+ else if (op->page_control > 0)
+ pr2serr("invalid field in cdb (perhaps page control (PC) not "
+ "supported)\n");
+ else
+ pr2serr("invalid field in cdb (perhaps page 0x%x not "
+ "supported)\n", op->pg_code);
+ } else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s\n", b);
+ }
+ ret = res;
+ if (0 == res) {
+ int medium_type, specific, headerlen;
+
+ ret = 0;
+ resp_mode6 = op->do_six;
+ if (op->do_flexible) {
+ num = rsp_buff[0];
+ if (op->do_six && (num < 3))
+ resp_mode6 = false;
+ if ((! op->do_six) && (num > 5)) {
+ if ((num > 11) && (0 == (num % 2)) && (0 == rsp_buff[4]) &&
+ (0 == rsp_buff[5]) && (0 == rsp_buff[6])) {
+ rsp_buff[1] = num;
+ rsp_buff[0] = 0;
+ pr2serr(">>> msense(10) but resp[0]=%d and not msense(6) "
+ "response so fix length\n", num);
+ } else
+ resp_mode6 = true;
+ }
+ }
+ cdbLenStr = resp_mode6 ? "6" : "10";
+ if (op->do_raw || (1 == op->do_hex) || (op->do_hex > 2))
+ ;
+ else {
+ if (resp_mode6 == op->do_six)
+ printf("Mode parameter header from MODE SENSE(%s):\n",
+ cdbLenStr);
+ else
+ printf(" >>> Mode parameter header from MODE SENSE(%s),\n"
+ " decoded as %s byte response:\n",
+ cdbLenStr, (resp_mode6 ? "6" : "10"));
+ }
+ rsp_buff_sz -= resid;
+ if (rsp_buff_sz < 0) {
+ pr2serr("MS(%s) resid=%d implies negative response length "
+ "(%d)\n", cdbLenStr, resid, rsp_buff_sz);
+ ret = SG_LIB_WILD_RESID;
+ goto fini;
+ }
+ if (resp_mode6) {
+ if (rsp_buff_sz < 4) {
+ pr2serr("MS(6) resid=%d implies abridged header length "
+ "(%d)\n", resid, rsp_buff_sz);
+ ret = SG_LIB_WILD_RESID;
+ goto fini;
+ }
+ headerlen = 4;
+ medium_type = rsp_buff[1];
+ specific = rsp_buff[2];
+ longlba = false;
+ } else { /* MODE SENSE(10) with resid */
+ if (rsp_buff_sz < 8) {
+ pr2serr("MS(10) resid=%d implies abridged header length "
+ "(%d)\n", resid, rsp_buff_sz);
+ ret = SG_LIB_WILD_RESID;
+ goto fini;
+ }
+ headerlen = 8;
+ medium_type = rsp_buff[2];
+ specific = rsp_buff[3];
+ longlba = !!(rsp_buff[4] & 1);
+ }
+ md_len = sg_msense_calc_length(rsp_buff, rsp_buff_sz, resp_mode6,
+ &bd_len);
+ if (md_len < 0) {
+ pr2serr("MS(%s): sg_msense_calc_length() failed\n", cdbLenStr);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ md_len = (md_len < rsp_buff_sz) ? md_len : rsp_buff_sz;
+ if ((bd_len + headerlen) > md_len) {
+ pr2serr("Invalid block descriptor length=%d, ignore\n", bd_len);
+ bd_len = 0;
+ }
+ if (op->do_raw || (op->do_hex > 2)) {
+ if (1 == op->do_raw)
+ dStrRaw(rsp_buff, md_len);
+ else if (op->do_raw > 1) {
+ bp = rsp_buff + bd_len + headerlen;
+ md_len -= bd_len + headerlen;
+ spf = !!(bp[0] & 0x40);
+ len = (spf ? (sg_get_unaligned_be16(bp + 2) + 4) :
+ (bp[1] + 2));
+ len = (len < md_len) ? len : md_len;
+ for (k = 0; k < len; ++k)
+ printf("%02x\n", bp[k]);
+ } else
+ hex2stdout(rsp_buff, md_len, -1);
+ goto fini;
+ }
+ if (1 == op->do_hex) {
+ hex2stdout(rsp_buff, md_len, 1);
+ goto fini;
+ } else if (op->do_hex > 1) {
+ hex2stdout(rsp_buff, headerlen, 1);
+ goto fini;
+ }
+ if ((PDT_DISK == inq_pdt) || (PDT_ZBC == inq_pdt))
+ printf(" Mode data length=%d, medium type=0x%.2x, WP=%d,"
+ " DpoFua=%d, longlba=%d\n", md_len, medium_type,
+ !!(specific & 0x80), !!(specific & 0x10), (int)longlba);
+ else
+ printf(" Mode data length=%d, medium type=0x%.2x, specific"
+ " param=0x%.2x, longlba=%d\n", md_len, medium_type,
+ specific, (int)longlba);
+ if (md_len > rsp_buff_sz) {
+ printf("Only fetched %d bytes of response, truncate output\n",
+ rsp_buff_sz);
+ md_len = rsp_buff_sz;
+ if (bd_len + headerlen > rsp_buff_sz)
+ bd_len = rsp_buff_sz - headerlen;
+ }
+ if (! op->do_dbout) {
+ printf(" Block descriptor length=%d\n", bd_len);
+ if (bd_len > 0) {
+ len = 8;
+ density_code_off = 0;
+ num = bd_len;
+ if (longlba) {
+ printf("> longlba direct access device block "
+ "descriptors:\n");
+ len = 16;
+ density_code_off = 8;
+ }
+ else if ((PDT_DISK == inq_pdt) || (PDT_ZBC == inq_pdt)) {
+ printf("> Direct access device block descriptors:\n");
+ density_code_off = 4;
+ }
+ else
+ printf("> General mode parameter block descriptors:\n");
+
+ bp = rsp_buff + headerlen;
+ while (num > 0) {
+ printf(" Density code=0x%x\n",
+ *(bp + density_code_off));
+ hex2stdout(bp, len, 1);
+ bp += len;
+ num -= len;
+ }
+ printf("\n");
+ }
+ }
+ bp = rsp_buff + bd_len + headerlen; /* start of mode page(s) */
+ md_len -= bd_len + headerlen; /* length of mode page(s) */
+ num_ua_pages = 0;
+ for (k = 0; md_len > 0; ++k) { /* got mode page(s) */
+ if ((k > 0) && (! op->do_all) &&
+ (SPG_CODE_ALL != op->subpg_code)) {
+ pr2serr("Unexpectedly received extra mode page responses, "
+ "ignore\n");
+ break;
+ }
+ uc = *bp;
+ spf = !!(uc & 0x40);
+ len = (spf ? (sg_get_unaligned_be16(bp + 2) + 4) : (bp[1] + 2));
+ page_num = bp[0] & PG_CODE_MASK;
+ if (0x0 == page_num) {
+ ++num_ua_pages;
+ if((num_ua_pages > 3) && (md_len > 0xa00)) {
+ pr2serr(">>> Seen 3 unit attention pages (only one "
+ "should be at end)\n and mpage length=%d, "
+ "looks malformed, try '-f' option\n", md_len);
+ break;
+ }
+ }
+ if (op->do_hex) {
+ if (spf)
+ printf(">> page_code=0x%x, subpage_code=0x%x, page_cont"
+ "rol=%d\n", page_num, bp[1], op->page_control);
+ else
+ printf(">> page_code=0x%x, page_control=%d\n", page_num,
+ op->page_control);
+ } else {
+ descp = NULL;
+ if ((0x18 == page_num) || (0x19 == page_num)) {
+ t_proto = (spf ? bp[5] : bp[2]) & 0xf;
+ descp = find_page_code_desc(page_num, (spf ? bp[1] : 0),
+ inq_pdt, encserv, mchngr,
+ t_proto);
+ } else
+ descp = find_page_code_desc(page_num, (spf ? bp[1] : 0),
+ inq_pdt, encserv, mchngr, -1);
+ if (NULL == descp) {
+ if (spf)
+ snprintf(ebuff, EBUFF_SZ, "0x%x, subpage_code: 0x%x",
+ page_num, bp[1]);
+ else
+ snprintf(ebuff, EBUFF_SZ, "0x%x", page_num);
+ }
+ if (descp)
+ printf(">> %s, page_control: %s\n", descp,
+ pg_control_str_arr[op->page_control]);
+ else
+ printf(">> page_code: %s, page_control: %s\n", ebuff,
+ pg_control_str_arr[op->page_control]);
+ }
+ num = (len > md_len) ? md_len : len;
+ if ((k > 0) && (num > UNLIKELY_ABOVE_LEN)) {
+ num = UNLIKELY_ABOVE_LEN;
+ pr2serr(">>> page length (%d) > %d bytes, unlikely, trim\n"
+ " Try '-f' option\n", len, num);
+ }
+ hex2stdout(bp, num , 1);
+ bp += len;
+ md_len -= len;
+ }
+ }
+
+fini:
+ if (sg_fd >= 0)
+ sg_cmds_close_device(sg_fd);
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if (0 == vb) {
+ if (! sg_if_can2stderr("sg_modes failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_opcodes.c b/src/sg_opcodes.c
new file mode 100644
index 00000000..9a5e3b83
--- /dev/null
+++ b/src/sg_opcodes.c
@@ -0,0 +1,1500 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2004-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program outputs information provided by a SCSI REPORT SUPPORTED
+ * OPERATION CODES [0xa3/0xc] (RSOC) and REPORT SUPPORTED TASK MANAGEMENT
+ * FUNCTIONS [0xa3/0xd] (RSTMF) commands.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+#include "sg_pt.h"
+
+static const char * version_str = "0.86 20221005"; /* spc6r06 */
+
+#define MY_NAME "sg_opcodes"
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT_SECS 60
+
+#define SG_MAINTENANCE_IN 0xa3
+#define RSOC_SA 0xc
+#define RSTMF_SA 0xd
+#define RSOC_CMD_LEN 12
+#define RSTMF_CMD_LEN 12
+#define MX_ALLOC_LEN 8192
+
+#define NAME_BUFF_SZ 128
+
+#define SEAGATE_READ_UDS_DATA_CMD 0xf7 /* may start reporting vendor cmds */
+
+static int peri_dtype = -1; /* ugly but not easy to pass to alpha compare */
+static bool no_final_msg = false;
+
+static struct option long_options[] = {
+ {"alpha", no_argument, 0, 'a'},
+ {"compact", no_argument, 0, 'c'},
+ {"enumerate", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"inhex", required_argument, 0, 'i'},
+ {"in", required_argument, 0, 'i'},
+ {"json", optional_argument, 0, 'j'},
+ {"mask", no_argument, 0, 'm'},
+ {"mlu", no_argument, 0, 'M'}, /* added in spc5r20 */
+ {"no-inquiry", no_argument, 0, 'n'},
+ {"no_inquiry", no_argument, 0, 'n'},
+ {"new", no_argument, 0, 'N'},
+ {"opcode", required_argument, 0, 'o'},
+ {"old", no_argument, 0, 'O'},
+ {"pdt", required_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"rctd", no_argument, 0, 'R'},
+ {"repd", no_argument, 0, 'q'},
+ {"sa", required_argument, 0, 's'},
+ {"tmf", no_argument, 0, 't'},
+ {"unsorted", no_argument, 0, 'u'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_alpha;
+ bool do_compact;
+ bool do_enumerate;
+ bool no_inquiry;
+ bool do_mask;
+ bool do_mlu;
+ bool do_raw;
+ bool do_rctd; /* Return command timeout descriptor */
+ bool do_repd;
+ bool do_unsorted;
+ bool do_taskman;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_help;
+ int do_hex;
+ int opcode;
+ int servact;
+ int verbose;
+ const char * device_name;
+ const char * inhex_fn;
+ sgj_state json_st;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_opcodes [--alpha] [--compact] [--enumerate] "
+ "[--help] [--hex]\n"
+ " [--inhex=FN] [--json[=JO]] [--mask] [--mlu] "
+ "[--no-inquiry]\n"
+ " [--opcode=OP[,SA]] [--pdt=DT] [--raw] "
+ "[--rctd]\n"
+ " [--repd] [--sa=SA] [--tmf] [--unsorted] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --alpha|-a output list of operation codes sorted "
+ "alphabetically\n"
+ " --compact|-c more compact output\n"
+ " --enumerate|-e use '--opcode=' and '--pdt=' to look up "
+ "name,\n"
+ " ignore DEVICE\n"
+ " --help|-h print usage message then exit\n"
+ " --hex|-H output response in hex, use -HHH for "
+ "hex\n"
+ " suitable for later use of --inhex= "
+ "option\n"
+ " --inhex=FN|-i FN contents of file FN treated as hex "
+ "and used\n"
+ " instead of DEVICE which is ignored\n"
+ " --json[=JO]|-jJO output in JSON instead of human "
+ "readable\n"
+ " test. Use --json=? for JSON help\n"
+ " --mask|-m show cdb usage data (a mask) when "
+ "all listed\n"
+ " --mlu|-M show MLU bit when all listed\n"
+ " --no-inquiry|-n don't output INQUIRY information\n"
+ " --opcode=OP[,SA]|-o OP[,SA] opcode (OP) and service "
+ "action (SA)\n"
+ " --pdt=DT|-p DT give peripheral device type for "
+ "'--no-inquiry'\n"
+ " '--enumerate'\n"
+ " --raw|-r output response in binary to stdout unless "
+ "--inhex=FN\n"
+ " is given then FN is parsed as binary "
+ "instead\n"
+ " --rctd|-R set RCTD (return command timeout "
+ "descriptor) bit\n"
+ " --repd|-q set Report Extended Parameter Data bit, "
+ "with --tmf\n"
+ " --sa=SA|-s SA service action in addition to opcode\n"
+ " --tmf|-t output list of supported task management "
+ "functions\n"
+ " --unsorted|-u output list of operation codes as is\n"
+ " (def: sort by opcode (then service "
+ "action))\n"
+ " --verbose|-v increase verbosity\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V print version string then exit\n\n"
+ "Performs a SCSI REPORT SUPPORTED OPERATION CODES or a REPORT "
+ "SUPPORTED\nTASK MANAGEMENT FUNCTIONS command. All values are "
+ "in decimal by default,\nprefix with '0x' or add a trailing 'h' "
+ "for hex numbers.\n");
+}
+
+static void
+usage_old()
+{
+ pr2serr("Usage: sg_opcodes [-a] [-c] [-e] [-H] [-j] [-m] [-M] [-n] "
+ "[-o=OP]\n"
+ " [-p=DT] [-q] [-r] [-R] [-s=SA] [-t] [-u] "
+ "[-v] [-V]\n"
+ " DEVICE\n"
+ " where:\n"
+ " -a output list of operation codes sorted "
+ "alphabetically\n"
+ " -c more compact output\n"
+ " -e use '--opcode=' and '--pdt=' to look up name, "
+ "ignore DEVICE\n"
+ " -H print response in hex\n"
+ " -j print response in JSON\n"
+ " -m show cdb usage data (a mask) when all listed\n"
+ " -M show MLU bit when all listed\n"
+ " -n don't output INQUIRY information\n"
+ " -o=OP first byte of command to query (in hex)\n"
+ " -p=DT alternate source of pdt (normally obtained from "
+ "inquiry)\n"
+ " -q set REPD bit for tmf_s\n"
+ " -r output response in binary to stdout\n"
+ " -R set RCTD (return command timeout "
+ "descriptor) bit\n"
+ " -s=SA in addition to opcode (in hex)\n"
+ " -t output list of supported task management functions\n"
+ " -u output list of operation codes as is (unsorted)\n"
+ " -v verbose\n"
+ " -V output version string\n"
+ " -N|--new use new interface\n"
+ " -? output this usage message\n\n"
+ "Performs a SCSI REPORT SUPPORTED OPERATION CODES (or a REPORT "
+ "TASK MANAGEMENT\nFUNCTIONS) command\n");
+}
+
+static const char * const rsoc_s = "Report supported operation codes";
+
+static int
+do_rsoc(struct sg_pt_base * ptvp, bool rctd, int rep_opts, int rq_opcode,
+ int rq_servact, void * resp, int mx_resp_len, int * act_resp_lenp,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t rsoc_cdb[RSOC_CMD_LEN] = {SG_MAINTENANCE_IN, RSOC_SA, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (rctd)
+ rsoc_cdb[2] |= 0x80;
+ if (rep_opts)
+ rsoc_cdb[2] |= (rep_opts & 0x7);
+ if (rq_opcode > 0)
+ rsoc_cdb[3] = (rq_opcode & 0xff);
+ if (rq_servact > 0)
+ sg_put_unaligned_be16((uint16_t)rq_servact, rsoc_cdb + 4);
+ if (act_resp_lenp)
+ *act_resp_lenp = 0;
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rsoc_cdb + 6);
+
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", rsoc_s,
+ sg_get_command_str(rsoc_cdb, RSOC_CMD_LEN, false,
+ sizeof(b), b));
+ }
+ clear_scsi_pt_obj(ptvp);
+ set_scsi_pt_cdb(ptvp, rsoc_cdb, sizeof(rsoc_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, DEF_TIMEOUT_SECS, verbose);
+ ret = sg_cmds_process_resp(ptvp, rsoc_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if (act_resp_lenp)
+ *act_resp_lenp = ret;
+ if ((verbose > 2) && (ret > 0)) {
+ pr2serr("%s response:\n", rsoc_s);
+ hex2stderr((const uint8_t *)resp, ret, 1);
+ }
+ ret = 0;
+ }
+ return ret;
+}
+
+static const char * const rstmf_s = "Report supported task management "
+ "functions";
+
+static int
+do_rstmf(struct sg_pt_base * ptvp, bool repd, void * resp, int mx_resp_len,
+ int * act_resp_lenp, bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t rstmf_cdb[RSTMF_CMD_LEN] = {SG_MAINTENANCE_IN, RSTMF_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (repd)
+ rstmf_cdb[2] = 0x80;
+ if (act_resp_lenp)
+ *act_resp_lenp = 0;
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rstmf_cdb + 6);
+
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", rstmf_s,
+ sg_get_command_str(rstmf_cdb, RSTMF_CMD_LEN, false,
+ sizeof(b), b));
+ }
+ clear_scsi_pt_obj(ptvp);
+ set_scsi_pt_cdb(ptvp, rstmf_cdb, sizeof(rstmf_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, DEF_TIMEOUT_SECS, verbose);
+ ret = sg_cmds_process_resp(ptvp, rstmf_s, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if (act_resp_lenp)
+ *act_resp_lenp = ret;
+ if ((verbose > 2) && (ret > 0)) {
+ pr2serr("%s response:\n", rstmf_s);
+ hex2stderr((const uint8_t *)resp, ret, 1);
+ }
+ ret = 0;
+ }
+ return ret;
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+ char * cp;
+ char b[32];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "acehHi:j::mMnNo:Op:qrRs:tuvV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ op->do_alpha = true;
+ break;
+ case 'c':
+ op->do_compact = true;
+ break;
+ case 'e':
+ op->do_enumerate = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->inhex_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'm':
+ op->do_mask = true;
+ break;
+ case 'M':
+ op->do_mlu = true;
+ break;
+ case 'n':
+ op->no_inquiry = true;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'o':
+ if (strlen(optarg) >= (sizeof(b) - 1)) {
+ pr2serr("argument to '--opcode' too long\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cp = strchr(optarg, ',');
+ if (cp) {
+ memset(b, 0, sizeof(b));
+ strncpy(b, optarg, cp - optarg);
+ n = sg_get_num(b);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad OP argument to '--opcode'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->opcode = n;
+ n = sg_get_num(cp + 1);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("bad SA argument to '--opcode'\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->servact = n;
+ } else {
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad argument to '--opcode'\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->opcode = n;
+ }
+ break;
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ n = -2;
+ if (isdigit((uint8_t)optarg[0]))
+ n = sg_get_num(optarg);
+ else if ((2 == strlen(optarg)) && (0 == strcmp("-1", optarg)))
+ n = -1;
+ if ((n < -1) || (n > PDT_MAX)) {
+ pr2serr("bad argument to '--pdt=DT', expect -1 to 31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ peri_dtype = n;
+ break;
+ case 'q':
+ op->do_repd = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->do_rctd = true;
+ break;
+ case 's':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("bad argument to '--sa'\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->servact = n;
+ break;
+ case 't':
+ op->do_taskman = true;
+ break;
+ case 'u':
+ op->do_unsorted = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, n, num;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'a':
+ op->do_alpha = true;
+ break;
+ case 'c':
+ op->do_compact = true;
+ break;
+ case 'e':
+ op->do_enumerate = true;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'j': /* don't accept argument with this old syntax */
+ sgj_init_state(&op->json_st, NULL);
+ break;
+ case 'm':
+ op->do_mask = true;
+ break;
+ case 'M':
+ op->do_mlu = true;
+ break;
+ case 'n':
+ op->no_inquiry = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'q':
+ op->do_repd = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->do_rctd = true;
+ break;
+ case 't':
+ op->do_taskman = true;
+ break;
+ case 'u':
+ op->do_unsorted = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("i=", cp, 2))
+ op->inhex_fn = cp + 2;
+ else if (0 == strncmp("o=", cp, 2)) {
+ num = sscanf(cp + 2, "%x", (unsigned int *)&n);
+ if ((1 != num) || (n > 255)) {
+ pr2serr("Bad number after 'o=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->opcode = n;
+ } else if (0 == strncmp("p=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n > PDT_MAX) || (n < -1)) {
+ pr2serr("Bad number after 'p=' option, expect -1 to "
+ "31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ peri_dtype = n;
+ } else if (0 == strncmp("s=", cp, 2)) {
+ num = sscanf(cp + 2, "%x", (unsigned int *)&n);
+ if (1 != num) {
+ pr2serr("Bad number after 's=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->servact = n;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (NULL == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* returns -1 when left < right, 0 when left == right, else returns 1 */
+static int
+opcode_num_compare(const void * left, const void * right)
+{
+ int l_serv_act = 0;
+ int r_serv_act = 0;
+ int l_opc, r_opc;
+ const uint8_t * ll = *(uint8_t **)left;
+ const uint8_t * rr = *(uint8_t **)right;
+
+ if (NULL == ll)
+ return -1;
+ if (NULL == rr)
+ return -1;
+ l_opc = ll[0];
+ if (ll[5] & 1)
+ l_serv_act = sg_get_unaligned_be16(ll + 2);
+ r_opc = rr[0];
+ if (rr[5] & 1)
+ r_serv_act = sg_get_unaligned_be16(rr + 2);
+ if (l_opc < r_opc)
+ return -1;
+ if (l_opc > r_opc)
+ return 1;
+ if (l_serv_act < r_serv_act)
+ return -1;
+ if (l_serv_act > r_serv_act)
+ return 1;
+ return 0;
+}
+
+/* returns -1 when left < right, 0 when left == right, else returns 1 */
+static int
+opcode_alpha_compare(const void * left, const void * right)
+{
+ const uint8_t * ll = *(uint8_t **)left;
+ const uint8_t * rr = *(uint8_t **)right;
+ int l_serv_act = 0;
+ int r_serv_act = 0;
+ char l_name_buff[NAME_BUFF_SZ];
+ char r_name_buff[NAME_BUFF_SZ];
+ int l_opc, r_opc;
+
+ if (NULL == ll)
+ return -1;
+ if (NULL == rr)
+ return -1;
+ l_opc = ll[0];
+ if (ll[5] & 1)
+ l_serv_act = sg_get_unaligned_be16(ll + 2);
+ l_name_buff[0] = '\0';
+ sg_get_opcode_sa_name(l_opc, l_serv_act, peri_dtype,
+ NAME_BUFF_SZ, l_name_buff);
+ r_opc = rr[0];
+ if (rr[5] & 1)
+ r_serv_act = sg_get_unaligned_be16(rr + 2);
+ r_name_buff[0] = '\0';
+ sg_get_opcode_sa_name(r_opc, r_serv_act, peri_dtype,
+ NAME_BUFF_SZ, r_name_buff);
+ return strncmp(l_name_buff, r_name_buff, NAME_BUFF_SZ);
+}
+
+/* For decoding a RSOC command's "All_commands" parameter data */
+static int
+list_all_codes(uint8_t * rsoc_buff, int rsoc_len, struct opts_t * op,
+ struct sg_pt_base * ptvp)
+{
+ bool sa_v;
+ int k, j, m, n, cd_len, serv_act, len, act_len, opcode, res;
+ uint8_t byt5;
+ unsigned int timeout;
+ uint8_t * bp;
+ uint8_t ** sort_arr = NULL;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p jop = NULL;
+ char name_buff[NAME_BUFF_SZ];
+ char sa_buff[8];
+ char b[192];
+ const int blen = sizeof(b);
+
+ cd_len = sg_get_unaligned_be32(rsoc_buff + 0);
+ if (cd_len > (rsoc_len - 4)) {
+ sgj_pr_hr(jsp, "sg_opcodes: command data length=%d, allocation=%d; "
+ "truncate\n", cd_len, rsoc_len - 4);
+ cd_len = ((rsoc_len - 4) / 8) * 8;
+ }
+ if (0 == cd_len) {
+ sgj_pr_hr(jsp, "sg_opcodes: no commands to display\n");
+ return 0;
+ }
+ if (op->do_rctd) { /* Return command timeout descriptor */
+ if (op->do_compact) {
+ sgj_pr_hr(jsp, "\nOpcode,sa Nominal Recommended Name\n");
+ sgj_pr_hr(jsp, " (hex) timeout timeout(sec) \n");
+ sgj_pr_hr(jsp, "-----------------------------------------------"
+ "---------\n");
+ } else {
+ sgj_pr_hr(jsp, "\nOpcode Service CDB Nominal Recommended "
+ "Name\n");
+ sgj_pr_hr(jsp, "(hex) action(h) size timeout timeout(sec) "
+ " \n");
+ sgj_pr_hr(jsp, "-------------------------------------------------"
+ "---------------\n");
+ }
+ } else { /* RCTD clear in cdb */
+ if (op->do_compact) {
+ sgj_pr_hr(jsp, "\nOpcode,sa Name\n");
+ sgj_pr_hr(jsp, " (hex) \n");
+ sgj_pr_hr(jsp, "---------------------------------------\n");
+ } else if (op->do_mlu) {
+ sgj_pr_hr(jsp, "\nOpcode Service CDB MLU Name\n");
+ sgj_pr_hr(jsp, "(hex) action(h) size \n");
+ sgj_pr_hr(jsp, "-------------------------------------------"
+ "----\n");
+ } else {
+ sgj_pr_hr(jsp, "\nOpcode Service CDB RWCDLP, Name\n");
+ sgj_pr_hr(jsp, "(hex) action(h) size CDLP \n");
+ sgj_pr_hr(jsp, "-------------------------------------------"
+ "----\n");
+ }
+ }
+ /* SPC-4 does _not_ require any ordering of opcodes in the response */
+ if (! op->do_unsorted) {
+ sort_arr = (uint8_t **)calloc(cd_len, sizeof(uint8_t *));
+ if (NULL == sort_arr) {
+ pr2serr("sg_opcodes: no memory to sort operation codes, "
+ "try '-u'\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ memset(sort_arr, 0, cd_len * sizeof(uint8_t *));
+ bp = rsoc_buff + 4;
+ for (k = 0, j = 0; k < cd_len; ++j, k += len, bp += len) {
+ sort_arr[j] = bp;
+ len = (bp[5] & 0x2) ? 20 : 8;
+ }
+ qsort(sort_arr, j, sizeof(uint8_t *),
+ (op->do_alpha ? opcode_alpha_compare : opcode_num_compare));
+ }
+
+ jap = sgj_named_subarray_r(jsp, jsp->basep, "all_command_descriptor");
+ for (k = 0, j = 0; k < cd_len; ++j, k += len) {
+ jop = sgj_new_unattached_object_r(jsp);
+
+ bp = op->do_unsorted ? (rsoc_buff + 4 + k) : sort_arr[j];
+ byt5 = bp[5];
+ len = (byt5 & 0x2) ? 20 : 8;
+ opcode = bp[0];
+ sa_v = !!(byt5 & 1); /* service action valid */
+ serv_act = 0;
+ name_buff[0] = '\0';
+ if (sa_v) {
+ serv_act = sg_get_unaligned_be16(bp + 2);
+ sg_get_opcode_sa_name(opcode, serv_act, peri_dtype, NAME_BUFF_SZ,
+ name_buff);
+ if (op->do_compact)
+ snprintf(sa_buff, sizeof(sa_buff), "%-4x", serv_act);
+ else
+ snprintf(sa_buff, sizeof(sa_buff), "%4x", serv_act);
+ } else {
+ sg_get_opcode_name(opcode, peri_dtype, NAME_BUFF_SZ, name_buff);
+ memset(sa_buff, ' ', sizeof(sa_buff));
+ }
+ if (op->do_rctd) {
+ n = 0;
+ if (byt5 & 0x2) { /* CTDP set */
+ /* don't show CDLP because it makes line too long */
+ if (op->do_compact)
+ n += sg_scnpr(b + n, blen - n, " %.2x%c%.4s", opcode,
+ (sa_v ? ',' : ' '), sa_buff);
+ else
+ n += sg_scnpr(b + n, blen - n, " %.2x %.4s %3d",
+ opcode,
+ sa_buff, sg_get_unaligned_be16(bp + 6));
+ timeout = sg_get_unaligned_be32(bp + 12);
+ if (0 == timeout)
+ n += sg_scnpr(b + n, blen - n, " -");
+ else
+ n += sg_scnpr(b + n, blen - n, " %8u", timeout);
+ timeout = sg_get_unaligned_be32(bp + 16);
+ if (0 == timeout)
+ n += sg_scnpr(b + n, blen - n, " -");
+ else
+ n += sg_scnpr(b + n, blen - n, " %8u", timeout);
+ sgj_pr_hr(jsp, "%s %s\n", b, name_buff);
+ } else /* CTDP clear */
+ if (op->do_compact)
+ sgj_pr_hr(jsp, " %.2x%c%.4s %s\n",
+ opcode, (sa_v ? ',' : ' '), sa_buff, name_buff);
+ else
+ sgj_pr_hr(jsp, " %.2x %.4s %3d "
+ " %s\n", opcode, sa_buff,
+ sg_get_unaligned_be16(bp + 6), name_buff);
+ } else { /* RCTD clear in cdb */
+ /* before version 0.69 treated RWCDLP (1 bit) and CDLP (2 bits),
+ * as a 3 bit field, now break them out separately */
+ int rwcdlp = (byt5 >> 2) & 0x3;
+ int cdlp = !!(0x40 & byt5);
+
+ if (op->do_compact)
+ sgj_pr_hr(jsp, " %.2x%c%.4s %s\n", bp[0],
+ (sa_v ? ',' : ' '), sa_buff, name_buff);
+ else if (op->do_mlu)
+ sgj_pr_hr(jsp, " %.2x %.4s %3d %3d %s\n",
+ bp[0], sa_buff, sg_get_unaligned_be16(bp + 6),
+ ((byt5 >> 4) & 0x3), name_buff);
+ else
+ sgj_pr_hr(jsp, " %.2x %.4s %3d %d,%d %s\n",
+ bp[0], sa_buff, sg_get_unaligned_be16(bp + 6),
+ rwcdlp, cdlp, name_buff);
+ }
+ if (jsp->pr_as_json) {
+ snprintf(b, blen, "0x%x", opcode);
+ sgj_js_nv_s(jsp, jop, "operation_code", b);
+ if (sa_v) {
+ snprintf(b, blen, "0x%x", serv_act);
+ sgj_js_nv_s(jsp, jop, "service_action", b);
+ }
+ if (name_buff[0])
+ sgj_js_nv_s(jsp, jop, "name", name_buff);
+ sgj_js_nv_i(jsp, jop, "rwcdlp", (byt5 >> 6) & 0x1);
+ sgj_js_nv_i(jsp, jop, "mlu", (byt5 >> 4) & 0x3);
+ sgj_js_nv_i(jsp, jop, "cdlp", (byt5 >> 2) & 0x3);
+ sgj_js_nv_i(jsp, jop, "ctdp", (byt5 >> 1) & 0x1);
+ sgj_js_nv_i(jsp, jop, "servactv", byt5 & 0x1);
+ sgj_js_nv_i(jsp, jop, "cdb_length",
+ sg_get_unaligned_be16(bp + 6));
+
+ sgj_js_nv_o(jsp, jap, NULL /* implies an array add */, jop);
+ }
+
+ if (op->do_mask && ptvp) {
+ int cdb_sz;
+ uint8_t d[64];
+
+ n = 0;
+ memset(d, 0, sizeof(d));
+ res = do_rsoc(ptvp, false, (sa_v ? 2 : 1), opcode, serv_act,
+ d, sizeof(d), &act_len, true, op->verbose);
+ if (0 == res) {
+ int nn;
+
+ cdb_sz = sg_get_unaligned_be16(d + 2);
+ cdb_sz = (cdb_sz < act_len) ? cdb_sz : act_len;
+ if ((cdb_sz > 0) && (cdb_sz <= 80)) {
+ if (op->do_compact)
+ n += sg_scnpr(b + n, blen - n,
+ " usage: ");
+ else
+ n += sg_scnpr(b + n, blen - n, " cdb usage: ");
+ nn = n;
+ for (m = 0; (m < cdb_sz) && ((4 + m) < (int)sizeof(d));
+ ++m)
+ n += sg_scnpr(b + n, blen - n, "%.2x ", d[4 + m]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json) {
+ int l;
+ char *b2p = b + nn;
+ sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop,
+ "one_command_descriptor");
+
+ l = strlen(b2p);
+ if ((l > 0) && (' ' == b2p[l - 1]))
+ b2p[l - 1] = '\0';
+ sgj_js_nv_i(jsp, jo2p, "cdb_size", cdb_sz);
+ sgj_js_nv_s(jsp, jo2p, "cdb_usage_data", b2p);
+ }
+ }
+ } else
+ goto err_out;
+ }
+ }
+ res = 0;
+err_out:
+ if (sort_arr)
+ free(sort_arr);
+ return res;
+}
+
+static void
+decode_cmd_timeout_desc(uint8_t * dp, int max_b_len, char * b,
+ struct opts_t * op)
+{
+ int len;
+ unsigned int timeout;
+ sgj_state * jsp = &op->json_st;
+
+ if ((max_b_len < 2) || (NULL == dp))
+ return;
+ b[max_b_len - 1] = '\0';
+ --max_b_len;
+ len = sg_get_unaligned_be16(dp + 0);
+ if (10 != len) {
+ snprintf(b, max_b_len, "command timeout descriptor length %d "
+ "(expect 10)", len);
+ return;
+ }
+ timeout = sg_get_unaligned_be32(dp + 4);
+ if (0 == timeout)
+ snprintf(b, max_b_len, "no nominal timeout, ");
+ else
+ snprintf(b, max_b_len, "nominal timeout: %u secs, ", timeout);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_i(jsp, jsp->userp, "command_specific", dp[3]);
+ sgj_js_nv_i(jsp, jsp->userp, "nominal_command_processing_timeout",
+ timeout);
+ }
+ len = strlen(b);
+ max_b_len -= len;
+ b += len;
+ timeout = sg_get_unaligned_be32(dp + 8);
+ if (0 == timeout)
+ snprintf(b, max_b_len, "no recommended timeout");
+ else
+ snprintf(b, max_b_len, "recommended timeout: %u secs", timeout);
+ if (jsp->pr_as_json)
+ sgj_js_nv_i(jsp, jsp->userp, "recommended_command_timeout", timeout);
+ return;
+}
+
+/* For decoding a RSOC command's "One_command" parameter data which includes
+ * cdb usage data. */
+static void
+list_one(uint8_t * rsoc_buff, int cd_len, int rep_opts,
+ struct opts_t * op)
+{
+ bool valid = false;
+ int k, mlu, cdlp, rwcdlp, support, ctdp;
+ int n = 0;
+ uint8_t * bp;
+ const char * cp;
+ const char * dlp;
+ const char * mlu_p;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jop = NULL;
+ char name_buff[NAME_BUFF_SZ];
+ char d[64];
+ char b[192];
+ const int blen = sizeof(b);
+
+
+ jop = sgj_named_subobject_r(jsp, jsp->basep, "one_command_descriptor");
+ n += sg_scnpr(b + n, blen - n, "\n Opcode=0x%.2x", op->opcode);
+ if (rep_opts > 1)
+ n += sg_scnpr(b + n, blen - n, " Service_action=0x%.4x", op->servact);
+ sgj_pr_hr(jsp, "%s\n", b);
+ sg_get_opcode_sa_name(((op->opcode > 0) ? op->opcode : 0),
+ ((op->servact > 0) ? op->servact : 0),
+ peri_dtype, NAME_BUFF_SZ, name_buff);
+ sgj_pr_hr(jsp, " Command_name: %s\n", name_buff);
+ ctdp = !!(0x80 & rsoc_buff[1]);
+ support = rsoc_buff[1] & 7;
+ switch(support) {
+ case 0:
+ cp = "not currently available";
+ break;
+ case 1:
+ cp = "NOT supported";
+ break;
+ case 3:
+ cp = "supported [conforming to SCSI standard]";
+ valid = true;
+ break;
+ case 5:
+ cp = "supported [in a vendor specific manner]";
+ valid = true;
+ break;
+ default:
+ snprintf(name_buff, NAME_BUFF_SZ, "support reserved [0x%x]",
+ rsoc_buff[1] & 7);
+ cp = name_buff;
+ break;
+ }
+ cdlp = 0x3 & (rsoc_buff[1] >> 3);
+ rwcdlp = rsoc_buff[0] & 1;
+ switch (cdlp) {
+ case 0:
+ if (rwcdlp)
+ dlp = "Reserved [RWCDLP=1, CDLP=0]";
+ else
+ dlp = "No command duration limit mode page";
+ break;
+ case 1:
+ if (rwcdlp)
+ dlp = "Command duration limit T2A mode page";
+ else
+ dlp = "Command duration limit A mode page";
+ break;
+ case 2:
+ if (rwcdlp)
+ dlp = "Command duration limit T2B mode page";
+ else
+ dlp = "Command duration limit B mode page";
+ break;
+ default:
+ dlp = "reserved [CDLP=3]";
+ break;
+ }
+ sgj_pr_hr(jsp, " Command is %s\n", cp);
+ sgj_pr_hr(jsp, " %s\n", dlp);
+ mlu = 0x3 & (rsoc_buff[1] >> 5);
+ switch (mlu) {
+ case 0:
+ mlu_p = "not reported";
+ break;
+ case 1:
+ mlu_p = "affects only this logical unit";
+ break;
+ case 2:
+ mlu_p = "affects more than 1, but not all LUs in this target";
+ break;
+ case 3:
+ mlu_p = "affects all LUs in this target";
+ break;
+ default:
+ snprintf(d, sizeof(d), "reserved [MLU=%d]", mlu);
+ mlu_p = d;
+ break;
+ }
+ sgj_pr_hr(jsp, " Multiple Logical Units (MLU): %s\n", mlu_p);
+ if (valid) {
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Usage data: ");
+ bp = rsoc_buff + 4;
+ for (k = 0; k < cd_len; ++k)
+ n += sg_scnpr(b + n, blen - n, "%.2x ", bp[k]);
+ sgj_pr_hr(jsp, "%s\n", b);
+ }
+ if (jsp->pr_as_json) {
+ int l;
+
+ snprintf(b, blen, "0x%x", op->opcode);
+ sgj_js_nv_s(jsp, jop, "operation_code", b);
+ if (rep_opts > 1) {
+ snprintf(b, blen, "0x%x", op->servact);
+ sgj_js_nv_s(jsp, jop, "service_action", b);
+ }
+ sgj_js_nv_i(jsp, jop, "rwcdlp", rwcdlp);
+ sgj_js_nv_i(jsp, jop, "ctdp", ctdp);
+ sgj_js_nv_i(jsp, jop, "mlu", mlu);
+ sgj_js_nv_i(jsp, jop, "cdlp", cdlp);
+ sgj_js_nv_i(jsp, jop, "support", support);
+ sgj_js_nv_s(jsp, jop, "support_str", cp);
+ sgj_js_nv_i(jsp, jop, "cdb_size", cd_len);
+ n = 0;
+ for (k = 0; k < cd_len; ++k)
+ n += sg_scnpr(b + n, blen - n, "%.2x ", rsoc_buff[k + 4]);
+ l = strlen(b);
+ if ((l > 0) && (' ' == b[l - 1]))
+ b[l - 1] = '\0';
+ sgj_js_nv_s(jsp, jop, "cdb_usage_data", b);
+ }
+ if (ctdp) {
+ jsp->userp = sgj_named_subobject_r(jsp, jsp->basep,
+ "command_timeouts_descriptor");
+ bp = rsoc_buff + 4 + cd_len;
+ decode_cmd_timeout_desc(bp, NAME_BUFF_SZ, name_buff, op);
+ sgj_pr_hr(jsp, " %s\n", name_buff);
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json;
+ int cd_len, res, len, act_len, rq_len, in_len, vb;
+ int rep_opts = 0;
+ int sg_fd = -1;
+ const char * cp;
+ struct opts_t * op;
+ const char * op_name;
+ uint8_t * rsoc_buff = NULL;
+ uint8_t * free_rsoc_buff = NULL;
+ struct sg_pt_base * ptvp = NULL;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ char buff[48];
+ char b[80];
+ struct sg_simple_inquiry_resp inq_resp;
+ struct opts_t opts;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->opcode = -1;
+ op->servact = -1;
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ return 0;
+ }
+ jsp = &op->json_st;
+ as_json = jsp->pr_as_json;
+ if (as_json) {
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ goto fini;
+ }
+ vb = op->verbose;
+ if (op->do_enumerate) {
+ char name_buff[NAME_BUFF_SZ];
+
+ if (op->do_taskman)
+ printf("enumerate not supported with task management "
+ "functions\n");
+ else { /* SCSI command */
+ if (op->opcode < 0)
+ op->opcode = 0;
+ if (op->servact < 0)
+ op->servact = 0;
+ if (peri_dtype < 0)
+ peri_dtype = 0;
+ printf("SCSI command:");
+ if (vb)
+ printf(" [opcode=0x%x, sa=0x%x, pdt=0x%x]\n", op->opcode,
+ op->servact, peri_dtype);
+ else
+ printf("\n");
+ sg_get_opcode_sa_name(op->opcode, op->servact, peri_dtype,
+ NAME_BUFF_SZ, name_buff);
+ printf(" %s\n", name_buff);
+ }
+ goto fini;
+ } else if (op->inhex_fn) {
+ if (op->device_name) {
+ if (! as_json)
+ pr2serr("ignoring DEVICE, best to give DEVICE or "
+ "--inhex=FN, but not both\n");
+ op->device_name = NULL;
+ }
+ } else if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ res = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if ((-1 != op->servact) && (-1 == op->opcode)) {
+ pr2serr("When '-s' is chosen, so must '-o' be chosen\n");
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ res = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if (op->do_unsorted && op->do_alpha)
+ pr2serr("warning: unsorted ('-u') and alpha ('-a') options chosen, "
+ "ignoring alpha\n");
+ if (op->do_taskman && ((-1 != op->opcode) || op->do_alpha ||
+ op->do_unsorted)) {
+ pr2serr("warning: task management functions ('-t') chosen so alpha "
+ "('-a'),\n unsorted ('-u') and opcode ('-o') "
+ "options ignored\n");
+ }
+ op_name = op->do_taskman ? "Report supported task management functions" :
+ "Report supported operation codes";
+
+ rsoc_buff = (uint8_t *)sg_memalign(MX_ALLOC_LEN, 0, &free_rsoc_buff,
+ false);
+ if (NULL == rsoc_buff) {
+ pr2serr("Unable to allocate memory\n");
+ res = sg_convert_errno(ENOMEM);
+ no_final_msg = true;
+ goto err_out;
+ }
+
+ if (op->inhex_fn) {
+ if ((res = sg_f2hex_arr(op->inhex_fn, op->do_raw, false, rsoc_buff,
+ &in_len, MX_ALLOC_LEN))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == res)
+ pr2serr("decode buffer [%d] not large enough??\n",
+ MX_ALLOC_LEN);
+ goto err_out;
+ }
+ if (op->verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (op->do_raw)
+ op->do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--inhex=%s only decoded %d bytes (needs 4 at "
+ "least)\n", op->inhex_fn, in_len);
+ res = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ res = 0;
+ act_len = in_len;
+ goto start_response;
+ }
+ if (op->opcode < 0) {
+ /* Try to open read-only */
+ if ((sg_fd = scsi_pt_open_device(op->device_name, true, vb)) < 0) {
+ int err = -sg_fd;
+
+ if (op->verbose)
+ pr2serr("sg_opcodes: error opening file (ro): %s: %s\n",
+ op->device_name, safe_strerror(err));
+#ifndef SG_LIB_WIN32
+ if (ENOENT == err) {
+ /* file or directory in the file's path doesn't exist, no
+ * point in retrying with read-write flag */
+ res = sg_convert_errno(err);
+ goto err_out;
+ }
+#endif
+ goto open_rw;
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose);
+ if (NULL == ptvp) {
+ pr2serr("Out of memory (ro)\n");
+ res = sg_convert_errno(ENOMEM);
+ no_final_msg = true;
+ goto err_out;
+ }
+ if (op->no_inquiry && (peri_dtype < 0))
+ pr2serr("--no-inquiry ignored because --pdt= not given\n");
+ if (op->no_inquiry && (peri_dtype >= 0))
+ ;
+ else if (0 == sg_simple_inquiry_pt(ptvp, &inq_resp, true, vb)) {
+ peri_dtype = inq_resp.peripheral_type;
+ if (! (as_json || op->do_raw || op->no_inquiry ||
+ (op->do_hex > 2))) {
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor,
+ inq_resp.product, inq_resp.revision);
+ cp = sg_get_pdt_str(peri_dtype, sizeof(buff), buff);
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_dtype);
+ }
+ } else {
+ pr2serr("sg_opcodes: %s doesn't respond to a SCSI INQUIRY\n",
+ op->device_name);
+ res = SG_LIB_CAT_OTHER;
+ no_final_msg = true;
+ goto err_out;
+ }
+ }
+
+open_rw: /* if not already open */
+ if (sg_fd < 0) {
+ sg_fd = scsi_pt_open_device(op->device_name, false /* RW */, vb);
+ if (sg_fd < 0) {
+ pr2serr("sg_opcodes: error opening file (rw): %s: %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ res = sg_convert_errno(-sg_fd);
+ no_final_msg = true;
+ goto err_out;
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose);
+ if (NULL == ptvp) {
+ pr2serr("Out of memory (rw)\n");
+ res = sg_convert_errno(ENOMEM);
+ no_final_msg = true;
+ goto err_out;
+ }
+ }
+ if (op->opcode >= 0)
+ rep_opts = ((op->servact >= 0) ? 2 : 1);
+ if (op->do_taskman) {
+ rq_len = (op->do_repd ? 16 : 4);
+ res = do_rstmf(ptvp, op->do_repd, rsoc_buff, rq_len, &act_len, true,
+ vb);
+ } else {
+ rq_len = MX_ALLOC_LEN;
+ res = do_rsoc(ptvp, op->do_rctd, rep_opts, op->opcode, op->servact,
+ rsoc_buff, rq_len, &act_len, true, vb);
+ }
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("%s: %s\n", op_name, b);
+ no_final_msg = true;
+ if ((0 == op->servact) && (op->opcode >= 0))
+ pr2serr(" >> perhaps try again without a service action "
+ "[SA] of 0\n");
+ goto err_out;
+ }
+ act_len = (rq_len < act_len) ? rq_len : act_len;
+
+start_response:
+ if (act_len < 4) {
+ pr2serr("Actual length of response [%d] is too small\n", act_len);
+ res = SG_LIB_CAT_OTHER;
+ no_final_msg = true;
+ goto err_out;
+ }
+ if (op->do_taskman) {
+ if (op->do_raw) {
+ dStrRaw((const char *)rsoc_buff, act_len);
+ goto fini;
+ }
+ if (op->do_hex) {
+ if (op->do_hex > 2)
+ hex2stdout(rsoc_buff, act_len, -1);
+ else {
+ printf("\nTask Management Functions supported by device:\n");
+ if (2 == op->do_hex)
+ hex2stdout(rsoc_buff, act_len, 0);
+ else
+ hex2stdout(rsoc_buff, act_len, 1);
+ }
+ goto fini;
+ }
+ if (jsp->pr_as_json) {
+ sgj_js_nv_b(jsp, jop, "ats", rsoc_buff[0] & 0x80);
+ sgj_js_nv_b(jsp, jop, "atss", rsoc_buff[0] & 0x40);
+ sgj_js_nv_b(jsp, jop, "cacas", rsoc_buff[0] & 0x20);
+ sgj_js_nv_b(jsp, jop, "ctss", rsoc_buff[0] & 0x10);
+ sgj_js_nv_b(jsp, jop, "lurs", rsoc_buff[0] & 0x8);
+ sgj_js_nv_b(jsp, jop, "qts", rsoc_buff[0] & 0x4);
+ sgj_js_nv_b(jsp, jop, "trs", rsoc_buff[0] & 0x2);
+ sgj_js_nv_b(jsp, jop, "ws", rsoc_buff[0] & 0x1);
+ sgj_js_nv_b(jsp, jop, "qaes", rsoc_buff[1] & 0x4);
+ sgj_js_nv_b(jsp, jop, "qtss", rsoc_buff[1] & 0x2);
+ sgj_js_nv_b(jsp, jop, "itnrs", rsoc_buff[1] & 0x1);
+ if (! jsp->pr_out_hr)
+ goto fini;
+ }
+ sgj_pr_hr(jsp, "\nTask Management Functions supported by device:\n");
+ if (rsoc_buff[0] & 0x80)
+ sgj_pr_hr(jsp, " Abort task\n");
+ if (rsoc_buff[0] & 0x40)
+ sgj_pr_hr(jsp, " Abort task set\n");
+ if (rsoc_buff[0] & 0x20)
+ sgj_pr_hr(jsp, " Clear ACA\n");
+ if (rsoc_buff[0] & 0x10)
+ sgj_pr_hr(jsp, " Clear task set\n");
+ if (rsoc_buff[0] & 0x8)
+ sgj_pr_hr(jsp, " Logical unit reset\n");
+ if (rsoc_buff[0] & 0x4)
+ sgj_pr_hr(jsp, " Query task\n");
+ if (rsoc_buff[0] & 0x2)
+ sgj_pr_hr(jsp, " Target reset (obsolete)\n");
+ if (rsoc_buff[0] & 0x1)
+ sgj_pr_hr(jsp, " Wakeup (obsolete)\n");
+ if (rsoc_buff[1] & 0x4)
+ sgj_pr_hr(jsp, " Query asynchronous event\n");
+ if (rsoc_buff[1] & 0x2)
+ sgj_pr_hr(jsp, " Query task set\n");
+ if (rsoc_buff[1] & 0x1)
+ sgj_pr_hr(jsp, " I_T nexus reset\n");
+ if (op->do_repd) {
+ if (rsoc_buff[3] < 0xc) {
+ pr2serr("when REPD given, byte 3 of response should be >= "
+ "12\n");
+ res = SG_LIB_CAT_OTHER;
+ no_final_msg = true;
+ goto err_out;
+ } else
+ sgj_pr_hr(jsp, " Extended parameter data:\n");
+ sgj_pr_hr(jsp, " TMFTMOV=%d\n", !!(rsoc_buff[4] & 0x1));
+ sgj_pr_hr(jsp, " ATTS=%d\n", !!(rsoc_buff[6] & 0x80));
+ sgj_pr_hr(jsp, " ATSTS=%d\n", !!(rsoc_buff[6] & 0x40));
+ sgj_pr_hr(jsp, " CACATS=%d\n", !!(rsoc_buff[6] & 0x20));
+ sgj_pr_hr(jsp, " CTSTS=%d\n", !!(rsoc_buff[6] & 0x10));
+ sgj_pr_hr(jsp, " LURTS=%d\n", !!(rsoc_buff[6] & 0x8));
+ sgj_pr_hr(jsp, " QTTS=%d\n", !!(rsoc_buff[6] & 0x4));
+ sgj_pr_hr(jsp, " QAETS=%d\n", !!(rsoc_buff[7] & 0x4));
+ sgj_pr_hr(jsp, " QTSTS=%d\n", !!(rsoc_buff[7] & 0x2));
+ sgj_pr_hr(jsp, " ITNRTS=%d\n", !!(rsoc_buff[7] & 0x1));
+ sgj_pr_hr(jsp, " tmf long timeout: %u (100 ms units)\n",
+ sg_get_unaligned_be32(rsoc_buff + 8));
+ sgj_pr_hr(jsp, " tmf short timeout: %u (100 ms units)\n",
+ sg_get_unaligned_be32(rsoc_buff + 12));
+ }
+ } else if (0 == rep_opts) { /* list all supported operation codes */
+ len = sg_get_unaligned_be32(rsoc_buff + 0) + 4;
+ len = (len < act_len) ? len : act_len;
+ if (op->do_raw) {
+ dStrRaw((const char *)rsoc_buff, len);
+ goto fini;
+ }
+ if (op->do_hex) {
+ if (op->do_hex > 2)
+ hex2stdout(rsoc_buff, len, -1);
+ else if (2 == op->do_hex)
+ hex2stdout(rsoc_buff, len, 0);
+ else
+ hex2stdout(rsoc_buff, len, 1);
+ goto fini;
+ }
+ list_all_codes(rsoc_buff, len, op, ptvp);
+ } else { /* asked about specific command */
+ cd_len = sg_get_unaligned_be16(rsoc_buff + 2);
+ len = cd_len + 4;
+ len = (len < act_len) ? len : act_len;
+ cd_len = (cd_len < act_len) ? cd_len : act_len;
+ if (op->do_raw) {
+ dStrRaw((const char *)rsoc_buff, len);
+ goto fini;
+ }
+ if (op->do_hex) {
+ if (op->do_hex > 2)
+ hex2stdout(rsoc_buff, len, -1);
+ else if (2 == op->do_hex)
+ hex2stdout(rsoc_buff, len, 0);
+ else
+ hex2stdout(rsoc_buff, len, 1);
+ goto fini;
+ }
+ list_one(rsoc_buff, cd_len, rep_opts, op);
+ }
+fini:
+ res = 0;
+
+err_out:
+ if (free_rsoc_buff)
+ free(free_rsoc_buff);
+ if (! op->inhex_fn) {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ if (sg_fd >= 0)
+ scsi_pt_close_device(sg_fd);
+ }
+ if ((0 == op->verbose) && (! no_final_msg)) {
+ if (! sg_if_can2stderr("sg_opcodes failed: ", res))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ res = (res >= 0) ? res : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, res, stdout);
+ sgj_finish(jsp);
+ }
+ return res;
+}
diff --git a/src/sg_persist.c b/src/sg_persist.c
new file mode 100644
index 00000000..872f16ee
--- /dev/null
+++ b/src/sg_persist.c
@@ -0,0 +1,1324 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2004-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program issues the SCSI PERSISTENT IN and OUT commands.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "0.69 20220118";
+
+
+#define PRIN_RKEY_SA 0x0
+#define PRIN_RRES_SA 0x1
+#define PRIN_RCAP_SA 0x2
+#define PRIN_RFSTAT_SA 0x3
+#define PROUT_REG_SA 0x0
+#define PROUT_RES_SA 0x1
+#define PROUT_REL_SA 0x2
+#define PROUT_CLEAR_SA 0x3
+#define PROUT_PREE_SA 0x4
+#define PROUT_PREE_AB_SA 0x5
+#define PROUT_REG_IGN_SA 0x6
+#define PROUT_REG_MOVE_SA 0x7
+#define PROUT_REPL_LOST_SA 0x8
+#define MX_ALLOC_LEN 8192
+#define MX_TIDS 32
+#define MX_TID_LEN 256
+
+#define ME "sg_persist"
+
+#define SG_PERSIST_IN_RDONLY "SG_PERSIST_IN_RDONLY"
+
+struct opts_t {
+ bool inquiry; /* set true by default (unlike most bools) */
+ bool param_alltgpt;
+ bool param_aptpl;
+ bool param_unreg;
+ bool pr_in; /* true: PR_IN (def); false: PR_OUT */
+ bool readonly;
+ bool readwrite_force;/* set when '-yy' given. Ooverrides environment
+ variable SG_PERSIST_IN_RDONLY and opens RW */
+ bool verbose_given;
+ bool version_given;
+ int hex;
+ int num_transportids;
+ int prin_sa;
+ int prout_sa;
+ int verbose;
+ uint32_t alloc_len;
+ uint32_t param_rtp;
+ uint32_t prout_type;
+ uint64_t param_rk;
+ uint64_t param_sark;
+ uint8_t transportid_arr[MX_TIDS * MX_TID_LEN];
+};
+
+
+static struct option long_options[] = {
+ {"alloc-length", required_argument, 0, 'l'},
+ {"alloc_length", required_argument, 0, 'l'},
+ {"clear", no_argument, 0, 'C'},
+ {"device", required_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", no_argument, 0, 'i'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"no-inquiry", no_argument, 0, 'n'},
+ {"no_inquiry", no_argument, 0, 'n'},
+ {"out", no_argument, 0, 'o'},
+ {"param-alltgpt", no_argument, 0, 'Y'},
+ {"param_alltgpt", no_argument, 0, 'Y'},
+ {"param-aptpl", no_argument, 0, 'Z'},
+ {"param_aptpl", no_argument, 0, 'Z'},
+ {"param-rk", required_argument, 0, 'K'},
+ {"param_rk", required_argument, 0, 'K'},
+ {"param-sark", required_argument, 0, 'S'},
+ {"param_sark", required_argument, 0, 'S'},
+ {"param-unreg", no_argument, 0, 'U'},
+ {"param_unreg", no_argument, 0, 'U'},
+ {"preempt", no_argument, 0, 'P'},
+ {"preempt-abort", no_argument, 0, 'A'},
+ {"preempt_abort", no_argument, 0, 'A'},
+ {"prout-type", required_argument, 0, 'T'},
+ {"prout_type", required_argument, 0, 'T'},
+ {"read-full-status", no_argument, 0, 's'},
+ {"read_full_status", no_argument, 0, 's'},
+ {"read-keys", no_argument, 0, 'k'},
+ {"read_keys", no_argument, 0, 'k'},
+ {"readonly", no_argument, 0, 'y'},
+ {"read-reservation", no_argument, 0, 'r'},
+ {"read_reservation", no_argument, 0, 'r'},
+ {"read-status", no_argument, 0, 's'},
+ {"read_status", no_argument, 0, 's'},
+ {"register", no_argument, 0, 'G'},
+ {"register-ignore", no_argument, 0, 'I'},
+ {"register_ignore", no_argument, 0, 'I'},
+ {"register-move", no_argument, 0, 'M'},
+ {"register_move", no_argument, 0, 'M'},
+ {"release", no_argument, 0, 'L'},
+ {"relative-target-port", required_argument, 0, 'Q'},
+ {"relative_target_port", required_argument, 0, 'Q'},
+ {"replace-lost", no_argument, 0, 'z'},
+ {"replace_lost", no_argument, 0, 'z'},
+ {"report-capabilities", no_argument, 0, 'c'},
+ {"report_capabilities", no_argument, 0, 'c'},
+ {"reserve", no_argument, 0, 'R'},
+ {"transport-id", required_argument, 0, 'X'},
+ {"transport_id", required_argument, 0, 'X'},
+ {"unreg", no_argument, 0, 'U'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}
+};
+
+static const char * prin_sa_strs[] = {
+ "Read keys",
+ "Read reservation",
+ "Report capabilities",
+ "Read full status",
+ "[reserved 0x4]",
+ "[reserved 0x5]",
+ "[reserved 0x6]",
+ "[reserved 0x7]",
+};
+static const int num_prin_sa_strs = SG_ARRAY_SIZE(prin_sa_strs);
+
+static const char * prout_sa_strs[] = {
+ "Register",
+ "Reserve",
+ "Release",
+ "Clear",
+ "Preempt",
+ "Preempt and abort",
+ "Register and ignore existing key",
+ "Register and move",
+ "Replace lost reservation",
+ "[reserved 0x9]",
+};
+static const int num_prout_sa_strs = SG_ARRAY_SIZE(prout_sa_strs);
+
+static const char * pr_type_strs[] = {
+ "obsolete [0]",
+ "Write Exclusive",
+ "obsolete [2]",
+ "Exclusive Access",
+ "obsolete [4]",
+ "Write Exclusive, registrants only",
+ "Exclusive Access, registrants only",
+ "Write Exclusive, all registrants",
+ "Exclusive Access, all registrants",
+ "obsolete [9]", "obsolete [0xa]", "obsolete [0xb]", "obsolete [0xc]",
+ "obsolete [0xd]", "obsolete [0xe]", "obsolete [0xf]",
+};
+
+
+static void
+usage(int help)
+{
+ if (help < 2) {
+ pr2serr("Usage: sg_persist [OPTIONS] [DEVICE]\n"
+ " where the main OPTIONS are:\n"
+ " --clear|-C PR Out: Clear\n"
+ " --help|-h print usage message, "
+ "twice for more\n"
+ " --in|-i request PR In command "
+ "(default)\n"
+ " --out|-o request PR Out command\n"
+ " --param-rk=RK|-K RK PR Out parameter reservation "
+ "key\n"
+ " (RK is in hex)\n"
+ " --param-sark=SARK|-S SARK PR Out parameter service "
+ "action\n"
+ " reservation key (SARK is "
+ "in hex)\n"
+ " --preempt|-P PR Out: Preempt\n"
+ " --preempt-abort|-A PR Out: Preempt and Abort\n"
+ " --prout-type=TYPE|-T TYPE PR Out type field (see "
+ "'-hh')\n"
+ " --read-full-status|-s PR In: Read Full Status\n"
+ " --read-keys|-k PR In: Read Keys "
+ "(default)\n");
+ pr2serr(" --read-reservation|-r PR In: Read Reservation\n"
+ " --read-status|-s PR In: Read Full Status\n"
+ " --register|-G PR Out: Register\n"
+ " --register-ignore|-I PR Out: Register and Ignore\n"
+ " --register-move|-M PR Out: Register and Move\n"
+ " for '--register-move'\n"
+ " --release|-L PR Out: Release\n"
+ " --replace-lost|-x PR Out: Replace Lost "
+ "Reservation\n"
+ " --report-capabilities|-c PR In: Report Capabilities\n"
+ " --reserve|-R PR Out: Reserve\n"
+ " --unreg|-U optional with PR Out "
+ "Register and Move\n\n"
+ "Performs a SCSI PERSISTENT RESERVE (IN or OUT) command. "
+ "Invoking\n'sg_persist DEVICE' will do a PR In Read Keys "
+ "command. Use '-hh'\nfor more options and TYPE meanings.\n");
+ } else {
+ pr2serr("Usage: sg_persist [OPTIONS] [DEVICE]\n"
+ " where the other OPTIONS are:\n"
+ " --alloc-length=LEN|-l LEN allocation length hex "
+ "value (used with\n"
+ " PR In only) (default: 8192 "
+ "(2000 in hex))\n"
+ " --device=DEVICE|-d DEVICE supply DEVICE as an option "
+ "rather than\n"
+ " an argument\n"
+ " --hex|-H output response in hex (for "
+ "PR In commands)\n"
+ " --maxlen=LEN|-m LEN allocation length in "
+ "decimal, by default.\n"
+ " like --alloc-len= "
+ "(def: 8192, 8k, 2000h)\n"
+ " --no-inquiry|-n skip INQUIRY (default: do "
+ "INQUIRY)\n"
+ " --param-alltgpt|-Y PR Out parameter "
+ "'ALL_TG_PT'\n"
+ " --param-aptpl|-Z PR Out parameter 'APTPL'\n"
+ " --readonly|-y open DEVICE read-only (def: "
+ "read-write)\n"
+ " --relative-target-port=RTPI|-Q RTPI relative target "
+ "port "
+ "identifier\n"
+ " --transport-id=TIDS|-X TIDS one or more "
+ "TransportIDs can\n"
+ " be given in several "
+ "forms\n"
+ " --verbose|-v output additional debug "
+ "information\n"
+ " --version|-V output version string\n\n"
+ "For the main options use '--help' or '-h' once.\n\n\n");
+ pr2serr("PR Out TYPE field value meanings:\n"
+ " 0: obsolete (was 'read shared' in SPC)\n"
+ " 1: write exclusive\n"
+ " 2: obsolete (was 'read exclusive')\n"
+ " 3: exclusive access\n"
+ " 4: obsolete (was 'shared access')\n"
+ " 5: write exclusive, registrants only\n"
+ " 6: exclusive access, registrants only\n"
+ " 7: write exclusive, all registrants\n"
+ " 8: exclusive access, all registrants\n");
+ }
+}
+
+static int
+prin_work(int sg_fd, const struct opts_t * op)
+{
+ int k, j, num, add_len, add_desc_len;
+ int res = 0;
+ unsigned int pr_gen;
+ uint8_t * bp;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = sg_ll_persistent_reserve_in(sg_fd, op->prin_sa, pr_buff,
+ op->alloc_len, true, op->verbose);
+ if (res) {
+ char b[64];
+ char bb[80];
+
+ if (op->prin_sa < num_prin_sa_strs)
+ snprintf(b, sizeof(b), "%s", prin_sa_strs[op->prin_sa]);
+ else
+ snprintf(b, sizeof(b), "service action=0x%x", op->prin_sa);
+
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR in (%s): command not supported\n", b);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR in (%s): bad field in cdb or parameter list (perhaps "
+ "unsupported service action)\n", b);
+ else {
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR in (%s): %s\n", b, bb);
+ }
+ goto fini;
+ }
+ if (PRIN_RCAP_SA == op->prin_sa) {
+ if (8 != pr_buff[1]) {
+ pr2serr("Unexpected response for PRIN Report Capabilities\n");
+ if (op->hex)
+ hex2stdout(pr_buff, pr_buff[1], 1);
+ res = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ if (op->hex)
+ hex2stdout(pr_buff, 8, 1);
+ else {
+ printf("Report capabilities response:\n");
+ printf(" Replace Lost Reservation Capable(RLR_C): %d\n",
+ !!(pr_buff[2] & 0x80)); /* added spc4r26 */
+ printf(" Compatible Reservation Handling(CRH): %d\n",
+ !!(pr_buff[2] & 0x10));
+ printf(" Specify Initiator Ports Capable(SIP_C): %d\n",
+ !!(pr_buff[2] & 0x8));
+ printf(" All Target Ports Capable(ATP_C): %d\n",
+ !!(pr_buff[2] & 0x4));
+ printf(" Persist Through Power Loss Capable(PTPL_C): %d\n",
+ !!(pr_buff[2] & 0x1));
+ printf(" Type Mask Valid(TMV): %d\n", !!(pr_buff[3] & 0x80));
+ printf(" Allow Commands: %d\n", (pr_buff[3] >> 4) & 0x7);
+ printf(" Persist Through Power Loss Active(PTPL_A): %d\n",
+ !!(pr_buff[3] & 0x1));
+ if (pr_buff[3] & 0x80) {
+ printf(" Support indicated in Type mask:\n");
+ printf(" %s: %d\n", pr_type_strs[7],
+ !!(pr_buff[4] & 0x80)); /* WR_EX_AR */
+ printf(" %s: %d\n", pr_type_strs[6],
+ !!(pr_buff[4] & 0x40)); /* EX_AC_RO */
+ printf(" %s: %d\n", pr_type_strs[5],
+ !!(pr_buff[4] & 0x20)); /* WR_EX_RO */
+ printf(" %s: %d\n", pr_type_strs[3],
+ !!(pr_buff[4] & 0x8)); /* EX_AC */
+ printf(" %s: %d\n", pr_type_strs[1],
+ !!(pr_buff[4] & 0x2)); /* WR_EX */
+ printf(" %s: %d\n", pr_type_strs[8],
+ !!(pr_buff[5] & 0x1)); /* EX_AC_AR */
+ }
+ }
+ } else {
+ pr_gen = sg_get_unaligned_be32(pr_buff + 0);
+ add_len = sg_get_unaligned_be32(pr_buff + 4);
+ if (op->hex) {
+ if (op->hex > 1)
+ hex2stdout(pr_buff, add_len + 8, ((2 == op->hex) ? 1 : -1));
+ else {
+ printf(" PR generation=0x%x, ", pr_gen);
+ if (add_len <= 0)
+ printf("Additional length=%d\n", add_len);
+ if ((uint32_t)add_len > (op->alloc_len - 8)) {
+ printf("Additional length too large=%d, truncate\n",
+ add_len);
+ hex2stdout((pr_buff + 8), op->alloc_len - 8, 1);
+ } else {
+ printf("Additional length=%d\n", add_len);
+ hex2stdout((pr_buff + 8), add_len, 1);
+ }
+ }
+ } else if (PRIN_RKEY_SA == op->prin_sa) {
+ printf(" PR generation=0x%x, ", pr_gen);
+ num = add_len / 8;
+ if (num > 0) {
+ if (1 == num)
+ printf("1 registered reservation key follows:\n");
+ else
+ printf("%d registered reservation keys follow:\n", num);
+ bp = pr_buff + 8;
+ for (k = 0; k < num; ++k, bp += 8)
+ printf(" 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(bp + 0));
+ } else
+ printf("there are NO registered reservation keys\n");
+ } else if (PRIN_RRES_SA == op->prin_sa) {
+ printf(" PR generation=0x%x, ", pr_gen);
+ num = add_len / 16;
+ if (num > 0) {
+ printf("Reservation follows:\n");
+ bp = pr_buff + 8;
+ printf(" Key=0x%" PRIx64 "\n", sg_get_unaligned_be64(bp));
+ j = ((bp[13] >> 4) & 0xf);
+ if (0 == j)
+ printf(" scope: LU_SCOPE, ");
+ else
+ printf(" scope: %d ", j);
+ j = (bp[13] & 0xf);
+ printf(" type: %s\n", pr_type_strs[j]);
+ } else
+ printf("there is NO reservation held\n");
+ } else if (PRIN_RFSTAT_SA == op->prin_sa) {
+ printf(" PR generation=0x%x\n", pr_gen);
+ bp = pr_buff + 8;
+ if (0 == add_len) {
+ printf(" No full status descriptors\n");
+ if (op->verbose)
+ printf(" So there are no registered IT nexuses\n");
+ }
+ for (k = 0; k < add_len; k += num, bp += num) {
+ add_desc_len = sg_get_unaligned_be32(bp + 20);
+ num = 24 + add_desc_len;
+ printf(" Key=0x%" PRIx64 "\n", sg_get_unaligned_be64(bp));
+ if (bp[12] & 0x2)
+ printf(" All target ports bit set\n");
+ else {
+ printf(" All target ports bit clear\n");
+ printf(" Relative port address: 0x%x\n",
+ sg_get_unaligned_be16(bp + 18));
+ }
+ if (bp[12] & 0x1) {
+ printf(" << Reservation holder >>\n");
+ j = ((bp[13] >> 4) & 0xf);
+ if (0 == j)
+ printf(" scope: LU_SCOPE, ");
+ else
+ printf(" scope: %d ", j);
+ j = (bp[13] & 0xf);
+ printf(" type: %s\n", pr_type_strs[j]);
+ } else
+ printf(" not reservation holder\n");
+ if (add_desc_len > 0) {
+ char b[1024];
+
+ printf("%s", sg_decode_transportid_str(" ", bp + 24,
+ add_desc_len, true, sizeof(b), b));
+ }
+ }
+ }
+ }
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+/* Compact the 2 dimensional transportid_arr into a one dimensional
+ * array in place returning the length. */
+static int
+compact_transportid_array(struct opts_t * op)
+{
+ int k, off, protocol_id, len;
+ int compact_len = 0;
+ uint8_t * bp = op->transportid_arr;
+
+ for (k = 0, off = 0; ((k < op->num_transportids) && (k < MX_TIDS));
+ ++k, off += MX_TID_LEN) {
+ protocol_id = bp[off] & 0xf;
+ if (TPROTO_ISCSI == protocol_id) {
+ len = sg_get_unaligned_be16(bp + off + 2) + 4;
+ if (len < 24)
+ len = 24;
+ if (off > compact_len)
+ memmove(bp + compact_len, bp + off, len);
+ compact_len += len;
+
+ } else {
+ if (off > compact_len)
+ memmove(bp + compact_len, bp + off, 24);
+ compact_len += 24;
+ }
+ }
+ return compact_len;
+}
+
+static int
+prout_work(int sg_fd, struct opts_t * op)
+{
+ int len, t_arr_len;
+ int res = 0;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+ char b[64];
+ char bb[80];
+
+ t_arr_len = compact_transportid_array(op);
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ sg_put_unaligned_be64(op->param_rk, pr_buff + 0);
+ sg_put_unaligned_be64(op->param_sark, pr_buff + 8);
+ if (op->param_alltgpt)
+ pr_buff[20] |= 0x4;
+ if (op->param_aptpl)
+ pr_buff[20] |= 0x1;
+ len = 24;
+ if (t_arr_len > 0) {
+ pr_buff[20] |= 0x8; /* set SPEC_I_PT bit */
+ memcpy(&pr_buff[28], op->transportid_arr, t_arr_len);
+ len += (t_arr_len + 4);
+ sg_put_unaligned_be32((uint32_t)t_arr_len, pr_buff + 24);
+ }
+ res = sg_ll_persistent_reserve_out(sg_fd, op->prout_sa, 0 /* rq_scope */,
+ op->prout_type, pr_buff, len, true,
+ op->verbose);
+ if (res || op->verbose) {
+ if (op->prout_sa < num_prout_sa_strs)
+ snprintf(b, sizeof(b), "%s", prout_sa_strs[op->prout_sa]);
+ else
+ snprintf(b, sizeof(b), "service action=0x%x", op->prout_sa);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR out (%s): command not supported\n", b);
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR out (%s): bad field in cdb or parameter list "
+ "(perhaps unsupported service action)\n", b);
+ else {
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR out (%s): %s\n", b, bb);
+ }
+ goto fini;
+ } else if (op->verbose)
+ pr2serr("PR out: command (%s) successful\n", b);
+ }
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+static int
+prout_reg_move_work(int sg_fd, struct opts_t * op)
+{
+ int len, t_arr_len;
+ int res = 0;
+ uint8_t * pr_buff = NULL;
+ uint8_t * free_pr_buff = NULL;
+
+ t_arr_len = compact_transportid_array(op);
+ pr_buff = sg_memalign(op->alloc_len, 0 /* page aligned */, &free_pr_buff,
+ false);
+ if (NULL == pr_buff) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->alloc_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ sg_put_unaligned_be64(op->param_rk, pr_buff + 0);
+ sg_put_unaligned_be64(op->param_sark, pr_buff + 8);
+ if (op->param_unreg)
+ pr_buff[17] |= 0x2;
+ if (op->param_aptpl)
+ pr_buff[17] |= 0x1;
+ sg_put_unaligned_be16(op->param_rtp, pr_buff + 18);
+ len = 24;
+ if (t_arr_len > 0) {
+ memcpy(&pr_buff[24], op->transportid_arr, t_arr_len);
+ len += t_arr_len;
+ sg_put_unaligned_be32((uint32_t)t_arr_len, pr_buff + 20);
+ }
+ res = sg_ll_persistent_reserve_out(sg_fd, PROUT_REG_MOVE_SA,
+ 0 /* rq_scope */, op->prout_type,
+ pr_buff, len, true, op->verbose);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("PR out (register and move): command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("PR out (register and move): bad field in cdb or "
+ "parameter list (perhaps unsupported service action)\n");
+ else {
+ char bb[80];
+
+ sg_get_category_sense_str(res, sizeof(bb), bb, op->verbose);
+ pr2serr("PR out (register and move): %s\n", bb);
+ }
+ goto fini;
+ } else if (op->verbose)
+ pr2serr("PR out: 'register and move' command successful\n");
+fini:
+ if (free_pr_buff)
+ free(free_pr_buff);
+ return res;
+}
+
+/* Decode various symbolic forms of TransportIDs into SPC-4 format.
+ * Returns 1 if one found, else returns 0. */
+static int
+decode_sym_transportid(const char * lcp, uint8_t * tidp)
+{
+ int k, j, n, b, c, len, alen;
+ unsigned int ui;
+ const char * ecp;
+ const char * isip;
+
+ memset(tidp, 0, 24);
+ if ((0 == memcmp("sas,", lcp, 4)) || (0 == memcmp("SAS,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SAS TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SAS;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[4 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("spi,", lcp, 4)) ||
+ (0 == memcmp("SPI,", lcp, 4))) {
+ lcp += 4;
+ if (2 != sscanf(lcp, "%d,%d", &b, &c)) {
+ pr2serr("badly formed symbolic SPI TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SPI;
+ sg_put_unaligned_be16((uint16_t)b, tidp + 2);
+ sg_put_unaligned_be16((uint16_t)c, tidp + 6);
+ return 1;
+ } else if ((0 == memcmp("fcp,", lcp, 4)) ||
+ (0 == memcmp("FCP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic FCP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_FCP;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("sbp,", lcp, 4)) ||
+ (0 == memcmp("SBP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SBP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_1394;
+ for (k = 0, j = 0, b = 0; k < 16; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if ((0 == memcmp("srp,", lcp, 4)) ||
+ (0 == memcmp("SRP,", lcp, 4))) {
+ lcp += 4;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF");
+ if (16 != k) {
+ pr2serr("badly formed symbolic SRP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SRP;
+ for (k = 0, j = 0, b = 0; k < 32; ++k) {
+ c = lcp[k];
+ if (isdigit(c))
+ n = c - 0x30;
+ else if (isupper(c))
+ n = c - 0x37;
+ else
+ n = c - 0x57;
+ if (k & 1) {
+ tidp[8 + j] = b | n;
+ ++j;
+ } else
+ b = n << 4;
+ }
+ return 1;
+ } else if (0 == memcmp("iqn.", lcp, 4)) {
+ ecp = strpbrk(lcp, " \t");
+ isip = strstr(lcp, ",i,0x");
+ if (ecp && (isip > ecp))
+ isip = NULL;
+ len = ecp ? (ecp - lcp) : (int)strlen(lcp);
+ tidp[0] = TPROTO_ISCSI | (isip ? 0x40 : 0x0);
+ alen = len + 1; /* at least one trailing null */
+ if (alen < 20)
+ alen = 20;
+ else if (0 != (alen % 4))
+ alen = ((alen / 4) + 1) * 4;
+ if (alen > 241) { /* sam5r02.pdf A.2 (Annex) */
+ pr2serr("iSCSI name too long, alen=%d\n", alen);
+ return 0;
+ }
+ tidp[3] = alen & 0xff;
+ memcpy(tidp + 4, lcp, len);
+ return 1;
+ } else if ((0 == memcmp("sop,", lcp, 4)) ||
+ (0 == memcmp("SOP,", lcp, 4))) {
+ lcp += 4;
+ if (2 != sscanf(lcp, "%x", &ui)) {
+ pr2serr("badly formed symbolic SOP TransportID: %s\n", lcp);
+ return 0;
+ }
+ tidp[0] = TPROTO_SOP;
+ sg_put_unaligned_be16((uint16_t)ui, tidp + 2);
+ return 1;
+ }
+ pr2serr("unable to parse symbolic TransportID: %s\n", lcp);
+ return 0;
+}
+
+/* Read one or more TransportIDs from the given file or stdin. Reads from
+ * stdin when 'fnp' is NULL. Returns 0 if successful, 1 otherwise. */
+static int
+decode_file_tids(const char * fnp, struct opts_t * op)
+{
+ bool split_line;
+ int in_len, k, j, m;
+ int off = 0;
+ int num = 0;
+ unsigned int h;
+ FILE * fp = stdin;
+ const char * lcp;
+ uint8_t * tid_arr = op->transportid_arr;
+ char line[1024];
+ char carry_over[4];
+
+ if (fnp) {
+ fp = fopen(fnp, "r");
+ if (NULL == fp) {
+ pr2serr("%s: unable to open %s\n", __func__, fnp);
+ return 1;
+ }
+ }
+ carry_over[0] = 0;
+ for (j = 0, off = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit((uint8_t)line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%x", &h))
+ tid_arr[off - 1] = h; /* back up and overwrite */
+ else {
+ pr2serr("%s: carry_over error ['%s'] around line %d\n",
+ __func__, carry_over, j + 1);
+ goto bad;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ if (decode_sym_transportid(lcp, tid_arr + off))
+ goto my_cont_a;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__, j + 1,
+ m + k + 1);
+ goto bad;
+ }
+ for (k = 0; k < 1024; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff in line %d, pos "
+ "%d\n", __func__, j + 1, (int)(lcp - line + 1));
+ goto bad;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= (int)sizeof(op->transportid_arr)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ goto bad;
+ }
+ op->transportid_arr[off + k] = h;/* keep code checker happy */
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error in line %d, at pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ goto bad;
+ }
+ }
+my_cont_a:
+ off += MX_TID_LEN;
+ if (off >= (MX_TIDS * MX_TID_LEN)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ goto bad;
+ }
+ ++num;
+ }
+ op->num_transportids = num;
+ if (fnp)
+ fclose(fp);
+ return 0;
+
+bad:
+ if (fnp)
+ fclose(fp);
+ return 1;
+}
+
+/* Build transportid array which may contain one or more TransportIDs.
+ * A single TransportID can appear on the command line either as a list of
+ * comma (or single space) separated ASCII hex bytes, or in some transport
+ * protocol specific form (e.g. "sas,5000c50005b32001"). One or more
+ * TransportIDs may be given in a file (syntax: "file=<name>") or read from
+ * stdin in (when "-" is given). Fuller description in manpage of
+ * sg_persist(8). Returns 0 if successful, else 1 .
+ */
+static int
+build_transportid(const char * inp, struct opts_t * op)
+{
+ int in_len;
+ int k = 0;
+ unsigned int h;
+ const char * lcp;
+ uint8_t * tid_arr = op->transportid_arr;
+ char * cp;
+ char * c2p;
+
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len) {
+ op->num_transportids = 0;
+ }
+ if (('-' == inp[0]) ||
+ (0 == memcmp("file=", inp, 5)) ||
+ (0 == memcmp("FILE=", inp, 5))) {
+ if ('-' == inp[0])
+ lcp = NULL; /* read from stdin */
+ else
+ lcp = inp + 5; /* read from given file */
+ return decode_file_tids(lcp, op);
+ } else { /* TransportID given directly on command line */
+ if (decode_sym_transportid(lcp, tid_arr))
+ goto my_cont_b;
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return 1;
+ }
+ for (k = 0; k < (int)sizeof(op->transportid_arr); ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ return 1;
+ }
+ tid_arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+my_cont_b:
+ op->num_transportids = 1;
+ if (k >= (int)sizeof(op->transportid_arr)) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool got_maxlen, ok;
+ bool flagged = false;
+ bool want_prin = false;
+ bool want_prout = false;
+ int c, k, res;
+ int help = 0;
+ int num_prin_sa = 0;
+ int num_prout_sa = 0;
+ int num_prout_param = 0;
+ int peri_type = 0;
+ int sg_fd = -1;
+ int ret = 0;
+ const char * cp;
+ const char * device_name = NULL;
+ struct opts_t * op;
+ char buff[48];
+ struct opts_t opts;
+ struct sg_simple_inquiry_resp inq_resp;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->pr_in = true;
+ op->prin_sa = -1;
+ op->prout_sa = -1;
+ op->inquiry = true;
+ op->alloc_len = MX_ALLOC_LEN;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv,
+ "AcCd:GHhiIkK:l:Lm:MnoPQ:rRsS:T:UvVX:yYzZ",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'A':
+ op->prout_sa = PROUT_PREE_AB_SA;
+ ++num_prout_sa;
+ break;
+ case 'c':
+ op->prin_sa = PRIN_RCAP_SA;
+ ++num_prin_sa;
+ break;
+ case 'C':
+ op->prout_sa = PROUT_CLEAR_SA;
+ ++num_prout_sa;
+ break;
+ case 'd':
+ device_name = optarg;
+ break;
+ case 'G':
+ op->prout_sa = PROUT_REG_SA;
+ ++num_prout_sa;
+ break;
+ case 'h':
+ ++help;
+ break;
+ case 'H':
+ ++op->hex;
+ break;
+ case 'i':
+ want_prin = true;
+ break;
+ case 'I':
+ op->prout_sa = PROUT_REG_IGN_SA;
+ ++num_prout_sa;
+ break;
+ case 'k':
+ op->prin_sa = PRIN_RKEY_SA;
+ ++num_prin_sa;
+ break;
+ case 'K':
+ if (1 != sscanf(optarg, "%" SCNx64 "", &op->param_rk)) {
+ pr2serr("bad argument to '--param-rk'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'm': /* --maxlen= and --alloc_length= are similar */
+ case 'l':
+ got_maxlen = ('m' == c);
+ cp = (got_maxlen ? "maxlen" : "alloc-length");
+ if (got_maxlen) {
+ k = sg_get_num(optarg);
+ ok = (-1 != k);
+ op->alloc_len = (unsigned int)k;
+ } else
+ ok = (1 == sscanf(optarg, "%x", &op->alloc_len));
+ if (! ok) {
+ pr2serr("bad argument to '--%s'\n", cp);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (MX_ALLOC_LEN < op->alloc_len) {
+ pr2serr("'--%s' argument exceeds maximum value (%d)\n", cp,
+ MX_ALLOC_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'L':
+ op->prout_sa = PROUT_REL_SA;
+ ++num_prout_sa;
+ break;
+ case 'M':
+ op->prout_sa = PROUT_REG_MOVE_SA;
+ ++num_prout_sa;
+ break;
+ case 'n':
+ op->inquiry = false;
+ break;
+ case 'o':
+ want_prout = true;
+ break;
+ case 'P':
+ op->prout_sa = PROUT_PREE_SA;
+ ++num_prout_sa;
+ break;
+ case 'Q':
+ if (1 != sscanf(optarg, "%x", &op->param_rtp)) {
+ pr2serr("bad argument to '--relative-target-port'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->param_rtp > 0xffff) {
+ pr2serr("argument to '--relative-target-port' 0 to ffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'r':
+ op->prin_sa = PRIN_RRES_SA;
+ ++num_prin_sa;
+ break;
+ case 'R':
+ op->prout_sa = PROUT_RES_SA;
+ ++num_prout_sa;
+ break;
+ case 's':
+ op->prin_sa = PRIN_RFSTAT_SA;
+ ++num_prin_sa;
+ break;
+ case 'S':
+ if (1 != sscanf(optarg, "%" SCNx64 "", &op->param_sark)) {
+ pr2serr("bad argument to '--param-sark'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'T':
+ if (1 != sscanf(optarg, "%x", &op->prout_type)) {
+ pr2serr("bad argument to '--prout-type'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'U':
+ op->param_unreg = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'X':
+ if (0 != build_transportid(optarg, op)) {
+ pr2serr("bad argument to '--transport-id'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ++num_prout_param;
+ break;
+ case 'y': /* differentiates -y, -yy and -yyy */
+ if (! op->readwrite_force) {
+ if (op->readonly) {
+ op->readwrite_force = true;
+ op->readonly = false;
+ } else
+ op->readonly = true;
+ }
+ break;
+ case 'Y':
+ op->param_alltgpt = true;
+ ++num_prout_param;
+ break;
+ case 'z':
+ op->prout_sa = PROUT_REPL_LOST_SA;
+ ++num_prout_sa;
+ break;
+ case 'Z':
+ op->param_aptpl = true;
+ ++num_prout_param;
+ break;
+ case '?':
+ usage(1);
+ return 0;
+ default:
+ pr2serr("unrecognised switch code 0x%x ??\n", c);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (help > 0) {
+ usage(help);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and "
+ "continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("No device name given\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (want_prout && want_prin) {
+ pr2serr("choose '--in' _or_ '--out' (not both)\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ } else if (want_prout) { /* syntax check on PROUT arguments */
+ op->pr_in = false;
+ if ((1 != num_prout_sa) || (0 != num_prin_sa)) {
+ pr2serr(">> For Persistent Reserve Out one and only one "
+ "appropriate\n>> service action must be chosen (e.g. "
+ "'--register')\n");
+ return SG_LIB_CONTRADICT;
+ }
+ } else { /* syntax check on PRIN arguments */
+ if (num_prout_sa > 0) {
+ pr2serr(">> When a service action for Persistent Reserve Out "
+ "is chosen the\n>> '--out' option must be given (as a "
+ "safeguard)\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (0 == num_prin_sa) {
+ pr2serr(">> No service action given; assume Persistent Reserve "
+ "In command\n>> with Read Keys service action\n");
+ op->prin_sa = 0;
+ ++num_prin_sa;
+ } else if (num_prin_sa > 1) {
+ pr2serr("Too many service actions given; choose one only\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ if ((op->param_unreg || op->param_rtp) &&
+ (PROUT_REG_MOVE_SA != op->prout_sa)) {
+ pr2serr("--unreg or --relative-target-port only useful with "
+ "--register-move\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ if ((PROUT_REG_MOVE_SA == op->prout_sa) &&
+ (1 != op->num_transportids)) {
+ pr2serr("with --register-move one (and only one) --transport-id "
+ "should be given\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+ if (((PROUT_RES_SA == op->prout_sa) ||
+ (PROUT_REL_SA == op->prout_sa) ||
+ (PROUT_PREE_SA == op->prout_sa) ||
+ (PROUT_PREE_AB_SA == op->prout_sa)) &&
+ (0 == op->prout_type)) {
+ pr2serr("warning>>> --prout-type probably needs to be given\n");
+ }
+ if ((op->verbose > 2) && op->num_transportids) {
+ char b[1024];
+ uint8_t * bp;
+
+ pr2serr("number of tranport-ids decoded from command line (or "
+ "stdin): %d\n", op->num_transportids);
+ pr2serr(" Decode given transport-ids:\n");
+ for (k = 0; k < op->num_transportids; ++k) {
+ bp = op->transportid_arr + (MX_TID_LEN * k);
+ printf("%s", sg_decode_transportid_str(" ", bp, MX_TID_LEN,
+ true, sizeof(b), b));
+ }
+ }
+
+ if (op->inquiry) {
+ if ((sg_fd = sg_cmds_open_device(device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("%s: error opening file (ro): %s: %s\n", ME,
+ device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ flagged = true;
+ goto fini;
+ }
+ ret = sg_simple_inquiry(sg_fd, &inq_resp, true, op->verbose);
+ if (0 == ret) {
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor, inq_resp.product,
+ inq_resp.revision);
+ peri_type = inq_resp.peripheral_type;
+ cp = sg_get_pdt_str(peri_type, sizeof(buff), buff);
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_type);
+ } else {
+ printf("%s: SCSI INQUIRY failed on %s", ME, device_name);
+ if (ret < 0) {
+ ret = -ret;
+ printf(": %s\n", safe_strerror(ret));
+ ret = sg_convert_errno(ret);
+ } else
+ printf("\n");
+ flagged = true;
+ goto fini;
+ }
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0)
+ pr2serr("%s: sg_cmds_close_device() failed res=%d\n", ME, res);
+ }
+
+ if (! op->readwrite_force) {
+ cp = getenv(SG_PERSIST_IN_RDONLY);
+ if (cp && op->pr_in)
+ op->readonly = true; /* SG_PERSIST_IN_RDONLY overrides default
+ which is open(RW) */
+ } else
+ op->readonly = false; /* '-yy' force open(RW) */
+ sg_fd = sg_cmds_open_device(device_name, op->readonly, op->verbose);
+ if (sg_fd < 0) {
+ pr2serr("%s: error opening file %s (r%s): %s\n", ME, device_name,
+ (op->readonly ? "o" : "w"), safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ flagged = true;
+ goto fini;
+ }
+
+ if (op->pr_in)
+ ret = prin_work(sg_fd, op);
+ else if (PROUT_REG_MOVE_SA == op->prout_sa)
+ ret = prout_reg_move_work(sg_fd, op);
+ else /* PROUT commands other than 'register and move' */
+ ret = prout_work(sg_fd, op);
+
+fini:
+ if (ret && (0 == op->verbose) && (! flagged)) {
+ if (! sg_if_can2stderr("sg_persist failed: ", ret))
+ pr2serr("Some error occurred [%d]\n", ret);
+ }
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_prevent.c b/src/sg_prevent.c
new file mode 100644
index 00000000..b2076c54
--- /dev/null
+++ b/src/sg_prevent.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2004-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This program issues the SCSI PREVENT ALLOW MEDIUM REMOVAL command to the
+ * given SCSI device.
+ */
+
+static const char * version_str = "1.13 20220826";
+
+#define ME "sg_prevent: "
+
+
+static struct option long_options[] = {
+ {"allow", no_argument, 0, 'a'},
+ {"help", no_argument, 0, 'h'},
+ {"prevent", required_argument, 0, 'p'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_prevent [--allow] [--help] [--prevent=PC] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --allow|-a allow media removal\n"
+ " --help|-h print usage message then exit\n"
+ " --prevent=PC|-p PC prevent code value (def: 1 -> "
+ "prevent)\n"
+ " 0 -> allow, 1 -> prevent\n"
+ " 2 -> persistent allow, 3 -> "
+ "persistent prevent\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI PREVENT ALLOW MEDIUM REMOVAL command\n");
+
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool allow = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, res, c;
+ int prevent = -1;
+ int verbose = 0;
+ const char * device_name = NULL;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ahp:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ allow = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'p':
+ prevent = sg_get_num(optarg);
+ if ((prevent < 0) || (prevent > 3)) {
+ pr2serr("bad argument to '--prevent'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (allow && (prevent >= 0)) {
+ pr2serr("can't give both '--allow' and '--prevent='\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (allow)
+ prevent = 0;
+ else if (prevent < 0)
+ prevent = 1; /* default is to prevent, as utility name suggests */
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ res = sg_ll_prevent_allow(sg_fd, prevent, true, verbose);
+ ret = res;
+ if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Prevent allow medium removal: %s\n", b);
+ }
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+fini:
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_prevent failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_raw.c b/src/sg_raw.c
new file mode 100644
index 00000000..a0254cd9
--- /dev/null
+++ b/src/sg_raw.c
@@ -0,0 +1,849 @@
+/*
+ * A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * Copyright (C) 2000-2022 Ingo van Lil <inguin@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program can be used to send raw SCSI commands (with an optional
+ * data phase) through a Generic SCSI interface.
+ */
+
+#define _XOPEN_SOURCE 600 /* clear up posix_memalign() warning */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_pt_nvme.h"
+#include "sg_pr2serr.h"
+#include "sg_unaligned.h"
+
+#define SG_RAW_VERSION "0.4.39 (2022-04-25)"
+
+#define DEFAULT_TIMEOUT 20
+#define MIN_SCSI_CDBSZ 6
+#define MAX_SCSI_CDBSZ 260
+#define MAX_SCSI_DXLEN (1024 * 1024)
+
+#define NVME_ADDR_DATA_IN 0xfffffffffffffffe
+#define NVME_ADDR_DATA_OUT 0xfffffffffffffffd
+#define NVME_DATA_LEN_DATA_IN 0xfffffffe
+#define NVME_DATA_LEN_DATA_OUT 0xfffffffd
+
+static struct option long_options[] = {
+ { "binary", no_argument, NULL, 'b' },
+ { "cmdfile", required_argument, NULL, 'c' },
+ { "cmdset", required_argument, NULL, 'C' },
+ { "enumerate", no_argument, NULL, 'e' },
+ { "help", no_argument, NULL, 'h' },
+ { "infile", required_argument, NULL, 'i' },
+ { "skip", required_argument, NULL, 'k' },
+ { "nosense", no_argument, NULL, 'n' },
+ { "nvm", no_argument, NULL, 'N' },
+ { "outfile", required_argument, NULL, 'o' },
+ { "raw", no_argument, NULL, 'w' },
+ { "request", required_argument, NULL, 'r' },
+ { "readonly", no_argument, NULL, 'R' },
+ { "scan", required_argument, NULL, 'Q' },
+ { "send", required_argument, NULL, 's' },
+ { "timeout", required_argument, NULL, 't' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { 0, 0, 0, 0 }
+};
+
+struct opts_t {
+ bool cmdfile_given;
+ bool do_datain;
+ bool datain_binary;
+ bool do_dataout;
+ bool do_enumerate;
+ bool no_sense;
+ bool do_nvm; /* the NVMe command set: NVM containing its READ+WRITE */
+ bool do_help;
+ bool verbose_given;
+ bool version_given;
+ int cdb_length;
+ int cmdset;
+ int datain_len;
+ int dataout_len;
+ int timeout;
+ int raw;
+ int readonly;
+ int scan_first;
+ int scan_last;
+ int verbose;
+ off_t dataout_offset;
+ uint8_t cdb[MAX_SCSI_CDBSZ]; /* might be NVMe command (64 byte) */
+ const char *cmd_file;
+ const char *datain_file;
+ const char *dataout_file;
+ char *device_name;
+};
+
+
+static void
+pr_version()
+{
+ pr2serr("sg_raw " SG_RAW_VERSION "\n"
+ "Copyright (C) 2007-2021 Ingo van Lil <inguin@gmx.de>\n"
+ "This is free software. You may redistribute copies of it "
+ "under the terms of\n"
+ "the GNU General Public License "
+ "<https://www.gnu.org/licenses/gpl.html>.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n");
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_raw [OPTION]* DEVICE [CDB0 CDB1 ...]\n"
+ "\n"
+ "Options:\n"
+ " --binary|-b Dump data in binary form, even when "
+ "writing to\n"
+ " stdout\n"
+ " --cmdfile=CF|-c CF CF is file containing command in hex "
+ "bytes\n"
+ " --cmdset=CS|-C CS CS is 0 (def) heuristic chooses "
+ "command set;\n"
+ " 1: force SCSI; 2: force NVMe\n"
+ " --enumerate|-e Decodes cdb name then exits; requires "
+ "DEVICE but\n"
+ " ignores it\n"
+ " --help|-h Show this message and exit\n"
+ " --infile=IFILE|-i IFILE Read binary data to send (i.e. "
+ "data-out)\n"
+ " from IFILE (default: stdin)\n"
+ " --nosense|-n Don't display sense information\n"
+ " --nvm|-N command is for NVM command set (e.g. "
+ "Read);\n"
+ " default, if NVMe fd, Admin command "
+ "set\n"
+ " --outfile=OFILE|-o OFILE Write binary data from device "
+ "(i.e. data-in)\n"
+ " to OFILE (def: hexdump to "
+ "stdout)\n"
+ " --raw|-w interpret CF (command file) as "
+ "binary (def:\n"
+ " interpret as ASCII hex)\n"
+ " --readonly|-R Open DEVICE read-only (default: "
+ "read-write)\n"
+ " --request=RLEN|-r RLEN Request up to RLEN bytes of data "
+ "(data-in)\n"
+ " --scan=FO,LO|-Q FO,LO scan command set from FO (first "
+ "opcode)\n"
+ " to LO (last opcode) inclusive. Uses "
+ "given\n"
+ " command bytes, varying the opcode\n"
+ " --send=SLEN|-s SLEN Send SLEN bytes of data (data-out)\n"
+ " --skip=KLEN|-k KLEN Skip the first KLEN bytes when "
+ "reading\n"
+ " data to send (default: 0)\n"
+ " --timeout=SECS|-t SECS Timeout in seconds (default: 20)\n"
+ " --verbose|-v Increase verbosity\n"
+ " --version|-V Show version information and exit\n"
+ "\n"
+ "Between 6 and 260 command bytes (two hex digits each) can be "
+ "specified\nand will be sent to DEVICE. Lengths RLEN, SLEN and "
+ "KLEN are decimal by\ndefault. Bidirectional commands "
+ "accepted.\n\nSimple example: Perform INQUIRY on /dev/sg0:\n"
+ " sg_raw -r 1k /dev/sg0 12 00 00 00 60 00\n");
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char *argv[])
+{
+ while (1) {
+ int c, n;
+ const char * cp;
+
+ c = getopt_long(argc, argv, "bc:C:ehi:k:nNo:Q:r:Rs:t:vVw",
+ long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ op->datain_binary = true;
+ break;
+ case 'c':
+ op->cmd_file = optarg;
+ op->cmdfile_given = true;
+ break;
+ case 'C':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 2)) {
+ pr2serr("Invalid argument to --cmdset= expect 0, 1 or 2\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->cmdset = n;
+ break;
+ case 'e':
+ op->do_enumerate = true;
+ break;
+ case 'h':
+ case '?':
+ op->do_help = true;
+ return 0;
+ case 'i':
+ if (op->dataout_file) {
+ pr2serr("Too many '--infile=' options\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->dataout_file = optarg;
+ break;
+ case 'k':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("Invalid argument to '--skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->dataout_offset = n;
+ break;
+ case 'n':
+ op->no_sense = true;
+ break;
+ case 'N':
+ op->do_nvm = true;
+ break;
+ case 'o':
+ if (op->datain_file) {
+ pr2serr("Too many '--outfile=' options\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->datain_file = optarg;
+ break;
+ case 'Q': /* --scan=FO,LO */
+ cp = strchr(optarg, ',');
+ if (NULL == cp) {
+ pr2serr("--scan= expects two numbers, comma separated\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Invalid first number to --scan= expect 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->scan_first = n;
+ n = sg_get_num(cp + 1);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("Invalid second number to --scan= expect 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->scan_last = n;
+ if (op->scan_first >= n)
+ pr2serr("Warning: scan range degenerate, ignore\n");
+ break;
+ case 'r':
+ op->do_datain = true;
+ n = sg_get_num(optarg);
+ if (n < 0 || n > MAX_SCSI_DXLEN) {
+ pr2serr("Invalid argument to '--request'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->datain_len = n;
+ break;
+ case 'R':
+ ++op->readonly;
+ break;
+ case 's':
+ op->do_dataout = true;
+ n = sg_get_num(optarg);
+ if (n < 0 || n > MAX_SCSI_DXLEN) {
+ pr2serr("Invalid argument to '--send'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->dataout_len = n;
+ break;
+ case 't':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("Invalid argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->timeout = n;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w': /* -r and -R already in use, this is --raw */
+ ++op->raw;
+ break;
+ default:
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (op->version_given
+#ifdef DEBUG
+ && ! op->verbose_given
+#endif
+ )
+ return 0;
+
+ if (optind >= argc) {
+ pr2serr("No device specified\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->device_name = argv[optind];
+ ++optind;
+
+ while (optind < argc) {
+ char *opt = argv[optind++];
+ char *endptr;
+ int cmd = strtol(opt, &endptr, 16);
+
+ if (*opt == '\0' || *endptr != '\0' || cmd < 0x00 || cmd > 0xff) {
+ pr2serr("Invalid command byte '%s'\n", opt);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->cdb_length >= MAX_SCSI_CDBSZ) {
+ pr2serr("CDB too long (max. %d bytes)\n", MAX_SCSI_CDBSZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->cdb[op->cdb_length] = cmd;
+ ++op->cdb_length;
+ }
+
+ if (op->cmdfile_given) {
+ int err;
+
+ err = sg_f2hex_arr(op->cmd_file, (op->raw > 0) /* as_binary */,
+ false /* no_space */, op->cdb, &op->cdb_length,
+ MAX_SCSI_CDBSZ);
+ if (err) {
+ pr2serr("Unable to parse: %s as %s\n", op->cmd_file,
+ (op->raw > 0) ? "binary" : "hex");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->verbose > 2) {
+ pr2serr("Read %d from %s . They are in hex:\n", op->cdb_length,
+ op->cmd_file);
+ hex2stderr(op->cdb, op->cdb_length, -1);
+ }
+ }
+ if (op->cdb_length < MIN_SCSI_CDBSZ) {
+ pr2serr("CDB too short (min. %d bytes)\n", MIN_SCSI_CDBSZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->do_enumerate || (op->verbose > 1)) {
+ bool is_scsi_cdb = sg_is_scsi_cdb(op->cdb, op->cdb_length);
+ int sa;
+ char b[80];
+
+ if ((1 == op->cmdset) && !is_scsi_cdb) {
+ is_scsi_cdb = true;
+ if (op->verbose > 3)
+ printf(">>> overriding cmdset guess to SCSI\n");
+ }
+ if ((2 == op->cmdset) && is_scsi_cdb) {
+ is_scsi_cdb = false;
+ if (op->verbose > 3)
+ printf(">>> overriding cmdset guess to NVMe\n");
+ }
+ if (is_scsi_cdb) {
+ if (op->cdb_length > 16) {
+ sa = sg_get_unaligned_be16(op->cdb + 8);
+ if ((0x7f != op->cdb[0]) && (0x7e != op->cdb[0]))
+ printf(">>> Unlikely to be SCSI CDB since all over 16 "
+ "bytes long should\n>>> start with 0x7f or "
+ "0x7e\n");
+ } else
+ sa = op->cdb[1] & 0x1f;
+ sg_get_opcode_sa_name(op->cdb[0], sa, 0, sizeof(b), b);
+ printf("Attempt to decode cdb name: %s\n", b);
+ } else
+ printf(">>> Seems to be NVMe %s command\n",
+ sg_get_nvme_opcode_name(op->cdb[0], ! op->do_nvm,
+ sizeof(b), b));
+ }
+ return 0;
+}
+
+static int
+skip(int fd, off_t offset)
+{
+ int err;
+ off_t remain;
+ char buffer[512];
+
+ if (lseek(fd, offset, SEEK_SET) >= 0)
+ return 0;
+
+ // lseek failed; fall back to reading and discarding data
+ remain = offset;
+ while (remain > 0) {
+ ssize_t amount, done;
+ amount = (remain < (off_t)sizeof(buffer)) ? remain
+ : (off_t)sizeof(buffer);
+ done = read(fd, buffer, amount);
+ if (done < 0) {
+ err = errno;
+ perror("Error reading input data to skip");
+ return sg_convert_errno(err);
+ } else if (done == 0) {
+ pr2serr("EOF on input file/stream\n");
+ return SG_LIB_FILE_ERROR;
+ } else
+ remain -= done;
+ }
+ return 0;
+}
+
+static uint8_t *
+fetch_dataout(struct opts_t * op, uint8_t ** free_buf, int * errp)
+{
+ bool ok = false;
+ int fd, len, tot_len, boff, err;
+ uint8_t *buf = NULL;
+
+ *free_buf = NULL;
+ if (errp)
+ *errp = 0;
+ if (op->dataout_file) {
+ fd = open(op->dataout_file, O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ if (errp)
+ *errp = sg_convert_errno(err);
+ perror(op->dataout_file);
+ goto bail;
+ }
+ } else
+ fd = STDIN_FILENO;
+ if (sg_set_binary_mode(fd) < 0) {
+ err = errno;
+ if (errp)
+ *errp = err;
+ perror("sg_set_binary_mode");
+ goto bail;
+ }
+
+ if (op->dataout_offset > 0) {
+ err = skip(fd, op->dataout_offset);
+ if (err != 0) {
+ if (errp)
+ *errp = err;
+ goto bail;
+ }
+ }
+
+ tot_len = op->dataout_len;
+ buf = sg_memalign(tot_len, 0 /* page_size */, free_buf, false);
+ if (buf == NULL) {
+ pr2serr("sg_memalign: failed to get %d bytes of memory\n", tot_len);
+ if (errp)
+ *errp = sg_convert_errno(ENOMEM);
+ goto bail;
+ }
+
+ for (boff = 0; boff < tot_len; boff += len) {
+ len = read(fd, buf + boff , tot_len - boff);
+ if (len < 0) {
+ err = errno;
+ if (errp)
+ *errp = sg_convert_errno(err);
+ perror("Failed to read input data");
+ goto bail;
+ } else if (0 == len) {
+ if (errp)
+ *errp = SG_LIB_FILE_ERROR;
+ pr2serr("EOF on input file/stream at buffer offset %d\n", boff);
+ goto bail;
+ }
+ }
+ ok = true;
+
+bail:
+ if (fd >= 0 && fd != STDIN_FILENO)
+ close(fd);
+ if (! ok) {
+ if (*free_buf) {
+ free(*free_buf);
+ *free_buf = NULL;
+ }
+ return NULL;
+ }
+ return buf;
+}
+
+static int
+write_dataout(const char *filename, uint8_t *buf, int len)
+{
+ int ret = SG_LIB_FILE_ERROR;
+ int fd;
+
+ if ((filename == NULL) ||
+ ((1 == strlen(filename)) && ('-' == filename[0])))
+ fd = STDOUT_FILENO;
+ else {
+ fd = creat(filename, 0666);
+ if (fd < 0) {
+ ret = sg_convert_errno(errno);
+ perror(filename);
+ goto bail;
+ }
+ }
+ if (sg_set_binary_mode(fd) < 0) {
+ perror("sg_set_binary_mode");
+ goto bail;
+ }
+
+ if (write(fd, buf, len) != len) {
+ ret = sg_convert_errno(errno);
+ perror(filename ? filename : "stdout");
+ goto bail;
+ }
+
+ ret = 0;
+
+bail:
+ if (fd >= 0 && fd != STDOUT_FILENO)
+ close(fd);
+ return ret;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ bool is_scsi_cdb = true;
+ bool do_scan = false;
+ int ret = 0;
+ int err = 0;
+ int res_cat, status, s_len, k, ret2;
+ int sg_fd = -1;
+ uint16_t sct_sc;
+ uint32_t result;
+ struct sg_pt_base *ptvp = NULL;
+ uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
+ uint8_t * dinp = NULL;
+ uint8_t * doutp = NULL;
+ uint8_t * free_buf_out = NULL;
+ uint8_t * wrkBuf = NULL;
+ struct opts_t opts;
+ struct opts_t * op;
+ char b[128];
+ const int b_len = sizeof(b);
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->timeout = DEFAULT_TIMEOUT;
+ ret = parse_cmd_line(op, argc, argv);
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr_version();
+ goto done;
+ }
+
+ if (ret != 0) {
+ pr2serr("\n"); /* blank line before outputting usage */
+ usage();
+ goto done;
+ } else if (op->do_help) {
+ usage();
+ goto done;
+ } else if (op->do_enumerate)
+ goto done;
+
+ sg_fd = scsi_pt_open_device(op->device_name, op->readonly,
+ op->verbose);
+ if (sg_fd < 0) {
+ pr2serr("%s: %s\n", op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto done;
+ }
+
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose);
+ if (ptvp == NULL) {
+ pr2serr("construct_scsi_pt_obj_with_fd() failed\n");
+ ret = SG_LIB_CAT_OTHER;
+ goto done;
+ }
+
+ if (op->scan_first < op->scan_last)
+ do_scan = true;
+
+and_again:
+ if (do_scan) {
+ op->cdb[0] = op->scan_first;
+ printf("Command bytes in hex:");
+ for (k = 0; k < op->cdb_length; ++k)
+ printf(" %02x", op->cdb[k]);
+ printf("\n");
+ }
+
+ is_scsi_cdb = sg_is_scsi_cdb(op->cdb, op->cdb_length);
+ if ((1 == op->cmdset) && !is_scsi_cdb)
+ is_scsi_cdb = true;
+ else if ((2 == op->cmdset) && is_scsi_cdb)
+ is_scsi_cdb = false;
+
+ if (op->do_dataout) {
+ uint32_t dout_len;
+
+ doutp = fetch_dataout(op, &free_buf_out, &err);
+ if (doutp == NULL) {
+ ret = err;
+ goto done;
+ }
+ dout_len = op->dataout_len;
+ if (op->verbose > 2)
+ pr2serr("dxfer_buffer_out=%p, length=%d\n",
+ (void *)doutp, dout_len);
+ set_scsi_pt_data_out(ptvp, doutp, dout_len);
+ if (op->cmdfile_given) {
+ if (NVME_ADDR_DATA_OUT ==
+ sg_get_unaligned_le64(op->cdb + SG_NVME_PT_ADDR))
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)doutp,
+ op->cdb + SG_NVME_PT_ADDR);
+ if (NVME_DATA_LEN_DATA_OUT ==
+ sg_get_unaligned_le32(op->cdb + SG_NVME_PT_DATA_LEN))
+ sg_put_unaligned_le32(dout_len,
+ op->cdb + SG_NVME_PT_DATA_LEN);
+ }
+ }
+ if (op->do_datain) {
+ uint32_t din_len = op->datain_len;
+
+ dinp = sg_memalign(din_len, 0 /* page_size */, &wrkBuf, false);
+ if (dinp == NULL) {
+ pr2serr("sg_memalign: failed to get %d bytes of memory\n",
+ din_len);
+ ret = sg_convert_errno(ENOMEM);
+ goto done;
+ }
+ if (op->verbose > 2)
+ pr2serr("dxfer_buffer_in=%p, length=%d\n", (void *)dinp, din_len);
+ set_scsi_pt_data_in(ptvp, dinp, din_len);
+ if (op->cmdfile_given) {
+ if (NVME_ADDR_DATA_IN ==
+ sg_get_unaligned_le64(op->cdb + SG_NVME_PT_ADDR))
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dinp,
+ op->cdb + SG_NVME_PT_ADDR);
+ if (NVME_DATA_LEN_DATA_IN ==
+ sg_get_unaligned_le32(op->cdb + SG_NVME_PT_DATA_LEN))
+ sg_put_unaligned_le32(din_len,
+ op->cdb + SG_NVME_PT_DATA_LEN);
+ }
+ }
+ if (op->verbose) {
+ char d[128];
+
+ pr2serr(" %s to send: ", is_scsi_cdb ? "cdb" : "cmd");
+ if (is_scsi_cdb) {
+ pr2serr("%s\n", sg_get_command_str(op->cdb, op->cdb_length,
+ op->verbose > 1,
+ sizeof(d), d));
+ } else { /* If not SCSI cdb then treat as NVMe command */
+ pr2serr("\n");
+ hex2stderr(op->cdb, op->cdb_length, -1);
+ if (op->verbose > 1)
+ pr2serr(" Command name: %s\n",
+ sg_get_nvme_opcode_name(op->cdb[0], ! op->do_nvm,
+ b_len, b));
+ }
+ }
+ set_scsi_pt_cdb(ptvp, op->cdb, op->cdb_length);
+ if (op->verbose > 2)
+ pr2serr("sense_buffer=%p, length=%d\n", (void *)sense_buffer,
+ (int)sizeof(sense_buffer));
+ set_scsi_pt_sense(ptvp, sense_buffer, sizeof(sense_buffer));
+
+ if (op->do_nvm)
+ ret = do_nvm_pt(ptvp, 0, op->timeout, op->verbose);
+ else
+ ret = do_scsi_pt(ptvp, -1, op->timeout, op->verbose);
+ if (ret > 0) {
+ switch (ret) {
+ case SCSI_PT_DO_BAD_PARAMS:
+ pr2serr("do_scsi_pt: bad pass through setup\n");
+ ret = SG_LIB_CAT_OTHER;
+ break;
+ case SCSI_PT_DO_TIMEOUT:
+ pr2serr("do_scsi_pt: timeout\n");
+ ret = SG_LIB_CAT_TIMEOUT;
+ break;
+ case SCSI_PT_DO_NVME_STATUS:
+ sct_sc = (uint16_t)get_scsi_pt_status_response(ptvp);
+ pr2serr("NVMe Status: %s [0x%x]\n",
+ sg_get_nvme_cmd_status_str(sct_sc, b_len, b), sct_sc);
+ if (op->verbose) {
+ result = get_pt_result(ptvp);
+ pr2serr("NVMe Result=0x%x\n", result);
+ s_len = get_scsi_pt_sense_len(ptvp);
+ if ((op->verbose > 1) && (s_len > 0)) {
+ pr2serr("NVMe completion queue 4 DWords (as byte "
+ "string):\n");
+ hex2stderr(sense_buffer, s_len, -1);
+ }
+ }
+ break;
+ case SCSI_PT_DO_NOT_SUPPORTED:
+ pr2serr("do_scsi_pt: not supported\n");
+ ret = SG_LIB_CAT_TIMEOUT;
+ break;
+ default:
+ pr2serr("do_scsi_pt: unknown error: %d\n", ret);
+ ret = SG_LIB_CAT_OTHER;
+ break;
+ }
+ goto done;
+ } else if (ret < 0) {
+ k = -ret;
+ pr2serr("do_scsi_pt: %s\n", safe_strerror(k));
+ err = get_scsi_pt_os_err(ptvp);
+ if ((err != 0) && (err != k))
+ pr2serr(" ... or perhaps: %s\n", safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto done;
+ }
+
+ s_len = get_scsi_pt_sense_len(ptvp);
+ if (is_scsi_cdb) {
+ res_cat = get_scsi_pt_result_category(ptvp);
+ switch (res_cat) {
+ case SCSI_PT_RESULT_GOOD:
+ ret = 0;
+ break;
+ case SCSI_PT_RESULT_SENSE:
+ ret = sg_err_category_sense(sense_buffer, s_len);
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ get_scsi_pt_transport_err_str(ptvp, b_len, b);
+ pr2serr(">>> transport error: %s\n", b);
+ ret = SG_LIB_CAT_OTHER;
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ get_scsi_pt_os_err_str(ptvp, b_len, b);
+ pr2serr(">>> os error: %s\n", b);
+ ret = SG_LIB_CAT_OTHER;
+ break;
+ default:
+ pr2serr(">>> unknown pass through result category (%d)\n",
+ res_cat);
+ ret = SG_LIB_CAT_OTHER;
+ break;
+ }
+
+ status = get_scsi_pt_status_response(ptvp);
+ pr2serr("SCSI Status: ");
+ sg_print_scsi_status(status);
+ pr2serr("\n\n");
+ if ((SAM_STAT_CHECK_CONDITION == status) && (! op->no_sense)) {
+ if (0 == s_len)
+ pr2serr(">>> Strange: status is CHECK CONDITION but no Sense "
+ "Information\n");
+ else {
+ pr2serr("Sense Information:\n");
+ sg_print_sense(NULL, sense_buffer, s_len, (op->verbose > 0));
+ pr2serr("\n");
+ }
+ }
+ if (SAM_STAT_RESERVATION_CONFLICT == status)
+ ret = SG_LIB_CAT_RES_CONFLICT;
+ } else { /* NVMe command */
+ result = get_pt_result(ptvp);
+ pr2serr("NVMe Result=0x%x\n", result);
+ if (op->verbose && (s_len > 0)) {
+ pr2serr("NVMe completion queue 4 DWords (as byte string):\n");
+ hex2stderr(sense_buffer, s_len, -1);
+ }
+ }
+
+ if (op->do_datain) {
+ int data_len = op->datain_len - get_scsi_pt_resid(ptvp);
+
+ if (ret && !(SG_LIB_CAT_RECOVERED == ret ||
+ SG_LIB_CAT_NO_SENSE == ret))
+ pr2serr("Error %d occurred, no data received\n", ret);
+ else if (data_len == 0) {
+ pr2serr("No data received\n");
+ } else {
+ if (op->datain_file == NULL && !op->datain_binary) {
+ pr2serr("Received %d bytes of data:\n", data_len);
+ hex2stderr(dinp, data_len, 0);
+ } else {
+ const char * cp = "stdout";
+
+ if (op->datain_file &&
+ ! ((1 == strlen(op->datain_file)) &&
+ ('-' == op->datain_file[0])))
+ cp = op->datain_file;
+ pr2serr("Writing %d bytes of data to %s\n", data_len, cp);
+ ret2 = write_dataout(op->datain_file, dinp,
+ data_len);
+ if (0 != ret2) {
+ if (0 == ret)
+ ret = ret2;
+ goto done;
+ }
+ }
+ }
+ }
+
+done:
+ if (do_scan) {
+ ++op->scan_first;
+ if (op->scan_first <= op->scan_last) {
+ clear_scsi_pt_obj(ptvp);
+ goto and_again;
+ }
+ }
+
+ if (op->verbose && is_scsi_cdb) {
+ sg_get_category_sense_str(ret, b_len, b, op->verbose - 1);
+ pr2serr("%s\n", b);
+ }
+ if (wrkBuf)
+ free(wrkBuf);
+ if (free_buf_out)
+ free(free_buf_out);
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ if (sg_fd >= 0)
+ scsi_pt_close_device(sg_fd);
+ return ret >= 0 ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rbuf.c b/src/sg_rbuf.c
new file mode 100644
index 00000000..d37cc259
--- /dev/null
+++ b/src/sg_rbuf.c
@@ -0,0 +1,688 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 1999-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program uses the SCSI command READ BUFFER on the given
+ * device, first to find out how big it is and then to read that
+ * buffer (data mode, buffer id 0).
+ */
+
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#define RB_MODE_DESC 3
+#define RB_MODE_DATA 2
+#define RB_MODE_ECHO_DESC 0xb
+#define RB_MODE_ECHO_DATA 0xa
+#define RB_DESC_LEN 4
+#define RB_DEF_SIZE (200*1024*1024)
+#define RB_OPCODE 0x3C
+#define RB_CMD_LEN 10
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif
+
+
+static const char * version_str = "5.09 20220425";
+
+static struct option long_options[] = {
+ {"buffer", required_argument, 0, 'b'},
+ {"dio", no_argument, 0, 'd'},
+ {"echo", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"mmap", no_argument, 0, 'm'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"quick", no_argument, 0, 'q'},
+ {"size", required_argument, 0, 's'},
+ {"time", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_dio;
+ bool do_echo;
+ bool do_mmap;
+ bool do_quick;
+ bool do_time;
+ bool verbose_given;
+ bool version_given;
+ bool opt_new;
+ int do_buffer;
+ int do_help;
+ int verbose;
+ int64_t do_size;
+ const char * device_name;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_rbuf [--buffer=EACH] [--dio] [--echo] "
+ "[--help] [--mmap]\n"
+ " [--quick] [--size=OVERALL] [--time] [--verbose] "
+ "[--version]\n"
+ " SG_DEVICE\n");
+ pr2serr(" where:\n"
+ " --buffer=EACH|-b EACH buffer size to use (in bytes)\n"
+ " --dio|-d requests dio ('-q' overrides it)\n"
+ " --echo|-e use echo buffer (def: use data mode)\n"
+ " --help|-h print usage message then exit\n"
+ " --mmap|-m requests mmap-ed IO (overrides -q, -d)\n"
+ " --quick|-q quick, don't xfer to user space\n");
+ pr2serr(" --size=OVERALL|-s OVERALL total size to read (in bytes)\n"
+ " default: 200 MiB\n"
+ " --time|-t time the data transfer\n"
+ " --verbose|-v increase verbosity (more debug)\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V print version string then exit\n\n"
+ "Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
+ "id 0)\nrepeatedly. This utility only works with Linux sg "
+ "devices.\n");
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_rbuf [-b=EACH_KIB] [-d] [-m] [-q] [-s=OVERALL_MIB] "
+ "[-t] [-v] [-V]\n SG_DEVICE\n");
+ printf(" where:\n");
+ printf(" -b=EACH_KIB num is buffer size to use (in KiB)\n");
+ printf(" -d requests dio ('-q' overrides it)\n");
+ printf(" -e use echo buffer (def: use data mode)\n");
+ printf(" -m requests mmap-ed IO (overrides -q, -d)\n");
+ printf(" -q quick, don't xfer to user space\n");
+ printf(" -s=OVERALL_MIB num is total size to read (in MiB) "
+ "(default: 200 MiB)\n");
+ printf(" maximum total size is 4000 MiB\n");
+ printf(" -t time the data transfer\n");
+ printf(" -v increase verbosity (more debug)\n");
+ printf(" -N|--new use new interface\n");
+ printf(" -V print version string then exit\n\n");
+ printf("Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
+ "id 0)\nrepeatedly. This utility only works with Linux sg "
+ "devices.\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+ int64_t nn;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dehmNOqs:tvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--buffer'\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_buffer = n;
+ break;
+ case 'd':
+ op->do_dio = true;
+ break;
+ case 'e':
+ op->do_echo = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'm':
+ op->do_mmap = true;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'q':
+ op->do_quick = true;
+ break;
+ case 's':
+ nn = sg_get_llnum(optarg);
+ if (nn < 0) {
+ pr2serr("bad argument to '--size'\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_size = nn;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num;
+ int64_t nn;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'd':
+ op->do_dio = true;
+ break;
+ case 'e':
+ op->do_echo = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'm':
+ op->do_mmap = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'q':
+ op->do_quick = true;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("b=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &op->do_buffer);
+ if ((1 != num) || (op->do_buffer <= 0)) {
+ printf("Couldn't decode number after 'b=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_buffer *= 1024;
+ }
+ else if (0 == strncmp("s=", cp, 2)) {
+ nn = sg_get_llnum(optarg);
+ if (nn < 0) {
+ printf("Couldn't decode number after 's=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_size = nn;
+ op->do_size *= 1024 * 1024;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+#ifdef DEBUG
+ bool clear = true;
+#endif
+ bool dio_incomplete = false;
+ int sg_fd, res, err;
+ int buf_capacity = 0;
+ int buf_size = 0;
+ size_t psz;
+ unsigned int k, num;
+ int64_t total_size = RB_DEF_SIZE;
+ struct opts_t * op;
+ uint8_t * rbBuff = NULL;
+ void * rawp = NULL;
+ uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
+ uint8_t rb_cdb [RB_CMD_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+ struct timeval start_tm, end_tm;
+ struct opts_t opts;
+
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+#else
+ psz = 4096; /* give up, pick likely figure */
+#endif
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->do_buffer > 0)
+ buf_size = op->do_buffer;
+ if (op->do_size > 0)
+ total_size = op->do_size;
+
+ sg_fd = open(op->device_name, O_RDONLY | O_NONBLOCK);
+ if (sg_fd < 0) {
+ err = errno;
+ perror("device open error");
+ return sg_convert_errno(err);
+ }
+ if (op->do_mmap) {
+ op->do_dio = false;
+ op->do_quick = false;
+ }
+ if (NULL == (rawp = malloc(512))) {
+ printf("out of memory (query)\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ rbBuff = (uint8_t *)rawp;
+
+ rb_cdb[0] = RB_OPCODE;
+ rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DESC : RB_MODE_DESC;
+ rb_cdb[8] = RB_DESC_LEN;
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = RB_DESC_LEN;
+ io_hdr.dxferp = rbBuff;
+ io_hdr.cmdp = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
+ if (op->verbose) {
+ char b[128];
+
+ pr2serr(" Read buffer (%sdescriptor) cdb: %s\n",
+ (op->do_echo ? "echo " : ""),
+ sg_get_command_str(rb_cdb, RB_CMD_LEN, false, sizeof(b), b));
+ }
+
+ /* do normal IO to find RB size (not dio or mmap-ed at this stage) */
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("SG_IO READ BUFFER descriptor error");
+ if (rawp)
+ free(rawp);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (op->verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("READ BUFFER descriptor, continuing", &io_hdr,
+ op->verbose > 1);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER descriptor error", &io_hdr,
+ op->verbose > 1);
+ if (rawp) free(rawp);
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+ }
+
+ if (op->do_echo) {
+ buf_capacity = 0x1fff & sg_get_unaligned_be16(rbBuff + 2);
+ printf("READ BUFFER reports: echo buffer capacity=%d\n",
+ buf_capacity);
+ } else {
+ buf_capacity = sg_get_unaligned_be24(rbBuff + 1);
+ printf("READ BUFFER reports: buffer capacity=%d, offset "
+ "boundary=%d\n", buf_capacity, (int)rbBuff[0]);
+ }
+
+ if (0 == buf_size)
+ buf_size = buf_capacity;
+ else if (buf_size > buf_capacity) {
+ printf("Requested buffer size=%d exceeds reported capacity=%d\n",
+ buf_size, buf_capacity);
+ if (rawp) free(rawp);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (rawp) {
+ free(rawp);
+ rawp = NULL;
+ }
+
+ if (! op->do_dio) {
+ k = buf_size;
+ if (op->do_mmap && (0 != (k % psz)))
+ k = ((k / psz) + 1) * psz; /* round up to page size */
+ res = ioctl(sg_fd, SG_SET_RESERVED_SIZE, &k);
+ if (res < 0)
+ perror("SG_SET_RESERVED_SIZE error");
+ }
+
+ if (op->do_mmap) {
+ rbBuff = (uint8_t *)mmap(NULL, buf_size, PROT_READ, MAP_SHARED,
+ sg_fd, 0);
+ if (MAP_FAILED == rbBuff) {
+ if (ENOMEM == errno) {
+ pr2serr("mmap() out of memory, try a smaller buffer size "
+ "than %d bytes\n", buf_size);
+ if (op->opt_new)
+ pr2serr(" [with '--buffer=EACH' where EACH is in "
+ "bytes]\n");
+ else
+ pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
+ } else
+ perror("error using mmap()");
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+ else { /* non mmap-ed IO */
+ rawp = (uint8_t *)malloc(buf_size + (op->do_dio ? psz : 0));
+ if (NULL == rawp) {
+ printf("out of memory (data)\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ /* perhaps use posix_memalign() instead */
+ if (op->do_dio) /* align to page boundary */
+ rbBuff= (uint8_t *)(((sg_uintptr_t)rawp + psz - 1) &
+ (~(psz - 1)));
+ else
+ rbBuff = (uint8_t *)rawp;
+ }
+
+ num = total_size / buf_size;
+ if (op->do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+ /* main data reading loop */
+ for (k = 0; k < num; ++k) {
+ memset(rb_cdb, 0, RB_CMD_LEN);
+ rb_cdb[0] = RB_OPCODE;
+ rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DATA : RB_MODE_DATA;
+ sg_put_unaligned_be24((uint32_t)buf_size, rb_cdb + 6);
+#ifdef DEBUG
+ memset(rbBuff, 0, buf_size);
+#endif
+
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = buf_size;
+ if (! op->do_mmap)
+ io_hdr.dxferp = rbBuff;
+ io_hdr.cmdp = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr.pack_id = k;
+ if (op->do_mmap)
+ io_hdr.flags |= SG_FLAG_MMAP_IO;
+ else if (op->do_dio)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+ else if (op->do_quick)
+ io_hdr.flags |= SG_FLAG_NO_DXFER;
+ if (op->verbose > 1) {
+ char b[128];
+
+ pr2serr(" Read buffer (%sdata) cdb: %s\n",
+ (op->do_echo ? "echo " : ""),
+ sg_get_command_str(rb_cdb, RB_CMD_LEN, false,
+ sizeof(b), b));
+ }
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ if (ENOMEM == errno) {
+ pr2serr("SG_IO data: out of memory, try a smaller buffer "
+ "size than %d bytes\n", buf_size);
+ if (op->opt_new)
+ pr2serr(" [with '--buffer=EACH' where EACH is in "
+ "bytes]\n");
+ else
+ pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
+ } else
+ perror("SG_IO READ BUFFER data error");
+ if (rawp) free(rawp);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (op->verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("READ BUFFER data, continuing", &io_hdr,
+ op->verbose > 1);
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER data error", &io_hdr,
+ op->verbose > 1);
+ if (rawp) free(rawp);
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+ }
+ if (op->do_dio &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ dio_incomplete = true; /* flag that dio not done (completely) */
+
+#ifdef DEBUG
+ if (clear) {
+ int j;
+
+ for (j = 0; j < buf_size; ++j) {
+ if (rbBuff[j] != 0) {
+ clear = false;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ if (op->do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
+ struct timeval res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+ b = (double)buf_size * num;
+ printf("time to read data from buffer was %d.%06d secs",
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if (a > 0.00001) {
+ if (b > 511)
+ printf(", %.2f MB/sec", b / (a * 1000000.0));
+ printf(", %.2f IOPS", num / a);
+ }
+ printf("\n");
+ }
+ if (dio_incomplete)
+ printf(">> direct IO requested but not done\n");
+ printf("Read %" PRId64 " MiB (actual: %" PRId64 " bytes), buffer "
+ "size=%d KiB (%d bytes)\n", (total_size / (1024 * 1024)),
+ (int64_t)num * buf_size, buf_size / 1024, buf_size);
+
+ if (rawp) free(rawp);
+ res = close(sg_fd);
+ if (res < 0) {
+ err = errno;
+ perror("close error");
+ return sg_convert_errno(err);
+ }
+#ifdef DEBUG
+ if (clear)
+ printf("read buffer always zero\n");
+ else
+ printf("read buffer non-zero\n");
+#endif
+ return res;
+}
diff --git a/src/sg_rdac.c b/src/sg_rdac.c
new file mode 100644
index 00000000..b0d1cdcf
--- /dev/null
+++ b/src/sg_rdac.c
@@ -0,0 +1,516 @@
+/*
+ * sg_rdac
+ *
+ * Retrieve / set RDAC options.
+ *
+ * Copyright (C) 2006-2018 Hannes Reinecke <hare@suse.de>
+ *
+ * Based on sg_modes.c and sg_emc_trespass.c; credits from there apply.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "1.17 20180512";
+
+uint8_t mode6_hdr[] = {
+ 0x75, /* Length */
+ 0, /* medium */
+ 0, /* params */
+ 8, /* Block descriptor length */
+};
+
+uint8_t mode10_hdr[] = {
+ 0x01, 0x18, /* Length */
+ 0, /* medium */
+ 0, /* params */
+ 0, 0, /* reserved */
+ 0, 0, /* block descriptor length */
+};
+
+uint8_t block_descriptor[] = {
+ 0, /* Density code */
+ 0, 0, 0, /* Number of blocks */
+ 0, /* Reserved */
+ 0, 0x02, 0, /* 512 byte blocks */
+};
+
+struct rdac_page_common {
+ uint8_t current_serial[16];
+ uint8_t alternate_serial[16];
+ uint8_t current_mode_msb;
+ uint8_t current_mode_lsb;
+ uint8_t alternate_mode_msb;
+ uint8_t alternate_mode_lsb;
+ uint8_t quiescence;
+ uint8_t options;
+};
+
+struct rdac_legacy_page {
+ uint8_t page_code;
+ uint8_t page_length;
+ struct rdac_page_common attr;
+ uint8_t lun_table[32];
+ uint8_t lun_table_exp[32];
+ unsigned short reserved;
+};
+
+struct rdac_expanded_page {
+ uint8_t page_code;
+ uint8_t subpage_code;
+ uint8_t page_length[2];
+ struct rdac_page_common attr;
+ uint8_t lun_table[256];
+ uint8_t reserved[2];
+};
+
+static int do_verbose = 0;
+
+static void dump_mode_page( uint8_t *page, int len )
+{
+ int i, k;
+
+ for (k = 0; k < len; k += 16) {
+
+ printf("%x:",k / 16);
+ for (i = 0; i < 16; i++) {
+ printf(" %02x", page[k + i]);
+ if (k + i >= len) {
+ printf("\n");
+ break;
+ }
+ }
+ printf("\n");
+ }
+
+}
+
+#define MX_ALLOC_LEN (1024 * 4)
+#define RDAC_CONTROLLER_PAGE 0x2c
+#define RDAC_CONTROLLER_PAGE_LEN 0x68
+#define LEGACY_PAGE 0x00
+#define EXPANDED_LUN_SPACE_PAGE 0x01
+#define EXPANDED_LUN_SPACE_PAGE_LEN 0x128
+#define RDAC_FAIL_ALL_PATHS 0x1
+#define RDAC_FAIL_SELECTED_PATHS 0x2
+#define RDAC_FORCE_QUIESCENCE 0x2
+#define RDAC_QUIESCENCE_TIME 10
+
+static int fail_all_paths(int fd, bool use_6_byte)
+{
+ struct rdac_legacy_page *rdac_page;
+ struct rdac_expanded_page *rdac_page_exp;
+ struct rdac_page_common *rdac_common = NULL;
+ uint8_t fail_paths_pg[308];
+
+ int res;
+ char b[80];
+
+ memset(fail_paths_pg, 0, 308);
+ if (use_6_byte) {
+ memcpy(fail_paths_pg, mode6_hdr, 4);
+ memcpy(fail_paths_pg + 4, block_descriptor, 8);
+ rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
+ rdac_page->page_code = RDAC_CONTROLLER_PAGE;
+ rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
+ rdac_common = &rdac_page->attr;
+ } else {
+ memcpy(fail_paths_pg, mode10_hdr, 8);
+ rdac_page_exp = (struct rdac_expanded_page *)
+ (fail_paths_pg + 8);
+ rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
+ rdac_page_exp->subpage_code = 0x1;
+ sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
+ rdac_page_exp->page_length + 0);
+ rdac_common = &rdac_page_exp->attr;
+ }
+
+ rdac_common->current_mode_lsb = RDAC_FAIL_ALL_PATHS;
+ rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
+ rdac_common->options = RDAC_FORCE_QUIESCENCE;
+
+ if (use_6_byte) {
+ res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
+ fail_paths_pg, 118,
+ true, (do_verbose ? 2 : 0));
+ } else {
+ res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
+ fail_paths_pg, 308,
+ true, (do_verbose ? 2: 0));
+ }
+
+ switch (res) {
+ case 0:
+ if (do_verbose)
+ pr2serr("fail paths successful\n");
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
+ pr2serr("fail paths failed: %s\n", b);
+ break;
+ }
+
+ return res;
+}
+
+static int fail_this_path(int fd, int lun, bool use_6_byte)
+{
+ int res;
+ struct rdac_legacy_page *rdac_page;
+ struct rdac_expanded_page *rdac_page_exp;
+ struct rdac_page_common *rdac_common = NULL;
+ uint8_t fail_paths_pg[308];
+ char b[80];
+
+ if (use_6_byte) {
+ if (lun > 31) {
+ pr2serr("must use 10 byte cdb to fail luns over 31\n");
+ return -1;
+ }
+ } else { /* 10 byte cdb case */
+ if (lun > 255) {
+ pr2serr("lun cannot exceed 255\n");
+ return -1;
+ }
+ }
+
+ memset(fail_paths_pg, 0, 308);
+ if (use_6_byte) {
+ memcpy(fail_paths_pg, mode6_hdr, 4);
+ memcpy(fail_paths_pg + 4, block_descriptor, 8);
+ rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
+ rdac_page->page_code = RDAC_CONTROLLER_PAGE;
+ rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
+ rdac_common = &rdac_page->attr;
+ memset(rdac_page->lun_table, 0x0, 32);
+ rdac_page->lun_table[lun] = 0x81;
+ } else {
+ memcpy(fail_paths_pg, mode10_hdr, 8);
+ rdac_page_exp = (struct rdac_expanded_page *)
+ (fail_paths_pg + 8);
+ rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
+ rdac_page_exp->subpage_code = 0x1;
+ sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
+ rdac_page_exp->page_length + 0);
+ rdac_common = &rdac_page_exp->attr;
+ memset(rdac_page_exp->lun_table, 0x0, 256);
+ rdac_page_exp->lun_table[lun] = 0x81;
+ }
+
+ rdac_common->current_mode_lsb = RDAC_FAIL_SELECTED_PATHS;
+ rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
+ rdac_common->options = RDAC_FORCE_QUIESCENCE;
+
+ if (use_6_byte) {
+ res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
+ fail_paths_pg, 118,
+ true, (do_verbose ? 2 : 0));
+ } else {
+ res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
+ fail_paths_pg, 308,
+ true, (do_verbose ? 2: 0));
+ }
+
+ switch (res) {
+ case 0:
+ if (do_verbose)
+ pr2serr("fail paths successful\n");
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
+ pr2serr("fail paths page (lun=%d) failed: %s\n", lun, b);
+ break;
+ }
+
+ return res;
+}
+
+static void print_rdac_mode(uint8_t *ptr, bool exp_subpg)
+{
+ int i, k, bd_len, lun_table_len;
+ uint8_t * lun_table = NULL;
+ struct rdac_legacy_page *legacy;
+ struct rdac_expanded_page *expanded;
+ struct rdac_page_common *rdac_ptr = NULL;
+
+ if (exp_subpg) {
+ bd_len = ptr[7];
+ expanded = (struct rdac_expanded_page *)(ptr + 8 + bd_len);
+ rdac_ptr = &expanded->attr;
+ lun_table = expanded->lun_table;
+ lun_table_len = 256;
+ } else {
+ bd_len = ptr[3];
+ legacy = (struct rdac_legacy_page *)(ptr + 4 + bd_len);
+ rdac_ptr = &legacy->attr;
+ lun_table = legacy->lun_table;
+ lun_table_len = 32;
+ }
+
+ printf("RDAC %s page\n", exp_subpg ? "Expanded" : "Legacy");
+ printf(" Controller serial: %s\n",
+ rdac_ptr->current_serial);
+ printf(" Alternate controller serial: %s\n",
+ rdac_ptr->alternate_serial);
+ printf(" RDAC mode (redundant processor): ");
+ switch (rdac_ptr->current_mode_msb) {
+ case 0x00:
+ printf("alternate controller not present; ");
+ break;
+ case 0x01:
+ printf("alternate controller present; ");
+ break;
+ default:
+ printf("(Unknown controller status 0x%x); ",
+ rdac_ptr->current_mode_msb);
+ break;
+ }
+ switch (rdac_ptr->current_mode_lsb) {
+ case 0x0:
+ printf("inactive\n");
+ break;
+ case 0x1:
+ printf("active\n");
+ break;
+ case 0x2:
+ printf("Dual active mode\n");
+ break;
+ default:
+ printf("(Unknown mode 0x%x)\n",
+ rdac_ptr->current_mode_lsb);
+ }
+
+ printf(" RDAC mode (alternate processor): ");
+ switch (rdac_ptr->alternate_mode_msb) {
+ case 0x00:
+ printf("alternate controller not present; ");
+ break;
+ case 0x01:
+ printf("alternate controller present; ");
+ break;
+ default:
+ printf("(Unknown status 0x%x); ",
+ rdac_ptr->alternate_mode_msb);
+ break;
+ }
+ switch (rdac_ptr->alternate_mode_lsb) {
+ case 0x0:
+ printf("inactive\n");
+ break;
+ case 0x1:
+ printf("active\n");
+ break;
+ case 0x2:
+ printf("Dual active mode\n");
+ break;
+ case 0x3:
+ printf("Not present\n");
+ break;
+ case 0x4:
+ printf("held in reset\n");
+ break;
+ default:
+ printf("(Unknown mode 0x%x)\n",
+ rdac_ptr->alternate_mode_lsb);
+ }
+ printf(" Quiescence timeout: %d\n", rdac_ptr->quiescence);
+ printf(" RDAC option 0x%x\n", rdac_ptr->options);
+ printf(" ALUA: %s\n", (rdac_ptr->options & 0x4 ? "Enabled" :
+ "Disabled" ));
+ printf(" Force Quiescence: %s\n", (rdac_ptr->options & 0x2 ?
+ "Enabled" : "Disabled" ));
+ printf (" LUN Table: (p = preferred, a = alternate, u = utm lun)\n");
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
+ for (k = 0; k < lun_table_len; k += 16) {
+ printf(" 0x%x:",k / 16);
+ for (i = 0; i < 16; i++) {
+ switch (lun_table[k + i]) {
+ case 0x0:
+ printf(" x");
+ break;
+ case 0x1:
+ printf(" p");
+ break;
+ case 0x2:
+ printf(" a");
+ break;
+ case 0x3:
+ printf(" u");
+ break;
+ default:
+ printf(" ?");
+ break;
+ }
+ if (i == 7) {
+ printf(" ");
+ }
+ }
+ printf("\n");
+ }
+}
+
+static void usage()
+{
+ printf("Usage: sg_rdac [-6] [-a] [-f=LUN] [-v] [-V] DEVICE\n"
+ " where:\n"
+ " -6 use 6 byte cdbs for mode sense/select\n"
+ " -a transfer all devices to the controller\n"
+ " serving DEVICE.\n"
+ " -f=LUN transfer the device at LUN to the\n"
+ " controller serving DEVICE\n"
+ " -v verbose\n"
+ " -V print version then exit\n\n"
+ " Display/Modify RDAC Redundant Controller Page 0x2c.\n"
+ " If [-a] or [-f] is not specified the current settings"
+ " are displayed.\n");
+}
+
+int main(int argc, char * argv[])
+{
+ bool fail_all = false;
+ bool fail_path = false;
+ bool use_6_byte = false;
+ int res, fd, k, resid, len, lun = -1;
+ int ret = 0;
+ char **argptr;
+ char * file_name = 0;
+ uint8_t rsp_buff[MX_ALLOC_LEN];
+
+ if (argc < 2) {
+ usage ();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ for (k = 1; k < argc; ++k) {
+ argptr = argv + k;
+ if (!strcmp (*argptr, "-v"))
+ ++do_verbose;
+ else if (!strncmp(*argptr, "-f=",3)) {
+ fail_path = true;
+ lun = strtoul(*argptr + 3, NULL, 0);
+ }
+ else if (!strcmp(*argptr, "-a")) {
+ fail_all = true;
+ }
+ else if (!strcmp(*argptr, "-6")) {
+ use_6_byte = true;
+ }
+ else if (!strcmp(*argptr, "-V")) {
+ pr2serr("sg_rdac version: %s\n", version_str);
+ return 0;
+ }
+ else if (*argv[k] == '-') {
+ pr2serr("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ pr2serr("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ fd = sg_cmds_open_device(file_name, false /* rw */, do_verbose);
+ if (fd < 0) {
+ pr2serr("open error: %s: %s\n", file_name, safe_strerror(-fd));
+ usage();
+ ret = sg_convert_errno(-fd);
+ goto fini;
+ }
+
+ if (fail_all) {
+ res = fail_all_paths(fd, use_6_byte);
+ } else if (fail_path) {
+ res = fail_this_path(fd, lun, use_6_byte);
+ } else {
+ resid = 0;
+ if (use_6_byte)
+ res = sg_ll_mode_sense6(fd, /* DBD */ false,
+ /* PC */ 0,
+ 0x2c /* page */,
+ 0 /*subpage */,
+ rsp_buff, 252,
+ true, do_verbose);
+ else
+ res = sg_ll_mode_sense10_v2(fd, /* llbaa */ false,
+ /* DBD */ false,
+ /* page control */0,
+ 0x2c, 0x1 /* subpage */,
+ rsp_buff, 308, 0, &resid,
+ true, do_verbose);
+
+ if (! res) {
+ len = sg_msense_calc_length(rsp_buff, 308, use_6_byte,
+ NULL);
+ if (resid > 0) {
+ len = ((308 - resid) < len) ? (308 - resid) :
+ len;
+ if (len < 2)
+ pr2serr("MS(10) residual value (%d) "
+ "a worry\n", resid);
+ }
+ if (do_verbose && (len > 1))
+ dump_mode_page(rsp_buff, len);
+ print_rdac_mode(rsp_buff, ! use_6_byte);
+ } else {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr(">>>>>> try again without the '-6' "
+ "switch for a 10 byte MODE SENSE "
+ "command\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("mode sense: invalid field in cdb "
+ "(perhaps subpages or page control "
+ "(PC) not supported)\n");
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b,
+ do_verbose);
+ pr2serr("mode sense failed: %s\n", b);
+ }
+ }
+ }
+ ret = res;
+
+ res = sg_cmds_close_device(fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(res);
+ }
+fini:
+ if (0 == do_verbose) {
+ if (! sg_if_can2stderr("sg_rdac failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_read.c b/src/sg_read.c
new file mode 100644
index 00000000..abeb4774
--- /dev/null
+++ b/src/sg_read.c
@@ -0,0 +1,931 @@
+/*
+ * A utility program for the Linux OS SCSI generic ("sg") device driver.
+ * Copyright (C) 2001 - 2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ This program reads data from the given SCSI device (typically a disk
+ or cdrom) and discards that data. Its primary goal is to time
+ multiple reads all starting from the same logical address. Its interface
+ is a subset of another member of this package: sg_dd which is a
+ "dd" variant. The input file can be a scsi generic device, a block device,
+ or a seekable file. Streams such as stdin are not acceptable. The block
+ size ('bs') is assumed to be 512 if not given.
+
+ This version should compile with Linux sg drivers with version numbers
+ >= 30000 . For mmap-ed IO the sg version number >= 30122 .
+
+*/
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <linux/major.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "1.38 20220118";
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_SCSI_CDBSZ 10
+#define MAX_SCSI_CDBSZ 16
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define ME "sg_read: "
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT 40000 /* 40,000 millisecs == 40 seconds */
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define FT_OTHER 1 /* filetype other than sg and ... */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_RAW 4 /* filetype is raw char device */
+#define FT_BLOCK 8 /* filetype is block device */
+#define FT_ERROR 64 /* couldn't "stat" file */
+
+#define MIN_RESERVED_SIZE 8192
+
+static int sum_of_resids = 0;
+
+static int64_t dd_count = -1;
+static int64_t orig_count = 0;
+static int64_t in_full = 0;
+static int in_partial = 0;
+
+static int pack_id_count = 0;
+static int verbose = 0;
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+
+static void
+install_handler (int sig_num, void (*sig_handler) (int sig))
+{
+ struct sigaction sigact;
+
+ sigaction (sig_num, NULL, &sigact);
+ if (sigact.sa_handler != SIG_IGN) {
+ sigact.sa_handler = sig_handler;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig_num, &sigact, NULL);
+ }
+}
+
+static void
+print_stats(int iters, const char * str)
+{
+ if (orig_count > 0) {
+ if (0 != dd_count)
+ pr2serr(" remaining block count=%" PRId64 "\n", dd_count);
+ pr2serr("%" PRId64 "+%d records in", in_full - in_partial,
+ in_partial);
+ if (iters > 0)
+ pr2serr(", %s commands issued: %d\n", (str ? str : ""), iters);
+ else
+ pr2serr("\n");
+ } else if (iters > 0)
+ pr2serr("%s commands issued: %d\n", (str ? str : ""), iters);
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ print_stats(0, NULL);
+ kill (getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ print_stats(0, NULL);
+}
+
+static int
+dd_filetype(const char * filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if (RAW_MAJOR == major(st.st_rdev))
+ return FT_RAW;
+ else if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ return FT_OTHER;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_read [blk_sgio=0|1] [bpt=BPT] [bs=BS] "
+ "[cdbsz=6|10|12|16]\n"
+ " count=COUNT [dio=0|1] [dpo=0|1] [fua=0|1] "
+ "if=IFILE\n"
+ " [mmap=0|1] [no_dfxer=0|1] [odir=0|1] "
+ "[skip=SKIP]\n"
+ " [time=TI] [verbose=VERB] [--help] "
+ "[--verbose]\n"
+ " [--version] "
+ " where:\n"
+ " blk_sgio 0->normal IO for block devices, 1->SCSI commands "
+ "via SG_IO\n"
+ " bpt is blocks_per_transfer (default is 128, or 64 KiB "
+ "for default BS)\n"
+ " setting 'bpt=0' will do COUNT zero block SCSI "
+ "READs\n"
+ " bs must match sector size if IFILE accessed via SCSI "
+ "commands\n"
+ " (def=512)\n"
+ " cdbsz size of SCSI READ command (default is 10)\n"
+ " count total bytes read will be BS*COUNT (if no "
+ "error)\n"
+ " (if negative, do |COUNT| zero block SCSI READs)\n"
+ " dio 1-> attempt direct IO on sg device, 0->indirect IO "
+ "(def)\n");
+ pr2serr(" dpo 1-> set disable page out (DPO) in SCSI READs\n"
+ " fua 1-> set force unit access (FUA) in SCSI READs\n"
+ " if an sg, block or raw device, or a seekable file (not "
+ "stdin)\n"
+ " mmap 1->perform mmap-ed IO on sg device, 0->indirect IO "
+ "(def)\n"
+ " no_dxfer 1->DMA to kernel buffers only, not user space, "
+ "0->normal(def)\n"
+ " odir 1->open block device O_DIRECT, 0->don't (def)\n"
+ " skip each transfer starts at this logical address "
+ "(def=0)\n"
+ " time 0->do nothing(def), 1->time from 1st cmd, 2->time "
+ "from 2nd, ...\n"
+ " verbose increase level of verbosity (def: 0)\n"
+ " --help|-h print this usage message then exit\n"
+ " --verbose|-v increase level of verbosity (def: 0)\n"
+ " --version|-V print version number then exit\n\n"
+ "Issue SCSI READ commands, each starting from the same logical "
+ "block address\n");
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool write_true, bool fua, bool dpo)
+{
+ int sz_ind;
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+
+ memset(cdbp, 0, cdb_sz);
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr(ME "for 6 byte commands, maximum number of blocks is "
+ "256\n");
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr(ME "for 6 byte commands, can't address blocks beyond "
+ "%d\n", 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr(ME "for 6 byte commands, neither dpo nor fua bits "
+ "supported\n");
+ return 1;
+ }
+ break;
+ case 10:
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr(ME "for 10 byte commands, maximum number of blocks is "
+ "%d\n", 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64(start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr(ME "expected cdb size of 6, 10, 12, or 16 but got %d\n",
+ cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+/* -3 medium/hardware error, -2 -> not ready, 0 -> successful,
+ 1 -> recoverable (ENOMEM), 2 -> try again (e.g. unit attention),
+ 3 -> try again (e.g. aborted command), -1 -> other unrecoverable error */
+static int
+sg_bread(int sg_fd, uint8_t * buff, int blocks, int64_t from_block, int bs,
+ int cdbsz, bool fua, bool dpo, bool * diop, bool do_mmap,
+ bool no_dxfer)
+{
+ uint8_t rdCmd[MAX_SCSI_CDBSZ];
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+
+ if (sg_build_scsi_cdb(rdCmd, cdbsz, blocks, from_block, false, fua,
+ dpo)) {
+ pr2serr(ME "bad cdb build, from_block=%" PRId64 ", blocks=%d\n",
+ from_block, blocks);
+ return -1;
+ }
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = cdbsz;
+ io_hdr.cmdp = rdCmd;
+ if (blocks > 0) {
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = bs * blocks;
+ /* next: shows dxferp unused during mmap-ed IO */
+ if (! do_mmap)
+ io_hdr.dxferp = buff;
+ if (diop && *diop)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+ else if (do_mmap)
+ io_hdr.flags |= SG_FLAG_MMAP_IO;
+ else if (no_dxfer)
+ io_hdr.flags |= SG_FLAG_NO_DXFER;
+ } else
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.pack_id = pack_id_count++;
+ if (verbose > 1) {
+ char b[128];
+
+ pr2serr(" READ cdb: %s\n",
+ sg_get_command_str(rdCmd, cdbsz, false, sizeof(b), b));
+ }
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ if (ENOMEM == errno)
+ return 1;
+ perror("reading (SG_IO) on sg device, error");
+ return -1;
+ }
+
+ if (verbose > 2)
+ pr2serr( " duration=%u ms\n", io_hdr.duration);
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ if (verbose > 1)
+ sg_chk_n_print3("reading, continue", &io_hdr, true);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ if (verbose)
+ sg_chk_n_print3("reading", &io_hdr, (verbose > 1));
+ return 2;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ if (verbose)
+ sg_chk_n_print3("reading", &io_hdr, (verbose > 1));
+ return 3;
+ case SG_LIB_CAT_NOT_READY:
+ if (verbose)
+ sg_chk_n_print3("reading", &io_hdr, (verbose > 1));
+ return -2;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (verbose)
+ sg_chk_n_print3("reading", &io_hdr, (verbose > 1));
+ return -3;
+ default:
+ sg_chk_n_print3("reading", &io_hdr, !! verbose);
+ return -1;
+ }
+ if (blocks > 0) {
+ if (diop && *diop &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ *diop = 0; /* flag that dio not done (completely) */
+ sum_of_resids += io_hdr.resid;
+ }
+ return 0;
+}
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+#define STR_SZ 1024
+#define INF_SZ 512
+#define EBUFF_SZ 768
+
+
+int
+main(int argc, char * argv[])
+{
+ bool count_given = false;
+ bool dio_tmp;
+ bool do_blk_sgio = false;
+ bool do_dio = false;
+ bool do_mmap = false;
+ bool do_odir = false;
+ bool dpo = false;
+ bool fua = false;
+ bool no_dxfer = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int bs = 0;
+ int bpt = DEF_BLOCKS_PER_TRANSFER;
+ int dio_incomplete = 0;
+ int do_time = 0;
+ int in_type = FT_OTHER;
+ int ret = 0;
+ int scsi_cdbsz = DEF_SCSI_CDBSZ;
+ int res, k, t, buf_sz, iters, infd, blocks, flags, blocks_per, err;
+ int n, keylen;
+ size_t psz;
+ int64_t skip = 0;
+ char * key;
+ char * buf;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * wrkPos = NULL;
+ char inf[INF_SZ];
+ char outf[INF_SZ];
+ char str[STR_SZ];
+ char ebuff[EBUFF_SZ];
+ const char * read_str;
+ struct timeval start_tm, end_tm;
+
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+#else
+ psz = 4096; /* give up, pick likely figure */
+#endif
+ inf[0] = '\0';
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ } else
+ continue;
+ for (key = str, buf = key; (*buf && (*buf != '=')); )
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key,"blk_sgio"))
+ do_blk_sgio = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"bpt")) {
+ bpt = sg_get_num(buf);
+ if ((bpt < 0) || (bpt > MAX_BPT_VALUE)) {
+ pr2serr( ME "bad argument to 'bpt'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"bs")) {
+ bs = sg_get_num(buf);
+ if ((bs < 0) || (bs > MAX_BPT_VALUE)) {
+ pr2serr( ME "bad argument to 'bs'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"cdbsz")) {
+ scsi_cdbsz = sg_get_num(buf);
+ if ((scsi_cdbsz < 0) || (scsi_cdbsz > 32)) {
+ pr2serr( ME "bad argument to 'cdbsz', expect 6, 10, 12, 16 "
+ "or 32\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"count")) {
+ count_given = true;
+ if ('-' == *buf) {
+ dd_count = sg_get_llnum(buf + 1);
+ if ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr( ME "bad argument to 'count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ dd_count = - dd_count;
+ } else {
+ dd_count = sg_get_llnum(buf);
+ if ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr( ME "bad argument to 'count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ } else if (0 == strcmp(key,"dio"))
+ do_dio = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"dpo"))
+ dpo = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"fua"))
+ fua = !! sg_get_num(buf);
+ else if (strcmp(key,"if") == 0) {
+ memcpy(inf, buf, INF_SZ - 1);
+ inf[INF_SZ - 1] = '\0';
+ } else if (0 == strcmp(key,"mmap"))
+ do_mmap = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"no_dxfer"))
+ no_dxfer = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"odir"))
+ do_odir = !! sg_get_num(buf);
+ else if (strcmp(key,"of") == 0) {
+ memcpy(outf, buf, INF_SZ - 1);
+ outf[INF_SZ - 1] = '\0';
+ } else if (0 == strcmp(key,"skip")) {
+ skip = sg_get_llnum(buf);
+ if ((skip < 0) || (skip > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr( 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_given = true;
+ verbose = sg_get_num(buf);
+ } else if (0 == strncmp(key, "--help", 6)) {
+ usage();
+ return 0;
+ } else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++verbose;
+ } else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ if (n > 0) {
+ usage();
+ return 0;
+ }
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ verbose += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ pr2serr( "Unrecognized argument '%s'\n", key);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr( ME ": %s\n", version_str);
+ return 0;
+ }
+
+ if (bs <= 0) {
+ bs = DEF_BLOCK_SIZE;
+ if ((dd_count > 0) && (bpt > 0))
+ pr2serr( "Assume default 'bs' (block size) of %d bytes\n", bs);
+ }
+ if (! count_given) {
+ pr2serr("'count' must be given\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (skip < 0) {
+ pr2serr("skip cannot be negative\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (bpt < 1) {
+ if (0 == bpt) {
+ if (dd_count > 0)
+ dd_count = - dd_count;
+ } else {
+ pr2serr("bpt must be greater than 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (do_dio && do_mmap) {
+ pr2serr("cannot select both dio and mmap\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (no_dxfer && (do_dio || do_mmap)) {
+ pr2serr("cannot select no_dxfer with dio or mmap\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+ install_handler (SIGINT, interrupt_handler);
+ install_handler (SIGQUIT, interrupt_handler);
+ install_handler (SIGPIPE, interrupt_handler);
+ install_handler (SIGUSR1, siginfo_handler);
+
+ if (! inf[0]) {
+ pr2serr("must provide 'if=<filename>'\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == strcmp("-", inf)) {
+ pr2serr("'-' (stdin) invalid as <filename>\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ in_type = dd_filetype(inf);
+ if (FT_ERROR == in_type) {
+ pr2serr("Unable to access: %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if ((FT_BLOCK & in_type) && do_blk_sgio)
+ in_type |= FT_SG;
+
+ if (FT_SG & in_type) {
+ if ((dd_count < 0) && (6 == scsi_cdbsz)) {
+ pr2serr(ME "SCSI READ (6) can't do zero block reads\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ flags = O_RDWR;
+ if (do_odir)
+ flags |= O_DIRECT;
+ if ((infd = open(inf, flags)) < 0) {
+ flags = O_RDONLY;
+ if (do_odir)
+ flags |= O_DIRECT;
+ if ((infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for sg reading", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ if (verbose)
+ pr2serr("Opened %s for SG_IO with flags=0x%x\n", inf, flags);
+ if ((dd_count > 0) && (! (FT_BLOCK & in_type))) {
+ if (verbose > 2) {
+ if (ioctl(infd, SG_GET_RESERVED_SIZE, &t) >= 0)
+ pr2serr(" SG_GET_RESERVED_SIZE yields: %d\n", t);
+ }
+ t = bs * bpt;
+ if ((do_mmap) && (0 != (t % psz)))
+ t = ((t / psz) + 1) * psz; /* round up to next pagesize */
+ res = ioctl(infd, SG_SET_RESERVED_SIZE, &t);
+ if (res < 0)
+ perror(ME "SG_SET_RESERVED_SIZE error");
+ res = ioctl(infd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ pr2serr(ME "sg driver prior to 3.x.y\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (do_mmap && (t < 30122)) {
+ pr2serr(ME "mmap-ed IO needs a sg driver version >= 3.1.22\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+ } else {
+ if (do_mmap) {
+ pr2serr(ME "mmap-ed IO only support on sg devices\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (dd_count < 0) {
+ pr2serr(ME "negative 'count' only supported with SCSI READs\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ flags = O_RDONLY;
+ if (do_odir)
+ flags |= O_DIRECT;
+ if ((infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (verbose)
+ pr2serr("Opened %s for Unix reads with flags=0x%x\n", inf, flags);
+ if (skip > 0) {
+ off64_t offset = skip;
+
+ offset *= bs; /* could exceed 32 bits here! */
+ if (lseek64(infd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "couldn't skip to required position on %s", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+
+ if (0 == dd_count)
+ return 0;
+ orig_count = dd_count;
+
+ if (dd_count > 0) {
+ if (do_dio || do_odir || (FT_RAW & in_type)) {
+ wrkBuff = (uint8_t *)malloc(bs * bpt + psz);
+ if (0 == wrkBuff) {
+ pr2serr("Not enough user memory for aligned storage\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ /* perhaps use posix_memalign() instead */
+ wrkPos = (uint8_t *)(((sg_uintptr_t)wrkBuff + psz - 1) &
+ (~(psz - 1)));
+ } else if (do_mmap) {
+ wrkPos = (uint8_t *)mmap(NULL, bs * bpt,
+ PROT_READ | PROT_WRITE, MAP_SHARED, infd, 0);
+ if (MAP_FAILED == wrkPos) {
+ perror(ME "error from mmap()");
+ return SG_LIB_CAT_OTHER;
+ }
+ } else {
+ wrkBuff = (uint8_t *)malloc(bs * bpt);
+ if (0 == wrkBuff) {
+ pr2serr("Not enough user memory\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ wrkPos = wrkBuff;
+ }
+ }
+
+ blocks_per = bpt;
+ start_tm.tv_sec = 0; /* just in case start set condition not met */
+ start_tm.tv_usec = 0;
+
+ if (verbose && (dd_count < 0))
+ pr2serr("About to issue %" PRId64 " zero block SCSI READs\n",
+ 0 - dd_count);
+
+ /* main loop */
+ for (iters = 0; dd_count != 0; ++iters) {
+ if ((do_time > 0) && (iters == (do_time - 1)))
+ gettimeofday(&start_tm, NULL);
+ if (dd_count < 0)
+ blocks = 0;
+ else
+ blocks = (dd_count > blocks_per) ? blocks_per : dd_count;
+ if (FT_SG & in_type) {
+ dio_tmp = do_dio;
+ res = sg_bread(infd, wrkPos, blocks, skip, bs, scsi_cdbsz,
+ fua, dpo, &dio_tmp, do_mmap, no_dxfer);
+ if (1 == res) { /* ENOMEM, find what's available+try that */
+ if (ioctl(infd, SG_GET_RESERVED_SIZE, &buf_sz) < 0) {
+ perror("RESERVED_SIZE ioctls failed");
+ break;
+ }
+ if (buf_sz < MIN_RESERVED_SIZE)
+ buf_sz = MIN_RESERVED_SIZE;
+ blocks_per = (buf_sz + bs - 1) / bs;
+ blocks = blocks_per;
+ pr2serr("Reducing read to %d blocks per loop\n", blocks_per);
+ res = sg_bread(infd, wrkPos, blocks, skip, bs, scsi_cdbsz,
+ fua, dpo, &dio_tmp, do_mmap, no_dxfer);
+ } else if (2 == res) {
+ pr2serr("Unit attention, try again (r)\n");
+ res = sg_bread(infd, wrkPos, blocks, skip, bs, scsi_cdbsz,
+ fua, dpo, &dio_tmp, do_mmap, no_dxfer);
+ }
+ if (0 != res) {
+ switch (res) {
+ case -3:
+ ret = SG_LIB_CAT_MEDIUM_HARD;
+ pr2serr(ME "SCSI READ medium/hardware error\n");
+ break;
+ case -2:
+ ret = SG_LIB_CAT_NOT_READY;
+ pr2serr(ME "device not ready\n");
+ break;
+ case 2:
+ ret = SG_LIB_CAT_UNIT_ATTENTION;
+ pr2serr(ME "SCSI READ unit attention\n");
+ break;
+ case 3:
+ ret = SG_LIB_CAT_ABORTED_COMMAND;
+ pr2serr(ME "SCSI READ aborted command\n");
+ break;
+ default:
+ ret = SG_LIB_CAT_OTHER;
+ pr2serr(ME "SCSI READ failed\n");
+ break;
+ }
+ break;
+ } else {
+ in_full += blocks;
+ if (do_dio && (0 == dio_tmp))
+ dio_incomplete++;
+ }
+ } else {
+ if (iters > 0) { /* subsequent iteration reset skip position */
+ off64_t offset = skip;
+
+ offset *= bs; /* could exceed 32 bits here! */
+ if (lseek64(infd, offset, SEEK_SET) < 0) {
+ perror(ME "could not reset skip position");
+ break;
+ }
+ }
+ while (((res = read(infd, wrkPos, blocks * bs)) < 0) &&
+ (EINTR == errno))
+ ;
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "reading, skip=%" PRId64 " ",
+ skip);
+ perror(ebuff);
+ break;
+ } else if (res < blocks * bs) {
+ pr2serr(ME "short read: wanted/got=%d/%d bytes, stop\n",
+ blocks * bs, res);
+ blocks = res / bs;
+ if ((res % bs) > 0) {
+ blocks++;
+ in_partial++;
+ }
+ dd_count -= blocks;
+ in_full += blocks;
+ break;
+ }
+ in_full += blocks;
+ }
+ if (dd_count > 0)
+ dd_count -= blocks;
+ else if (dd_count < 0)
+ ++dd_count;
+ }
+ read_str = (FT_SG & in_type) ? "SCSI READ" : "read";
+ if (do_time > 0) {
+ gettimeofday(&end_tm, NULL);
+ if (start_tm.tv_sec || start_tm.tv_usec) {
+ struct timeval res_tm;
+ double a, b, c;
+
+ 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);
+ if (orig_count > 0) {
+ b = (double)bs * (orig_count - dd_count);
+ if (do_time > 1)
+ c = b - ((double)bs * ((do_time - 1.0) * bpt));
+ else
+ c = 0.0;
+ } else {
+ b = 0.0;
+ c = 0.0;
+ }
+
+ if (1 == do_time) {
+ pr2serr("Time for all %s commands was %d.%06d secs", read_str,
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((orig_count > 0) && (a > 0.00001) && (b > 511))
+ pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ } else if (2 == do_time) {
+ pr2serr("Time from second %s command to end was %d.%06d secs",
+ read_str, (int)res_tm.tv_sec,
+ (int)res_tm.tv_usec);
+ if ((orig_count > 0) && (a > 0.00001) && (c > 511))
+ pr2serr(", %.2f MB/sec\n", c / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ } else {
+ pr2serr("Time from start of %s command "
+ "#%d to end was %d.%06d secs", read_str, do_time,
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((orig_count > 0) && (a > 0.00001) && (c > 511))
+ pr2serr(", %.2f MB/sec\n", c / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ }
+ if ((iters > 0) && (a > 0.00001))
+ pr2serr("Average number of %s commands per second was %.2f\n",
+ read_str, (double)iters / a);
+ }
+ }
+
+ if (wrkBuff)
+ free(wrkBuff);
+
+ close(infd);
+ if (0 != dd_count) {
+ pr2serr("Some error occurred,");
+ if (0 == ret)
+ ret = SG_LIB_CAT_OTHER;
+ }
+ print_stats(iters, read_str);
+
+ if (dio_incomplete) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ dio_incomplete);
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (sum_of_resids)
+ pr2serr(">> Non-zero sum of residual counts=%d\n", sum_of_resids);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_read_attr.c b/src/sg_read_attr.c
new file mode 100644
index 00000000..89ca26da
--- /dev/null
+++ b/src/sg_read_attr.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (c) 2016-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI READ ATTRIBUTE command to the given SCSI device
+ * and decodes the response. Based on spc5r08.pdf
+ */
+
+static const char * version_str = "1.16 20211114";
+
+#define MAX_RATTR_BUFF_LEN (1024 * 1024)
+#define DEF_RATTR_BUFF_LEN (1024 * 8)
+
+#define SG_READ_ATTRIBUTE_CMD 0x8c
+#define SG_READ_ATTRIBUTE_CMDLEN 16
+
+#define RA_ATTR_VAL_SA 0x0
+#define RA_ATTR_LIST_SA 0x1
+#define RA_LV_LIST_SA 0x2
+#define RA_PART_LIST_SA 0x3
+#define RA_SMC2_SA 0x4
+#define RA_SUP_ATTR_SA 0x5
+#define RA_HIGHEST_SA 0x5
+
+#define RA_FMT_BINARY 0x0
+#define RA_FMT_ASCII 0x1
+#define RA_FMT_TEXT 0x2 /* takes into account locale */
+#define RA_FMT_RES 0x3 /* reserved */
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+struct opts_t {
+ bool cache;
+ bool enumerate;
+ bool do_raw;
+ bool o_readonly;
+ bool verbose_given;
+ bool version_given;
+ int elem_addr;
+ int filter;
+ int fai;
+ int do_hex;
+ int lvn;
+ int maxlen;
+ int pn;
+ int quiet;
+ int sa;
+ int verbose;
+};
+
+struct acron_nv_t {
+ const char * acron;
+ const char * name;
+ int val;
+};
+
+struct attr_name_info_t {
+ int id;
+ const char * name; /* tab ('\t') suggest line break */
+ int format; /* RA_FMT_BINARY and friends, -1 --> unknown */
+ int len; /* -1 --> not fixed (variable) */
+ int process; /* 0 --> print decimal if binary, 1 --> print hex,
+ * 2 --> further processing */
+};
+
+static struct option long_options[] = {
+ {"cache", no_argument, 0, 'c'},
+ {"enumerate", no_argument, 0, 'e'},
+ {"element", required_argument, 0, 'E'}, /* SMC-3 element address */
+ {"filter", required_argument, 0, 'f'},
+ {"first", required_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'},
+ {"lvn", required_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"partition", required_argument, 0, 'p'},
+ {"quiet", required_argument, 0, 'q'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"sa", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}, /* sentinel */
+};
+
+static struct acron_nv_t sa_acron_arr[] = {
+ {"av", "attribute values", 0},
+ {"al", "attribute list", 1},
+ {"lvl", "logical volume list", 2},
+ {"pl", "partition list", 3},
+ {"smc", "SMC-2 should define this", 4},
+ {"sa", "supported attributes", 5},
+ {NULL, NULL, -1}, /* sentinel */
+};
+
+static struct attr_name_info_t attr_name_arr[] = {
+/* Device type attributes */
+ {0x0, "Remaining capacity in partition [MiB]", RA_FMT_BINARY, 8, 0},
+ {0x1, "Maximum capacity in partition [MiB]", RA_FMT_BINARY, 8, 0},
+ {0x2, "TapeAlert flags", RA_FMT_BINARY, 8, 0}, /* SSC-4 */
+ {0x3, "Load count", RA_FMT_BINARY, 8, 0},
+ {0x4, "MAM space remaining [B]", RA_FMT_BINARY, 8, 0},
+ {0x5, "Assigning organization", RA_FMT_ASCII, 8, 0}, /* SSC-4 */
+ {0x6, "Format density code", RA_FMT_BINARY, 1, 1}, /* SSC-4 */
+ {0x7, "Initialization count", RA_FMT_BINARY, 2, 0},
+ {0x8, "Volume identifier", RA_FMT_ASCII, 32, 0},
+ {0x9, "Volume change reference", RA_FMT_BINARY, -1, 1}, /* SSC-4 */
+ {0x20a, "Density vendor/serial number at last load", RA_FMT_ASCII, 40, 0},
+ {0x20b, "Density vendor/serial number at load-1", RA_FMT_ASCII, 40, 0},
+ {0x20c, "Density vendor/serial number at load-2", RA_FMT_ASCII, 40, 0},
+ {0x20d, "Density vendor/serial number at load-3", RA_FMT_ASCII, 40, 0},
+ {0x220, "Total MiB written in medium life", RA_FMT_BINARY, 8, 0},
+ {0x221, "Total MiB read in medium life", RA_FMT_BINARY, 8, 0},
+ {0x222, "Total MiB written in current/last load", RA_FMT_BINARY, 8, 0},
+ {0x223, "Total MiB read in current/last load", RA_FMT_BINARY, 8, 0},
+ {0x224, "Logical position of first encrypted block", RA_FMT_BINARY, 8, 2},
+ {0x225, "Logical position of first unencrypted block\tafter first "
+ "encrypted block", RA_FMT_BINARY, 8, 2},
+ {0x340, "Medium usage history", RA_FMT_BINARY, 90, 2},
+ {0x341, "Partition usage history", RA_FMT_BINARY, 60, 2},
+
+/* Medium type attributes */
+ {0x400, "Medium manufacturer", RA_FMT_ASCII, 8, 0},
+ {0x401, "Medium serial number", RA_FMT_ASCII, 32, 0},
+ {0x402, "Medium length [m]", RA_FMT_BINARY, 4, 0}, /* SSC-4 */
+ {0x403, "Medium width [0.1 mm]", RA_FMT_BINARY, 4, 0}, /* SSC-4 */
+ {0x404, "Assigning organization", RA_FMT_ASCII, 8, 0}, /* SSC-4 */
+ {0x405, "Medium density code", RA_FMT_BINARY, 1, 1}, /* SSC-4 */
+ {0x406, "Medium manufacture date", RA_FMT_ASCII, 8, 0},
+ {0x407, "MAM capacity [B]", RA_FMT_BINARY, 8, 0},
+ {0x408, "Medium type", RA_FMT_BINARY, 1, 1},
+ {0x409, "Medium type information", RA_FMT_BINARY, 2, 1},
+ {0x40a, "Numeric medium serial number", -1, -1, 1},
+
+/* Host type attributes */
+ {0x800, "Application vendor", RA_FMT_ASCII, 8, 0},
+ {0x801, "Application name", RA_FMT_ASCII, 32, 0},
+ {0x802, "Application version", RA_FMT_ASCII, 8, 0},
+ {0x803, "User medium text label", RA_FMT_TEXT, 160, 0},
+ {0x804, "Date and time last written", RA_FMT_ASCII, 12, 0},
+ {0x805, "Text localization identifier", RA_FMT_BINARY, 1, 0},
+ {0x806, "Barcode", RA_FMT_ASCII, 32, 0},
+ {0x807, "Owning host textual name", RA_FMT_TEXT, 80, 0},
+ {0x808, "Media pool", RA_FMT_TEXT, 160, 0},
+ {0x809, "Partition user text label", RA_FMT_ASCII, 16, 0},
+ {0x80a, "Load/unload at partition", RA_FMT_BINARY, 1, 0},
+ {0x80a, "Application format version", RA_FMT_ASCII, 16, 0},
+ {0x80c, "Volume coherency information", RA_FMT_BINARY, -1, 1},
+ /* SSC-5 */
+ {0x820, "Medium globally unique identifier", RA_FMT_BINARY, 36, 1},
+ {0x821, "Media pool globally unique identifier", RA_FMT_BINARY, 36, 1},
+
+ {-1, NULL, -1, -1, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_read_attr [--cache] [--element=EA] [--enumerate] "
+ "[--filter=FL]\n"
+ " [--first=FAI] [--help] [--hex] [--in=FN] "
+ "[--lvn=LVN]\n"
+ " [--maxlen=LEN] [--partition=PN] [--quiet] "
+ "[--raw]\n"
+ " [--readonly] [--sa=SA] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n");
+ pr2serr(" where:\n"
+ " --cache|-c set CACHE bit in cdn (def: clear)\n"
+ " --enumerate|-e enumerate known attributes and service "
+ "actions\n"
+ " --element=EA|-E EA EA is placed in 'element address' "
+ "field in\n"
+ " cdb [SMC-3] (def: 0)\n"
+ " --filter=FL|-f FL FL is parameter code to match (def: "
+ "-1 -> all)\n"
+ " --first=FAI|-F FAI FAI is placed in 'first attribute "
+ "identifier'\n"
+ " field in cdb (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output response in hexadecimal; used "
+ "twice\n"
+ " shows decoded values in hex\n"
+ " --in=FN|-i FN FN is a filename containing attribute "
+ "values in\n"
+ " ASCII hex or binary if --raw also "
+ "given\n"
+ " --lvn=LVN|-l LVN logical volume number (LVN) (def:0)\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> 8192 bytes)\n"
+ " --partition=PN|-p PN partition number (PN) (def:0)\n"
+ " --quiet|-q reduce the amount of output, can use "
+ "more than once\n"
+ " --raw|-r output response in binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --sa=SA|-s SA SA is service action (def: 0)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI READ ATTRIBUTE command. Even though it is "
+ "defined in\nSPC-3 and later it is typically used on tape "
+ "systems.\n");
+}
+
+/* Invokes a SCSI READ ATTRIBUTE command (SPC+SMC). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_read_attr(int sg_fd, void * resp, int * residp, bool noisy,
+ const struct opts_t * op)
+{
+ int ret, res, sense_cat;
+ uint8_t ra_cdb[SG_READ_ATTRIBUTE_CMDLEN] =
+ {SG_READ_ATTRIBUTE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ ra_cdb[1] = 0x1f & op->sa;
+ if (op->elem_addr)
+ sg_put_unaligned_be16(op->elem_addr, ra_cdb + 2);
+ if (op->lvn)
+ ra_cdb[5] = 0xff & op->lvn;
+ if (op->pn)
+ ra_cdb[7] = 0xff & op->pn;
+ if (op->fai)
+ sg_put_unaligned_be16(op->fai, ra_cdb + 8);
+ sg_put_unaligned_be32((uint32_t)op->maxlen, ra_cdb + 10);
+ if (op->cache)
+ ra_cdb[14] |= 0x1;
+ if (op->verbose) {
+ char b[128];
+
+ pr2serr(" Read attribute cdb: %s\n",
+ sg_get_command_str(ra_cdb, SG_READ_ATTRIBUTE_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, ra_cdb, sizeof(ra_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, op->maxlen);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, op->verbose);
+ ret = sg_cmds_process_resp(ptvp, "read attribute", res, noisy,
+ op->verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static int
+find_sa_acron(const char * cp)
+{
+ int k;
+ const struct acron_nv_t * anvp;
+ const char * mp;
+
+ for (anvp = sa_acron_arr; anvp->acron ; ++anvp) {
+ for (mp = cp, k = 0; *mp; ++mp, ++k) {
+ if (0 == anvp->acron[k])
+ return anvp->val;
+ if (tolower((uint8_t)*mp) != (uint8_t)anvp->acron[k])
+ break;
+ }
+ if ((0 == *mp) && (0 == anvp->acron[k]))
+ return anvp->val;
+ }
+ return -1; /* not found */
+}
+
+const char * a_format[] = {
+ "binary",
+ "ascii",
+ "text",
+ "format[0x3]",
+};
+
+static void
+enum_attributes(void)
+{
+ const struct attr_name_info_t * anip;
+ const char * cp;
+ char b[32];
+
+ printf("Attribute ID\tLength\tFormat\tName\n");
+ printf("------------------------------------------\n");
+ for (anip = attr_name_arr; anip->name ; ++anip) {
+ if (anip->format < 0)
+ snprintf(b, sizeof(b), "unknown");
+ else
+ snprintf(b, sizeof(b), "%s", a_format[0x3 & anip->format]);
+ printf(" 0x%04x:\t%d\t%s\t", anip->id, anip->len, b);
+ cp = strchr(anip->name, '\t');
+ if (cp ) {
+ printf("%.*s\n", (int)(cp - anip->name), anip->name);
+ printf("\t\t\t\t%s\n", cp + 1);
+ } else
+ printf("%s\n", anip->name);
+ }
+}
+
+static void
+enum_sa_acrons(void)
+{
+ const struct acron_nv_t * anvp;
+
+ printf("SA_value\tAcronym\tDescription\n");
+ printf("------------------------------------------\n");
+ for (anvp = sa_acron_arr; anvp->acron ; ++anvp)
+ printf(" %d:\t\t%s\t%s\n", anvp->val, anvp->acron, anvp->name);
+}
+
+/* Returns 1 if 'bp' all 0xff bytes, returns 2 is all 0xff bytes apart
+ * from last being 0xfe; otherwise returns 0. */
+static int
+all_ffs_or_last_fe(const uint8_t * bp, int len)
+{
+ for ( ; len > 0; ++bp, --len) {
+ if (*bp < 0xfe)
+ return 0;
+ if (0xfe == *bp)
+ return (1 == len) ? 2 : 0;
+
+ }
+ return 1;
+}
+
+static char *
+attr_id_lookup(unsigned int id, const struct attr_name_info_t ** anipp,
+ int blen, char * b)
+{
+ const struct attr_name_info_t * anip;
+
+ for (anip = attr_name_arr; anip->name; ++anip) {
+ if (id == (unsigned int)anip->id)
+ break;
+ }
+ if (anip->name) {
+ snprintf(b, blen, "%s", anip->name);
+ if (anipp)
+ *anipp = anip;
+ return b;
+ }
+ if (anipp)
+ *anipp = NULL;
+ if (id < 0x400)
+ snprintf(b, blen, "Unknown device attribute 0x%x", id);
+ else if (id < 0x800)
+ snprintf(b, blen, "Unknown medium attribute 0x%x", id);
+ else if (id < 0xc00)
+ snprintf(b, blen, "Unknown host attribute 0x%x", id);
+ else if (id < 0x1000)
+ snprintf(b, blen, "Vendor specific device attribute 0x%x", id);
+ else if (id < 0x1400)
+ snprintf(b, blen, "Vendor specific medium attribute 0x%x", id);
+ else if (id < 0x1800)
+ snprintf(b, blen, "Vendor specific host attribute 0x%x", id);
+ else
+ snprintf(b, blen, "Reserved attribute 0x%x", id);
+ return b;
+}
+
+static void
+decode_attr_list(const uint8_t * alp, int len, bool supported,
+ const struct opts_t * op)
+{
+ int id;
+ char b[160];
+ char * cp;
+ char * c2p;
+ const char * leadin = supported ? "Supported a" : "A";
+
+ if (op->verbose)
+ printf("%sttribute list: [len=%d]\n", leadin, len);
+ else if (0 == op->quiet)
+ printf("%sttribute list:\n", leadin);
+ if (op->do_hex) {
+ hex2stdout(alp, len, 0);
+ return;
+ }
+ for ( ; len > 0; alp += 2, len -= 2) {
+ id = sg_get_unaligned_be16(alp + 0);
+ if ((op->filter >= 0) && (op->filter != id))
+ continue;
+ if (op->verbose)
+ printf(" 0x%.4x:\t", id);
+ cp = attr_id_lookup(id, NULL, sizeof(b), b);
+ c2p = strchr(cp, '\t');
+ if (c2p) {
+ printf(" %.*s -\n", (int)(c2p - cp), cp);
+ if (op->verbose)
+ printf("\t\t %s\n", c2p + 1);
+ else
+ printf(" %s\n", c2p + 1);
+ } else
+ printf(" %s\n", cp);
+ }
+}
+
+static void
+helper_full_attr(const uint8_t * alp, int len, int id,
+ const struct attr_name_info_t * anip,
+ const struct opts_t * op)
+{
+ int k;
+ const uint8_t * bp;
+
+ if (op->verbose)
+ printf("[r%c] ", (0x80 & alp[2]) ? 'o' : 'w');
+ if (op->verbose > 3)
+ pr2serr("%s: id=0x%x, len=%d, anip->format=%d, anip->len=%d\n",
+ __func__, id, len, anip->format, anip->len);
+ switch (id) {
+ case 0x224: /* logical position of first encrypted block */
+ k = all_ffs_or_last_fe(alp + 5, len - 5);
+ if (1 == k)
+ printf("<unknown> [ff]\n");
+ else if (2 == k)
+ printf("<unknown [fe]>\n");
+ else {
+ if ((len - 5) <= 8)
+ printf("%" PRIx64, sg_get_unaligned_be(len - 5, alp + 5));
+ else {
+ printf("\n");
+ hex2stdout((alp + 5), len - 5, 0);
+ }
+ }
+ break;
+ case 0x225: /* logical position of first unencrypted block
+ * after first encrypted block */
+ k = all_ffs_or_last_fe(alp + 5, len - 5);
+ if (1 == k)
+ printf("<unknown> [ff]\n");
+ else if (2 == k)
+ printf("<unknown [fe]>\n");
+ else {
+ if ((len - 5) <= 8)
+ printf("%" PRIx64, sg_get_unaligned_be(len - 5, alp + 5));
+ else {
+ printf("\n");
+ hex2stdout(alp + 5, len - 5, 0);
+ }
+ }
+ break;
+ case 0x340: /* Medium Usage history */
+ bp = alp + 5;
+ printf("\n");
+ if ((len - 5) < 90) {
+ pr2serr("%s: expected 90 bytes, got %d\n", __func__, len - 5);
+ break;
+ }
+ printf(" Current amount of data written [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 0));
+ printf(" Current write retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 6));
+ printf(" Current amount of data read [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 12));
+ printf(" Current read retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 18));
+ printf(" Previous amount of data written [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 24));
+ printf(" Previous write retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 30));
+ printf(" Previous amount of data read [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 36));
+ printf(" Previous read retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 42));
+ printf(" Total amount of data written [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 48));
+ printf(" Total write retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 54));
+ printf(" Total amount of data read [MiB]: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 60));
+ printf(" Total read retry count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 66));
+ printf(" Load count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 72));
+ printf(" Total change partition count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 78));
+ printf(" Total partition initialization count: %" PRIu64 "\n",
+ sg_get_unaligned_be48(bp + 84));
+ break;
+ case 0x341: /* Partition Usage history */
+ bp = alp + 5;
+ printf("\n");
+ if ((len - 5) < 60) {
+ pr2serr("%s: expected 60 bytes, got %d\n", __func__, len - 5);
+ break;
+ }
+ printf(" Current amount of data written [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 0));
+ printf(" Current write retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 4));
+ printf(" Current amount of data read [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 8));
+ printf(" Current read retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 12));
+ printf(" Previous amount of data written [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 16));
+ printf(" Previous write retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 20));
+ printf(" Previous amount of data read [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 24));
+ printf(" Previous read retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 28));
+ printf(" Total amount of data written [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 32));
+ printf(" Total write retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 36));
+ printf(" Total amount of data read [MiB]: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 40));
+ printf(" Total read retry count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 44));
+ printf(" Load count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 48));
+ printf(" change partition count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 52));
+ printf(" partition initialization count: %" PRIu32 "\n",
+ sg_get_unaligned_be32(bp + 56));
+ break;
+ default:
+ pr2serr("%s: unknown attribute id: 0x%x\n", __func__, id);
+ printf(" In hex:\n");
+ hex2stdout(alp, len, 0);
+ break;
+ }
+}
+
+static void
+decode_attr_vals(const uint8_t * alp, int len, const struct opts_t * op)
+{
+ int bump, id, alen;
+ uint64_t ull;
+ char * cp;
+ char * c2p;
+ const struct attr_name_info_t * anip;
+ char b[160];
+
+ if (op->verbose)
+ printf("Attribute values: [len=%d]\n", len);
+ else if (op->filter < 0) {
+ if (0 == op->quiet)
+ printf("Attribute values:\n");
+ if (op->do_hex) { /* only expect -HH to get through here */
+ hex2stdout(alp, len, 0);
+ return;
+ }
+ }
+ for ( ; len > 4; alp += bump, len -= bump) {
+ id = sg_get_unaligned_be16(alp + 0);
+ bump = sg_get_unaligned_be16(alp + 3) + 5;
+ alen = bump - 5;
+ if ((op->filter >= 0) && (op->filter != id)) {
+ if (id < op->filter)
+ continue;
+ else
+ break; /* Assume array is ascending id order */
+ }
+ anip = NULL;
+ cp = attr_id_lookup(id, &anip, sizeof(b), b);
+ if (op->quiet < 2) {
+ c2p = strchr(cp, '\t');
+ if (c2p) {
+ printf(" %.*s -\n", (int)(c2p - cp), cp);
+ printf(" %s: ", c2p + 1);
+ } else
+ printf(" %s: ", cp);
+ }
+ if (op->verbose)
+ printf("[r%c] ", (0x80 & alp[2]) ? 'o' : 'w');
+ if (anip) {
+ if ((RA_FMT_BINARY == anip->format) && (bump <= 13)) {
+ ull = sg_get_unaligned_be(alen, alp + 5);
+ if (0 == anip->process)
+ printf("%" PRIu64 "\n", ull);
+ else if (1 == anip->process)
+ printf("0x%" PRIx64 "\n", ull);
+ else
+ helper_full_attr(alp, bump, id, anip, op);
+ if (op->verbose) {
+ if ((anip->len > 0) && (alen > 0) && (alen != anip->len))
+ printf(" <<< T10 length (%d) differs from length in "
+ "response (%d) >>>\n", anip->len, alen);
+ }
+ } else if (RA_FMT_BINARY == anip->format) {
+ if (2 == anip->process)
+ helper_full_attr(alp, bump, id, anip, op);
+ else {
+ printf("\n");
+ hex2stdout(alp + 5, alen, 0);
+ }
+ } else {
+ if (2 == anip->process)
+ helper_full_attr(alp, bump, id, anip, op);
+ else {
+ printf("%.*s\n", alen, alp + 5);
+ if (op->verbose) {
+ if ((anip->len > 0) && (alen > 0) &&
+ (alen != anip->len))
+ printf(" <<< T10 length (%d) differs from length "
+ "in response (%d) >>>\n", anip->len, alen);
+ }
+ }
+ }
+ } else {
+ if (op->verbose > 1)
+ printf("Attribute id lookup failed, in hex:\n");
+ else
+ printf("\n");
+ hex2stdout(alp + 5, alen, 0);
+ }
+ }
+ if (op->verbose && (len > 0) && (len <= 4))
+ pr2serr("warning: iterate of attributes should end a residual of "
+ "%d\n", len);
+}
+
+static void
+decode_all_sa_s(const uint8_t * rabp, int len, const struct opts_t * op)
+{
+ if (op->do_hex && (2 != op->do_hex)) {
+ hex2stdout(rabp, len, ((1 == op->do_hex) ? 1 : -1));
+ return;
+ }
+ switch (op->sa) {
+ case RA_ATTR_VAL_SA:
+ decode_attr_vals(rabp + 4, len - 4, op);
+ break;
+ case RA_ATTR_LIST_SA:
+ decode_attr_list(rabp + 4, len - 4, false, op);
+ break;
+ case RA_LV_LIST_SA:
+ if ((0 == op->quiet) || op->verbose)
+ printf("Logical volume list:\n");
+ if (len < 4) {
+ pr2serr(">>> response length unexpectedly short: %d bytes\n",
+ len);
+ break;
+ }
+ printf(" First logical volume number: %u\n", rabp[2]);
+ printf(" Number of logical volumes available: %u\n", rabp[3]);
+ break;
+ case RA_PART_LIST_SA:
+ if ((0 == op->quiet) || op->verbose)
+ printf("Partition number list:\n");
+ if (len < 4) {
+ pr2serr(">>> response length unexpectedly short: %d bytes\n",
+ len);
+ break;
+ }
+ printf(" First partition number: %u\n", rabp[2]);
+ printf(" Number of partitions available: %u\n", rabp[3]);
+ break;
+ case RA_SMC2_SA:
+ printf("Used by SMC-2, not information, output in hex:\n");
+ hex2stdout(rabp, len, 0);
+ break;
+ case RA_SUP_ATTR_SA:
+ decode_attr_list(rabp + 4, len - 4, true, op);
+ break;
+ default:
+ printf("Unrecognized service action [0x%x], response in hex:\n",
+ op->sa);
+ hex2stdout(rabp, len, 0);
+ break;
+ }
+}
+
+int
+main(int argc, char * argv[])
+{
+ int sg_fd, res, c, len, resid, rlen;
+ unsigned int ra_len;
+ int in_len = 0;
+ int ret = 0;
+ const char * device_name = NULL;
+ const char * fname = NULL;
+ uint8_t * rabp = NULL;
+ uint8_t * free_rabp = NULL;
+ struct opts_t opts;
+ struct opts_t * op;
+ char b[80];
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->filter = -1;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ceE:f:F:hHi:l:m:p:qrRs:vV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ op->cache = true;
+ break;
+ case 'e':
+ op->enumerate = true;
+ break;
+ case 'E':
+ op->elem_addr = sg_get_num(optarg);
+ if ((op->elem_addr < 0) || (op->elem_addr > 65535)) {
+ pr2serr("bad argument to '--element=EA', expect 0 to 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'f':
+ op->filter = sg_get_num(optarg);
+ if ((op->filter < -3) || (op->filter > 65535)) {
+ pr2serr("bad argument to '--filter=FL', expect -3 to "
+ "65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'F':
+ op->fai = sg_get_num(optarg);
+ if ((op->fai < 0) || (op->fai > 65535)) {
+ pr2serr("bad argument to '--first=FAI', expect 0 to 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ fname = optarg;
+ break;
+ case 'l':
+ op->lvn = sg_get_num(optarg);
+ if ((op->lvn < 0) || (op->lvn > 255)) {
+ pr2serr("bad argument to '--lvn=LVN', expect 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'm':
+ op->maxlen = sg_get_num(optarg);
+ if ((op->maxlen < 0) || (op->maxlen > MAX_RATTR_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or "
+ "less\n", MAX_RATTR_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ op->pn = sg_get_num(optarg);
+ if ((op->pn < 0) || (op->pn > 255)) {
+ pr2serr("bad argument to '--pn=PN', expect 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'q':
+ ++op->quiet;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 's':
+ if (isdigit((uint8_t)*optarg)) {
+ op->sa = sg_get_num(optarg);
+ if ((op->sa < 0) || (op->sa > 63)) {
+ pr2serr("bad argument to '--sa=SA', expect 0 to 63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ res = find_sa_acron(optarg);
+ if (res < 0) {
+ enum_sa_acrons();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->sa = res;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (op->enumerate) {
+ enum_attributes();
+ printf("\n");
+ enum_sa_acrons();
+ return 0;
+ }
+
+ if (fname && device_name) {
+ pr2serr("since '--in=FN' given, ignoring DEVICE\n");
+ device_name = NULL;
+ }
+
+ if (0 == op->maxlen)
+ op->maxlen = DEF_RATTR_BUFF_LEN;
+ rabp = (uint8_t *)sg_memalign(op->maxlen, 0, &free_rabp, op->verbose > 3);
+ if (NULL == rabp) {
+ pr2serr("unable to sg_memalign %d bytes\n", op->maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+
+ if (NULL == device_name) {
+ if (fname) {
+ if ((ret = sg_f2hex_arr(fname, op->do_raw, false /* no space */,
+ rabp, &in_len, op->maxlen)))
+ goto clean_up;
+ if (op->do_raw)
+ op->do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n",
+ fname, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto clean_up;
+ }
+ decode_all_sa_s(rabp, in_len, op);
+ goto clean_up;
+ }
+ pr2serr("missing device name!\n");
+ usage();
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto clean_up;
+ }
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto clean_up;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, op->o_readonly, op->verbose);
+ if (sg_fd < 0) {
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto clean_up;
+ }
+
+ res = sg_ll_read_attr(sg_fd, rabp, &resid, op->verbose > 0, op);
+ ret = res;
+ if (0 == res) {
+ rlen = op->maxlen - resid;
+ if (rlen < 4) {
+ pr2serr("Response length (%d) too short\n", rlen);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto close_then_end;
+ }
+ if ((op->sa <= RA_HIGHEST_SA) && (op->sa != RA_SMC2_SA)) {
+ ra_len = ((RA_LV_LIST_SA == op->sa) ||
+ (RA_PART_LIST_SA == op->sa)) ?
+ (unsigned int)sg_get_unaligned_be16(rabp + 0) :
+ sg_get_unaligned_be32(rabp + 0) + 2;
+ ra_len += 2;
+ } else
+ ra_len = rlen;
+ if ((int)ra_len > rlen) {
+ if (op->verbose)
+ pr2serr("ra_len available is %d, response length is %d\n",
+ ra_len, rlen);
+ len = rlen;
+ } else
+ len = (int)ra_len;
+ if (op->do_raw) {
+ dStrRaw((const char *)rabp, len);
+ goto close_then_end;
+ }
+ decode_all_sa_s(rabp, len, op);
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Read attribute command not supported\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("Read attribute command: %s\n", b);
+ }
+
+close_then_end:
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+clean_up:
+ if (free_rabp)
+ free(free_rabp);
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_read_attr failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_read_block_limits.c b/src/sg_read_block_limits.c
new file mode 100644
index 00000000..4fc1fae6
--- /dev/null
+++ b/src/sg_read_block_limits.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2009-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI READ BLOCK LIMITS command (SSC) to the given
+ * SCSI device.
+ */
+
+static const char * version_str = "1.09 20221101";
+
+#define DEF_READ_BLOCK_LIMITS_LEN 6
+#define MLIO_READ_BLOCK_LIMITS_LEN 20
+#define MAX_READ_BLOCK_LIMITS_LEN MLIO_READ_BLOCK_LIMITS_LEN
+
+static uint8_t readBlkLmtBuff[MAX_READ_BLOCK_LIMITS_LEN];
+
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"mloi", no_argument, 0, 'm'}, /* added in ssc4r02.pdf */
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_read_block_limits [--help] [--hex] [--mloi] "
+ "[--raw]\n"
+ " [--readonly] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output response in hexadecimal\n"
+ " --mloi|-m output maximum logical object "
+ "identifier\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --readonly|-R open DEVICE in read-only mode\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI READ BLOCK LIMITS command and decode the "
+ "response\n"
+ );
+}
+
+static void
+dStrRaw(const char * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool do_mloi = false;
+ bool do_raw = false;
+ bool readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, k, m, res, c, max_resp_len;
+ int resid = 0;
+ int actual_len = 0;
+ int do_hex = 0;
+ int verbose = 0;
+ int ret = 0;
+ uint32_t max_block_size;
+ uint64_t mloi;
+ uint16_t min_block_size;
+ uint8_t granularity;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hHmrRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'm':
+ do_mloi = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("invalid option -%c ??\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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ printf("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto the_end2;
+ }
+
+ max_resp_len = do_mloi ? MLIO_READ_BLOCK_LIMITS_LEN :
+ DEF_READ_BLOCK_LIMITS_LEN;
+ memset(readBlkLmtBuff, 0x0, sizeof(readBlkLmtBuff));
+ res = sg_ll_read_block_limits_v2(sg_fd, do_mloi, readBlkLmtBuff,
+ max_resp_len, &resid, true, verbose);
+ ret = res;
+ if (0 == res) {
+ actual_len = max_resp_len - resid;
+ if (do_hex) {
+ int fl = -1;
+
+ if (1 == do_hex)
+ fl = 1;
+ else if (2 == do_hex)
+ fl = 0;
+ hex2stdout(readBlkLmtBuff, actual_len, fl);
+ goto the_end;
+ } else if (do_raw) {
+ dStrRaw((const char *)readBlkLmtBuff, actual_len);
+ goto the_end;
+ }
+
+ if (do_mloi) {
+ if (actual_len < MLIO_READ_BLOCK_LIMITS_LEN) {
+ pr2serr("Expected at least %d bytes in response but only "
+ "%d bytes\n", MLIO_READ_BLOCK_LIMITS_LEN, actual_len);
+ goto the_end;
+ }
+ printf("Read Block Limits (MLOI=1) results:\n");
+ mloi = sg_get_unaligned_be64(readBlkLmtBuff + 12);
+ printf(" Maximum logical block identifier: %" PRIu64 "\n",
+ mloi);
+ } else { /* MLOI=0 (only case before ssc4r02.pdf) */
+ if (actual_len < DEF_READ_BLOCK_LIMITS_LEN) {
+ pr2serr("Expected at least %d bytes in response but only "
+ "%d bytes\n", DEF_READ_BLOCK_LIMITS_LEN, actual_len);
+ goto the_end;
+ }
+ max_block_size = sg_get_unaligned_be32(readBlkLmtBuff + 0);
+ // first byte contains granularity field
+ granularity = (max_block_size >> 24) & 0x1F;
+ max_block_size = max_block_size & 0xFFFFFF;
+ min_block_size = sg_get_unaligned_be16(readBlkLmtBuff + 4);
+ k = min_block_size / 1024;
+ printf("Read Block Limits results:\n");
+ printf(" Minimum block size: %u byte(s)",
+ (unsigned int)min_block_size);
+ if (k != 0)
+ printf(", %d KB", k);
+ printf("\n");
+ k = max_block_size / 1024;
+ m = max_block_size / 1048576;
+ printf(" Maximum block size: %u byte(s)",
+ (unsigned int)max_block_size);
+ if (k != 0)
+ printf(", %d KB", k);
+ if (m != 0)
+ printf(", %d MB", m);
+ printf("\n");
+ printf(" Granularity: %u",
+ (unsigned int)granularity);
+ printf("\n");
+ }
+ } else { /* error detected */
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Read block limits: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' option for more information\n");
+ }
+
+the_end:
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+the_end2:
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_read_block_limits failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_read_buffer.c b/src/sg_read_buffer.c
new file mode 100644
index 00000000..8dbd1703
--- /dev/null
+++ b/src/sg_read_buffer.c
@@ -0,0 +1,844 @@
+/*
+ * Copyright (c) 2006-2022 Luben Tuikov 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/*
+ * This utility issues the SCSI READ BUFFER(10 or 16) command to the given
+ * device.
+ */
+
+static const char * version_str = "1.35 20220217"; /* spc6r06 */
+
+#ifndef SG_READ_BUFFER_10_CMD
+#define SG_READ_BUFFER_10_CMD 0x3c
+#define SG_READ_BUFFER_10_CMDLEN 10
+#endif
+#ifndef SG_READ_BUFFER_16_CMD
+#define SG_READ_BUFFER_16_CMD 0x9b
+#define SG_READ_BUFFER_16_CMDLEN 16
+#endif
+
+#define MODE_HEADER_DATA 0
+#define MODE_VENDOR 1
+#define MODE_DATA 2
+#define MODE_DESCRIPTOR 3
+#define MODE_ECHO_BUFFER 0x0A
+#define MODE_ECHO_BDESC 0x0B
+#define MODE_READ_MICROCODE_ST 0x0F
+#define MODE_EN_EX_ECHO 0x1A
+#define MODE_ERR_HISTORY 0x1C
+
+#define MAX_DEF_INHEX_LEN 8192
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+#define DEF_RESPONSE_LEN 4 /* increased to 64 for MODE_ERR_HISTORY */
+
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'L'},
+ {"eh_code", required_argument, 0, 'e'},
+ {"eh-code", required_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"id", required_argument, 0, 'i'},
+ {"inhex", required_argument, 0, 'I'},
+ {"length", required_argument, 0, 'l'},
+ {"long", no_argument, 0, 'L'},
+ {"mode", required_argument, 0, 'm'},
+ {"no_output", no_argument, 0, 'N'},
+ {"no-output", no_argument, 0, 'N'},
+ {"offset", required_argument, 0, 'o'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"specific", required_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}, /* sentinel */
+};
+
+struct opts_t {
+ bool do_long;
+ bool o_readonly;
+ bool do_raw;
+ bool eh_code_given;
+ bool no_output;
+ bool rb_id_given;
+ bool rb_len_given;
+ bool rb_mode_given;
+ bool verbose_given;
+ bool version_given;
+ int sg_fd;
+ int do_help;
+ int do_hex;
+ int eh_code;
+ int rb_id;
+ int rb_len;
+ int rb_mode;
+ int rb_mode_sp;
+ int verbose;
+ uint64_t rb_offset;
+ const char * device_name;
+ const char * inhex_name;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_read_buffer [--16] [--eh_code=EHC] [--help] [--hex] "
+ "[--id=ID]\n"
+ " [--inhex=FN] [--length=LEN] [--long] "
+ "[--mode=MO]\n"
+ " [--no_output] [--offset=OFF] [--raw] "
+ "[--readonly]\n"
+ " [--specific=MS] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --16|-L issue READ BUFFER(16) (def: 10)\n"
+ " --eh_code=EHC|-e EHC same as '-m eh -i EHC' where "
+ "EHC is the\n"
+ " error history code\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H print output in hex\n"
+ " --id=ID|-i ID buffer identifier (0 (default) to 255)\n"
+ " --inhex=FN|-I FN filename FN contains hex data to "
+ "decode\n"
+ " rather than DEVICE. If --raw given "
+ "then binary\n"
+ " --length=LEN|-l LEN length in bytes to read (def: 4, "
+ "64 for eh)\n"
+ " --long|-L issue READ BUFFER(16) (def: 10)\n"
+ " --mode=MO|-m MO read buffer mode, MO is number or "
+ "acronym (def: 0)\n"
+ " --no_output|-N perform the command then exit\n"
+ " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --specific=MS|-S MS mode specific value; 3 bit field (0 "
+ "to 7)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI READ BUFFER (10 or 16) command. Use '-m xxx' to "
+ "list\navailable modes. Some responses are decoded, others are "
+ "output in hex.\n"
+ );
+}
+
+
+static struct mode_s {
+ const char *mode_string;
+ int mode;
+ const char *comment;
+} modes[] = {
+ { "hd", MODE_HEADER_DATA, "combined header and data"},
+ { "vendor", MODE_VENDOR, "vendor specific"},
+ { "data", MODE_DATA, "data"},
+ { "desc", MODE_DESCRIPTOR, "descriptor"},
+ { "echo", MODE_ECHO_BUFFER, "read data from echo buffer "
+ "(spc-2)"},
+ { "echo_desc", MODE_ECHO_BDESC, "echo buffer descriptor (spc-2)"},
+ { "rd_microc_st", MODE_READ_MICROCODE_ST, "read microcode status "
+ "(spc-5)"},
+ { "en_ex", MODE_EN_EX_ECHO,
+ "enable expander communications protocol and echo buffer (spc-3)"},
+ { "err_hist|eh", MODE_ERR_HISTORY, "error history (spc-4)"},
+ { NULL, 999, NULL}, /* end sentinel */
+};
+
+
+static void
+print_modes(void)
+{
+ const struct mode_s *mp;
+
+ pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+ "or symbolic:\n");
+ for (mp = modes; mp->mode_string; ++mp) {
+ pr2serr(" %2d (0x%02x) %-16s%s\n", mp->mode, mp->mode,
+ mp->mode_string, mp->comment);
+ }
+}
+
+/* Invokes a SCSI READ BUFFER(10) command (spc5r02). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_read_buffer_10(void * resp, int * residp, bool noisy,
+ const struct opts_t * op)
+{
+ int ret, res, sense_cat;
+ uint8_t rb10_cb[SG_READ_BUFFER_10_CMDLEN] =
+ {SG_READ_BUFFER_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ rb10_cb[1] = (uint8_t)(op->rb_mode & 0x1f);
+ if (op->rb_mode_sp)
+ rb10_cb[1] |= (uint8_t)((op->rb_mode_sp & 0x7) << 5);
+ rb10_cb[2] = (uint8_t)op->rb_id;
+ sg_put_unaligned_be24(op->rb_offset, rb10_cb + 3);
+ sg_put_unaligned_be24(op->rb_len, rb10_cb + 6);
+ if (op->verbose) {
+ char b[128];
+
+ pr2serr(" Read buffer(10) cdb: %s\n",
+ sg_get_command_str(rb10_cb, SG_READ_BUFFER_10_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Read buffer(10): out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rb10_cb, sizeof(rb10_cb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, op->rb_len);
+ res = do_scsi_pt(ptvp, op->sg_fd, DEF_PT_TIMEOUT, op->verbose);
+ ret = sg_cmds_process_resp(ptvp, "Read buffer(10)", res, noisy,
+ op->verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((op->verbose > 2) && (ret > 0)) {
+ pr2serr(" Read buffer(10): response%s\n",
+ (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), -1);
+ }
+ ret = 0;
+ }
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI READ BUFFER(16) command (spc5r02). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_read_buffer_16(void * resp, int * residp, bool noisy,
+ const struct opts_t * op)
+{
+ int ret, res, sense_cat;
+ uint8_t rb16_cb[SG_READ_BUFFER_16_CMDLEN] =
+ {SG_READ_BUFFER_16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ rb16_cb[1] = (uint8_t)(op->rb_mode & 0x1f);
+ if (op->rb_mode_sp)
+ rb16_cb[1] |= (uint8_t)((op->rb_mode_sp & 0x7) << 5);
+ sg_put_unaligned_be64(op->rb_offset, rb16_cb + 2);
+ sg_put_unaligned_be32(op->rb_len, rb16_cb + 10);
+ rb16_cb[14] = (uint8_t)op->rb_id;
+ if (op->verbose) {
+ char b[128];
+
+ pr2serr(" Read buffer(16) cdb: %s\n",
+ sg_get_command_str(rb16_cb, SG_READ_BUFFER_16_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rb16_cb, sizeof(rb16_cb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, op->rb_len);
+ res = do_scsi_pt(ptvp, op->sg_fd, DEF_PT_TIMEOUT, op->verbose);
+ ret = sg_cmds_process_resp(ptvp, "Read buffer(16)", res, noisy,
+ op->verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ if ((op->verbose > 2) && (ret > 0)) {
+ pr2serr(" Read buffer(16): response%s\n",
+ (ret > 256 ? ", first 256 bytes" : ""));
+ hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), -1);
+ }
+ ret = 0;
+ }
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Microcode status: active, redundant and download */
+static const char * act_micro_st_arr[] = {
+ "Microcode status not reported",
+ "Activated microcode is valid",
+ "Activated microcode is not valid",
+ "Activated microcode is not a full microcode image",
+};
+
+static const char * red_micro_st_arr[] = {
+ "Redundant microcode status is not reported",
+ "At least one redundant microcode copy is valid",
+ "No redundant microcode copy is valid",
+ "Redundant microcode is not a full microcode image",
+};
+
+/* Major overlap between this SPC-4 table and SES-4r2 table 63 */
+struct sg_lib_simple_value_name_t down_micro_st_arr[] = {
+ {0x0, "No download microcode operation in progress"},
+ {0x1, "Download in progress, awaiting more"}, /* SES */
+ {0x2, "Download complete, updating storage"}, /* SES */
+ {0x3, "Updating storage with deferred microcode"}, /* SES */
+ {0x10, "Complete, no error, starting now"}, /* SES */
+ {0x11, "Complete, no error, start after hard reset or power "
+ "cycle"}, /* SES */
+ {0x12, "Complete, no error, start after power cycle"}, /* SES */
+ {0x13, "Complete, no error, start after activate_mc, hard reset or "
+ "power cycle"}, /* SES */
+ {0x21, "Download in progress, awaiting more"}, /* SPC-6 */
+ {0x22, "Download complete, updating storage"}, /* SPC-6 */
+ {0x23, "Updating storage with deferred microcode"}, /* SPC-6 */
+ {0x30, "Deferred microcode download complete, no reports"}, /* SPC-6 */
+ {0x31, "Deferred download ok, await hard reset or power cycle"},
+ {0x32, "Deferred download ok, await power cycle"}, /* SPC-6 */
+ {0x33, "Deferred download ok, await any event"}, /* SPC-6 */
+ {0x34, "Deferred download ok, await Write buffer command"}, /* SPC-6 */
+ {0x35, "Deferred download ok, await any event, WB only this LU"},
+ {0x80, "Error, discarded, see additional status"}, /* SES */
+ {0x81, "Error, discarded, image error"}, /* SES */
+ {0x82, "Timeout, discarded"}, /* SES */
+ {0x83, "Internal error, need new microcode before reset"}, /* SES */
+ {0x84, "Internal error, need new microcode, reset safe"}, /* SES */
+ {0x85, "Unexpected activate_mc received"}, /* SES */
+ {0x90, "Error, discarded, see additional status"}, /* SPC-6 */
+ {0x91, "Error, discarded, image error"}, /* SPC-6 */
+ {0x92, "Timeout, discarded"}, /* SPC-6 */
+ {0x93, "Internal error, need new microcode before reset"}, /* SPC-6 */
+ {0x94, "Internal error, need new microcode, reset safe"}, /* SPC-6 */
+ {0x95, "Unexpected activate_mc received, mcrocode discard"}, /* SPC-6 */
+ {0x1000, NULL}, /* End sentinel */
+};
+
+static void
+decode_microcode_status(const uint8_t * resp, const struct opts_t * op)
+{
+ int n;
+ uint32_t u;
+ const char * cp;
+ const struct sg_lib_simple_value_name_t * vnp;
+ char b[32];
+
+ if ((NULL == resp) || (op->rb_len < 1))
+ return;
+ n = resp[0];
+ if (n < (int)SG_ARRAY_SIZE(act_micro_st_arr))
+ cp = act_micro_st_arr[n];
+ else {
+ snprintf(b, sizeof(b), "unknown [0x%x]", n);
+ cp = b;
+ }
+ printf("Activated microcode status: %s\n", cp);
+
+ if (op->rb_len < 2)
+ return;
+ n = resp[1];
+ if (n < (int)SG_ARRAY_SIZE(red_micro_st_arr))
+ cp = red_micro_st_arr[n];
+ else {
+ snprintf(b, sizeof(b), "unknown [0x%x]", n);
+ cp = b;
+ }
+ printf("Redundant microcode status: %s\n", cp);
+
+ if (op->rb_len < 3)
+ return;
+ n = resp[2];
+ for (vnp = down_micro_st_arr, cp = NULL; vnp->name; ++vnp) {
+ if (vnp->value == n) {
+ cp = vnp->name;
+ break;
+ }
+ }
+ if (NULL == cp) {
+ snprintf(b, sizeof(b), "unknown [0x%x]", n);
+ cp = b;
+ }
+ printf("Download microcode status: %s\n", cp);
+
+ if (op->rb_len > 7) {
+ u = sg_get_unaligned_be32(resp + 4);
+ printf("Download microcode maximum size (bytes): %u [0x%x]\n", u, u);
+ }
+ if (op->rb_len > 15) {
+ u = sg_get_unaligned_be32(resp + 12);
+ printf("Download microcode expected buffer offset (bytes): %u "
+ "[0x%x]\n", u, u);
+ }
+}
+
+static void
+decode_error_history(const uint8_t * resp, const struct opts_t * op)
+{
+ static const char * eh_s = "Error history";
+ int k, num;
+ uint32_t dir_len;
+ const uint8_t * up;
+
+ if (op->rb_id < 0x4) { /* eh directory variants */
+ if (op->rb_len < 8) {
+ pr2serr("%s response buffer too short [%d] to show directory "
+ "header\n", eh_s, op->rb_len);
+ return;
+ }
+ printf("%s directory header:\n", eh_s);
+ printf(" T10 Vendor: %.8s\n", resp + 0);
+ printf(" Version: %u\n", resp[8]);
+ printf(" EHS_retrieved: %u\n", 0x3 & (resp[9] >> 3));
+ printf(" EHS_source: %u\n", 0x3 & (resp[9] >> 1));
+ printf(" CLR_SUP: %u\n", 0x1 & resp[9]);
+ if (op->rb_len < 32) {
+ pr2serr("%s response buffer too short [%d] to show directory "
+ "length\n", eh_s, op->rb_len);
+ return;
+ }
+ dir_len = sg_get_unaligned_be16(resp + 30);
+ printf(" Directory length: %u\n", dir_len);
+ if ((unsigned)op->rb_len < (32 + dir_len)) {
+ pr2serr("%s directory entries truncated, try adding '-l %u' "
+ "option\n", eh_s, 32 + dir_len);
+ }
+ num = (op->rb_len - 32) / 8;
+ for (k = 0, up = resp + 32; k < num; ++k, up += 8) {
+ if (k > 0)
+ printf("\n");
+ printf(" Supported buffer ID: 0x%x\n", up[0]);
+ printf(" Buffer format: 0x%x\n", up[1]);
+ printf(" Buffer source: 0x%x\n", 0xf & up[2]);
+ printf(" Maximum available length: 0x%x\n",
+ sg_get_unaligned_be32(up + 4));
+ }
+ } else if ((op->rb_id >= 0x10) && (op->rb_id <= 0xef))
+ hex2stdout(resp, op->rb_len, (op->verbose > 1 ? 0 : 1));
+ else if (0xfe == op->rb_id)
+ pr2serr("clear %s I_T nexus [0x%x]\n", eh_s, op->rb_id);
+ else if (0xff == op->rb_id)
+ pr2serr("clear %s I_T nexus and release any snapshots [0x%x]\n",
+ eh_s, op->rb_id);
+ else
+ pr2serr("Reserved Buffer ID value [0x%x] for %s\n", op->rb_id, eh_s);
+
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+int
+main(int argc, char * argv[])
+{
+ int res, c, len, k;
+ int inhex_len = 0;
+ int resid = 0;
+ int ret = 0;
+ int64_t ll;
+ const char * cp = NULL;
+ uint8_t * resp = NULL;
+ uint8_t * free_resp = NULL;
+ const struct mode_s * mp;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op = &opts;
+
+ op->sg_fd = -1;
+ op->rb_len = DEF_RESPONSE_LEN;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "e:hHi:I:l:Lm:No:rRS:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'e':
+ if (op->rb_mode_given && (MODE_ERR_HISTORY != op->rb_mode)) {
+ pr2serr("mode incompatible with --eh_code= option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->eh_code = sg_get_num(optarg);
+ if ((op->eh_code < 0) || (op->eh_code > 255)) {
+ pr2serr("argument to '--eh_code=' should be in the range 0 "
+ "to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->rb_mode = MODE_ERR_HISTORY;
+ op->eh_code_given = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->rb_id = sg_get_num(optarg);
+ if ((op->rb_id < 0) || (op->rb_id > 255)) {
+ pr2serr("argument to '--id=' should be in the range 0 to "
+ "255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->rb_id_given = true;
+ break;
+ case 'I':
+ if (op->inhex_name) {
+ pr2serr("--inhex= option given more than once. Once only "
+ "please\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->inhex_name = optarg;
+ break;
+ case 'l':
+ op->rb_len = sg_get_num(optarg);
+ if (op->rb_len < 0) {
+ pr2serr("bad argument to '--length'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->rb_len > 0xffffff) {
+ pr2serr("argument to '--length' must be <= 0xffffff\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->rb_len_given = true;
+ break;
+ case 'L':
+ op->do_long = true;
+ break;
+ case 'm':
+ if (NULL == optarg) {
+ pr2serr("bad argument to '--mode'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (isdigit((uint8_t)*optarg)) {
+ op->rb_mode = sg_get_num(optarg);
+ if ((op->rb_mode < 0) || (op->rb_mode > 31)) {
+ pr2serr("argument to '--mode' should be in the range 0 "
+ "to 31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ len = strlen(optarg);
+ for (mp = modes; mp->mode_string; ++mp) {
+ cp = strchr(mp->mode_string, '|');
+ if (NULL == cp) {
+ if (0 == strncmp(mp->mode_string, optarg, len)) {
+ op->rb_mode = mp->mode;
+ break;
+ }
+ } else {
+ int f_len = cp - mp->mode_string;
+
+ if ((f_len == len) &&
+ (0 == memcmp(mp->mode_string, optarg, len))) {
+ op->rb_mode = mp->mode;
+ break;
+ }
+ if (0 == strncmp(cp + 1, optarg, len)) {
+ op->rb_mode = mp->mode;
+ break;
+ }
+ }
+ }
+ if (NULL == mp->mode_string) {
+ print_modes();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->eh_code_given && (MODE_ERR_HISTORY != op->rb_mode)) {
+ pr2serr("mode incompatible with --eh_code= option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->rb_mode_given = true;
+ break;
+ case 'N':
+ op->no_output = true;
+ break;
+ case 'o':
+ ll = sg_get_llnum(optarg);
+ if (ll < 0) {
+ pr2serr("bad argument to '--offset'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->rb_offset = ll;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 'S':
+ op->rb_mode_sp = sg_get_num(optarg);
+ if ((op->rb_mode_sp < 0) || (op->rb_mode_sp > 7)) {
+ pr2serr("expected argument to '--specific' to be 0 to 7\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->do_help) {
+ if (op->do_help > 1) {
+ usage();
+ pr2serr("\n");
+ print_modes();
+ } else
+ usage();
+ return 0;
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+ if ((MODE_ERR_HISTORY == op->rb_mode) && (NULL == op->inhex_name)) {
+ if (! op->rb_len_given)
+ op->rb_len = 64;
+ }
+ if (op->eh_code_given) {
+ if (op->rb_id_given && (op->eh_code != op->rb_id)) {
+ pr2serr("Buffer ID incompatible with --eh_code= option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->rb_id = op->eh_code;
+ }
+
+ if (op->device_name && op->inhex_name) {
+ pr2serr("Confused: both DEVICE (%s) and --inhex= option given. One "
+ "only please\n", op->device_name);
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (op->inhex_name) {
+ op->rb_len = (op->rb_len > MAX_DEF_INHEX_LEN) ? op->rb_len :
+ MAX_DEF_INHEX_LEN;
+ resp = (uint8_t *)sg_memalign(op->rb_len, 0, &free_resp, false);
+ ret = sg_f2hex_arr(op->inhex_name, op->do_raw, false, resp,
+ &inhex_len, op->rb_len);
+ if (ret)
+ goto fini;
+ if (op->do_raw)
+ op->do_raw = false; /* only used for input in this case */
+ op->rb_len = inhex_len;
+ resid = 0;
+ goto decode_result;
+ } else if (NULL == op->device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ len = op->rb_len ? op->rb_len : 8;
+ resp = (uint8_t *)sg_memalign(len, 0, &free_resp, false);
+ if (NULL == resp) {
+ pr2serr("unable to allocate %d bytes on the heap\n", len);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (op->verbose > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+ op->sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly,
+ op->verbose);
+ if (op->sg_fd < 0) {
+ if (op->verbose)
+ pr2serr("open error: %s: %s\n", op->device_name,
+ safe_strerror(-op->sg_fd));
+ ret = sg_convert_errno(-op->sg_fd);
+ goto fini;
+ }
+
+ if (op->do_long)
+ res = sg_ll_read_buffer_16(resp, &resid, true, op);
+ else if (op->rb_offset > 0xffffff) {
+ pr2serr("--offset value is too large for READ BUFFER(10), try "
+ "--16\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ } else
+ res = sg_ll_read_buffer_10(resp, &resid, true, op);
+ if (0 != res) {
+ char b[80];
+
+ ret = res;
+ if (res > 0) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("Read buffer(%d) failed: %s\n",
+ (op->do_long ? 16 : 10), b);
+ }
+ goto fini;
+ }
+ if (resid > 0)
+ op->rb_len -= resid; /* got back less than requested */
+ if (op->no_output)
+ goto fini;
+decode_result:
+ if (op->rb_len > 0) {
+ if (op->do_raw)
+ dStrRaw(resp, op->rb_len);
+ else if (op->do_hex || (op->rb_len < 4)) {
+ k = (op->do_hex > 2) ? -1 : (2 - op->do_hex);
+ hex2stdout(resp, op->rb_len, k);
+ } else {
+ switch (op->rb_mode) {
+ case MODE_DESCRIPTOR:
+ k = sg_get_unaligned_be24(resp + 1);
+ printf("OFFSET BOUNDARY: %d, Buffer offset alignment: "
+ "%d-byte\n", resp[0], (1 << resp[0]));
+ printf("BUFFER CAPACITY: %d (0x%x)\n", k, k);
+ break;
+ case MODE_ECHO_BDESC:
+ k = sg_get_unaligned_be16(resp + 2) & 0x1fff;
+ printf("EBOS:%d\n", resp[0] & 1 ? 1 : 0);
+ printf("Echo buffer capacity: %d (0x%x)\n", k, k);
+ break;
+ case MODE_READ_MICROCODE_ST:
+ decode_microcode_status(resp, op);
+ break;
+ case MODE_ERR_HISTORY:
+ decode_error_history(resp, op);
+ break;
+ default:
+ hex2stdout(resp, op->rb_len, (op->verbose > 1 ? 0 : 1));
+ break;
+ }
+ }
+ }
+
+fini:
+ if (free_resp)
+ free(free_resp);
+ if (op->sg_fd >= 0) {
+ res = sg_cmds_close_device(op->sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_read_buffer failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_read_long.c b/src/sg_read_long.c
new file mode 100644
index 00000000..0a5e6a68
--- /dev/null
+++ b/src/sg_read_long.c
@@ -0,0 +1,325 @@
+/* A utility program for the Linux OS SCSI subsystem.
+ * Copyright (C) 2004-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program issues the SCSI command READ LONG to a given SCSI device.
+ * It sends the command with the logical block address passed as the lba
+ * argument, and the transfer length set to the xfer_len argument. the
+ * buffer to be written to the device filled with 0xff, this buffer includes
+ * the sector data and the ECC bytes.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <errno.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"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.27 20180627";
+
+#define MAX_XFER_LEN 10000
+
+#define ME "sg_read_long: "
+
+#define EBUFF_SZ 512
+
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'S'},
+ {"correct", no_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"lba", required_argument, 0, 'l'},
+ {"out", required_argument, 0, 'o'},
+ {"pblock", no_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"xfer_len", required_argument, 0, 'x'},
+ {"xfer-len", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_read_long [--16] [--correct] [--help] [--lba=LBA] "
+ "[--out=OF]\n"
+ " [--pblock] [--readonly] [--verbose] "
+ "[--version]\n"
+ " [--xfer_len=BTL] DEVICE\n"
+ " where:\n"
+ " --16|-S do READ LONG(16) (default: "
+ "READ LONG(10))\n"
+ " --correct|-c use ECC to correct data "
+ "(default: don't)\n"
+ " --help|-h print out usage message\n"
+ " --lba=LBA|-l LBA logical block address"
+ " (default: 0)\n"
+ " --out=OF|-o OF output in binary to file named OF\n"
+ " --pblock|-p fetch physical block containing LBA\n"
+ " --readonly|-r open DEVICE read-only (def: open it "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and"
+ " exit\n"
+ " --xfer_len=BTL|-x BTL byte transfer length (< 10000)"
+ " default 520\n\n"
+ "Perform a SCSI READ LONG (10 or 16) command. Reads a single "
+ "block with\nassociated ECC data. The user data could be "
+ "encoded or encrypted.\n");
+}
+
+/* Returns 0 if successful */
+static int
+process_read_long(int sg_fd, bool do_16, bool pblock, bool correct,
+ uint64_t llba, void * data_out, int xfer_len, int verbose)
+{
+ int offset, res;
+ const char * ten_or;
+ char b[80];
+
+ if (do_16)
+ res = sg_ll_read_long16(sg_fd, pblock, correct, llba, data_out,
+ xfer_len, &offset, true, verbose);
+ else
+ res = sg_ll_read_long10(sg_fd, pblock, correct, (unsigned int)llba,
+ data_out, xfer_len, &offset, true, verbose);
+ ten_or = do_16 ? "16" : "10";
+ switch (res) {
+ case 0:
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO:
+ pr2serr("<<< device indicates 'xfer_len' should be %d >>>\n",
+ xfer_len - offset);
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr(" SCSI READ LONG (%s): %s\n", ten_or, b);
+ break;
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool correct = false;
+ bool do_16 = false;
+ bool pblock = false;
+ bool readonly = false;
+ bool got_stdout;
+ bool verbose_given = false;
+ bool version_given = false;
+ int outfd, res, c;
+ int sg_fd = -1;
+ int ret = 0;
+ int xfer_len = 520;
+ int verbose = 0;
+ uint64_t llba = 0;
+ int64_t ll;
+ uint8_t * readLongBuff = NULL;
+ uint8_t * rawp = NULL;
+ uint8_t * free_rawp = NULL;
+ const char * device_name = NULL;
+ char out_fname[256];
+ char ebuff[EBUFF_SZ];
+
+ memset(out_fname, 0, sizeof out_fname);
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "chl:o:prSvVx:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ correct = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ llba = (uint64_t)ll;
+ break;
+ case 'o':
+ strncpy(out_fname, optarg, sizeof(out_fname) - 1);
+ break;
+ case 'p':
+ pblock = true;
+ break;
+ case 'r':
+ readonly = true;
+ break;
+ case 'S':
+ do_16 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'x':
+ xfer_len = sg_get_num(optarg);
+ if (-1 == xfer_len) {
+ pr2serr("bad argument to '--xfer_len'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (xfer_len >= MAX_XFER_LEN){
+ pr2serr("xfer_len (%d) is out of range ( < %d)\n", xfer_len,
+ MAX_XFER_LEN);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ if (NULL == (rawp = (uint8_t *)sg_memalign(MAX_XFER_LEN, 0, &free_rawp,
+ false))) {
+ if (verbose)
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ readLongBuff = (uint8_t *)rawp;
+ memset(rawp, 0x0, MAX_XFER_LEN);
+
+ pr2serr(ME "issue read long (%s) to device %s\n xfer_len=%d (0x%x), "
+ "lba=%" PRIu64 " (0x%" PRIx64 "), correct=%d\n",
+ (do_16 ? "16" : "10"), device_name, xfer_len, xfer_len, llba,
+ llba, (int)correct);
+
+ if ((ret = process_read_long(sg_fd, do_16, pblock, correct, llba,
+ readLongBuff, xfer_len, verbose)))
+ goto err_out;
+
+ if ('\0' == out_fname[0])
+ hex2stdout((const uint8_t *)rawp, xfer_len, 0);
+ else {
+ got_stdout = (0 == strcmp(out_fname, "-"));
+ if (got_stdout)
+ outfd = STDOUT_FILENO;
+ else {
+ if ((outfd = open(out_fname, O_WRONLY | O_CREAT | O_TRUNC,
+ 0666)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for writing", out_fname);
+ perror(ebuff);
+ goto err_out;
+ }
+ }
+ if (sg_set_binary_mode(outfd) < 0) {
+ perror("sg_set_binary_mode");
+ goto err_out;
+ }
+ res = write(outfd, readLongBuff, xfer_len);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't write to %s", out_fname);
+ perror(ebuff);
+ goto err_out;
+ }
+ if (! got_stdout)
+ close(outfd);
+ }
+
+err_out:
+ if (free_rawp)
+ free(free_rawp);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_read_long failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_readcap.c b/src/sg_readcap.c
new file mode 100644
index 00000000..571f92e3
--- /dev/null
+++ b/src/sg_readcap.c
@@ -0,0 +1,682 @@
+/* This code is does a SCSI READ CAPACITY command on the given device
+ * and outputs the result.
+ *
+ * Copyright (C) 1999 - 2020 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program was originally written with Linux 2.4 kernel series.
+ * It now builds for the Linux 2.6, 3 and 4 kernel series and various other
+ * operating systems.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "4.05 20200122";
+
+#define ME "sg_readcap: "
+
+#define RCAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"lba", required_argument, 0, 'L'},
+ {"long", no_argument, 0, 'l'},
+ {"16", no_argument, 0, 'l'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"pmi", no_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"zbc", no_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_brief;
+ bool do_long;
+ bool do_pmi;
+ bool do_raw;
+ bool o_readonly;
+ bool do_zbc;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_help;
+ int do_hex;
+ int do_lba;
+ int verbose;
+ uint64_t llba;
+ const char * device_name;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_readcap [--16] [--brief] [--help] [--hex] "
+ "[--lba=LBA] [--long]\n"
+ " [--pmi] [--raw] [--readonly] [--verbose] "
+ "[--version]\n"
+ " [--zbc] DEVICE\n"
+ " where:\n"
+ " --16 use READ CAPACITY (16) cdb (same as "
+ "--long)\n"
+ " --brief|-b brief, two hex numbers: number of blocks "
+ "and block size\n"
+ " --help|-h print this usage message and exit\n"
+ " --hex|-H output response in hexadecimal to stdout\n"
+ " --lba=LBA|-L LBA yields the last block prior to (head "
+ "movement) delay\n"
+ " after LBA [in decimal (def: 0) "
+ "valid with '--pmi']\n"
+ " --long|-l use READ CAPACITY (16) cdb (def: use "
+ "10 byte cdb)\n"
+ " --pmi|-p partial medium indicator (without this "
+ "option shows\n"
+ " total disk capacity) [made obsolete in "
+ "sbc3r26]\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --readonly|-R open DEVICE read-only (def: RCAP(16) "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --zbc|-z show rc_basis ZBC field (implies --16)\n\n"
+ "Perform a SCSI READ CAPACITY (10 or 16) command\n");
+}
+
+static void
+usage_old()
+{
+ pr2serr("Usage: sg_readcap [-16] [-b] [-h] [-H] [-lba=LBA] "
+ "[-pmi] [-r] [-R]\n"
+ " [-v] [-V] [-z] DEVICE\n"
+ " where:\n"
+ " -16 use READ CAPACITY (16) cdb (def: use "
+ "10 byte cdb)\n"
+ " -b brief, two hex numbers: number of blocks "
+ "and block size\n"
+ " -h print this usage message and exit\n"
+ " -H output response in hexadecimal to stdout\n"
+ " -lba=LBA yields the last block prior to (head "
+ "movement) delay\n"
+ " after LBA [in hex (def: 0) "
+ "valid with -pmi]\n"
+ " -pmi partial medium indicator (without this option "
+ "shows total\n"
+ " disk capacity)\n"
+ " -r output response in binary to stdout\n"
+ " -R open DEVICE read-only (def: RCAP(16) read-write)\n"
+ " -v increase verbosity\n"
+ " -V print version string and exit\n"
+ " -N|--new use new interface\n"
+ " -z show rc_basis ZBC field (implies -16)\n\n"
+ "Perform a SCSI READ CAPACITY (10 or 16) command\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c;
+ int a_one = 0;
+ int64_t nn;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "16bhHlL:NOprRvVz", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '1':
+ ++a_one;
+ break;
+ case '6':
+ if (a_one)
+ op->do_long = true;
+ break;
+ case 'b':
+ op->do_brief = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ op->do_long = true;
+ break;
+ case 'L':
+ nn = sg_get_llnum(optarg);
+ if (-1 == nn) {
+ pr2serr("bad argument to '--lba='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->llba = nn;
+ /* force READ_CAPACITY16 for large lbas */
+ if (op->llba > 0xfffffffeULL)
+ op->do_long = true;
+ ++op->do_lba;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ op->do_pmi = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'z':
+ op->do_zbc = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num;
+ const char * cp;
+ uint64_t uu;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case '1':
+ if ('6' == *(cp + 1)) {
+ op->do_long = true;
+ ++cp;
+ --plen;
+ } else
+ jmp_out = true;
+ break;
+ case 'b':
+ op->do_brief = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'p':
+ if (0 == strncmp("pmi", cp, 3)) {
+ op->do_pmi = true;
+ cp += 2;
+ plen -= 2;
+ } else
+ jmp_out = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'z':
+ op->do_zbc = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("lba=", cp, 4)) {
+ num = sscanf(cp + 4, "%" SCNx64 "", &uu);
+ if (1 != num) {
+ printf("Bad value after 'lba=' option\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ /* force READ_CAPACITY16 for large lbas */
+ if (uu > 0xfffffffeULL)
+ op->do_long = true;
+ op->llba = uu;
+ ++op->do_lba;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static const char *
+rc_basis_str(int rc_basis, char * b, int blen)
+{
+ switch (rc_basis) {
+ case 0:
+ snprintf(b, blen, "last contiguous that's not seq write required");
+ break;
+ case 1:
+ snprintf(b, blen, "last LBA on logical unit");
+ break;
+ default:
+ snprintf(b, blen, "reserved (0x%x)", rc_basis);
+ break;
+ }
+ return b;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool rw_0_flag;
+ int res, prot_en, p_type, lbppbe;
+ int sg_fd = -1;
+ int ret = 0;
+ uint32_t last_blk_addr, block_size;
+ uint64_t llast_blk_addr;
+ uint8_t * resp_buff;
+ uint8_t * free_resp_buff;
+ const int resp_buff_sz = RCAP16_REPLY_LEN;
+ char b[80];
+ struct opts_t opts;
+ struct opts_t * op;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return res;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (op->do_zbc) {
+ if (! op->do_long)
+ op->do_long = true;
+ }
+
+ resp_buff = sg_memalign(resp_buff_sz, 0, &free_resp_buff, false);
+ if (NULL == resp_buff) {
+ pr2serr("Unable to allocate %d bytes on heap\n", resp_buff_sz);
+ return sg_convert_errno(ENOMEM);
+ }
+ if ((! op->do_pmi) && (op->llba > 0)) {
+ pr2serr(ME "lba can only be non-zero when '--pmi' is set\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (op->do_long)
+ rw_0_flag = op->o_readonly;
+ else
+ rw_0_flag = true; /* RCAP(10) has opened RO in past, so leave */
+ if ((sg_fd = sg_cmds_open_device(op->device_name, rw_0_flag,
+ op->verbose)) < 0) {
+ pr2serr(ME "error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (! op->do_long) {
+ res = sg_ll_readcap_10(sg_fd, op->do_pmi, (unsigned int)op->llba,
+ resp_buff, RCAP_REPLY_LEN, true,
+ op->verbose);
+ ret = res;
+ if (0 == res) {
+ if (op->do_hex || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp_buff, RCAP_REPLY_LEN);
+ else if (op->do_hex > 2)
+ hex2stdout(resp_buff, RCAP_REPLY_LEN, -1);
+ else
+ hex2stdout(resp_buff, RCAP_REPLY_LEN, 1);
+ goto fini;
+ }
+ last_blk_addr = sg_get_unaligned_be32(resp_buff + 0);
+ if (0xffffffff != last_blk_addr) {
+ block_size = sg_get_unaligned_be32(resp_buff + 4);
+ if (op->do_brief) {
+ printf("0x%" PRIx32 " 0x%" PRIx32 "\n",
+ last_blk_addr + 1, block_size);
+ goto fini;
+ }
+ printf("Read Capacity results:\n");
+ if (op->do_pmi)
+ printf(" PMI mode: given lba=0x%" PRIx64 ", last lba "
+ "before delay=0x%" PRIx32 "\n", op->llba,
+ last_blk_addr);
+ else
+ printf(" Last LBA=%" PRIu32 " (0x%" PRIx32 "), Number "
+ "of logical blocks=%" PRIu32 "\n", last_blk_addr,
+ last_blk_addr, last_blk_addr + 1);
+ printf(" Logical block length=%u bytes\n", block_size);
+ if (! op->do_pmi) {
+ uint64_t total_sz = last_blk_addr + 1;
+ double sz_mb, sz_gb;
+
+ total_sz *= block_size;
+ sz_mb = ((double)(last_blk_addr + 1) * block_size) /
+ (double)(1048576);
+ sz_gb = ((double)(last_blk_addr + 1) * block_size) /
+ (double)(1000000000L);
+ printf("Hence:\n");
+#ifdef SG_LIB_MINGW
+ printf(" Device size: %" PRIu64 " bytes, %g MiB, %g "
+ "GB", total_sz, sz_mb, sz_gb);
+#else
+ printf(" Device size: %" PRIu64 " bytes, %.1f MiB, "
+ "%.2f GB", total_sz, sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ goto fini;
+ } else {
+ printf("READ CAPACITY (10) indicates device capacity too "
+ "large\n now trying 16 byte cdb variant\n");
+ op->do_long = true;
+ }
+ } else if (SG_LIB_CAT_INVALID_OP == res) {
+ op->do_long = true;
+ sg_cmds_close_device(sg_fd);
+ if ((sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly,
+ op->verbose)) < 0) {
+ pr2serr(ME "error re-opening file: %s (rw): %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ if (op->verbose)
+ pr2serr("READ CAPACITY (10) not supported, trying READ "
+ "CAPACITY (16)\n");
+ } else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("READ CAPACITY (10) failed: %s\n", b);
+ }
+ }
+ if (op->do_long) {
+ res = sg_ll_readcap_16(sg_fd, op->do_pmi, op->llba, resp_buff,
+ RCAP16_REPLY_LEN, true, op->verbose);
+ ret = res;
+ if (0 == res) {
+ if (op->do_hex || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp_buff, RCAP16_REPLY_LEN);
+ else if (op->do_hex > 2)
+ hex2stdout(resp_buff, RCAP16_REPLY_LEN, -1);
+ else
+ hex2stdout(resp_buff, RCAP16_REPLY_LEN, 1);
+ goto fini;
+ }
+ llast_blk_addr = sg_get_unaligned_be64(resp_buff + 0);
+ block_size = sg_get_unaligned_be32(resp_buff + 8);
+ if (op->do_brief) {
+ printf("0x%" PRIx64 " 0x%" PRIx32 "\n", llast_blk_addr + 1,
+ block_size);
+ goto fini;
+ }
+ prot_en = !!(resp_buff[12] & 0x1);
+ p_type = ((resp_buff[12] >> 1) & 0x7);
+ printf("Read Capacity results:\n");
+ printf(" Protection: prot_en=%d, p_type=%d, p_i_exponent=%d",
+ prot_en, p_type, ((resp_buff[13] >> 4) & 0xf));
+ if (prot_en)
+ printf(" [type %d protection]\n", p_type + 1);
+ else
+ printf("\n");
+ if (op->do_zbc) {
+ int rc_basis = (resp_buff[12] >> 4) & 0x3;
+
+ printf(" ZBC's rc_basis=%d [%s]\n", rc_basis,
+ rc_basis_str(rc_basis, b, sizeof(b)));
+ }
+ printf(" Logical block provisioning: lbpme=%d, lbprz=%d\n",
+ !!(resp_buff[14] & 0x80), !!(resp_buff[14] & 0x40));
+ if (op->do_pmi)
+ printf(" PMI mode: given lba=0x%" PRIx64 ", last lba "
+ "before delay=0x%" PRIx64 "\n", op->llba,
+ llast_blk_addr);
+ else
+ printf(" Last LBA=%" PRIu64 " (0x%" PRIx64 "), Number of "
+ "logical blocks=%" PRIu64 "\n", llast_blk_addr,
+ llast_blk_addr, llast_blk_addr + 1);
+ printf(" Logical block length=%" PRIu32 " bytes\n", block_size);
+ lbppbe = resp_buff[13] & 0xf;
+ printf(" Logical blocks per physical block exponent=%d",
+ lbppbe);
+ if (lbppbe > 0)
+ printf(" [so physical block length=%u bytes]\n",
+ block_size * (1 << lbppbe));
+ else
+ printf("\n");
+ printf(" Lowest aligned LBA=%d\n",
+ ((resp_buff[14] & 0x3f) << 8) + resp_buff[15]);
+ if (! op->do_pmi) {
+ uint64_t total_sz = llast_blk_addr + 1;
+ double sz_mb, sz_gb;
+
+ total_sz *= block_size;
+ sz_mb = ((double)(llast_blk_addr + 1) * block_size) /
+ (double)(1048576);
+ sz_gb = ((double)(llast_blk_addr + 1) * block_size) /
+ (double)(1000000000L);
+ printf("Hence:\n");
+#ifdef SG_LIB_MINGW
+ printf(" Device size: %" PRIu64 " bytes, %g MiB, %g GB",
+ total_sz, sz_mb, sz_gb);
+#else
+ printf(" Device size: %" PRIu64 " bytes, %.1f MiB, %.2f "
+ "GB", total_sz, sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ goto fini;
+ } else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("bad field in READ CAPACITY (16) cdb including "
+ "unsupported service action\n");
+ else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("READ CAPACITY (16) failed: %s\n", b);
+ }
+ }
+ if (op->do_brief)
+ printf("0x0 0x0\n");
+fini:
+ if (free_resp_buff)
+ free(free_resp_buff);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_readcap failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_reassign.c b/src/sg_reassign.c
new file mode 100644
index 00000000..68668ec8
--- /dev/null
+++ b/src/sg_reassign.c
@@ -0,0 +1,508 @@
+/*
+ * Copyright (c) 2005-2019 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This utility invokes the REASSIGN BLOCKS SCSI command to reassign
+ * an existing (possibly damaged) lba on a direct access device (e.g.
+ * a disk) to a new physical location. The previous contents is
+ * recoverable then it is written to the remapped lba otherwise
+ * vendor specific data is written.
+ */
+
+static const char * version_str = "1.27 20191001";
+
+#define DEF_DEFECT_LIST_FORMAT 4 /* bytes from index */
+
+#define MAX_NUM_ADDR 1024
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+
+static struct option long_options[] = {
+ {"address", required_argument, 0, 'a'},
+ {"dummy", no_argument, 0, 'd'},
+ {"eight", required_argument, 0, 'e'},
+ {"grown", no_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"longlist", required_argument, 0, 'l'},
+ {"primary", no_argument, 0, 'p'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_reassign [--address=A,A...] [--dummy] [--eight=0|1] "
+ "[--grown]\n"
+ " [--help] [--hex] [--longlist=0|1] "
+ "[--primary] [--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --address=A,A...|-a A,A... comma separated logical block "
+ "addresses\n"
+ " one or more, assumed to be "
+ "decimal\n"
+ " --address=-|-a - read stdin for logical block "
+ "addresses\n"
+ " --dummy|-d prepare but do not execute REASSIGN "
+ "BLOCKS command\n"
+ " --eight=0|1\n"
+ " -e 0|1 force eight byte (64 bit) lbas "
+ "when 1,\n"
+ " four byte (32 bit) lbas when 0 "
+ "(def)\n"
+ " --grown|-g fetch grown defect list length, "
+ "don't reassign\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H print response in hex (for '-g' or "
+ "'-p')\n"
+ " --longlist=0|1\n"
+ " -l 0|1 use 4 byte list length when 1, safe to "
+ "ignore\n"
+ " (def: 0 (2 byte list length))\n"
+ " --primary|-p fetch primary defect list length, "
+ "don't reassign\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Perform a SCSI REASSIGN BLOCKS command (or READ DEFECT LIST)\n");
+}
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space) separated list) or from stdin (one per line, comma
+ * separated list or space separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or error code. */
+static int
+build_lba_arr(const char * inp, uint64_t * lba_arr,
+ int * lba_arr_len, int max_arr_len)
+{
+ int in_len, k, j, m;
+ const char * lcp;
+ int64_t ll;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == lba_arr) ||
+ (NULL == lba_arr_len))
+ return SG_LIB_LOGIC_ERROR;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *lba_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ char line[1024];
+ int off = 0;
+
+ for (j = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), stdin))
+ break;
+ // could improve with carry_over logic if sizeof(line) too small
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ }
+ }
+ if (in_len < 1)
+ continue;
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxX ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__,
+ j + 1, m + k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < 1024; ++k) {
+ ll = sg_get_llnum_nomult(lcp);
+ if (-1 != ll) {
+ if ((off + k) >= max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba_arr[off + k] = (uint64_t)ll;
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error in line %d, at pos %d\n", __func__,
+ j + 1, (int)(lcp - line + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ off += (k + 1);
+ }
+ *lba_arr_len = off;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxX, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ ll = sg_get_llnum_nomult(lcp);
+ if (-1 != ll) {
+ lba_arr[k] = (uint64_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ *lba_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool dummy = false;
+ bool eight = false;
+ bool eight_given = false;
+ bool got_addr = false;
+ bool longlist = false;
+ bool primary = false;
+ bool grown = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, num, k, j;
+ int sg_fd = -1;
+ int addr_arr_len = 0;
+ int do_hex = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ uint64_t addr_arr[MAX_NUM_ADDR];
+ uint8_t param_arr[4 + (MAX_NUM_ADDR * 8)];
+ char b[80];
+ int param_len = 4;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "a:de:ghHl:pvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ memset(addr_arr, 0, sizeof(addr_arr));
+ if ((res = build_lba_arr(optarg, addr_arr, &addr_arr_len,
+ MAX_NUM_ADDR))) {
+ pr2serr("bad argument to '--address'\n");
+ return res;
+ }
+ got_addr = true;
+ break;
+ case 'd':
+ dummy = true;
+ break;
+ case 'e':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && ((0 == res) || (1 == res)))
+ eight = !! res;
+ else {
+ pr2serr("value for '--eight=' must be 0 or 1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ eight_given = true;
+ break;
+ case 'g':
+ grown = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'l':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && ((0 == res) || (1 == res)))
+ longlist = !!res;
+ else {
+ pr2serr("value for '--longlist=' must be 0 or 1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ primary = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (grown || primary) {
+ if (got_addr) {
+ pr2serr("can't have '--address=' with '--grown' or '--primary'\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ } else if ((! got_addr) || (addr_arr_len < 1)) {
+ pr2serr("need at least one address (see '--address=')\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (got_addr) {
+ for (k = 0; k < addr_arr_len; ++k) {
+ if (addr_arr[k] >= UINT32_MAX) {
+ if (! eight_given) {
+ eight = true;
+ break;
+ } else if (! eight) {
+ pr2serr("address number %d exceeds 32 bits so "
+ "'--eight=0' invalid\n", k + 1);
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ }
+ if (! eight_given)
+ eight = false;
+
+ k = 4;
+ for (j = 0; j < addr_arr_len; ++j) {
+ if (eight) {
+ sg_put_unaligned_be64(addr_arr[j], param_arr + k);
+ k += 8;
+ } else {
+ sg_put_unaligned_be32((uint32_t)addr_arr[j], param_arr + k);
+ k += 4;
+ }
+ }
+ param_len = k;
+ k -= 4;
+ if (longlist)
+ sg_put_unaligned_be32((uint32_t)k, param_arr + 0);
+ else
+ sg_put_unaligned_be16((uint16_t)k, param_arr + 2);
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ if (got_addr) {
+ if (dummy) {
+ pr2serr(">>> dummy: REASSIGN BLOCKS not executed\n");
+ if (verbose) {
+ pr2serr(" Would have reassigned these blocks:\n");
+ for (j = 0; j < addr_arr_len; ++j)
+ printf(" 0x%" PRIx64 "\n", addr_arr[j]);
+ }
+ return 0;
+ }
+ res = sg_ll_reassign_blocks(sg_fd, eight, longlist, param_arr,
+ param_len, true, verbose);
+ ret = res;
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("REASSIGN BLOCKS: %s\n", b);
+ goto err_out;
+ }
+ } else /* if (grown || primary) */ {
+ int dl_format = DEF_DEFECT_LIST_FORMAT;
+ int div = 0;
+ int dl_len;
+ bool got_grown, got_primary;
+ const char * lstp;
+
+ param_len = 4;
+ memset(param_arr, 0, param_len);
+ res = sg_ll_read_defect10(sg_fd, primary, grown, dl_format,
+ param_arr, param_len, false, verbose);
+ ret = res;
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("READ DEFECT DATA(10): %s\n", b);
+ goto err_out;
+ }
+ if (do_hex) {
+ hex2stdout(param_arr, param_len, 1);
+ goto err_out; /* ret is zero */
+ }
+ got_grown = !!(param_arr[1] & 0x8);
+ got_primary = !!(param_arr[1] & 0x10);
+ if (got_grown && got_primary)
+ lstp = "grown and primary defect lists";
+ else if (got_grown)
+ lstp = "grown defect list";
+ else if (got_primary)
+ lstp = "primary defect list";
+ else {
+ pr2serr("didn't get grown or primary list in response\n");
+ goto err_out;
+ }
+ if (verbose)
+ pr2serr("asked for defect list format %d, got %d\n", dl_format,
+ (param_arr[1] & 0x7));
+ dl_format = (param_arr[1] & 0x7);
+ switch (dl_format) { /* Defect list formats: */
+ case 0: /* short block */
+ div = 4;
+ break;
+ case 1: /* extended bytes from index */
+ div = 8;
+ break;
+ case 2: /* extended physical sector */
+ div = 8;
+ break;
+ case 3: /* long block */
+ case 4: /* bytes from index */
+ case 5: /* physical sector */
+ div = 8;
+ break;
+ case 6: /* vendor specific */
+ if (verbose)
+ pr2serr("defect list format: vendor specific\n");
+ break;
+ default:
+ pr2serr("defect list format %d unknown\n", dl_format);
+ break;
+ }
+ dl_len = sg_get_unaligned_be16(param_arr + 2);
+ if (0 == dl_len)
+ printf(">> Elements in %s: 0\n", lstp);
+ else {
+ if (0 == div)
+ printf(">> %s length=%d bytes [unknown number of elements]\n",
+ lstp, dl_len);
+ else
+ printf(">> Elements in %s: %d\n", lstp,
+ dl_len / div);
+ }
+ }
+
+err_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_reassign failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_referrals.c b/src/sg_referrals.c
new file mode 100644
index 00000000..35cf50e6
--- /dev/null
+++ b/src/sg_referrals.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2010-2018 Hannes Reinecke.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/*
+ * A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI REPORT REFERRALS command to the given
+ * SCSI device.
+ */
+
+static const char * version_str = "1.13 20180628"; /* sbc4r10 */
+
+#define MAX_REFER_BUFF_LEN (1024 * 1024)
+#define DEF_REFER_BUFF_LEN 256
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_LB_DEPENDENT 0x4
+#define TPGS_STATE_OFFLINE 0xe /* SPC-4 rev 9 */
+#define TPGS_STATE_TRANSITIONING 0xf
+
+static uint8_t referralBuff[DEF_REFER_BUFF_LEN];
+
+static const char *decode_tpgs_state(const int st)
+{
+ switch (st) {
+ case TPGS_STATE_OPTIMIZED:
+ return "active/optimized";
+ break;
+ case TPGS_STATE_NONOPTIMIZED:
+ return "active/non optimized";
+ break;
+ case TPGS_STATE_STANDBY:
+ return "standby";
+ break;
+ case TPGS_STATE_UNAVAILABLE:
+ return "unavailable";
+ break;
+ case TPGS_STATE_LB_DEPENDENT:
+ return "logical block dependent";
+ break;
+ case TPGS_STATE_OFFLINE:
+ return "offline";
+ break;
+ case TPGS_STATE_TRANSITIONING:
+ return "transitioning between states";
+ break;
+ default:
+ return "unknown";
+ break;
+ }
+}
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"lba", required_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"one-segment", no_argument, 0, 's'},
+ {"one_segment", no_argument, 0, 's'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_referrals [--help] [--hex] [--lba=LBA] "
+ "[--maxlen=LEN]\n"
+ " [--one-segment] [--raw] [--readonly] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output in hexadecimal\n"
+ " --lba=LBA|-l LBA starting LBA (logical block address) "
+ "(def: 0)\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> %d bytes)\n",
+ DEF_REFER_BUFF_LEN );
+ pr2serr(" --one-segment|-s return information about the specified "
+ "segment only\n"
+ " --raw|-r output in binary\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REPORT REFERRALS command (SBC-3)\n"
+ );
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Decodes given user data referral segment descriptor
+ * the number of blocks and returns the number of bytes processed,
+ * -1 for error.
+ */
+static int
+decode_referral_desc(const uint8_t * bp, int bytes)
+{
+ int j, n;
+ uint64_t first, last;
+
+ if (NULL == bp)
+ return -1;
+
+ if (bytes < 20)
+ return -1;
+
+ first = sg_get_unaligned_be64(bp + 4);
+ last = sg_get_unaligned_be64(bp + 12);
+
+ printf(" target port descriptors: %d\n", bp[3]);
+ printf(" user data segment: first lba %" PRIu64 ", last lba %"
+ PRIu64 "\n", first, last);
+ n = 20;
+ bytes -= n;
+ for (j = 0; j < bp[3]; j++) {
+ if (bytes < 4)
+ return -1;
+ printf(" target port descriptor %d:\n", j);
+ printf(" port group %x state (%s)\n",
+ sg_get_unaligned_be16(bp + n + 2),
+ decode_tpgs_state(bp[n] & 0xf));
+ n += 4;
+ bytes -= 4;
+ }
+ return n;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_one_segment = false;
+ bool o_readonly = false;
+ bool do_raw = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int k, res, c, rlen;
+ int sg_fd = -1;
+ int do_hex = 0;
+ int maxlen = DEF_REFER_BUFF_LEN;
+ int verbose = 0;
+ int desc = 0;
+ int ret = 0;
+ int64_t ll;
+ uint64_t lba = 0;
+ const char * device_name = NULL;
+ const uint8_t * bp;
+ uint8_t * referralBuffp = referralBuff;
+ uint8_t * free_referralBuffp = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hHl:m:rRsvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba = (uint64_t)ll;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_REFER_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_REFER_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ do_one_segment = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (maxlen > DEF_REFER_BUFF_LEN) {
+ referralBuffp = (uint8_t *)sg_memalign(maxlen, 0,
+ &free_referralBuffp,
+ verbose > 3);
+ if (NULL == referralBuffp) {
+ pr2serr("unable to allocate %d bytes on heap\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto free_buff;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto free_buff;
+ }
+
+ res = sg_ll_report_referrals(sg_fd, lba, do_one_segment, referralBuffp,
+ maxlen, true, verbose);
+ ret = res;
+ if (0 == res) {
+ if (maxlen >= 4)
+ /*
+ * This is strictly speaking incorrect. However, the
+ * spec reserved bytes 0 and 1, so some implementations
+ * might want to use them to increase the number of
+ * possible user segments.
+ * And maybe someone takes a pity and updates the spec ...
+ */
+ rlen = sg_get_unaligned_be32(referralBuffp + 0) + 4;
+ else
+ rlen = maxlen;
+ k = (rlen > maxlen) ? maxlen : rlen;
+ if (do_raw) {
+ dStrRaw(referralBuffp, k);
+ goto the_end;
+ }
+ if (do_hex) {
+ hex2stdout(referralBuffp, k, 1);
+ goto the_end;
+ }
+ if (maxlen < 4) {
+ if (verbose)
+ pr2serr("Exiting because allocation length (maxlen) less "
+ "than 4\n");
+ goto the_end;
+ }
+ if ((verbose > 1) || (verbose && (rlen > maxlen))) {
+ pr2serr("response length %d bytes\n", rlen);
+ if (rlen > maxlen)
+ pr2serr(" ... which is greater than maxlen (allocation "
+ "length %d), truncation\n", maxlen);
+ }
+ if (rlen > maxlen)
+ rlen = maxlen;
+
+ bp = referralBuffp + 4;
+ k = 0;
+ printf("Report referrals:\n");
+ while (k < rlen - 4) {
+ printf(" descriptor %d:\n", desc);
+ res = decode_referral_desc(bp + k, rlen - 4 - k);
+ if (res < 0) {
+ pr2serr("bad user data segment referral descriptor\n");
+ break;
+ }
+ k += res;
+ desc++;
+ }
+ } else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Report Referrals command failed: %s\n", b);
+ }
+
+the_end:
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+free_buff:
+ if (free_referralBuffp)
+ free(free_referralBuffp);
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_referrals failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rem_rest_elem.c b/src/sg_rem_rest_elem.c
new file mode 100644
index 00000000..eae79798
--- /dev/null
+++ b/src/sg_rem_rest_elem.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This program issues one of the following SCSI commands:
+ * - REMOVE ELEMENT AND TRUNCATE
+ * - RESTORE ELEMENTS AND REBUILD
+ */
+
+static const char * version_str = "1.01 20221027";
+
+#define REMOVE_ELEM_SA 0x18
+#define RESTORE_ELEMS_SA 0x19
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"capacity", required_argument, 0, 'c'},
+ {"element", required_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"quick", no_argument, 0, 'q'},
+ {"remove", no_argument, 0, 'r'},
+ {"restore", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static const char * remove_cmd_s = "Remove element and truncate";
+static const char * restore_cmd_s = "Restore elements and rebuild";
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_rem_rest_elem [--capacity=RC] [--element=EID] [--help] "
+ "[--quick]\n"
+ " [--remove] [--restore] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n");
+ pr2serr(" where:\n"
+ " --capacity=RC|-c RC RC is requested capacity (unit: "
+ "block; def: 0)\n"
+ " --element=EID|-e EID EID is the element identifier to "
+ "remove;\n"
+ " default is 0 which is an invalid "
+ "EID\n"
+ " --help|-h print out usage message\n"
+ " --quick|-q bypass 15 second warn and wait\n"
+ " --remove|-r issue REMOVE ELEMENT AND TRUNCATE "
+ "command\n"
+ " --restore|-R issue RESTORE ELEMENTS AND REBUILD "
+ "command\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REMOVE ELEMENT AND TRUNCATE or RESTORE "
+ "ELEMENTS AND\nREBUILD command. Either the --remove or "
+ "--restore option needs to be given.\n");
+}
+
+/* Return of 0 -> success, various SG_LIB_CAT_* positive values or -1 ->
+ * other errors */
+static int
+sg_ll_rem_rest_elem(int sg_fd, int sa, uint64_t req_cap, uint32_t e_id,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ struct sg_pt_base * ptvp;
+ uint8_t sai16_cdb[16] =
+ {SG_SERVICE_ACTION_IN_16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ const char * cmd_name;
+
+ sai16_cdb[1] = 0x1f & sa;
+ if (REMOVE_ELEM_SA == sa) {
+ sg_put_unaligned_be64(req_cap, sai16_cdb + 2);
+ sg_put_unaligned_be32(e_id, sai16_cdb + 10);
+ cmd_name = remove_cmd_s;
+ } else
+ cmd_name = restore_cmd_s;
+ if (verbose) {
+ char d[128];
+
+ pr2serr(" %s cdb: %s\n", cmd_name,
+ sg_get_command_str(sai16_cdb, 16, false, sizeof(d), d));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", cmd_name);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, sai16_cdb, sizeof(sai16_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy,
+ verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool quick = false;
+ bool reat = false;
+ bool resar = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c;
+ int sg_fd = -1;
+ int verbose = 0;
+ int ret = 0;
+ int sa = 0;
+ uint32_t e_id = 0;
+ uint64_t req_cap = 0;
+ int64_t ll;
+ const char * device_name = NULL;
+ const char * cmd_name;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:e:hqrRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("--capacity= expects a numeric argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ req_cap = (uint64_t)ll;
+ break;
+ case 'e':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--element=EID'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == ll)
+ pr2serr("Warning: 0 is an invalid element identifier\n");
+ e_id = (uint64_t)ll;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'q':
+ quick = true;
+ break;
+ case 'r':
+ reat = true;
+ sa = REMOVE_ELEM_SA;
+ break;
+ case 'R':
+ resar = true;
+ sa = RESTORE_ELEMS_SA;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (1 != ((int)reat + (int)resar)) {
+ pr2serr("One, and only one, of these options needs to be given:\n"
+ " --remove or --restore\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ cmd_name = reat ? remove_cmd_s : restore_cmd_s;
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ int err = -sg_fd;
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ if (! quick) {
+ int k;
+ char b[80] SG_C_CPP_ZERO_INIT;
+ char ch;
+
+ for (k = 0; k < (int)sizeof(b) - 1; ++k) {
+ ch = cmd_name[k];
+ if ('\0' == ch)
+ break;
+ else if (islower(ch))
+ b[k] = toupper(ch);
+ else
+ b[k] = ch;
+ }
+ sg_warn_and_wait(b, device_name, false);
+ }
+
+ res = sg_ll_rem_rest_elem(sg_fd, sa, req_cap, e_id, true, verbose);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_rem_rest_elem failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rep_density.c b/src/sg_rep_density.c
new file mode 100644
index 00000000..afe6f299
--- /dev/null
+++ b/src/sg_rep_density.c
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.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_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI REPORT DENSITY SUPPORT command to the given
+ * SCSI (tape) device and outputs the response. Based on ssc5r06.pdf
+ */
+
+static const char * version_str = "1.00 20220120";
+
+#define MAX_RDS_BUFF_LEN (64 * 1024 - 1)
+#define DEF_RDS_BUFF_LEN 4096
+
+#define REPORT_DENSITY_SUPPORT_CMD 0x44
+#define REPORT_DENSITY_SUPPORT_CMDLEN 10
+
+#define RDS_DENSITY_DESC_LEN 52
+#define RDS_MEDIUM_T_DESC_LEN 56
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+static const char * rds_s = "Report density support";
+
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* silent, same as --inhex= */
+ {"inhex", required_argument, 0, 'i'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"media", no_argument, 0, 'M'}, /* Media field; byte 1, bit 0 */
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"typem", no_argument, 0, 't'}, /* Medium type field, byte 1, bit 1 */
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage(void)
+{
+ pr2serr("Usage: "
+ "sg_rep_density [--help] [--hex] [--inhex=FN] [--maxlen=LEN] "
+ "[--media]\n"
+ " [--raw] [--readonly] [--typem] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n");
+ pr2serr(" where:\n"
+ " --help|-h prints out this usage message\n"
+ " --hex|-H output response in hexadecimal "
+ "(default); used\n"
+ " twice: hex without addresses at start "
+ "of line\n"
+ " --inhex=FN decode contents of FN, ignore DEVICE\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 512 bytes)\n"
+ " --media|-M report on media in drive (def: report "
+ "on drive)\n"
+ " --raw|-r output response in binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --typem|-t report medium types (def: density "
+ "codes)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Sends a SCSI REPORT DENSITY SUPPORT command outputs the "
+ "response in\nASCII hexadecimal or binary. By default it reports "
+ "on density codes supported\nby the drive (LU).\n");
+}
+
+/* Invokes a SCSI REPORT PROVISIONING INITIALIZATION PATTERN command (SBC).
+ * Return of 0 -> success, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+static int
+sg_ll_report_density(int sg_fd, bool media, bool m_type, void * resp,
+ int mx_resp_len, int * residp, bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t rds_cdb[REPORT_DENSITY_SUPPORT_CMDLEN] =
+ {REPORT_DENSITY_SUPPORT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (media)
+ rds_cdb[1] |= 0x1;
+ if (m_type)
+ rds_cdb[1] |= 0x2;
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, rds_cdb + 7);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", rds_s,
+ sg_get_command_str(rds_cdb, REPORT_DENSITY_SUPPORT_CMDLEN,
+ false, sizeof(b), b));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rds_cdb, sizeof(rds_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, rds_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+decode_medium_type(const uint8_t * up, int num_desc)
+{
+ int k, j, n, q;
+
+ for (k = 0; k < num_desc; ++k, up += RDS_MEDIUM_T_DESC_LEN) {
+ if (0 == k)
+ printf("Medium type descriptor%s\n", ((num_desc > 1) ? "s" : ""));
+ printf(" descriptor %d\n", k + 1);
+ printf(" Medium type: %u\n", up[0]);
+ n = up[4];
+ printf(" Number of density codes: %d\n", n);
+ if (n > 9)
+ n = 9;
+ for (j = 0; j < n; ++j) {
+ q = up[5 + j];
+ if (q > 0)
+ printf(" Primary density code: %d\n", q);
+ }
+ printf(" Media width: %u\n", sg_get_unaligned_be16(up + 14));
+ printf(" Medium length: %u\n", sg_get_unaligned_be16(up + 16));
+ printf(" Assigning organization: %.8s\n", (const char *)(up + 20));
+ printf(" Medium type name: %.8s\n", (const char *)(up + 28));
+ printf(" Description: %.20s\n", (const char *)(up + 36));
+ }
+}
+
+static void
+decode_density_code(const uint8_t * up, int num_desc)
+{
+ int k;
+
+ for (k = 0; k < num_desc; ++k, up += RDS_DENSITY_DESC_LEN) {
+ if (0 == k)
+ printf("Density support data block descriptor%s\n",
+ ((num_desc > 1) ? "s" : ""));
+ printf(" descriptor %d\n", k + 1);
+ printf(" Primary density code: %u\n", up[0]);
+ printf(" Secondary density code: %u\n", up[1]);
+ printf(" WRT: %u\n", !!(0x80 & up[2]));
+ printf(" DUP: %u\n", !!(0x40 & up[2]));
+ printf(" DEFLT: %u\n", !!(0x20 & up[2]));
+ printf(" DLV: %u\n", !!(0x1 & up[2]));
+ printf(" Bits per mm: %u\n", sg_get_unaligned_be24(up + 5));
+ printf(" Media width: %u\n", sg_get_unaligned_be16(up + 8));
+ printf(" Tracks: %u\n", sg_get_unaligned_be16(up + 10));
+ printf(" Capacity: %u\n", sg_get_unaligned_be32(up + 12));
+ printf(" Assigning organization: %.8s\n", (const char *)(up + 16));
+ printf(" Density name: %.8s\n", (const char *)(up + 24));
+ printf(" Description: %.20s\n", (const char *)(up + 32));
+ }
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_raw = false;
+ bool media = false;
+ bool m_type = false;
+ bool no_final_msg = false;
+ bool o_readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, rlen, desc_len, ads_len, num_desc;
+ int resid = 0;
+ int sg_fd = -1;
+ int do_help = 0;
+ int do_hex = 0;
+ int maxlen = 0;
+ int in_len = 0;
+ int ret = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ const char * inhex_fn = NULL;
+ uint8_t * rdsBuff = NULL;
+ uint8_t * free_rds = NULL;
+ char b[80];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hHi:m:MrRtvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ inhex_fn = optarg;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_RDS_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_RDS_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'M':
+ media = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 't':
+ m_type = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (do_help) {
+ usage();
+ return 0;
+ }
+ if (device_name && inhex_fn) {
+ pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
+ "not both\n");
+ device_name = NULL;
+ }
+ if (0 == maxlen)
+ maxlen = DEF_RDS_BUFF_LEN;
+ rdsBuff = (uint8_t *)sg_memalign(maxlen, 0, &free_rds, verbose > 3);
+ if (NULL == rdsBuff) {
+ pr2serr("unable to sg_memalign %d bytes\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ if (NULL == device_name) {
+ if (inhex_fn) {
+ if ((ret = sg_f2hex_arr(inhex_fn, do_raw, false, rdsBuff,
+ &in_len, maxlen))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
+ no_final_msg = true;
+ pr2serr("... decode what we have, --maxlen=%d needs to "
+ "be increased\n", maxlen);
+ } else
+ goto the_end;
+ }
+ if (verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (do_raw)
+ do_raw = false; /* otherwise interferes with decoding */
+ if (in_len < 4) {
+ pr2serr("--inhex=%s only decoded %d bytes (needs 4 at "
+ "least)\n", inhex_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto the_end;
+ }
+ res = 0;
+ maxlen = in_len;
+ goto start_response;
+ } else {
+ pr2serr("missing device name!\n\n");
+ usage();
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto the_end;
+ }
+ } else
+ in_len = 0;
+
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto the_end;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto the_end;
+ }
+
+ res = sg_ll_report_density(sg_fd, media, m_type, rdsBuff, maxlen, &resid,
+ true, verbose);
+start_response:
+ ret = res;
+ if (0 == res) {
+ rlen = maxlen - resid;
+ if (rlen < 4) {
+ pr2serr("Response length (%d) too short\n", rlen);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ if (do_raw) {
+ dStrRaw(rdsBuff, rlen);
+ goto the_end;
+ }
+ if (do_hex) {
+ if (2 != do_hex)
+ hex2stdout(rdsBuff, rlen, ((1 == do_hex) ? 1 : -1));
+ else
+ hex2stdout(rdsBuff, rlen, 0);
+ goto the_end;
+ }
+ desc_len = m_type ? RDS_MEDIUM_T_DESC_LEN : RDS_DENSITY_DESC_LEN;
+ ads_len = sg_get_unaligned_be16(rdsBuff + 0) + 2;
+ if (4 == ads_len)
+ goto the_end;
+ if (ads_len < 4) {
+ pr2serr("Badly formatted response, ads_len=%d\n", ads_len - 2);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ if (ads_len > rlen) {
+ if (verbose)
+ pr2serr("Trimming response from %d to %d bytes\n", ads_len,
+ rlen);
+ ads_len = rlen;
+ if (4 == ads_len)
+ goto the_end;
+ }
+ num_desc = (ads_len - 4) / desc_len;
+ if (0 != ((ads_len - 4) % desc_len)) {
+ if (verbose)
+ pr2serr("Truncating response to %d descriptors\n", num_desc);
+ }
+ if (m_type)
+ decode_medium_type(rdsBuff + 4, num_desc);
+ else
+ decode_density_code(rdsBuff + 4, num_desc);
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", rds_s);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", rds_s, b);
+ }
+
+the_end:
+ if (free_rds)
+ free(free_rds);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if ((0 == verbose) && (! no_final_msg)) {
+ if (! sg_if_can2stderr("sg_rep_density failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rep_pip.c b/src/sg_rep_pip.c
new file mode 100644
index 00000000..beadaa23
--- /dev/null
+++ b/src/sg_rep_pip.c
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2014-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.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_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI REPORT PROVISIONING INITIALIZATION PATTERN
+ * command to the given SCSI device and outputs the response. Based on
+ * sbc4r21.pdf
+ */
+
+static const char * version_str = "1.04 20220120";
+
+#define MAX_RPIP_BUFF_LEN (1024 * 1024)
+#define DEF_RPIP_BUFF_LEN 512
+
+#define SG_MAINT_IN_CMDLEN 12
+
+#define REPORT_PROVISIONING_INITIALIZATION_PATTERN_SA 0x1d
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+static const char * rpip_s = "Report provisioning initialization pattern";
+
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage(void)
+{
+ pr2serr("Usage: "
+ "sg_rep_pip [--help] [--hex] [--maxlen=LEN] [--raw] "
+ "[--readonly]\n"
+ " [--verbose] [--version] DEVICE\n");
+ pr2serr(" where:\n"
+ " --help|-h prints out this usage message\n"
+ " --hex|-H output response in hexadecimal "
+ "(default); used\n"
+ " twice: hex without addresses at start "
+ "of line\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 512 bytes)\n"
+ " --raw|-r output response in binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Sends a SCSI REPORT PROVISIONING INITIALIZATION PATTERN "
+ "command and outputs\nthe response in ASCII hexadecimal or "
+ "binary.\n");
+}
+
+/* Invokes a SCSI REPORT PROVISIONING INITIALIZATION PATTERN command (SBC).
+ * Return of 0 -> success, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+static int
+sg_ll_report_pip(int sg_fd, void * resp, int mx_resp_len, int * residp,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t rpip_cdb[SG_MAINT_IN_CMDLEN] =
+ {SG_MAINTENANCE_IN, REPORT_PROVISIONING_INITIALIZATION_PATTERN_SA,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rpip_cdb + 6);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", rpip_s,
+ sg_get_command_str(rpip_cdb, SG_MAINT_IN_CMDLEN, false,
+ sizeof(b), b));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rpip_cdb, sizeof(rpip_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, rpip_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_raw = false;
+ bool o_readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, resid, rlen;
+ int sg_fd = -1;
+ int do_help = 0;
+ int do_hex = 0;
+ int maxlen = 0;
+ int ret = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ uint8_t * rpipBuff = NULL;
+ uint8_t * free_rpip = NULL;
+ char b[80];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hHm:rRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_RPIP_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_RPIP_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (do_help) {
+ usage();
+ return 0;
+ }
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto the_end;
+ }
+
+ if (0 == maxlen)
+ maxlen = DEF_RPIP_BUFF_LEN;
+ rpipBuff = (uint8_t *)sg_memalign(maxlen, 0, &free_rpip, verbose > 3);
+ if (NULL == rpipBuff) {
+ pr2serr("unable to sg_memalign %d bytes\n", maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+ res = sg_ll_report_pip(sg_fd, rpipBuff, maxlen, &resid, true, verbose);
+ ret = res;
+ if (0 == res) {
+ rlen = maxlen - resid;
+ if (rlen < 4) {
+ pr2serr("Response length (%d) too short\n", rlen);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ if (do_raw) {
+ dStrRaw(rpipBuff, rlen);
+ goto the_end;
+ }
+ if (do_hex) {
+ if (2 != do_hex)
+ hex2stdout(rpipBuff, rlen, ((1 == do_hex) ? 1 : -1));
+ else
+ hex2stdout(rpipBuff, rlen, 0);
+ goto the_end;
+ } else {
+ printf("Printing response in hex:\n");
+ hex2stdout(rpipBuff, rlen, 1);
+ goto the_end;
+ }
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", rpip_s);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", rpip_s, b);
+ }
+
+the_end:
+ if (free_rpip)
+ free(free_rpip);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_rep_pip failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rep_zones.c b/src/sg_rep_zones.c
new file mode 100644
index 00000000..c0d19d31
--- /dev/null
+++ b/src/sg_rep_zones.c
@@ -0,0 +1,1525 @@
+/*
+ * Copyright (c) 2014-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.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_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI REPORT ZONES, REPORT ZONE DOMAINS or REPORT
+ * REALMS command to the given SCSI device and decodes the response.
+ * Based on zbc2r12.pdf
+ */
+
+static const char * version_str = "1.42 20220807";
+
+#define MY_NAME "sg_rep_zones"
+
+#define WILD_RZONES_BUFF_LEN (1 << 28)
+#define MAX_RZONES_BUFF_LEN (2 * 1024 * 1024)
+#define DEF_RZONES_BUFF_LEN (1024 * 16)
+#define RCAP16_REPLY_LEN 32
+
+#define SG_ZONING_IN_CMDLEN 16
+#define REPORT_ZONES_DESC_LEN 64
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+/* Three zone service actions supported by this utility */
+enum zone_report_sa_e {
+ REPORT_ZONES_SA = 0x0,
+ REPORT_REALMS_SA = 0x6,
+ REPORT_ZONE_DOMAINS_SA = 0x7
+};
+
+struct opts_t {
+ bool do_brief;
+ bool do_force;
+ bool do_partial;
+ bool do_raw;
+ bool do_realms;
+ bool do_zdomains;
+ bool maxlen_given;
+ bool o_readonly;
+ bool statistics;
+ bool verbose_given;
+ bool version_given;
+ bool wp_only;
+ enum zone_report_sa_e serv_act;
+ int do_help;
+ int do_hex;
+ int do_num;
+ int find_zt; /* negative values: find first not equal to */
+ int maxlen;
+ int reporting_opt;
+ int vb;
+ uint64_t st_lba;
+ const char * in_fn;
+ sgj_state json_st;
+};
+
+struct zt_num2abbrev_t {
+ int ztn;
+ const char * abbrev;
+};
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'}, /* only header and last descriptor */
+ {"domain", no_argument, 0, 'd'},
+ {"domains", no_argument, 0, 'd'},
+ {"force", no_argument, 0, 'f'},
+ {"find", required_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* silent, same as --inhex= */
+ {"inhex", required_argument, 0, 'i'},
+ {"json", optional_argument, 0, 'j'},
+ {"locator", required_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"num", required_argument, 0, 'n'},
+ {"partial", no_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"realm", no_argument, 0, 'e'},
+ {"realms", no_argument, 0, 'e'},
+ {"report", required_argument, 0, 'o'},
+ {"start", required_argument, 0, 's'},
+ {"statistics", no_argument, 0, 'S'},
+ {"stats", no_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wp", no_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+/* Zone types */
+static struct zt_num2abbrev_t zt_num2abbrev[] = {
+ {0, "none"},
+ {1, "c"}, /* conventionial */
+ {2, "swr"}, /* sequential write required */
+ {3, "swp"}, /* sequential write preferred */
+ {4, "sobr"}, /* sequential or before required */
+ {5, "g"}, /* gap */
+ {-1, NULL}, /* sentinel */
+};
+
+static const char * zn_dnum_s = "zone descriptor number: ";
+
+static const char * meaning_s = "meaning";
+
+
+static void
+prn_zone_type_abbrevs(void)
+{
+ const struct zt_num2abbrev_t * n2ap = zt_num2abbrev;
+ char b[32];
+
+ pr2serr("Zone type number\tAbbreviation\tName\n");
+ pr2serr("----------------\t------------\t----\n");
+ for ( ; n2ap->abbrev; ++n2ap) {
+ if (n2ap == zt_num2abbrev)
+ pr2serr("\t%d\t\t%s\t\t[reserved]\n",
+ n2ap->ztn, n2ap->abbrev);
+ else
+ pr2serr("\t%d\t\t%s\t\t%s\n", n2ap->ztn, n2ap->abbrev,
+ sg_get_zone_type_str(n2ap->ztn, sizeof(b), b));
+ }
+}
+
+static void
+usage(int h)
+{
+ if (h > 1) goto h_twoormore;
+ pr2serr("Usage: "
+ "sg_rep_zones [--domain] [--find=ZT] [--force] [--help] "
+ "[--hex]\n"
+ " [--inhex=FN] [--json[=JO]] "
+ "[--locator=LBA]\n"
+ " [--maxlen=LEN] [--num=NUM] [--partial] "
+ "[--raw]\n"
+ " [--readonly] [--realm] [--report=OPT] "
+ "[--start=LBA]\n"
+ " [--statistics] [--verbose] [--version] "
+ "[--wp]\n"
+ " DEVICE\n");
+ pr2serr(" where:\n"
+ " --domain|-d sends a REPORT ZONE DOMAINS command\n"
+ " --find=ZT|-F ZT find first zone with ZT zone type, "
+ "starting at LBA\n"
+ " if first character of ZT is - or !, "
+ "find first\n"
+ " zone that is not ZT\n"
+ " --force|-f bypass some sanity checks when decoding "
+ "response\n"
+ " --help|-h print out usage message, use twice for "
+ "more help\n"
+ " --hex|-H output response in hexadecimal; used "
+ "twice\n"
+ " shows decoded values in hex\n"
+ " --inhex=FN|-i FN decode contents of FN, ignore DEVICE\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --locator=LBA|-l LBA similar to --start= option\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> 8192 bytes)\n"
+ " --num=NUM|-n NUM number of zones to output (def: 0 -> "
+ "all)\n"
+ " --partial|-p sets PARTIAL bit in cdb (def: 0 -> "
+ "zone list\n"
+ " length not altered by allocation length "
+ "in cdb)\n"
+ " --raw|-r output response in binary\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --realm|-e sends a REPORT REALMS command\n"
+ " --report=OPT|-o OP reporting options (def: 0: all "
+ "zones)\n"
+ " --start=LBA|-s LBA report zones from the LBA (def: 0)\n"
+ " need not be a zone starting LBA\n"
+ " --statistics|-S gather statistics by reviewing zones\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --wp|-w output write pointer only\n\n"
+ "Sends a SCSI REPORT ZONES, REPORT ZONE DOMAINS or REPORT REALMS "
+ "command.\nBy default sends a REPORT ZONES command. Give help "
+ "option twice\n(e.g. '-hh') to see reporting options "
+ "enumerated.\n");
+ return;
+h_twoormore:
+ pr2serr("Reporting options for REPORT ZONES:\n"
+ " 0x0 list all zones\n"
+ " 0x1 list zones with a zone condition of EMPTY\n"
+ " 0x2 list zones with a zone condition of IMPLICITLY "
+ "OPENED\n"
+ " 0x3 list zones with a zone condition of EXPLICITLY "
+ "OPENED\n"
+ " 0x4 list zones with a zone condition of CLOSED\n"
+ " 0x5 list zones with a zone condition of FULL\n"
+ " 0x6 list zones with a zone condition of READ ONLY\n"
+ " 0x7 list zones with a zone condition of OFFLINE\n"
+ " 0x8 list zones with a zone condition of INACTIVE\n"
+ " 0x10 list zones with RWP Recommended set to true\n"
+ " 0x11 list zones with Non-sequential write resources "
+ "active set to true\n"
+ " 0x3e list zones except those with zone type: GAP\n"
+ " 0x3f list zones with a zone condition of NOT WRITE "
+ "POINTER\n\n");
+ pr2serr("Reporting options for REPORT ZONE DOMAINS:\n"
+ " 0x0 list all zone domains\n"
+ " 0x1 list all zone domains in which all zones are active\n"
+ " 0x2 list all zone domains that contain active zones\n"
+ " 0x3 list all zone domains that do not contain any active "
+ "zones\n\n");
+ pr2serr("Reporting options for REPORT REALMS:\n"
+ " 0x0 list all realms\n"
+ " 0x1 list all realms that contain active Sequential Or "
+ "Before Required zones\n"
+ " 0x2 list all realms that contain active Sequential Write "
+ "Required zones\n"
+ " 0x3 list all realms that contain active Sequential Write "
+ "Preferred zones\n");
+ pr2serr("\n");
+ prn_zone_type_abbrevs();
+}
+
+/* Invokes a SCSI REPORT ZONES, REPORT ZONE DOMAINS or REPORT REALMS command
+ * (see ZBC and ZBC-2). Return of 0 -> success, various SG_LIB_CAT_* positive
+ * values or -1 -> other errors */
+static int
+sg_ll_report_zzz(int sg_fd, enum zone_report_sa_e serv_act, uint64_t zs_lba,
+ bool partial, int report_opts, void * resp, int mx_resp_len,
+ int * residp, bool noisy, int vb)
+{
+ int ret, res, sense_cat;
+ uint8_t rz_cdb[SG_ZONING_IN_CMDLEN] =
+ {SG_ZONING_IN, REPORT_ZONES_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ rz_cdb[1] = (uint8_t)serv_act;
+ sg_put_unaligned_be64(zs_lba, rz_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rz_cdb + 10);
+ rz_cdb[14] = report_opts & 0x3f;
+ if (partial)
+ rz_cdb[14] |= 0x80;
+ if (vb) {
+ char b[128];
+
+ pr2serr(" %s\n", sg_get_command_str(rz_cdb, SG_ZONING_IN_CMDLEN,
+ true, sizeof(b), b));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rz_cdb, sizeof(rz_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, vb);
+ ret = sg_cmds_process_resp(ptvp, "report zone/domain/realm", res, noisy,
+ vb, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static const char *
+zone_condition_str(int zc, char * b, int blen, int vb)
+{
+ const char * cp;
+
+ if (NULL == b)
+ return "zone_condition_str: NULL ptr)";
+ switch (zc) {
+ case 0:
+ cp = "Not write pointer";
+ break;
+ case 1:
+ cp = "Empty";
+ break;
+ case 2:
+ cp = "Implicitly opened";
+ break;
+ case 3:
+ cp = "Explicitly opened";
+ break;
+ case 4:
+ cp = "Closed";
+ break;
+ case 5:
+ cp = "Inactive";
+ break;
+ case 0xd:
+ cp = "Read only";
+ break;
+ case 0xe:
+ cp = "Full";
+ break;
+ case 0xf:
+ cp = "Offline";
+ break;
+ default:
+ cp = NULL;
+ break;
+ }
+ if (cp) {
+ if (vb)
+ snprintf(b, blen, "%s [0x%x]", cp, zc);
+ else
+ snprintf(b, blen, "%s", cp);
+ } else
+ snprintf(b, blen, "Reserved [0x%x]", zc);
+ return b;
+}
+
+static const char * same_desc_arr[16] = {
+ "zone type and length may differ in each descriptor",
+ "zone type and length same in each descriptor",
+ "zone type and length same apart from length in last descriptor",
+ "zone type for each descriptor may be different",
+ "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
+ "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
+ "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
+};
+
+static uint64_t
+prt_a_zn_desc(const uint8_t *bp, const struct opts_t * op,
+ sgj_state * jsp, sgj_opaque_p jop)
+{
+ uint8_t zt, zc;
+ uint64_t lba, len, wp;
+ char b[80];
+
+ jop = jop ? jop : jsp->basep;
+ zt = bp[0] & 0xf;
+ zc = (bp[1] >> 4) & 0xf;
+ sg_get_zone_type_str(zt, sizeof(b), b);
+ sgj_pr_hr(jsp, " Zone type: %s\n", b);
+ sgj_js_nv_istr(jsp, jop, "zone_type", zt, meaning_s, b);
+ zone_condition_str(zc, b, sizeof(b), op->vb);
+ sgj_pr_hr(jsp, " Zone condition: %s\n", b);
+ sgj_js_nv_istr(jsp, jop, "zone_condition", zc, meaning_s, b);
+ sgj_haj_vi(jsp, jop, 3, "PUEP", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[1] & 0x4), false);
+ sgj_haj_vi(jsp, jop, 3, "NON_SEQ", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[1] & 0x2), false);
+ sgj_haj_vi(jsp, jop, 3, "RESET", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[1] & 0x1), false);
+ len = sg_get_unaligned_be64(bp + 8);
+ sgj_pr_hr(jsp, " Zone Length: 0x%" PRIx64 "\n", len);
+ sgj_js_nv_ihex(jsp, jop, "zone_length", (int64_t)len);
+ lba = sg_get_unaligned_be64(bp + 16);
+ sgj_pr_hr(jsp, " Zone start LBA: 0x%" PRIx64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jop, "zone_start_lba", (int64_t)lba);
+ wp = sg_get_unaligned_be64(bp + 24);
+ if (sg_all_ffs((const uint8_t *)&wp, sizeof(wp)))
+ sgj_pr_hr(jsp, " Write pointer LBA: -1\n");
+ else
+ sgj_pr_hr(jsp, " Write pointer LBA: 0x%" PRIx64 "\n", wp);
+ sgj_js_nv_ihex(jsp, jop, "write_pointer_lba", (int64_t)wp);
+ return lba + len;
+}
+
+static int
+decode_rep_zones(const uint8_t * rzBuff, int act_len, uint32_t decod_len,
+ const struct opts_t * op, sgj_state * jsp)
+{
+ bool as_json = jsp ? jsp->pr_as_json : false;
+ int k, same, num_zd;
+ uint64_t wp, ul, mx_lba;
+ sgj_opaque_p jop = jsp ? jsp->basep : NULL;
+ sgj_opaque_p jap = NULL;
+ const uint8_t * bp;
+
+ if ((uint32_t)act_len < decod_len) {
+ num_zd = (act_len >= 64) ? ((act_len - 64) / REPORT_ZONES_DESC_LEN)
+ : 0;
+ if (act_len == op->maxlen) {
+ if (op->maxlen_given)
+ pr2serr("decode length [%u bytes] may be constrained by "
+ "given --maxlen value, try increasing\n", decod_len);
+ else
+ pr2serr("perhaps --maxlen=%u needs to be used\n", decod_len);
+ } else if (op->in_fn)
+ pr2serr("perhaps %s has been truncated\n", op->in_fn);
+ } else
+ num_zd = (decod_len - 64) / REPORT_ZONES_DESC_LEN;
+ same = rzBuff[4] & 0xf;
+ mx_lba = sg_get_unaligned_be64(rzBuff + 8);
+ if (op->wp_only) {
+ ;
+ } else if (op->do_hex) {
+ hex2stdout(rzBuff, 64, -1);
+ printf("\n");
+ } else {
+ uint64_t rzslbag = sg_get_unaligned_be64(rzBuff + 16);
+ static const char * rzslbag_s = "Reported zone starting LBA "
+ "granularity";
+
+ sgj_pr_hr(jsp, " Same=%d: %s\n", same, same_desc_arr[same]);
+ sgj_js_nv_istr(jsp, jop, "same", same, meaning_s,
+ same_desc_arr[same]);
+ sgj_pr_hr(jsp, " Maximum LBA: 0x%" PRIx64 "\n\n", mx_lba);
+ sgj_js_nv_ihex(jsp, jop, "maximum_lba", mx_lba);
+ sgj_pr_hr(jsp, " %s: 0x%" PRIx64 "\n\n", rzslbag_s, rzslbag);
+ sgj_js_nv_ihex(jsp, jop, rzslbag_s, rzslbag);
+ }
+ if (op->do_num > 0)
+ num_zd = (num_zd > op->do_num) ? op->do_num : num_zd;
+ if (((uint32_t)act_len < decod_len) &&
+ ((num_zd * REPORT_ZONES_DESC_LEN) + 64 > act_len)) {
+ pr2serr("Skip due to truncated response, try using --num= to a "
+ "value less than %d\n", num_zd);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (op->do_brief && (num_zd > 0)) {
+ bp = rzBuff + 64 + ((num_zd - 1) * REPORT_ZONES_DESC_LEN);
+ if (op->do_hex) {
+ if (op->wp_only)
+ hex2stdout(bp + 24, 8, -1);
+ else
+ hex2stdout(bp, 64, -1);
+ return 0;
+ }
+ sgj_pr_hr(jsp, "From last descriptor in this response:\n");
+ sgj_pr_hr(jsp, " %s%d\n", zn_dnum_s, num_zd - 1);
+ sgj_js_nv_i(jsp, jop, "zone_descriptor_index", num_zd - 1);
+ ul = prt_a_zn_desc(bp, op, jsp, jop);
+ if (ul > mx_lba)
+ sgj_pr_hr(jsp, " >> This zone seems to be the last one\n");
+ else
+ sgj_pr_hr(jsp, " >> Probable next Zone start LBA: 0x%" PRIx64
+ "\n", ul);
+ return 0;
+ }
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, NULL, "zone_descriptors_list");
+ for (k = 0, bp = rzBuff + 64; k < num_zd;
+ ++k, bp += REPORT_ZONES_DESC_LEN) {
+ sgj_opaque_p jo2p;
+
+ if (! op->wp_only)
+ sgj_pr_hr(jsp, " %s%d\n", zn_dnum_s, k);
+ if (op->do_hex) {
+ hex2stdout(bp, 64, -1);
+ continue;
+ }
+ if (op->wp_only) {
+ if (op->do_hex)
+ hex2stdout(bp + 24, 8, -1);
+ else {
+ wp = sg_get_unaligned_be64(bp + 24);
+ if (sg_all_ffs((const uint8_t *)&wp, sizeof(wp)))
+ sgj_pr_hr(jsp, "-1\n");
+ else
+ sgj_pr_hr(jsp, "0x%" PRIx64 "\n", wp);
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jo2p, "write_pointer_lba", (int64_t)wp);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ continue;
+ }
+ jo2p = sgj_new_unattached_object_r(jsp);
+ prt_a_zn_desc(bp, op, jsp, jo2p);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if ((op->do_num == 0) && (! op->wp_only) && (! op->do_hex)) {
+ if ((64 + (REPORT_ZONES_DESC_LEN * (uint32_t)num_zd)) < decod_len)
+ sgj_pr_hr(jsp, "\n>>> Beware: Zone list truncated, may need "
+ "another call\n");
+ }
+ return 0;
+}
+
+static int
+decode_rep_realms(const uint8_t * rzBuff, int act_len,
+ const struct opts_t * op, sgj_state * jsp)
+{
+ uint32_t k, realms_count, derived_realms_count, r_desc_len,
+ zdomains_count;
+ uint64_t nr_locator;
+ const uint8_t * bp;
+ sgj_opaque_p jop = jsp ? jsp->basep : NULL;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p ja2p = NULL;
+
+ if (act_len < 12) {
+ pr2serr("need more than 12 bytes to decode, got %u\n", act_len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ realms_count = sg_get_unaligned_be32(rzBuff + 4);
+ r_desc_len = sg_get_unaligned_be32(rzBuff + 8);
+ if (act_len < 20)
+ nr_locator = sg_get_unaligned_be64(rzBuff + 12);
+ else
+ nr_locator = 0;
+ sgj_haj_vi(jsp, jop, 0, "Realms_count", SGJ_SEP_EQUAL_NO_SPACE,
+ realms_count, true);
+ sgj_haj_vi(jsp, jop, 0, "Realms_descriptor_length",
+ SGJ_SEP_EQUAL_NO_SPACE, r_desc_len, true);
+ sgj_pr_hr(jsp, "Next_realm_locator=0x%" PRIx64 "\n", nr_locator);
+ sgj_js_nv_ihex(jsp, jop, "Next_realm_locator", nr_locator);
+ if ((realms_count < 1) || (act_len < (64 + 16)) || (r_desc_len < 16)) {
+ if (op->vb) {
+ pr2serr("%s: exiting early because ", __func__);
+ if (realms_count < 1)
+ pr2serr("realms_count is zero\n");
+ else if (r_desc_len < 16)
+ pr2serr("realms descriptor length less than 16\n");
+ else
+ pr2serr("actual_length (%u) too short\n", act_len);
+ }
+ return 0;
+ }
+ derived_realms_count = (act_len - 64) / r_desc_len;
+ if (derived_realms_count > realms_count) {
+ if (op->vb)
+ pr2serr("%s: derived_realms_count [%u] > realms_count [%u]\n",
+ __func__, derived_realms_count, realms_count);
+ } else if (derived_realms_count < realms_count) {
+ if (op->vb)
+ pr2serr("%s: derived_realms_count [%u] < realms_count [%u], "
+ "use former\n", __func__, derived_realms_count,
+ realms_count);
+ realms_count = derived_realms_count;
+ }
+ zdomains_count = (r_desc_len - 16) / 16;
+
+ if (op->do_num > 0)
+ realms_count = (realms_count > (uint32_t)op->do_num) ?
+ (uint32_t)op->do_num : realms_count;
+ jap = sgj_named_subarray_r(jsp, jop, "realm_descriptors_list");
+
+ for (k = 0, bp = rzBuff + 64; k < realms_count; ++k, bp += r_desc_len) {
+ uint32_t j;
+ uint16_t restrictions;
+ const uint8_t * zp;
+ sgj_opaque_p jo2p;
+
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_haj_vi(jsp, jo2p, 1, "Realms_id", SGJ_SEP_EQUAL_NO_SPACE,
+ sg_get_unaligned_be32(bp + 0), true);
+ if (op->do_hex) {
+ hex2stdout(bp, r_desc_len, -1);
+ continue;
+ }
+ restrictions = sg_get_unaligned_be16(bp + 4);
+ sgj_pr_hr(jsp, " realm_restrictions=0x%hu\n", restrictions);
+ sgj_js_nv_ihex(jsp, jo2p, "realm_restrictions", restrictions);
+ sgj_haj_vi(jsp, jo2p, 3, "active_zone_domain_id",
+ SGJ_SEP_EQUAL_NO_SPACE, bp[7], true);
+
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "realm_start_end_descriptors_list");
+ for (j = 0, zp = bp + 16; j < zdomains_count; ++j, zp += 16) {
+ uint64_t lba;
+ sgj_opaque_p jo3p;
+
+ jo3p = sgj_new_unattached_object_r(jsp);
+ sgj_pr_hr(jsp, " zone_domain=%u\n", j);
+ sgj_js_nv_i(jsp, jo3p, "corresponding_zone_domain_id", j);
+ lba = sg_get_unaligned_be64(zp + 0);
+ sgj_pr_hr(jsp, " starting_lba=0x%" PRIx64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jo3p, "realm_starting_lba", (int64_t)lba);
+ lba = sg_get_unaligned_be64(zp + 8);
+ sgj_pr_hr(jsp, " ending_lba=0x%" PRIx64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jo3p, "realm_ending_lba", (int64_t)lba);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ return 0;
+}
+
+static int
+decode_rep_zdomains(const uint8_t * rzBuff, int act_len,
+ const struct opts_t * op, sgj_state * jsp)
+{
+ uint32_t k, zd_len, zd_ret_len, zdoms_sup, zdoms_rep, zd_rep_opts;
+ uint32_t num, der_zdoms;
+ uint64_t zd_locator;
+ sgj_opaque_p jop = jsp ? jsp->basep : NULL;
+ sgj_opaque_p jap = NULL;
+ const uint8_t * bp;
+
+ if (act_len < 12) {
+ pr2serr("need more than 12 bytes to decode, got %u\n", act_len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ zd_len = sg_get_unaligned_be32(rzBuff + 0);
+ zd_ret_len = sg_get_unaligned_be32(rzBuff + 4);
+ zdoms_sup = rzBuff[8];
+ zdoms_rep = rzBuff[9];
+ zd_rep_opts = rzBuff[10];
+ if (act_len < 24)
+ zd_locator = sg_get_unaligned_be64(rzBuff + 16);
+ else
+ zd_locator = 0;
+ sgj_haj_vi(jsp, jop, 0, "Zone_domains_returned_list_length=",
+ SGJ_SEP_EQUAL_NO_SPACE, zd_ret_len, true);
+ sgj_haj_vi(jsp, jop, 0, "Zone_domains_supported",
+ SGJ_SEP_EQUAL_NO_SPACE, zdoms_sup, true);
+ sgj_haj_vi(jsp, jop, 0, "Zone_domains_reported",
+ SGJ_SEP_EQUAL_NO_SPACE, zdoms_rep, true);
+ sgj_pr_hr(jsp, "Reporting_options=0x%x\n", zd_rep_opts);
+ sgj_js_nv_ihex(jsp, jop, "Reporting_options", zd_rep_opts);
+ sgj_pr_hr(jsp, "Zone_domain_locator=0x%" PRIx64 "\n", zd_locator);
+ sgj_js_nv_ihex(jsp, jop, "Zone_domain_locator", zd_locator);
+
+ der_zdoms = zd_len / 96;
+ if (op->vb > 1)
+ pr2serr("Derived zdomains=%u\n", der_zdoms);
+ num = ((der_zdoms < zdoms_rep) ? der_zdoms : zdoms_rep) * 96;
+ jap = sgj_named_subarray_r(jsp, jop, "zone_domain_descriptors_list");
+
+ for (k = 0, bp = rzBuff + 64; k < num; k += 96, bp += 96) {
+ uint64_t lba;
+ sgj_opaque_p jo2p;
+
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_haj_vi(jsp, jo2p, 3, "zone_domain",
+ SGJ_SEP_EQUAL_NO_SPACE, bp[0], true);
+ lba = sg_get_unaligned_be64(bp + 16);
+ sgj_pr_hr(jsp, " zone_count=%" PRIu64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jo2p, "zone_count", lba);
+ lba = sg_get_unaligned_be64(bp + 24);
+ sgj_pr_hr(jsp, " starting_lba=0x%" PRIx64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jo2p, "starting_lba", lba);
+ lba = sg_get_unaligned_be64(bp + 32);
+ sgj_pr_hr(jsp, " ending_lba=0x%" PRIx64 "\n", lba);
+ sgj_js_nv_ihex(jsp, jo2p, "ending_lba", lba);
+ sgj_pr_hr(jsp, " zone_domain_zone_type=0x%x\n", bp[40]);
+ sgj_js_nv_ihex(jsp, jo2p, "zone_domain_zone_type", bp[40]);
+ sgj_haj_vi(jsp, jo2p, 5, "VZDZT", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(0x2 & bp[42]), false);
+ sgj_haj_vi(jsp, jo2p, 5, "SRB", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(0x1 & bp[42]), false);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ return 0;
+}
+
+static int
+find_report_zones(int sg_fd, uint8_t * rzBuff, const char * cmd_name,
+ struct opts_t * op, sgj_state * jsp)
+{
+ bool as_json = (jsp && (0 == op->do_hex)) ? jsp->pr_as_json : false;
+ bool found = false;
+ uint8_t zt;
+ int k, res, resid, rlen, num_zd, num_rem;
+ uint32_t zn_dnum = 0;
+ uint64_t slba = op->st_lba;
+ uint64_t mx_lba = 0;
+ const uint8_t * bp = rzBuff;
+ char b[96];
+
+ num_rem = op->do_num ? op->do_num : INT_MAX;
+ for ( ; num_rem > 0; num_rem -= num_zd) {
+ resid = 0;
+ if (sg_fd >= 0) {
+ res = sg_ll_report_zzz(sg_fd, REPORT_ZONES_SA, slba,
+ true /* set partial */, op->reporting_opt,
+ rzBuff, op->maxlen, &resid, true, op->vb);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s: %s%u, %s command not supported\n", __func__,
+ zn_dnum_s, zn_dnum, cmd_name);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->vb);
+ pr2serr("%s: %s%u, %s command: %s\n", __func__,
+ zn_dnum_s, zn_dnum, cmd_name, b);
+ }
+ break;
+ }
+ } else
+ res = 0;
+ rlen = op->maxlen - resid;
+ if (rlen <= 64)
+ break;
+ mx_lba = sg_get_unaligned_be64(rzBuff + 8);
+ num_zd = (rlen - 64) / REPORT_ZONES_DESC_LEN;
+ if (num_zd > num_rem)
+ num_zd = num_rem;
+ for (k = 0, bp = rzBuff + 64; k < num_zd;
+ ++k, bp += REPORT_ZONES_DESC_LEN, ++zn_dnum) {
+ zt = 0xf & bp[0];
+ if (op->find_zt > 0) {
+ if ((uint8_t)op->find_zt == zt )
+ break;
+ } else if (op->find_zt < 0) {
+ if ((uint8_t)(-op->find_zt) != zt )
+ break;
+ }
+ slba = sg_get_unaligned_be64(bp + 16) +
+ sg_get_unaligned_be64(bp + 8);
+ }
+ if (k < num_zd) {
+ found = true;
+ break;
+ } else if ((slba > mx_lba) || (sg_fd < 0))
+ break;
+ } /* end of outer for loop */
+ if (res == 0) {
+ sgj_opaque_p jo2p = as_json ?
+ sgj_named_subobject_r(jsp, NULL, "find_condition") : NULL;
+
+ if (found) {
+ if (op->do_hex) {
+ hex2stdout(rzBuff, 64, -1);
+ printf("\n");
+ hex2stdout(bp, 64, -1);
+ } else {
+ sgj_pr_hr(jsp, "Condition met at:\n");
+ sgj_pr_hr(jsp, " %s: %d\n", zn_dnum_s, zn_dnum);
+ sgj_js_nv_b(jsp, jo2p, "met", true);
+ sgj_js_nv_i(jsp, jo2p, "zone_descriptor_index", zn_dnum);
+ prt_a_zn_desc(bp, op, jsp, jo2p);
+ }
+ } else {
+ if (op->do_hex) {
+ memset(b, 0xff, 64);
+ hex2stdout((const uint8_t *)b, 64, -1);
+ } else {
+ sgj_js_nv_b(jsp, jo2p, "met", false);
+ sgj_js_nv_i(jsp, jo2p, "zone_descriptor_index", zn_dnum);
+ if (num_rem < 1)
+ sgj_pr_hr(jsp, "Condition NOT met, checked %d zones; "
+ "next %s%u\n", op->do_num, zn_dnum_s, zn_dnum);
+ else
+ sgj_pr_hr(jsp, "Condition NOT met; next %s%u\n",
+ zn_dnum_s, zn_dnum);
+ }
+ }
+ }
+ return res;
+}
+
+struct statistics_t {
+ uint32_t zt_conv_num;
+ uint32_t zt_swr_num;
+ uint32_t zt_swp_num;
+ uint32_t zt_sob_num;
+ uint32_t zt_gap_num;
+ uint32_t zt_unk_num;
+
+ uint32_t zc_nwp_num;
+ uint32_t zc_mt_num;
+ uint32_t zc_iop_num;
+ uint32_t zc_eop_num;
+ uint32_t zc_cl_num;
+ uint32_t zc_ina_num;
+ uint32_t zc_ro_num;
+ uint32_t zc_full_num;
+ uint32_t zc_off_num;
+ uint32_t zc_unk_num;
+
+ /* The following LBAs have 1 added to them, initialized to 0 */
+ uint64_t zt_swr_1st_lba1;
+ uint64_t zt_swp_1st_lba1;
+ uint64_t zt_sob_1st_lba1;
+ uint64_t zt_gap_1st_lba1;
+
+ uint64_t zc_nwp_1st_lba1;
+ uint64_t zc_mt_1st_lba1;
+ uint64_t zc_iop_1st_lba1;
+ uint64_t zc_eop_1st_lba1;
+ uint64_t zc_cl_1st_lba1;
+ uint64_t zc_ina_1st_lba1;
+ uint64_t zc_ro_1st_lba1;
+ uint64_t zc_full_1st_lba1;
+ uint64_t zc_off_1st_lba1;
+
+ uint64_t wp_max_lba1; /* ... that isn't Zone start LBA */
+ uint64_t wp_blk_num; /* sum of (zwp - zs_lba) */
+ uint64_t conv_blk_num; /* sum of (z_blks) of zt=conv */
+};
+
+static int
+gather_statistics(int sg_fd, uint8_t * rzBuff, const char * cmd_name,
+ struct opts_t * op)
+{
+ uint8_t zt, zc;
+ int k, res, resid, rlen, num_zd, num_rem;
+ uint32_t zn_dnum = 0;
+ uint64_t slba = op->st_lba;
+ uint64_t mx_lba = 0;
+ uint64_t zs_lba, zwp, z_blks;
+ const uint8_t * bp = rzBuff;
+ struct statistics_t st SG_C_CPP_ZERO_INIT;
+ char b[96];
+
+ if (op->serv_act != REPORT_ZONES_SA) {
+ pr2serr("%s: do not support statistics for %s yet\n", __func__,
+ cmd_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ num_rem = op->do_num ? op->do_num : INT_MAX;
+ for ( ; num_rem > 0; num_rem -= num_zd) {
+ resid = 0;
+ zs_lba = slba;
+ if (sg_fd >= 0) {
+ res = sg_ll_report_zzz(sg_fd, REPORT_ZONES_SA, slba,
+ true /* set partial */, op->reporting_opt,
+ rzBuff, op->maxlen, &resid, true, op->vb);
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s: %s%u, %s command not supported\n", __func__,
+ zn_dnum_s, zn_dnum, cmd_name);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->vb);
+ pr2serr("%s: %s%u, %s command: %s\n", __func__,
+ zn_dnum_s, zn_dnum, cmd_name, b);
+ }
+ break;
+ }
+ } else
+ res = 0;
+ rlen = op->maxlen - resid;
+ if (rlen <= 64) {
+ break;
+ }
+ mx_lba = sg_get_unaligned_be64(rzBuff + 8);
+ num_zd = (rlen - 64) / REPORT_ZONES_DESC_LEN;
+ if (num_zd > num_rem)
+ num_zd = num_rem;
+ for (k = 0, bp = rzBuff + 64; k < num_zd;
+ ++k, bp += REPORT_ZONES_DESC_LEN, ++zn_dnum) {
+ z_blks = sg_get_unaligned_be64(bp + 8);
+ zs_lba = sg_get_unaligned_be64(bp + 16);
+ zwp = sg_get_unaligned_be64(bp + 24);
+ zt = 0xf & bp[0];
+ switch (zt) {
+ case 1: /* conventional */
+ ++st.zt_conv_num;
+ st.conv_blk_num += z_blks;
+ break;
+ case 2: /* sequential write required */
+ ++st.zt_swr_num;
+ if (0 == st.zt_swr_1st_lba1)
+ st.zt_swr_1st_lba1 = zs_lba + 1;
+ break;
+ case 3: /* sequential write preferred */
+ ++st.zt_swp_num;
+ if (0 == st.zt_swp_1st_lba1)
+ st.zt_swp_1st_lba1 = zs_lba + 1;
+ break;
+ case 4: /* sequential or before (write) */
+ ++st.zt_sob_num;
+ if (0 == st.zt_sob_1st_lba1)
+ st.zt_sob_1st_lba1 = zs_lba + 1;
+ break;
+ case 5: /* gap */
+ ++st.zt_gap_num;
+ if (0 == st.zt_gap_1st_lba1)
+ st.zt_gap_1st_lba1 = zs_lba + 1;
+ break;
+ default:
+ ++st.zt_unk_num;
+ break;
+ }
+ zc = (bp[1] >> 4) & 0xf;
+ switch (zc) {
+ case 0: /* not write pointer (zone) */
+ ++st.zc_nwp_num;
+ if (0 == st.zc_nwp_1st_lba1)
+ st.zc_nwp_1st_lba1 = zs_lba + 1;
+ break;
+ case 1: /* empty */
+ ++st.zc_mt_num;
+ if (0 == st.zc_mt_1st_lba1)
+ st.zc_mt_1st_lba1 = zs_lba + 1;
+ break;
+ case 2: /* implicitly opened */
+ ++st.zc_iop_num;
+ if (0 == st.zc_iop_1st_lba1)
+ st.zc_iop_1st_lba1 = zs_lba + 1;
+ if (zwp > zs_lba) {
+ st.wp_max_lba1 = zwp + 1;
+ st.wp_blk_num += zwp - zs_lba;
+ }
+ break;
+ case 3: /* explicitly opened */
+ ++st.zc_eop_num;
+ if (0 == st.zc_eop_1st_lba1)
+ st.zc_eop_1st_lba1 = zs_lba + 1;
+ if (zwp > zs_lba) {
+ st.wp_max_lba1 = zwp + 1;
+ st.wp_blk_num += zwp - zs_lba;
+ }
+ break;
+ case 4: /* closed */
+ ++st.zc_cl_num;
+ if (0 == st.zc_cl_1st_lba1)
+ st.zc_cl_1st_lba1 = zs_lba + 1;
+ if (zwp > zs_lba) {
+ st.wp_max_lba1 = zwp + 1;
+ st.wp_blk_num += zwp - zs_lba;
+ }
+ break;
+ case 5: /* inactive */
+ ++st.zc_ina_num;
+ if (0 == st.zc_ina_1st_lba1)
+ st.zc_ina_1st_lba1 = zs_lba + 1;
+ break;
+ case 0xd: /* read-only */
+ ++st.zc_ro_num;
+ if (0 == st.zc_ro_1st_lba1)
+ st.zc_ro_1st_lba1 = zs_lba + 1;
+ break;
+ case 0xe: /* full */
+ ++st.zc_full_num;
+ if (0 == st.zc_full_1st_lba1)
+ st.zc_full_1st_lba1 = zs_lba + 1;
+ st.wp_blk_num += z_blks;
+ break;
+ case 0xf: /* offline */
+ ++st.zc_off_num;
+ if (0 == st.zc_off_1st_lba1)
+ st.zc_off_1st_lba1 = zs_lba + 1;
+ break;
+ default:
+ ++st.zc_unk_num;
+ break;
+ }
+ slba = zs_lba + z_blks;
+ } /* end of inner for loop */
+ if ((slba > mx_lba) || (sg_fd < 0))
+ break;
+ } /* end of outer for loop */
+ printf("Number of conventional type zones: %u\n", st.zt_conv_num);
+ if (st.zt_swr_num > 0)
+ printf("Number of sequential write required type zones: %u\n",
+ st.zt_swr_num);
+ if (st.zt_swr_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zt_swr_1st_lba1 - 1);
+ if (st.zt_swp_num > 0)
+ printf("Number of sequential write preferred type zones: %u\n",
+ st.zt_swp_num);
+ if (st.zt_swp_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zt_swp_1st_lba1 - 1);
+ if (st.zt_sob_num > 0)
+ printf("Number of sequential or before type zones: %u\n",
+ st.zt_sob_num);
+ if (st.zt_sob_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zt_sob_1st_lba1 - 1);
+ if (st.zt_gap_num > 0)
+ printf("Number of gap type zones: %u\n", st.zt_gap_num);
+ if (st.zt_gap_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zt_gap_1st_lba1 - 1);
+ if (st.zt_unk_num > 0)
+ printf("Number of unknown type zones: %u\n", st.zt_unk_num);
+
+ printf("Number of 'not write pointer' condition zones: %u\n",
+ st.zc_nwp_num);
+ if (st.zc_nwp_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_nwp_1st_lba1 - 1);
+ printf("Number of empty condition zones: %u\n", st.zc_mt_num);
+ if (st.zc_mt_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_mt_1st_lba1 - 1);
+ if (st.zc_iop_num > 0)
+ printf("Number of implicitly open condition zones: %u\n",
+ st.zc_iop_num);
+ if (st.zc_iop_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_iop_1st_lba1 - 1);
+ if (st.zc_eop_num)
+ printf("Number of explicitly open condition zones: %u\n",
+ st.zc_eop_num);
+ if (st.zc_eop_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_eop_1st_lba1 - 1);
+ if (st.zc_cl_num)
+ printf("Number of closed condition zones: %u\n", st.zc_cl_num);
+ if (st.zc_cl_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_cl_1st_lba1 - 1);
+ if (st.zc_ina_num)
+ printf("Number of inactive condition zones: %u\n", st.zc_ina_num);
+ if (st.zc_ina_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_ina_1st_lba1 - 1);
+ if (st.zc_ro_num)
+ printf("Number of inactive condition zones: %u\n", st.zc_ro_num);
+ if (st.zc_ro_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_ro_1st_lba1 - 1);
+ if (st.zc_full_num)
+ printf("Number of full condition zones: %u\n", st.zc_full_num);
+ if (st.zc_full_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_full_1st_lba1 - 1);
+ if (st.zc_off_num)
+ printf("Number of offline condition zones: %u\n", st.zc_off_num);
+ if (st.zc_off_1st_lba1 > 0)
+ printf(" Lowest starting LBA: 0x%" PRIx64 "\n",
+ st.zc_off_1st_lba1 - 1);
+ if (st.zc_unk_num > 0)
+ printf("Number of unknown condition zones: %u\n", st.zc_unk_num);
+
+ if (st.wp_max_lba1 > 0)
+ printf("Highest active write pointer LBA: 0x%" PRIx64 "\n",
+ st.wp_max_lba1 - 1);
+ printf("Number of used blocks in write pointer zones: 0x%" PRIx64 "\n",
+ st.wp_blk_num);
+
+ if ((sg_fd >= 0) && (op->maxlen >= RCAP16_REPLY_LEN) &&
+ ((st.wp_blk_num > 0) || (st.conv_blk_num > 0))) {
+ uint32_t block_size = 0;
+ uint64_t total_sz;
+ double sz_mb, sz_gb;
+
+ res = sg_ll_readcap_16(sg_fd, false, 0, rzBuff,
+ RCAP16_REPLY_LEN, true, op->vb);
+ if (SG_LIB_CAT_INVALID_OP == res) {
+ pr2serr("READ CAPACITY (16) cdb not supported\n");
+ } else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("bad field in READ CAPACITY (16) cdb including "
+ "unsupported service action\n");
+ else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->vb);
+ pr2serr("READ CAPACITY (16) failed: %s\n", b);
+ } else
+ block_size = sg_get_unaligned_be32(rzBuff + 8);
+
+ if (st.wp_blk_num) {
+ total_sz = st.wp_blk_num * block_size;
+ sz_mb = (double)(total_sz) / (double)(1048576);
+ sz_gb = (double)(total_sz) / (double)(1000000000L);
+#ifdef SG_LIB_MINGW
+ printf(" associated size: %" PRIu64 " bytes, %g MiB, %g GB",
+ total_sz, sz_mb, sz_gb);
+#else
+ printf(" associated size: %" PRIu64 " bytes, %.1f MiB, %.2f "
+ "GB", total_sz, sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ if (st.conv_blk_num) {
+ total_sz = st.conv_blk_num * block_size;
+ sz_mb = (double)(total_sz) / (double)(1048576);
+ sz_gb = (double)(total_sz) / (double)(1000000000L);
+ printf("Size of all conventional zones: ");
+#ifdef SG_LIB_MINGW
+ printf("%" PRIu64 " bytes, %g MiB, %g GB", total_sz, sz_mb,
+ sz_gb);
+#else
+ printf("%" PRIu64 " bytes, %.1f MiB, %.2f GB", total_sz,
+ sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool no_final_msg = false;
+ bool as_json;
+ int res, c, act_len, rlen, in_len, off;
+ int sg_fd = -1;
+ int resid = 0;
+ int ret = 0;
+ uint32_t decod_len;
+ int64_t ll;
+ const char * device_name = NULL;
+ uint8_t * rzBuff = NULL;
+ uint8_t * free_rzbp = NULL;
+ const char * cmd_name = "Report zones";
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ char b[80];
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op = &opts;
+
+ op->serv_act = REPORT_ZONES_SA;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bdefF:hHi:j::l:m:n:o:prRs:SvVw",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ op->do_brief = true;
+ break;
+ case 'd':
+ op->do_zdomains = true;
+ op->serv_act = REPORT_ZONE_DOMAINS_SA;
+ break;
+ case 'e':
+ op->do_realms = true;
+ op->serv_act = REPORT_REALMS_SA;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'F':
+ off = (('-' == *optarg) || ('!' == *optarg)) ? 1 : 0;
+ if (isdigit(*(optarg + off))) {
+ op->find_zt = sg_get_num_nomult(optarg + off);
+ if (op->find_zt < 0) {
+ pr2serr("bad numeric argument to '--find='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (off)
+ op->find_zt = -op->find_zt; /* find first not equal */
+ } else { /* check for abbreviation */
+ struct zt_num2abbrev_t * zn2ap = zt_num2abbrev;
+
+ for ( ; zn2ap->abbrev; ++zn2ap) {
+ if (0 == strcmp(optarg + off, zn2ap->abbrev))
+ break;
+ }
+ if (NULL == zn2ap->abbrev) {
+ pr2serr("bad abbreviation argument to '--find='\n\n");
+ prn_zone_type_abbrevs();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->find_zt = off ? -zn2ap->ztn : zn2ap->ztn;
+ }
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->in_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ /* case 'l': is under case 's': */
+ case 'm':
+ op->maxlen = sg_get_num(optarg);
+ if ((op->maxlen < 0) || (op->maxlen > MAX_RZONES_BUFF_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or "
+ "less\n", MAX_RZONES_BUFF_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->maxlen_given = true;
+ break;
+ case 'n':
+ op->do_num = sg_get_num(optarg);
+ if (op->do_num < 0) {
+ pr2serr("argument to '--num' should be zero or more\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'o':
+ op->reporting_opt = sg_get_num_nomult(optarg);
+ if ((op->reporting_opt < 0) || (op->reporting_opt > 63)) {
+ pr2serr("bad argument to '--report=OPT', expect 0 to "
+ "63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ op->do_partial = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 's':
+ case 'l': /* --locator= and --start= are interchangeable */
+ if ((2 == strlen(optarg)) && (0 == memcmp("-1", optarg, 2))) {
+ op->st_lba = UINT64_MAX;
+ break;
+ }
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--start=LBA' or '--locator=LBA\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->st_lba = (uint64_t)ll;
+ break;
+ case 'S':
+ op->statistics = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->vb;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->wp_only = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->vb = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->vb = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->vb);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (op->do_help) {
+ usage(op->do_help);
+ return 0;
+ }
+ as_json = op->json_st.pr_as_json;
+ jsp = &op->json_st;
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (op->do_zdomains && op->do_realms) {
+ pr2serr("Can't have both --domain and --realm\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (op->do_zdomains)
+ cmd_name = "Report zone domains";
+ else if (op->do_realms)
+ cmd_name = "Report realms";
+ if (as_json)
+ sgj_js_nv_s(jsp, jop, "scsi_command_name", cmd_name);
+ if ((op->serv_act != REPORT_ZONES_SA) && op->do_partial) {
+ pr2serr("Can only use --partial with REPORT ZONES\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (device_name && op->in_fn) {
+ pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
+ "not both\n");
+ device_name = NULL;
+ }
+ if (0 == op->maxlen)
+ op->maxlen = DEF_RZONES_BUFF_LEN;
+ rzBuff = (uint8_t *)sg_memalign(op->maxlen, 0, &free_rzbp, op->vb > 3);
+ if (NULL == rzBuff) {
+ pr2serr("unable to sg_memalign %d bytes\n", op->maxlen);
+ return sg_convert_errno(ENOMEM);
+ }
+
+ if (NULL == device_name) {
+ if (op->in_fn) {
+ if ((ret = sg_f2hex_arr(op->in_fn, op->do_raw, false, rzBuff,
+ &in_len, op->maxlen))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
+ no_final_msg = true;
+ pr2serr("... decode what we have, --maxlen=%d needs to "
+ "be increased\n", op->maxlen);
+ } else
+ goto the_end;
+ }
+ if (op->vb > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (op->do_raw)
+ op->do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--inhex=%s only decoded %d bytes (needs 4 at "
+ "least)\n", op->in_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto the_end;
+ }
+ res = 0;
+ if (op->find_zt) { /* so '-F none' will drop through */
+ op->maxlen = in_len;
+ ret = find_report_zones(sg_fd, rzBuff, cmd_name, op, jsp);
+ goto the_end;
+ } else if (op->statistics) {
+ op->maxlen = in_len;
+ ret = gather_statistics(sg_fd, rzBuff, cmd_name, op);
+ goto the_end;
+ }
+ goto start_response;
+ } else {
+ pr2serr("missing device name!\n\n");
+ usage(1);
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto the_end;
+ }
+ }
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto the_end;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, op->o_readonly, op->vb);
+ if (sg_fd < 0) {
+ if (op->vb)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto the_end;
+ }
+
+ if (op->find_zt) { /* so '-F none' will drop through */
+ ret = find_report_zones(sg_fd, rzBuff, cmd_name, op, jsp);
+ goto the_end;
+ } else if (op->statistics) {
+ ret = gather_statistics(sg_fd, rzBuff, cmd_name, op);
+ goto the_end;
+ }
+ res = sg_ll_report_zzz(sg_fd, op->serv_act, op->st_lba, op->do_partial,
+ op->reporting_opt, rzBuff, op->maxlen, &resid,
+ true, op->vb);
+ ret = res;
+start_response:
+ if (0 == res) {
+ rlen = op->in_fn ? in_len : (op->maxlen - resid);
+ if (rlen < 4) {
+ pr2serr("Decoded response length (%d) too short\n", rlen);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ decod_len = sg_get_unaligned_be32(rzBuff + 0) + 64;
+ if (decod_len > WILD_RZONES_BUFF_LEN) {
+ if (! op->do_force) {
+ pr2serr("decode length [%u bytes] seems wild, use --force "
+ "override\n", decod_len);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ }
+ if (decod_len > (uint32_t)rlen) {
+ if ((REPORT_ZONES_SA == op->serv_act) && (! op->do_partial)) {
+ pr2serr("%u zones starting from LBA 0x%" PRIx64 " available "
+ "but only %d zones returned\n",
+ (decod_len - 64) / REPORT_ZONES_DESC_LEN, op->st_lba,
+ (rlen - 64) / REPORT_ZONES_DESC_LEN);
+ decod_len = rlen;
+ act_len = rlen;
+ } else {
+ pr2serr("decoded response length is %u bytes, but system "
+ "reports %d bytes received??\n", decod_len, rlen);
+ if (op->do_force)
+ act_len = rlen;
+ else {
+ pr2serr("Exiting, use --force to override\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ }
+ } else
+ act_len = decod_len;
+ if (op->do_raw) {
+ dStrRaw(rzBuff, act_len);
+ goto the_end;
+ }
+ if (op->do_hex && (2 != op->do_hex)) {
+ hex2stdout(rzBuff, act_len, ((1 == op->do_hex) ? 1 : -1));
+ goto the_end;
+ }
+ if (! op->wp_only && (! op->do_hex))
+ sgj_pr_hr(jsp, "%s response:\n", cmd_name);
+
+ if (act_len < 64) {
+ pr2serr("Zone length [%d] too short (perhaps after truncation\n)",
+ act_len);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ if (REPORT_ZONES_SA == op->serv_act)
+ ret = decode_rep_zones(rzBuff, act_len, decod_len, op, jsp);
+ else if (op->do_realms)
+ ret = decode_rep_realms(rzBuff, act_len, op, jsp);
+ else if (op->do_zdomains)
+ ret = decode_rep_zdomains(rzBuff, act_len, op, jsp);
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->vb);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+
+the_end:
+ if (free_rzbp)
+ free(free_rzbp);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if ((0 == op->vb && (! no_final_msg))) {
+ if (! sg_if_can2stderr("sg_rep_zones failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
diff --git a/src/sg_requests.c b/src/sg_requests.c
new file mode 100644
index 00000000..2779cbd0
--- /dev/null
+++ b/src/sg_requests.c
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2004-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pr2serr.h"
+#include "sg_pt.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI command REQUEST SENSE to the given SCSI device.
+ */
+
+static const char * version_str = "1.40 20220607";
+
+#define MAX_REQS_RESP_LEN 255
+#define DEF_REQS_RESP_LEN 252
+
+#define SENSE_BUFF_LEN 96 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+#define REQUEST_SENSE_CMD 0x3
+#define REQUEST_SENSE_CMDLEN 6
+
+#define ME "sg_requests: "
+
+
+static struct option long_options[] = {
+ {"desc", no_argument, 0, 'd'},
+ {"error", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"num", required_argument, 0, 'n'},
+ {"number", required_argument, 0, 'n'},
+ {"progress", no_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"status", no_argument, 0, 's'},
+ {"time", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_requests [--desc] [--error] [--help] [--hex] "
+ "[--maxlen=LEN]\n"
+ " [--num=NUM] [--number=NUM] [--progress] "
+ "[--raw]\n"
+ " [--status] [--time] [--verbose] "
+ "[--version] DEVICE\n"
+ " where:\n"
+ " --desc|-d set flag for descriptor sense "
+ "format\n"
+ " --error|-e change opcode to 0xff; to measure "
+ "overhead\n"
+ " twice: skip ioctl call\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H output in hexadecimal\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> 252 bytes)\n"
+ " --num=NUM|-n NUM number of REQUEST SENSE commands "
+ "to send (def: 1)\n"
+ " --number=NUM same action as '--num=NUM'\n"
+ " --progress|-p output a progress indication (percentage) "
+ "if available\n"
+ " --raw|-r output in binary (to stdout)\n"
+ " --status|-s set exit status from parameter data "
+ "(def: only set\n"
+ " exit status from autosense)\n"
+ " --time|-t time the transfer, calculate commands "
+ "per second\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REQUEST SENSE command\n"
+ );
+
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+int
+main(int argc, char * argv[])
+{
+ int c, n, k, progress, rs, sense_cat, act_din_len;
+ int do_error = 0;
+ int err = 0;
+ int num_errs = 0;
+ int num_din_errs = 0;
+ int most_recent_skey = 0;
+ int sg_fd = -1;
+ int res = 0;
+ uint8_t rsBuff[MAX_REQS_RESP_LEN + 1];
+ bool desc = false;
+ bool do_progress = false;
+ bool do_raw = false;
+ bool do_status = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool not_raw_hex;
+ int num_rs = 1;
+ int do_hex = 0;
+ int maxlen = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ int ret = 0;
+ struct sg_pt_base * ptvp = NULL;
+ char b[256];
+ uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] =
+ {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+#ifndef SG_LIB_MINGW
+ bool do_time = false;
+ struct timeval start_tm, end_tm;
+#endif
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "dehHm:n:prstvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ desc = true;
+ break;
+ case 'e':
+ ++do_error;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if ((maxlen < 0) || (maxlen > MAX_REQS_RESP_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MAX_REQS_RESP_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'n':
+ num_rs = sg_get_num(optarg);
+ if (num_rs < 1) {
+ pr2serr("bad argument to '--num'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ do_progress = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 's':
+ do_status = true;
+ break;
+ case 't':
+#ifndef SG_LIB_MINGW
+ do_time = true;
+#endif
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (0 == maxlen)
+ maxlen = DEF_REQS_RESP_LEN;
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (do_raw || do_hex) {
+ not_raw_hex = false;
+#ifdef SG_LIB_MINGW
+ bool prog_time = do_progress;
+#else
+ bool prog_time = do_progress || do_time;
+#endif
+
+ if (prog_time) {
+ pr2serr("With either --raw or --hex, --progress and --time "
+ "contradict\n");
+ ret = SG_LIB_CONTRADICT;
+ goto finish;
+ }
+ } else
+ not_raw_hex = true;
+
+ sg_fd = sg_cmds_open_device(device_name, true /* ro */, verbose);
+ if (sg_fd < 0) {
+ if (not_raw_hex && verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto finish;
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if ((NULL == ptvp) || ((err = get_scsi_pt_os_err(ptvp)))) {
+ if (not_raw_hex)
+ pr2serr("%s: unable to construct pt object\n", __func__);
+ ret = sg_convert_errno(err ? err : ENOMEM);
+ goto finish;
+ }
+ if (do_error)
+ rs_cdb[0] = 0xff;
+ if (desc)
+ rs_cdb[1] |= 0x1;
+ rs_cdb[4] = maxlen;
+ if (do_progress) {
+ for (k = 0; k < num_rs; ++k) {
+ act_din_len = 0;
+ if (k > 0)
+ sg_sleep_secs(30);
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ memset(rsBuff, 0x0, sizeof(rsBuff));
+ set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff));
+ set_scsi_pt_packet_id(ptvp, k + 1);
+ if (do_error > 1) {
+ ++num_errs;
+ n = 0;
+ } else {
+ if (verbose && (0 == k)) {
+ char bb[128];
+
+ pr2serr(" cdb: %s\n",
+ sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN,
+ true, sizeof(bb), bb));
+ }
+ rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k),
+ verbose, &sense_cat);
+ }
+ if (-1 == n) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ goto finish;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++num_errs;
+ if (1 == num_rs) {
+ ret = sense_cat;
+ printf("device not ready\n");
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++num_errs;
+ if (verbose) {
+ pr2serr("Ignoring Unit attention (sense key)\n");
+ }
+ break;
+ default:
+ ++num_errs;
+ if (1 == num_rs) {
+ ret = sense_cat;
+ sg_get_category_sense_str(sense_cat, sizeof(b), b,
+ verbose);
+ printf("%s\n", b);
+ break; // return k;
+ }
+ break;
+ }
+ }
+ if (n >= 0)
+ act_din_len = n;
+ if (ret)
+ goto finish;
+
+ if (verbose > 1) {
+ pr2serr("Parameter data in hex\n");
+ hex2stderr(rsBuff, act_din_len, 1);
+ }
+ progress = -1;
+ sg_get_sense_progress_fld(rsBuff, act_din_len, &progress);
+ if (progress < 0) {
+ ret = res;
+ if (verbose > 1)
+ pr2serr("No progress indication found, iteration %d\n",
+ k + 1);
+ /* N.B. exits first time there isn't a progress indication */
+ break;
+ } else
+ printf("Progress indication: %d.%02d%% done\n",
+ (progress * 100) / 65536,
+ ((progress * 100) % 65536) / 656);
+ partial_clear_scsi_pt_obj(ptvp);
+ } /* >>>>> end of for(num_rs) loop */
+ goto finish;
+ }
+
+#ifndef SG_LIB_MINGW
+ if (not_raw_hex && do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+#endif
+
+ rsBuff[0] = '\0';
+ rsBuff[7] = '\0';
+ for (k = 0; k < num_rs; ++k) {
+ act_din_len = 0;
+ ret = 0;
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ memset(rsBuff, 0x0, sizeof(rsBuff));
+ set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff));
+ set_scsi_pt_packet_id(ptvp, k + 1);
+ if (do_error > 1) {
+ ++num_errs;
+ n = 0;
+ } else {
+ if (verbose && (0 == k)) {
+ char bb[128];
+
+ pr2serr(" cdb: %s\n",
+ sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN,
+ true, sizeof(bb), bb));
+ }
+ rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k),
+ verbose, &sense_cat);
+ }
+ if (-1 == n) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ goto finish;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++num_errs;
+ if (1 == num_rs) {
+ ret = sense_cat;
+ printf("device not ready\n");
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++num_errs;
+ if (verbose) {
+ pr2serr("Ignoring Unit attention (sense key)\n");
+ }
+ break;
+ default:
+ ++num_errs;
+ if (1 == num_rs) {
+ ret = sense_cat;
+ sg_get_category_sense_str(sense_cat, sizeof(b), b,
+ verbose);
+ printf("%s\n", b);
+ break; // return k;
+ }
+ break;
+ }
+ }
+ if (n >= 0)
+ act_din_len = n;
+
+ if (act_din_len > 7) {
+ struct sg_scsi_sense_hdr ssh;
+
+ if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) {
+ if (ssh.sense_key > 0) {
+ ++num_din_errs;
+ most_recent_skey = ssh.sense_key;
+ }
+ if (not_raw_hex && ((1 == num_rs) || verbose)) {
+ char bb[144];
+
+ sg_get_sense_str(NULL, rsBuff, act_din_len,
+ false, sizeof(bb), bb);
+ pr2serr("data-in decoded as sense:\n%s\n", bb);
+ }
+ }
+ }
+ partial_clear_scsi_pt_obj(ptvp);
+ if (ret)
+ goto finish;
+
+ if (act_din_len > 0) {
+ if (do_raw)
+ dStrRaw(rsBuff, act_din_len);
+ else if (do_hex)
+ hex2stdout(rsBuff, act_din_len, 1);
+ }
+ } /* <<<<< end of for(num_rs) loop */
+ if ((0 == ret) && do_status) {
+ ret = sg_err_category_sense(rsBuff, act_din_len);
+ if (SG_LIB_CAT_NO_SENSE == ret) {
+ struct sg_scsi_sense_hdr ssh;
+
+ if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) {
+ if ((0 == ssh.asc) && (0 == ssh.ascq))
+ ret = 0;
+ }
+ }
+ }
+#ifndef SG_LIB_MINGW
+ if (not_raw_hex && do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
+ struct timeval res_tm;
+ double den, num;
+
+ 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;
+ }
+ den = res_tm.tv_sec;
+ den += (0.000001 * res_tm.tv_usec);
+ num = (double)num_rs;
+ printf("time to perform commands was %d.%06d secs",
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if (den > 0.00001)
+ printf("; %.2f operations/sec\n", num / den);
+ else
+ printf("\n");
+ }
+#endif
+
+finish:
+ if (not_raw_hex) {
+ if (num_errs > 0)
+ printf("Number of command errors detected: %d\n", num_errs);
+ if (num_din_errs > 0)
+ printf("Number of data-in errors detected: %d, most recent "
+ "sense_key=%d\n", num_din_errs, most_recent_skey);
+ }
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ if (not_raw_hex)
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (not_raw_hex && (0 == verbose)) {
+ if (! sg_if_can2stderr("sg_requests failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_reset.c b/src/sg_reset.c
new file mode 100644
index 00000000..739a7f2d
--- /dev/null
+++ b/src/sg_reset.c
@@ -0,0 +1,314 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 1999-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program send either device, bus or host resets to device,
+ * or bus or host associated with the given sg device. This is a Linux
+ * only utility (perhaps Android as well).
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_io_linux.h"
+
+
+#define ME "sg_reset: "
+
+static const char * version_str = "0.68 20220308";
+
+#ifndef SG_SCSI_RESET
+#define SG_SCSI_RESET 0x2284
+#endif
+
+#ifndef SG_SCSI_RESET_NOTHING
+#define SG_SCSI_RESET_NOTHING 0
+#define SG_SCSI_RESET_DEVICE 1
+#define SG_SCSI_RESET_BUS 2
+#define SG_SCSI_RESET_HOST 3
+#endif
+
+#ifndef SG_SCSI_RESET_TARGET
+#define SG_SCSI_RESET_TARGET 4
+#endif
+
+#ifndef SG_SCSI_RESET_NO_ESCALATE
+#define SG_SCSI_RESET_NO_ESCALATE 0x100
+#endif
+
+static struct option long_options[] = {
+ {"bus", no_argument, 0, 'b'},
+ {"device", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'z'},
+ {"host", no_argument, 0, 'H'},
+ {"no-esc", no_argument, 0, 'N'},
+ {"no_esc", no_argument, 0, 'N'},
+ {"no-escalate", no_argument, 0, 'N'},
+ {"target", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+#if defined(__GNUC__) || defined(__clang__)
+static int pr2serr(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#else
+static int pr2serr(const char * fmt, ...);
+#endif
+
+
+static int
+pr2serr(const char * fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+static void
+usage(int compat_mode)
+{
+ pr2serr("Usage: sg_reset [--bus] [--device] [--help] [--host] [--no-esc] "
+ "[--no-escalate] [--target]\n"
+ " [--verbose] [--version] DEVICE\n"
+ " where:\n"
+ " --bus|-b SCSI bus reset (SPI concept), might be all "
+ "targets\n"
+ " --device|-d device (logical unit) reset\n");
+ if (compat_mode) {
+ pr2serr(" --help|-z print usage information then exit\n"
+ " --host|-h|-H host (bus adapter: HBA) reset\n");
+ } else {
+ pr2serr(" --help|-h print usage information then exit\n"
+ " --host|-H host (bus adapter: HBA) reset\n");
+ }
+ pr2serr(" --no-esc|-N overrides default action and only does "
+ "reset requested\n"
+ " --no-escalate The same as --no-esc|-N\n"
+ " --target|-t target reset. The target holds the DEVICE "
+ "and perhaps\n"
+ " other LUs\n"
+ " --verbose|-v increase the level of verbosity\n"
+ " --version|-V print version number then exit\n\n"
+ "Use SG_SCSI_RESET ioctl to send a reset to the "
+ "host/bus/target/device\nalong the DEVICE path. The DEVICE "
+ "itself is known as a logical unit (LU)\nin SCSI terminology.\n"
+ "Be warned: if the '-N' option is not given then if '-d' "
+ "fails then a\ntarget reset ('-t') is instigated. And it "
+ "'-t' fails then a bus reset\n('-b') is instigated. And if "
+ "'-b' fails then a host reset ('h') is\ninstigated. It is "
+ "recommended to use '-N' to stop the reset escalation.\n"
+ );
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool do_device_reset = false;
+ bool do_bus_reset = false;
+ bool do_host_reset = false;
+ bool no_escalate = false;
+ bool do_target_reset = false;
+ int c, sg_fd, res, k, hold_errno;
+ int verbose = 0;
+ char * device_name = NULL;
+ char * cp = NULL;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (NULL == cp)
+ cp = getenv("SG_RESET_OLD_OPTS");
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bdhHNtvVz", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ do_bus_reset = true;
+ break;
+ case 'd':
+ do_device_reset = true;
+ break;
+ case 'h':
+ if (cp) {
+ do_host_reset = true;
+ break;
+ } else {
+ usage(!!cp);
+ return 0;
+ }
+ case 'H':
+ do_host_reset = true;
+ break;
+ case 'N':
+ no_escalate = true;
+ break;
+ case 't':
+ do_target_reset = true;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ case 'z':
+ usage(!!cp);
+ return 0;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage(!!cp);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage(!!cp);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (NULL == device_name) {
+ pr2serr("Missing DEVICE name. Use '--help' to see usage.\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (cp && (0 == verbose))
+ ++verbose; // older behaviour was more verbose
+
+ if (((int)do_device_reset + (int)do_target_reset + (int)do_bus_reset +
+ (int)do_host_reset) > 1) {
+ pr2serr("Can only request one type of reset per invocation\n");
+ return 1;
+ }
+
+ sg_fd = open(device_name, O_RDWR | O_NONBLOCK);
+ if (sg_fd < 0) {
+ pr2serr(ME "open error: %s: ", device_name);
+ perror("");
+ return 1;
+ }
+
+ k = SG_SCSI_RESET_NOTHING;
+ if (do_device_reset) {
+ if (verbose)
+ printf(ME "starting device reset\n");
+ k = SG_SCSI_RESET_DEVICE;
+ }
+ else if (do_target_reset) {
+ if (verbose)
+ printf(ME "starting target reset\n");
+ k = SG_SCSI_RESET_TARGET;
+ }
+ else if (do_bus_reset) {
+ if (verbose)
+ printf(ME "starting bus reset\n");
+ k = SG_SCSI_RESET_BUS;
+ }
+ else if (do_host_reset) {
+ if (verbose)
+ printf(ME "starting host reset\n");
+ k = SG_SCSI_RESET_HOST;
+ }
+ if (no_escalate)
+ k += SG_SCSI_RESET_NO_ESCALATE;
+ if (verbose > 2)
+ pr2serr(" third argument to ioctl(SG_SCSI_RESET) is 0x%x\n", k);
+
+ res = ioctl(sg_fd, SG_SCSI_RESET, &k);
+ if (res < 0) {
+ hold_errno = errno;
+ switch (errno) {
+ case EBUSY:
+ pr2serr(ME "BUSY, may be resetting now\n");
+ break;
+ case ENODEV:
+ pr2serr(ME "'no device' error, may be temporary while device is "
+ "resetting\n");
+ break;
+ case EAGAIN:
+ pr2serr(ME "try again later, may be resetting now\n");
+ break;
+ case EIO:
+ pr2serr(ME "reset (for value=0x%x) may not be available\n", k);
+ break;
+ case EPERM:
+ case EACCES:
+ pr2serr(ME "reset requires CAP_SYS_ADMIN (root) permission\n");
+ break;
+ case EINVAL:
+ pr2serr(ME "SG_SCSI_RESET not supported (for value=0x%x)\n", k);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ default:
+ perror(ME "SG_SCSI_RESET failed");
+ break;
+ }
+ if (verbose > 1)
+ pr2serr(ME "ioctl(SG_SCSI_RESET) returned %d, errno=%d\n", res,
+ hold_errno);
+ close(sg_fd);
+ return 1;
+ }
+
+ if (no_escalate)
+ k -= SG_SCSI_RESET_NO_ESCALATE;
+ if (verbose) {
+ if (SG_SCSI_RESET_NOTHING == k)
+ printf(ME "did nothing, device is normal mode\n");
+ else if (SG_SCSI_RESET_DEVICE == k)
+ printf(ME "completed device %sreset\n", (no_escalate ?
+ "" : "(or target or bus or host) "));
+ else if (SG_SCSI_RESET_TARGET == k)
+ printf(ME "completed target %sreset\n", (no_escalate ?
+ "" : "(or bus or host) "));
+ else if (SG_SCSI_RESET_BUS == k)
+ printf(ME "completed bus %sreset\n", (no_escalate ?
+ "" : "(or host) "));
+ else if (SG_SCSI_RESET_HOST == k)
+ printf(ME "completed host reset\n");
+ }
+
+ if (close(sg_fd) < 0) {
+ perror(ME "close error");
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/sg_reset_wp.c b/src/sg_reset_wp.c
new file mode 100644
index 00000000..1580c843
--- /dev/null
+++ b/src/sg_reset_wp.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2014-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI RESET WRITE POINTER command to the given SCSI
+ * device. Based on zbc-r04c.pdf .
+ */
+
+static const char * version_str = "1.16 20211114";
+
+#define SG_ZONING_OUT_CMDLEN 16
+#define RESET_WRITE_POINTER_SA 0x4
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"all", no_argument, 0, 'a'},
+ {"count", required_argument, 0, 'C'},
+ {"help", no_argument, 0, 'h'},
+ {"reset-all", no_argument, 0, 'R'},
+ {"reset_all", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"zone", required_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_reset_wp [--all] [--count=ZC] [--help] [--verbose]\n"
+ " [--version] [--zone=ID] DEVICE\n");
+ pr2serr(" where:\n"
+ " --all|-a sets the ALL flag in the cdb\n"
+ " --count=ZC|-C ZC set zone count field (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --zone=ID|-z ID ID is the starting LBA of the zone "
+ "whose\n"
+ " write pointer is to be reset\n\n"
+ "Performs a SCSI RESET WRITE POINTER command. ID is decimal by "
+ "default,\nfor hex use a leading '0x' or a trailing 'h'. "
+ "Either the --zone=ID\nor --all option needs to be given.\n");
+}
+
+/* Invokes a SCSI RESET WRITE POINTER command (ZBC). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_reset_write_pointer(int sg_fd, uint64_t zid, uint16_t zc, bool all,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t rwp_cdb[SG_ZONING_OUT_CMDLEN] = {SG_ZONING_OUT,
+ RESET_WRITE_POINTER_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be64(zid, rwp_cdb + 2);
+ sg_put_unaligned_be16(zc, rwp_cdb + 12);
+ if (all)
+ rwp_cdb[14] = 0x1;
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" Reset write pointer cdb: %s\n",
+ sg_get_command_str(rwp_cdb, SG_ZONING_OUT_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Reset write pointer: out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rwp_cdb, sizeof(rwp_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, "reset write pointer", res, noisy,
+ verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool all = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool zid_given = false;
+ int res, c, n;
+ int sg_fd = -1;
+ int ret = 0;
+ int verbose = 0;
+ uint16_t zc = 0;
+ uint64_t zid = 0;
+ int64_t ll;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aC:hRvVz:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ case 'R':
+ all = true;
+ break;
+ case 'C':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("--count= expects an argument between 0 and 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ zc = (uint16_t)n;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'z':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--zone=ID'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ zid = (uint64_t)ll;
+ zid_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if ((! zid_given) && (! all)) {
+ pr2serr("either the --zone=ID or --all option is required\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ int err = -sg_fd;
+
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+
+ res = sg_ll_reset_write_pointer(sg_fd, zid, zc, all, true, verbose);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Reset write pointer command not supported\n");
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Reset write pointer command: %s\n", b);
+ }
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_reset_wp failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rmsn.c b/src/sg_rmsn.c
new file mode 100644
index 00000000..c413ea43
--- /dev/null
+++ b/src/sg_rmsn.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2005-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program was originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI command READ MEDIA SERIAL NUMBER
+ * to the given SCSI device.
+ */
+
+static const char * version_str = "1.18 20180628";
+
+#define SERIAL_NUM_SANITY_LEN (16 * 1024)
+
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void usage()
+{
+ pr2serr("Usage: sg_rmsn [--help] [--raw] [--readonly] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --help|-h print out usage message\n"
+ " --raw|-r output serial number to stdout "
+ "(potentially binary)\n"
+ " --readonly|-R open DEVICE read-only (def: open it "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI READ MEDIA SERIAL NUMBER command\n");
+}
+
+int main(int argc, char * argv[])
+{
+ bool raw = false;
+ bool readonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, sn_len, n;
+ int sg_fd = -1;
+ int ret = 0;
+ int verbose = 0;
+ uint8_t rmsn_buff[4];
+ uint8_t * bp = NULL;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hrRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'r':
+ raw = true;
+ break;
+ case 'R':
+ readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ memset(rmsn_buff, 0x0, sizeof(rmsn_buff));
+
+ res = sg_ll_read_media_serial_num(sg_fd, rmsn_buff, sizeof(rmsn_buff),
+ true, verbose);
+ ret = res;
+ if (0 == res) {
+ sn_len = sg_get_unaligned_be32(rmsn_buff + 0);
+ if (! raw)
+ printf("Reported serial number length = %d\n", sn_len);
+ if (0 == sn_len) {
+ pr2serr(" This implies the media has no serial number\n");
+ goto err_out;
+ }
+ if (sn_len > SERIAL_NUM_SANITY_LEN) {
+ pr2serr(" That length (%d) seems too long for a serial "
+ "number\n", sn_len);
+ goto err_out;
+ }
+ sn_len += 4;
+ bp = (uint8_t *)malloc(sn_len);
+ if (NULL == bp) {
+ pr2serr(" Out of memory (ram)\n");
+ goto err_out;
+ }
+ res = sg_ll_read_media_serial_num(sg_fd, bp, sn_len, true, verbose);
+ if (0 == res) {
+ sn_len = sg_get_unaligned_be32(bp + 0);
+ if (raw) {
+ if (sn_len > 0) {
+ n = fwrite(bp + 4, 1, sn_len, stdout);
+ if (n) { ; } /* unused, dummy to suppress warning */
+ }
+ } else {
+ printf("Serial number:\n");
+ if (sn_len > 0)
+ hex2stdout(bp + 4, sn_len, 0);
+ }
+ }
+ }
+ if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Read Media Serial Number: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' for more information\n");
+ }
+
+err_out:
+ if (bp)
+ free(bp);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_rmsn failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rtpg.c b/src/sg_rtpg.c
new file mode 100644
index 00000000..d83bf363
--- /dev/null
+++ b/src/sg_rtpg.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2004-2018 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI command REPORT TARGET PORT GROUPS
+ * to the given SCSI device.
+ */
+
+static const char * version_str = "1.27 20180628";
+
+#define REPORT_TGT_GRP_BUFF_LEN 1024
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_LB_DEPENDENT 0x4
+#define TPGS_STATE_OFFLINE 0xe /* SPC-4 rev 9 */
+#define TPGS_STATE_TRANSITIONING 0xf
+
+#define STATUS_CODE_NOSTATUS 0x0
+#define STATUS_CODE_CHANGED_BY_SET 0x1
+#define STATUS_CODE_CHANGED_BY_IMPLICIT 0x2
+
+static struct option long_options[] = {
+ {"decode", no_argument, 0, 'd'},
+ {"extended", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_rtpg [--decode] [--extended] [--help] [--hex] "
+ "[--raw] [--readonly]\n"
+ " [--verbose] [--version] 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"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI REPORT TARGET PORT GROUPS command\n");
+
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static void
+decode_status(const int st)
+{
+ switch (st) {
+ case STATUS_CODE_NOSTATUS:
+ printf(" (no status available)");
+ break;
+ case STATUS_CODE_CHANGED_BY_SET:
+ printf(" (target port asym. state changed by SET TARGET PORT "
+ "GROUPS command)");
+ break;
+ case STATUS_CODE_CHANGED_BY_IMPLICIT:
+ printf(" (target port asym. state changed by implicit lu "
+ "behaviour)");
+ break;
+ default:
+ printf(" (unknown status code)");
+ break;
+ }
+}
+
+static void
+decode_tpgs_state(const int st)
+{
+ switch (st) {
+ case TPGS_STATE_OPTIMIZED:
+ printf(" (active/optimized)");
+ break;
+ case TPGS_STATE_NONOPTIMIZED:
+ printf(" (active/non optimized)");
+ break;
+ case TPGS_STATE_STANDBY:
+ printf(" (standby)");
+ break;
+ case TPGS_STATE_UNAVAILABLE:
+ printf(" (unavailable)");
+ break;
+ case TPGS_STATE_LB_DEPENDENT:
+ printf(" (logical block dependent)");
+ break;
+ case TPGS_STATE_OFFLINE:
+ printf(" (offline)");
+ break;
+ case TPGS_STATE_TRANSITIONING:
+ printf(" (transitioning between states)");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool decode = false;
+ bool hex = false;
+ bool raw = false;
+ bool o_readonly = false;
+ bool extended = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int k, j, off, res, c, report_len, buff_len, tgt_port_count;
+ int sg_fd = -1;
+ int ret = 0;
+ int verbose = 0;
+ uint8_t * reportTgtGrpBuff = NULL;
+ uint8_t * bp;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "dehHrRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ decode = true;
+ break;
+ case 'e':
+ extended = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ hex = true;
+ break;
+ case 'r':
+ raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("Version: %s\n", version_str);
+ return 0;
+ }
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ buff_len = REPORT_TGT_GRP_BUFF_LEN;
+
+retry:
+ reportTgtGrpBuff = (uint8_t *)malloc(buff_len);
+ if (NULL == reportTgtGrpBuff) {
+ pr2serr(" Out of memory (ram)\n");
+ goto err_out;
+ }
+ memset(reportTgtGrpBuff, 0x0, buff_len);
+
+ res = sg_ll_report_tgt_prt_grp2(sg_fd, reportTgtGrpBuff,
+ buff_len,
+ extended, true, verbose);
+ ret = res;
+ if (0 == res) {
+ report_len = sg_get_unaligned_be32(reportTgtGrpBuff + 0) + 4;
+ if (report_len > buff_len) {
+ free(reportTgtGrpBuff);
+ buff_len = report_len;
+ goto retry;
+ }
+ if (raw) {
+ dStrRaw(reportTgtGrpBuff, report_len);
+ goto err_out;
+ }
+ if (verbose)
+ printf("Report list length = %d\n", report_len);
+ if (hex) {
+ if (verbose)
+ printf("\nOutput response in hex:\n");
+ hex2stdout(reportTgtGrpBuff, report_len, 1);
+ goto err_out;
+ }
+ printf("Report target port groups:\n");
+ bp = reportTgtGrpBuff + 4;
+ if (extended) {
+ if (0x10 != (bp[0] & 0x70)) {
+ pr2serr(" <<invalid extended header format\n");
+ goto err_out;
+ }
+ printf(" Implicit transition time: %d\n", bp[1]);
+ bp += 4;
+ }
+ for (k = bp - reportTgtGrpBuff; k < report_len;
+ k += off, bp += off) {
+
+ printf(" target port group id : 0x%x , Pref=%d, Rtpg_fmt=%d\n",
+ sg_get_unaligned_be16(bp + 2), !!(bp[0] & 0x80),
+ (bp[0] >> 4) & 0x07);
+ printf(" target port group asymmetric access state : ");
+ printf("0x%02x", bp[0] & 0x0f);
+ if (decode)
+ decode_tpgs_state(bp[0] & 0x0f);
+ printf("\n");
+
+ printf(" T_SUP : %d, ", !!(bp[1] & 0x80));
+ printf("O_SUP : %d, ", !!(bp[1] & 0x40));
+ printf("LBD_SUP : %d, ", !!(bp[1] & 0x10));
+ printf("U_SUP : %d, ", !!(bp[1] & 0x08));
+ printf("S_SUP : %d, ", !!(bp[1] & 0x04));
+ printf("AN_SUP : %d, ", !!(bp[1] & 0x02));
+ printf("AO_SUP : %d\n", !!(bp[1] & 0x01));
+
+ printf(" status code : ");
+ printf("0x%02x", bp[5]);
+ if (decode)
+ decode_status(bp[5]);
+ printf("\n");
+
+ printf(" vendor unique status : ");
+ printf("0x%02x\n", bp[6]);
+
+ printf(" target port count : ");
+ tgt_port_count = bp[7];
+ printf("%02x\n", tgt_port_count);
+
+ for (j = 0; j < tgt_port_count * 4; j += 4) {
+ if (0 == j)
+ printf(" Relative target port ids:\n");
+ printf(" 0x%02x\n",
+ sg_get_unaligned_be16(bp + 8 + j + 2));
+ }
+ off = 8 + j;
+ }
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Report Target Port Groups command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("bad field in Report Target Port Groups cdb including "
+ "unsupported service action\n");
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Report Target Port Groups: %s\n", b);
+ }
+
+err_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (reportTgtGrpBuff)
+ free(reportTgtGrpBuff);
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_rtpg failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_safte.c b/src/sg_safte.c
new file mode 100644
index 00000000..588d47f8
--- /dev/null
+++ b/src/sg_safte.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2004-2018 Hannes Reinecke 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ * This program accesses a processor device which operates according
+ * to the 'SCSI Accessed Fault-Tolerant Enclosures' (SAF-TE) spec.
+ */
+
+static const char * version_str = "0.33 20180628";
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+#define EBUFF_SZ 256
+
+#define RB_MODE_DESC 3
+#define RWB_MODE_DATA 2
+#define RWB_MODE_VENDOR 1
+#define RB_DESC_LEN 4
+
+#define SAFTE_CFG_FLAG_DOORLOCK 1
+#define SAFTE_CFG_FLAG_ALARM 2
+#define SAFTE_CFG_FLAG_CELSIUS 3
+
+struct safte_cfg_t {
+ int fans;
+ int psupplies;
+ int slots;
+ int temps;
+ int thermostats;
+ int vendor_specific;
+ int flags;
+};
+
+struct safte_cfg_t safte_cfg;
+
+static unsigned int buf_capacity = 64;
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Buffer ID 0x0: Read Enclosure Configuration (mandatory) */
+static int
+read_safte_configuration(int sg_fd, uint8_t *rb_buff,
+ unsigned int rb_len, int verbose)
+{
+ int res;
+
+ if (rb_len < buf_capacity) {
+ pr2serr("SCSI BUFFER size too small (%d/%d bytes)\n", rb_len,
+ buf_capacity);
+ return SG_LIB_CAT_ILLEGAL_REQ;
+ }
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=0 to fetch "
+ "configuration\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 0, 0,
+ rb_buff, rb_len, true, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED)
+ return res;
+
+ safte_cfg.fans = rb_buff[0];
+ safte_cfg.psupplies = rb_buff[1];
+ safte_cfg.slots = rb_buff[2];
+ safte_cfg.temps = rb_buff[4];
+ if (rb_buff[3])
+ safte_cfg.flags |= SAFTE_CFG_FLAG_DOORLOCK;
+ if (rb_buff[5])
+ safte_cfg.flags |= SAFTE_CFG_FLAG_ALARM;
+ if (rb_buff[6] & 0x80)
+ safte_cfg.flags |= SAFTE_CFG_FLAG_CELSIUS;
+
+ safte_cfg.thermostats = rb_buff[6] & 0x0f;
+ safte_cfg.vendor_specific = rb_buff[63];
+
+ return 0;
+}
+
+static int
+print_safte_configuration(void)
+{
+ printf("Enclosure Configuration:\n");
+ printf("\tNumber of Fans: %d\n", safte_cfg.fans);
+ printf("\tNumber of Power Supplies: %d\n", safte_cfg.psupplies);
+ printf("\tNumber of Device Slots: %d\n", safte_cfg.slots);
+ printf("\tNumber of Temperature Sensors: %d\n", safte_cfg.temps);
+ printf("\tNumber of Thermostats: %d\n", safte_cfg.thermostats);
+ printf("\tVendor unique bytes: %d\n", safte_cfg.vendor_specific);
+
+ return 0;
+}
+
+/* Buffer ID 0x01: Read Enclosure Status (mandatory) */
+static int
+do_safte_encl_status(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i, offset;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+
+ rb_len = safte_cfg.fans + safte_cfg.psupplies + safte_cfg.slots +
+ safte_cfg.temps + 5 + safte_cfg.vendor_specific;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=1 to read "
+ "enclosure status\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 1, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED)
+ return res;
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Enclosure Status:\n");
+ offset = 0;
+ for (i = 0; i < safte_cfg.fans; i++) {
+ printf("\tFan %d status: ", i);
+ switch(rb_buff[i]) {
+ case 0:
+ printf("operational\n");
+ break;
+ case 1:
+ printf("malfunctioning\n");
+ break;
+ case 2:
+ printf("not installed\n");
+ break;
+ case 80:
+ printf("not reportable\n");
+ break;
+ default:
+ printf("unknown\n");
+ break;
+ }
+ }
+
+ offset += safte_cfg.fans;
+ for (i = 0; i < safte_cfg.psupplies; i++) {
+ printf("\tPower supply %d status: ", i);
+ switch(rb_buff[i + offset]) {
+ case 0:
+ printf("operational / on\n");
+ break;
+ case 1:
+ printf("operational / off\n");
+ break;
+ case 0x10:
+ printf("malfunctioning / on\n");
+ break;
+ case 0x11:
+ printf("malfunctioning / off\n");
+ break;
+ case 0x20:
+ printf("not present\n");
+ break;
+ case 0x21:
+ printf("present\n");
+ break;
+ case 0x80:
+ printf("not reportable\n");
+ break;
+ default:
+ printf("unknown\n");
+ break;
+ }
+ }
+
+ offset += safte_cfg.psupplies;
+ for (i = 0; i < safte_cfg.slots; i++) {
+ printf("\tDevice Slot %d: SCSI ID %d\n", i, rb_buff[i + offset]);
+ }
+
+ offset += safte_cfg.slots;
+ if (safte_cfg.flags & SAFTE_CFG_FLAG_DOORLOCK) {
+ switch(rb_buff[offset]) {
+ case 0x0:
+ printf("\tDoor lock status: locked\n");
+ break;
+ case 0x01:
+ printf("\tDoor lock status: unlocked\n");
+ break;
+ case 0x80:
+ printf("\tDoor lock status: not reportable\n");
+ break;
+ }
+ } else {
+ printf("\tDoor lock status: not installed\n");
+ }
+
+ offset++;
+ if (!(safte_cfg.flags & SAFTE_CFG_FLAG_ALARM)) {
+ printf("\tSpeaker status: not installed\n");
+ } else {
+ switch(rb_buff[offset]) {
+ case 0x0:
+ printf("\tSpeaker status: off\n");
+ break;
+ case 0x01:
+ printf("\tSpeaker status: on\n");
+ break;
+ }
+ }
+
+ offset++;
+ for (i = 0; i < safte_cfg.temps; i++) {
+ int temp = rb_buff[i + offset];
+ int is_celsius = !!(safte_cfg.flags & SAFTE_CFG_FLAG_CELSIUS);
+
+ if (! is_celsius)
+ temp -= 10;
+
+ printf("\tTemperature sensor %d: %d deg %c\n", i, temp,
+ is_celsius ? 'C' : 'F');
+ }
+
+ offset += safte_cfg.temps;
+ if (safte_cfg.thermostats) {
+ if (rb_buff[offset] & 0x80) {
+ printf("\tEnclosure Temperature alert status: abnormal\n");
+ } else {
+ printf("\tEnclosure Temperature alert status: normal\n");
+ }
+ }
+ return 0;
+}
+
+/* Buffer ID 0x02: Read Usage Statistics (optional) */
+static int
+do_safte_usage_statistics(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+ unsigned int minutes;
+
+ rb_len = 16 + safte_cfg.vendor_specific;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=2 to read "
+ "usage statistics\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 2, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Usage Statistics:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Usage Statistics:\n");
+ minutes = sg_get_unaligned_be32(rb_buff + 0);
+ printf("\tPower on Minutes: %u\n", minutes);
+ minutes = sg_get_unaligned_be32(rb_buff + 4);
+ printf("\tPower on Cycles: %u\n", minutes);
+
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x03: Read Device Insertions (optional) */
+static int
+do_safte_slot_insertions(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i;
+ unsigned int rb_len;
+ uint8_t *rb_buff, slot_status;
+
+ rb_len = safte_cfg.slots * 2;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=3 to read "
+ "device insertions\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 3, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res ) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Slot insertions:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Slot insertions:\n");
+ for (i = 0; i < safte_cfg.slots; i++) {
+ slot_status = sg_get_unaligned_be16(rb_buff + (i * 2));
+ printf("\tSlot %d: %d insertions", i, slot_status);
+ }
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x04: Read Device Slot Status (mandatory) */
+static int
+do_safte_slot_status(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i;
+ unsigned int rb_len;
+ uint8_t *rb_buff, slot_status;
+
+ rb_len = safte_cfg.slots * 4;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=4 to read "
+ "device slot status\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 4, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Slot status:\n");
+ for (i = 0; i < safte_cfg.slots; i++) {
+ slot_status = rb_buff[i * 4 + 3];
+ printf("\tSlot %d: ", i);
+ if (slot_status & 0x7) {
+ if (slot_status & 0x1)
+ printf("inserted ");
+ if (slot_status & 0x2)
+ printf("ready ");
+ if (slot_status & 0x4)
+ printf("activated ");
+ printf("\n");
+ } else {
+ printf("empty\n");
+ }
+ }
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x05: Read Global Flags (optional) */
+static int
+do_safte_global_flags(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+
+ rb_len = 16;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=5 to read "
+ "global flags\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 5, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res ) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Global Flags:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Global Flags:\n");
+ printf("\tAudible Alarm Control: %s\n",
+ rb_buff[0] & 0x1?"on":"off");
+ printf("\tGlobal Failure Indicator: %s\n",
+ rb_buff[0] & 0x2?"on":"off");
+ printf("\tGlobal Warning Indicator: %s\n",
+ rb_buff[0] & 0x4?"on":"off");
+ printf("\tEnclosure Power: %s\n",
+ rb_buff[0] & 0x8?"on":"off");
+ printf("\tCooling Failure: %s\n",
+ rb_buff[0] & 0x10?"yes":"no");
+ printf("\tPower Failure: %s\n",
+ rb_buff[0] & 0x20?"yes":"no");
+ printf("\tDrive Failure: %s\n",
+ rb_buff[0] & 0x40?"yes":"no");
+ printf("\tDrive Warning: %s\n",
+ rb_buff[0] & 0x80?"yes":"no");
+ printf("\tArray Failure: %s\n",
+ rb_buff[1] & 0x1?"yes":"no");
+ printf("\tArray Warning: %s\n",
+ rb_buff[0] & 0x2?"yes":"no");
+ printf("\tEnclosure Lock: %s\n",
+ rb_buff[0] & 0x4?"on":"off");
+ printf("\tEnclosure Identify: %s\n",
+ rb_buff[0] & 0x8?"on":"off");
+
+ free(rb_buff);
+ return 0;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_safte [--config] [--devstatus] [--encstatus] "
+ "[--flags] [--help]\n"
+ " [--hex] [--insertions] [--raw] [--usage] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --config|-c output enclosure configuration\n"
+ " --devstatus|-d output device slot status\n"
+ " --encstatus|-s output enclosure status\n"
+ " --flags|-f output global flags\n"
+ " --help|-h output command usage message then "
+ "exit\n"
+ " --hex|-H output enclosure config in hex\n"
+ " --insertions|-i output insertion statistics\n"
+ " --raw|-r output enclosure config in binary "
+ "to stdout\n"
+ " --usage|-u output usage statistics\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-v output version then exit\n\n"
+ "Queries a SAF-TE processor device\n");
+}
+
+static struct option long_options[] = {
+ {"config", 0, 0, 'c'},
+ {"devstatus", 0, 0, 'd'},
+ {"encstatus", 0, 0, 's'},
+ {"flags", 0, 0, 'f'},
+ {"help", 0, 0, 'h'},
+ {"hex", 0, 0, 'H'},
+ {"insertions", 0, 0, 'i'},
+ {"raw", 0, 0, 'r'},
+ {"usage", 0, 0, 'u'},
+ {"verbose", 0, 0, 'v'},
+ {"version", 0, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+int
+main(int argc, char * argv[])
+{
+ bool do_insertions = false;
+ bool no_hex_raw;
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, ret, peri_type;
+ int sg_fd = -1;
+ int res = SG_LIB_CAT_OTHER;
+ const char * device_name = NULL;
+ char ebuff[EBUFF_SZ];
+ uint8_t *rb_buff;
+ bool do_config = false;
+ bool do_status = false;
+ bool do_slots = false;
+ bool do_flags = false;
+ bool do_usage = false;
+ int do_hex = 0;
+ int do_raw = 0;
+ int verbose = 0;
+ const char * cp;
+ char buff[48];
+ char b[80];
+ struct sg_simple_inquiry_resp inq_resp;
+ const char op_name[] = "READ BUFFER";
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "cdfhHirsuvV?", long_options,
+ &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ do_config = true;
+ break;
+ case 'd':
+ do_slots = true;
+ break;
+ case 'f':
+ do_flags = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ do_insertions = true;
+ break;
+ case 'r':
+ ++do_raw;
+ break;
+ case 's':
+ do_status = true;
+ break;
+ case 'u':
+ do_usage = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if ((sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose))
+ < 0) {
+ if (verbose) {
+ snprintf(ebuff, EBUFF_SZ, "sg_safte: error opening file: %s (rw)",
+ device_name);
+ perror(ebuff);
+ }
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ no_hex_raw = ((0 == do_hex) && (0 == do_raw));
+
+ if (no_hex_raw) {
+ if (0 == sg_simple_inquiry(sg_fd, &inq_resp, true, verbose)) {
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor,
+ inq_resp.product, inq_resp.revision);
+ peri_type = inq_resp.peripheral_type;
+ cp = sg_get_pdt_str(peri_type, sizeof(buff), buff);
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_type);
+ } else {
+ pr2serr("sg_safte: %s doesn't respond to a SCSI INQUIRY\n",
+ device_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+
+ rb_buff = (uint8_t *)malloc(buf_capacity);
+ if (!rb_buff)
+ goto err_out;
+
+ memset(rb_buff, 0, buf_capacity);
+
+ res = read_safte_configuration(sg_fd, rb_buff, buf_capacity, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ if (1 == do_raw) {
+ dStrRaw(rb_buff, buf_capacity);
+ goto finish;
+ }
+ if (1 == do_hex) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ goto finish;
+ }
+
+ if (do_config && no_hex_raw)
+ print_safte_configuration();
+
+ if (do_status) {
+ res = do_safte_encl_status(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_usage) {
+ res = do_safte_usage_statistics(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_insertions) {
+ res = do_safte_slot_insertions(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_slots) {
+ res = do_safte_slot_status(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_flags) {
+ res = do_safte_global_flags(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+finish:
+ res = 0;
+
+err_out:
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s failed: %s\n", op_name, b);
+ break;
+ }
+ ret = res;
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_safte failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sanitize.c b/src/sg_sanitize.c
new file mode 100644
index 00000000..89108fed
--- /dev/null
+++ b/src/sg_sanitize.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 2011-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.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_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.19 20220608";
+
+#define ME "sg_sanitize: "
+
+#define SANITIZE_OP 0x48
+#define SANITIZE_OP_LEN 10
+#define SANITIZE_SA_OVERWRITE 0x1
+#define SANITIZE_SA_BLOCK_ERASE 0x2
+#define SANITIZE_SA_CRYPTO_ERASE 0x3
+#define SANITIZE_SA_EXIT_FAIL_MODE 0x1f
+#define DEF_REQS_RESP_LEN 252
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define MAX_XFER_LEN 65535
+#define EBUFF_SZ 256
+
+#define SHORT_TIMEOUT 20 /* 20 seconds unless immed=0 ... */
+#define LONG_TIMEOUT (15 * 3600) /* 15 hours ! */
+ /* Seagate ST32000444SS 2TB disk takes 9.5 hours to format */
+#define POLL_DURATION_SECS 60
+
+
+static struct option long_options[] = {
+ {"ause", no_argument, 0, 'A'},
+ {"block", no_argument, 0, 'B'},
+ {"count", required_argument, 0, 'c'},
+ {"crypto", no_argument, 0, 'C'},
+ {"desc", no_argument, 0, 'd'},
+ {"dry-run", no_argument, 0, 'D'},
+ {"dry_run", no_argument, 0, 'D'},
+ {"early", no_argument, 0, 'e'},
+ {"fail", no_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"invert", no_argument, 0, 'I'},
+ {"ipl", required_argument, 0, 'i'},
+ {"overwrite", no_argument, 0, 'O'},
+ {"pattern", required_argument, 0, 'p'},
+ {"quick", no_argument, 0, 'Q'},
+ {"test", required_argument, 0, 'T'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wait", no_argument, 0, 'w'},
+ {"zero", no_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool ause;
+ bool block;
+ bool crypto;
+ bool desc;
+ bool dry_run;
+ bool early;
+ bool fail;
+ bool invert;
+ bool overwrite;
+ bool quick;
+ bool verbose_given;
+ bool version_given;
+ bool wait;
+ bool znr;
+ int count;
+ int ipl; /* initialization pattern length */
+ int test;
+ int timeout; /* in seconds */
+ int verbose;
+ int zero;
+ const char * pattern_fn;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_sanitize [--ause] [--block] [--count=OC] [--crypto] "
+ "[--dry-run]\n"
+ " [--early] [--fail] [--help] [--invert] "
+ "[--ipl=LEN]\n"
+ " [--overwrite] [--pattern=PF] [--quick] "
+ "[--test=TE]\n"
+ " [--timeout=SECS] [--verbose] [--version] "
+ "[--wait]\n"
+ " [--zero] [--znr] DEVICE\n"
+ " where:\n"
+ " --ause|-A set AUSE bit in cdb\n"
+ " --block|-B do BLOCK ERASE sanitize\n"
+ " --count=OC|-c OC OC is overwrite count field (from 1 "
+ "(def) to 31)\n"
+ " --crypto|-C do CRYPTOGRAPHIC ERASE sanitize\n"
+ " --desc|-d polling request sense sets 'desc' "
+ "field\n"
+ " (def: clear 'desc' field)\n"
+ " --dry-run|-D to preparation but bypass SANITIZE "
+ "command\n"
+ " --early|-e exit once sanitize started (IMMED set "
+ "in cdb)\n"
+ " user can monitor progress with REQUEST "
+ "SENSE\n"
+ " --fail|-F do EXIT FAILURE MODE sanitize\n"
+ " --help|-h print out usage message\n"
+ " --invert|-I set INVERT bit in OVERWRITE parameter "
+ "list\n"
+ " --ipl=LEN|-i LEN initialization pattern length (in "
+ "bytes)\n"
+ " --overwrite|-O do OVERWRITE sanitize\n"
+ " --pattern=PF|-p PF PF is file containing initialization "
+ "pattern\n"
+ " for OVERWRITE\n"
+ " --quick|-Q start sanitize without pause for user\n"
+ " intervention (i.e. no time to "
+ "reconsider)\n"
+ " --test=TE|-T TE TE is placed in TEST field of "
+ "OVERWRITE\n"
+ " parameter list (def: 0)\n"
+ " --timeout=SECS|-t SECS SANITIZE command timeout in "
+ "seconds\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --wait|-w wait for command to finish (could "
+ "take hours)\n"
+ " --zero|-z use pattern of zeros for "
+ "OVERWRITE\n"
+ " --znr|-Z set ZNR (zone no reset) bit in cdb\n\n"
+ "Performs a SCSI SANITIZE command.\n <<<WARNING>>>: all data "
+ "on DEVICE will be lost.\nDefault action is to give user time to "
+ "reconsider; then execute SANITIZE\ncommand with IMMED bit set; "
+ "then use REQUEST SENSE command every 60\nseconds to poll for a "
+ "progress indication; then exit when there is no\nmore progress "
+ "indication.\n"
+ );
+}
+
+/* Invoke SCSI SANITIZE command. Returns 0 if successful, otherwise error */
+static int
+do_sanitize(int sg_fd, const struct opts_t * op, const void * param_lstp,
+ int param_lst_len)
+{
+ bool immed;
+ int ret, res, sense_cat, timeout;
+ uint8_t san_cdb[SANITIZE_OP_LEN];
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (op->early || op->wait)
+ immed = op->early;
+ else
+ immed = true;
+ timeout = (immed ? SHORT_TIMEOUT : LONG_TIMEOUT);
+ /* only use command line timeout if it exceeds previous defaults */
+ if (op->timeout > timeout)
+ timeout = op->timeout;
+ memset(san_cdb, 0, sizeof(san_cdb));
+ san_cdb[0] = SANITIZE_OP;
+ if (op->overwrite)
+ san_cdb[1] = SANITIZE_SA_OVERWRITE;
+ else if (op->block)
+ san_cdb[1] = SANITIZE_SA_BLOCK_ERASE;
+ else if (op->crypto)
+ san_cdb[1] = SANITIZE_SA_CRYPTO_ERASE;
+ else if (op->fail)
+ san_cdb[1] = SANITIZE_SA_EXIT_FAIL_MODE;
+ else
+ return SG_LIB_SYNTAX_ERROR;
+ if (immed)
+ san_cdb[1] |= 0x80;
+ if (op->znr) /* added sbc4r07 */
+ san_cdb[1] |= 0x40;
+ if (op->ause)
+ san_cdb[1] |= 0x20;
+ sg_put_unaligned_be16((uint16_t)param_lst_len, san_cdb + 7);
+
+ if (op->verbose > 1) {
+ char b[128];
+
+ pr2serr(" Sanitize cdb: %s\n",
+ sg_get_command_str(san_cdb, SANITIZE_OP_LEN, false,
+ sizeof(b), b));
+ if (op->verbose > 2) {
+ if (param_lst_len > 0) {
+ pr2serr(" Parameter list contents:\n");
+ hex2stderr((const uint8_t *)param_lstp, param_lst_len, -1);
+ }
+ pr2serr(" Sanitize command timeout: %d seconds\n", timeout);
+ }
+ }
+ if (op->dry_run) {
+ pr2serr("Due to --dry-run option, bypassing SANITIZE command\n");
+ return 0;
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Sanitize: out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, san_cdb, sizeof(san_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)param_lstp, param_lst_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout, op->verbose);
+ ret = sg_cmds_process_resp(ptvp, "Sanitize", res, true /*noisy */,
+ op->verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ {
+ bool valid;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid)
+ pr2serr("Medium or hardware error starting at "
+ "lba=%" PRIu64 " [0x%" PRIx64 "]\n", ull, ull);
+ }
+ ret = sense_cat;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else {
+ ret = 0;
+ if (op->verbose)
+ pr2serr("Sanitize command %s without error\n",
+ (immed ? "launched" : "completed"));
+ }
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+#define VPD_DEVICE_ID 0x83
+#define VPD_ASSOC_LU 0
+#define VPD_ASSOC_TPORT 1
+#define TPROTO_ISCSI 5
+
+static char *
+get_lu_name(const uint8_t * bp, int u_len, char * b, int b_len)
+{
+ int len, off, sns_dlen, dlen, k;
+ uint8_t u_sns[512];
+ char * cp;
+
+ len = u_len - 4;
+ bp += 4;
+ off = -1;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 8 /* SCSI name string (sns) */,
+ 3 /* UTF-8 */)) {
+ sns_dlen = bp[off + 3];
+ memcpy(u_sns, bp + off + 4, sns_dlen);
+ /* now want to check if this is iSCSI */
+ off = -1;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_TPORT,
+ 8 /* SCSI name string (sns) */,
+ 3 /* UTF-8 */)) {
+ if ((0x80 & bp[1]) && (TPROTO_ISCSI == (bp[0] >> 4))) {
+ snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
+ return b;
+ }
+ }
+ } else
+ sns_dlen = 0;
+ if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 3 /* NAA */, 1 /* binary */)) {
+ dlen = bp[off + 3];
+ if (! ((8 == dlen) || (16 ==dlen)))
+ return b;
+ cp = b;
+ for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
+ snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
+ cp += 2;
+ b_len -= 2;
+ }
+ } else if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
+ 2 /* EUI */, 1 /* binary */)) {
+ dlen = bp[off + 3];
+ if (! ((8 == dlen) || (12 == dlen) || (16 ==dlen)))
+ return b;
+ cp = b;
+ for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
+ snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
+ cp += 2;
+ b_len -= 2;
+ }
+ } else if (sns_dlen > 0)
+ snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
+ return b;
+}
+
+#define SAFE_STD_INQ_RESP_LEN 36
+#define VPD_SUPPORTED_VPDS 0x0
+#define VPD_UNIT_SERIAL_NUM 0x80
+#define VPD_DEVICE_ID 0x83
+
+static int
+print_dev_id(int fd, uint8_t * sinq_resp, int max_rlen, int verbose)
+{
+ int res, k, n, verb, pdt, has_sn, has_di;
+ uint8_t b[256];
+ char a[256];
+ char pdt_name[64];
+
+ verb = (verbose > 1) ? verbose - 1 : 0;
+ memset(sinq_resp, 0, max_rlen);
+ res = sg_ll_inquiry(fd, false, false /* evpd */, 0 /* pg_op */, b,
+ SAFE_STD_INQ_RESP_LEN, 1, verb);
+ if (res)
+ return res;
+ n = b[4] + 5;
+ if (n > SAFE_STD_INQ_RESP_LEN)
+ n = SAFE_STD_INQ_RESP_LEN;
+ memcpy(sinq_resp, b, (n < max_rlen) ? n : max_rlen);
+ if (n == SAFE_STD_INQ_RESP_LEN) {
+ pdt = b[0] & PDT_MASK;
+ printf(" %.8s %.16s %.4s peripheral_type: %s [0x%x]\n",
+ (const char *)(b + 8), (const char *)(b + 16),
+ (const char *)(b + 32),
+ sg_get_pdt_str(pdt, sizeof(pdt_name), pdt_name), pdt);
+ if (verbose)
+ printf(" PROTECT=%d\n", !!(b[5] & 1));
+ if (b[5] & 1)
+ printf(" << supports protection information>>\n");
+ } else {
+ pr2serr("Short INQUIRY response: %d bytes, expect at least 36\n", n);
+ return SG_LIB_CAT_OTHER;
+ }
+ res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_SUPPORTED_VPDS, b,
+ SAFE_STD_INQ_RESP_LEN, 1, verb);
+ if (res) {
+ if (verbose)
+ pr2serr("VPD_SUPPORTED_VPDS gave res=%d\n", res);
+ return 0;
+ }
+ if (VPD_SUPPORTED_VPDS != b[1]) {
+ if (verbose)
+ pr2serr("VPD_SUPPORTED_VPDS corrupted\n");
+ return 0;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (SAFE_STD_INQ_RESP_LEN - 4))
+ n = (SAFE_STD_INQ_RESP_LEN - 4);
+ for (k = 0, has_sn = 0, has_di = 0; k < n; ++k) {
+ if (VPD_UNIT_SERIAL_NUM == b[4 + k])
+ ++has_sn;
+ else if (VPD_DEVICE_ID == b[4 + k]) {
+ ++has_di;
+ break;
+ }
+ }
+ if (has_sn) {
+ res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_UNIT_SERIAL_NUM,
+ b, sizeof(b), 1, verb);
+ if (res) {
+ if (verbose)
+ pr2serr("VPD_UNIT_SERIAL_NUM gave res=%d\n", res);
+ return 0;
+ }
+ if (VPD_UNIT_SERIAL_NUM != b[1]) {
+ if (verbose)
+ pr2serr("VPD_UNIT_SERIAL_NUM corrupted\n");
+ return 0;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (int)(sizeof(b) - 4))
+ n = (sizeof(b) - 4);
+ printf(" Unit serial number: %.*s\n", n, (const char *)(b + 4));
+ }
+ if (has_di) {
+ res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_DEVICE_ID, b,
+ sizeof(b), 1, verb);
+ if (res) {
+ if (verbose)
+ pr2serr("VPD_DEVICE_ID gave res=%d\n", res);
+ return 0;
+ }
+ if (VPD_DEVICE_ID != b[1]) {
+ if (verbose)
+ pr2serr("VPD_DEVICE_ID corrupted\n");
+ return 0;
+ }
+ n = sg_get_unaligned_be16(b + 2);
+ if (n > (int)(sizeof(b) - 4))
+ n = (sizeof(b) - 4);
+ n = strlen(get_lu_name(b, n + 4, a, sizeof(a)));
+ if (n > 0)
+ printf(" LU name: %.*s\n", n, a);
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool got_stdin = false;
+ int k, res, c, infd, progress, vb, n, resp_len, err;
+ int sg_fd = -1;
+ int param_lst_len = 0;
+ int ret = -1;
+ const char * device_name = NULL;
+ char ebuff[EBUFF_SZ];
+ char b[80];
+ uint8_t rsBuff[DEF_REQS_RESP_LEN];
+ uint8_t * wBuff = NULL;
+ uint8_t * free_wBuff = NULL;
+ struct opts_t opts;
+ struct opts_t * op;
+ struct stat a_stat;
+ uint8_t inq_resp[SAFE_STD_INQ_RESP_LEN];
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->count = 1;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ABc:CdDeFhi:IOp:Qt:T:vVwzZ",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'A':
+ op->ause = true;
+ break;
+ case 'B':
+ op->block = true;
+ break;
+ case 'c':
+ op->count = sg_get_num(optarg);
+ if ((op->count < 1) || (op->count > 31)) {
+ pr2serr("bad argument to '--count', expect 1 to 31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'C':
+ op->crypto = true;
+ break;
+ case 'd':
+ op->desc = true;
+ break;
+ case 'D':
+ op->dry_run = true;
+ break;
+ case 'e':
+ op->early = true;
+ break;
+ case 'F':
+ op->fail = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ op->ipl = sg_get_num(optarg);
+ if ((op->ipl < 1) || (op->ipl > 65535)) {
+ pr2serr("bad argument to '--ipl', expect 1 to 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'I':
+ op->invert = true;
+ break;
+ case 'O':
+ op->overwrite = true;
+ break;
+ case 'p':
+ op->pattern_fn = optarg;
+ break;
+ case 'Q':
+ op->quick = true;
+ break;
+ case 't':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout=SECS', want 0 or more\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'T':
+ op->test = sg_get_num(optarg);
+ if ((op->test < 0) || (op->test > 3)) {
+ pr2serr("bad argument to '--test', expect 0 to 3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->wait = true;
+ break;
+ case 'z':
+ ++op->zero;
+ break;
+ case 'Z':
+ op->znr = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ vb = op->verbose;
+ n = (int)op->block + (int)op->crypto + (int)op->fail + (int)op->overwrite;
+ if (1 != n) {
+ pr2serr("one and only one of '--block', '--crypto', '--fail' or "
+ "'--overwrite' please\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->overwrite) {
+ if (op->zero) {
+ if (op->pattern_fn) {
+ pr2serr("confused: both '--pattern=PF' and '--zero' "
+ "options\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->ipl = 4;
+ } else {
+ if (NULL == op->pattern_fn) {
+ pr2serr("'--overwrite' requires '--pattern=PF' or '--zero' "
+ "option\n");
+ return SG_LIB_CONTRADICT;
+ }
+ got_stdin = (0 == strcmp(op->pattern_fn, "-"));
+ if (! got_stdin) {
+ memset(&a_stat, 0, sizeof(a_stat));
+ if (stat(op->pattern_fn, &a_stat) < 0) {
+ err = errno;
+ pr2serr("pattern file: unable to stat(%s): %s\n",
+ op->pattern_fn, safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto err_out;
+ }
+ if (op->ipl <= 0) {
+ op->ipl = (int)a_stat.st_size;
+ if (op->ipl > MAX_XFER_LEN) {
+ pr2serr("pattern file length exceeds 65535 bytes, "
+ "need '--ipl=LEN' option\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ }
+ if (op->ipl < 1) {
+ pr2serr("'--overwrite' requires '--ipl=LEN' option if can't "
+ "get PF length\n");
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ if (op->verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ ret = print_dev_id(sg_fd, inq_resp, sizeof(inq_resp), op->verbose);
+ if (ret)
+ goto err_out;
+
+ if (op->overwrite) {
+ param_lst_len = op->ipl + 4;
+ wBuff = (uint8_t*)sg_memalign(op->ipl + 4, 0, &free_wBuff, false);
+ if (NULL == wBuff) {
+ pr2serr("unable to allocate %d bytes of memory with calloc()\n",
+ op->ipl + 4);
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ if (op->zero) {
+ if (2 == op->zero) /* treat -zz as fill with 0xff bytes */
+ memset(wBuff + 4, 0xff, op->ipl);
+ else
+ memset(wBuff + 4, 0, op->ipl);
+ } else {
+ if (got_stdin) {
+ infd = STDIN_FILENO;
+ if (sg_set_binary_mode(STDIN_FILENO) < 0)
+ perror("sg_set_binary_mode");
+ } else {
+ if ((infd = open(op->pattern_fn, O_RDONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "could not open %s for "
+ "reading", op->pattern_fn);
+ perror(ebuff);
+ ret = sg_convert_errno(err);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ }
+ res = read(infd, wBuff + 4, op->ipl);
+ if (res < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+ op->pattern_fn);
+ perror(ebuff);
+ if (! got_stdin)
+ close(infd);
+ ret = sg_convert_errno(err);
+ goto err_out;
+ }
+ if (res < op->ipl) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ op->ipl, op->pattern_fn, res);
+ pr2serr(" so pad with 0x0 bytes and continue\n");
+ }
+ if (! got_stdin)
+ close(infd);
+ }
+ wBuff[0] = op->count & 0x1f;
+ if (op->test)
+ wBuff[0] |= ((op->test & 0x3) << 5);
+ if (op->invert)
+ wBuff[0] |= 0x80;
+ sg_put_unaligned_be16((uint16_t)op->ipl, wBuff + 2);
+ }
+
+ if ((! op->quick) && (! op->fail))
+ sg_warn_and_wait("SANITIZE", device_name, true);
+
+ ret = do_sanitize(sg_fd, op, wBuff, param_lst_len);
+ if (ret) {
+ sg_get_category_sense_str(ret, sizeof(b), b, vb);
+ pr2serr("Sanitize failed: %s\n", b);
+ }
+
+ if ((0 == ret) && (! op->early) && (! op->wait)) {
+ for (k = 0; ;++k) { /* unbounded, exits via break */
+ if (op->dry_run && (k > 0)) {
+ pr2serr("Due to --dry-run option, leave poll loop\n");
+ break;
+ }
+ sg_sleep_secs(POLL_DURATION_SECS);
+ memset(rsBuff, 0x0, sizeof(rsBuff));
+ res = sg_ll_request_sense(sg_fd, op->desc, rsBuff, sizeof(rsBuff),
+ 1, vb);
+ if (res) {
+ ret = res;
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("Request Sense command not supported\n");
+ else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
+ pr2serr("bad field in Request Sense cdb\n");
+ if (op->desc) {
+ pr2serr("Descriptor type sense may not be supported, "
+ "try again with fixed type\n");
+ op->desc = false;
+ continue;
+ }
+ } else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("Request Sense: %s\n", b);
+ if (0 == vb)
+ pr2serr(" try the '-v' option for more "
+ "information\n");
+ }
+ break;
+ }
+ /* "Additional sense length" same in descriptor and fixed */
+ resp_len = rsBuff[7] + 8;
+ if (vb > 2) {
+ pr2serr("Parameter data in hex\n");
+ hex2stderr(rsBuff, resp_len, -1);
+ }
+ progress = -1;
+ sg_get_sense_progress_fld(rsBuff, resp_len, &progress);
+ if (progress < 0) {
+ ret = res;
+ if (vb > 1)
+ pr2serr("No progress indication found, iteration %d\n",
+ k + 1);
+ if ((0 == k) && vb)
+ pr2serr("Sanitize seems to be successful and finished "
+ "quickly\n");
+ /* N.B. exits first time there isn't a progress indication */
+ break;
+ } else
+ printf("Progress indication: %d%% done\n",
+ (progress * 100) / 65536);
+ }
+ }
+
+err_out:
+ if (free_wBuff)
+ free(free_wBuff);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_sanitize failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sat_identify.c b/src/sg_sat_identify.c
new file mode 100644
index 00000000..7a8d8251
--- /dev/null
+++ b/src/sg_sat_identify.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.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"
+#include "sg_pr2serr.h"
+#include "sg_unaligned.h"
+
+/* This program uses a ATA PASS-THROUGH SCSI command to package an
+ * ATA IDENTIFY (PACKAGE) DEVICE command. It is based on the SCSI to
+ * ATA Translation (SAT) drafts and standards. See https://www.t10.org
+ * for drafts. SAT is a standard: SAT ANSI INCITS 431-2007 (draft prior
+ * to that is sat-r09.pdf). SAT-2 is also a standard: SAT-2 ANSI INCITS
+ * 465-2010 and the draft prior to that is sat2r09.pdf . The SAT-3 is
+ * now a standard: SAT-3 ANSI INCITS 517-2015. The most current draft of
+ * SAT-4 is revision 5c (sat4r05c.pdf).
+ */
+
+#define SAT_ATA_PASS_THROUGH32_LEN 32
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_PASS_THROUGH12 0xa1 /* clashes with MMC BLANK command */
+#define SAT_ATA_PASS_THROUGH12_LEN 12
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
+
+#define ATA_IDENTIFY_DEVICE 0xec
+#define ATA_IDENTIFY_PACKET_DEVICE 0xa1
+#define ID_RESPONSE_LEN 512
+
+#define DEF_TIMEOUT 20
+
+#define EBUFF_SZ 256
+
+static const char * version_str = "1.19 20220425";
+
+static struct option long_options[] = {
+ {"ck-cond", no_argument, 0, 'c'},
+ {"ck_cond", no_argument, 0, 'c'},
+ {"extend", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"len", required_argument, 0, 'l'},
+ {"ident", no_argument, 0, 'i'},
+ {"packet", no_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_sat_identify [--ck_cond] [--extend] [--help] [--hex] "
+ "[--ident]\n"
+ " [--len=CLEN] [--packet] [--raw] "
+ "[--readonly]\n"
+ " [--verbose] [--version] DEVICE\n"
+ " where:\n"
+ " --ck_cond|-c sets ck_cond bit in cdb (def: 0)\n"
+ " --extend|-e sets extend bit in cdb (def: 0)\n"
+ " --help|-h print out usage message then exit\n"
+ " --hex|-H output response in hex\n"
+ " --ident|-i output WWN prefixed by 0x, if not "
+ "available output\n"
+ " 0x0000000000000000\n"
+ " --len=CLEN| -l CLEN CLEN is cdb length: 12, 16 or 32 "
+ "bytes\n"
+ " (default: 16)\n"
+ " --packet|-p do IDENTIFY PACKET DEVICE (def: IDENTIFY "
+ "DEVICE)\n"
+ " command\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --readonly|-R open DEVICE read-only (def: read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a ATA IDENTIFY (PACKET) DEVICE command via a SAT "
+ "layer using\na SCSI ATA PASS-THROUGH(12), (16) or (32) command. "
+ "Only SAT layers\ncompliant with SAT-4 revision 5 or later will "
+ "support the SCSI ATA\nPASS-THROUGH(32) command.\n");
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static int
+do_identify_dev(int sg_fd, bool do_packet, int cdb_len, bool ck_cond,
+ bool extend, bool do_ident, int do_hex, bool do_raw,
+ int verbose)
+{
+ const bool t_type = false;/* false -> 512 byte blocks,
+ true -> device's LB size */
+ bool t_dir = true; /* false -> to device, true -> from device */
+ bool byte_block = true; /* false -> bytes, true -> 512 byte blocks (if
+ t_type=false) */
+ bool got_ard = false; /* got ATA result descriptor */
+ bool got_fixsense = false; /* got ATA result in fixed format sense */
+ bool ok;
+ int j, res, ret, sb_sz;
+ /* Following for ATA READ/WRITE MULTIPLE (EXT) cmds, normally 0 */
+ int multiple_count = 0;
+ int protocol = 4; /* PIO data-in */
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ int resid = 0;
+ uint64_t ull;
+ struct sg_scsi_sense_hdr ssh;
+ uint8_t inBuff[ID_RESPONSE_LEN];
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
+ {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t apt32_cdb[SAT_ATA_PASS_THROUGH32_LEN] SG_C_CPP_ZERO_INIT;
+ const unsigned short * usp;
+
+ sb_sz = sizeof(sense_buffer);
+ ok = false;
+ switch (cdb_len) {
+ case SAT_ATA_PASS_THROUGH32_LEN: /* SAT-4 revision 5 or later */
+ /* Prepare SCSI ATA PASS-THROUGH COMMAND(32) command */
+ sg_put_unaligned_be16(1, apt32_cdb + 22); /* count=1 */
+ apt32_cdb[25] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE :
+ ATA_IDENTIFY_DEVICE);
+ apt32_cdb[10] = (multiple_count << 5) | (protocol << 1);
+ if (extend)
+ apt32_cdb[10] |= 0x1;
+ apt32_cdb[11] = t_length;
+ if (ck_cond)
+ apt32_cdb[11] |= 0x20;
+ if (t_type)
+ apt32_cdb[11] |= 0x10;
+ if (t_dir)
+ apt32_cdb[11] |= 0x8;
+ if (byte_block)
+ apt32_cdb[11] |= 0x4;
+ /* following call takes care of all bytes below offset 10 in cdb */
+ res = sg_ll_ata_pt(sg_fd, apt32_cdb, cdb_len, DEF_TIMEOUT, inBuff,
+ NULL /* doutp */, ID_RESPONSE_LEN, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ break;
+ case SAT_ATA_PASS_THROUGH16_LEN:
+ /* Prepare SCSI ATA PASS-THROUGH COMMAND(16) command */
+ apt_cdb[6] = 1; /* sector count */
+ apt_cdb[14] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE :
+ ATA_IDENTIFY_DEVICE);
+ apt_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ if (extend)
+ apt_cdb[1] |= 0x1;
+ apt_cdb[2] = t_length;
+ if (ck_cond)
+ apt_cdb[2] |= 0x20;
+ if (t_type)
+ apt_cdb[2] |= 0x10;
+ if (t_dir)
+ apt_cdb[2] |= 0x8;
+ if (byte_block)
+ apt_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt_cdb, cdb_len, DEF_TIMEOUT, inBuff,
+ NULL /* doutp */, ID_RESPONSE_LEN, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ break;
+ case SAT_ATA_PASS_THROUGH12_LEN:
+ /* Prepare SCSI ATA PASS-THROUGH COMMAND(12) command */
+ apt12_cdb[4] = 1; /* sector count */
+ apt12_cdb[9] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE :
+ ATA_IDENTIFY_DEVICE);
+ apt12_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ apt12_cdb[2] = t_length;
+ if (ck_cond)
+ apt12_cdb[2] |= 0x20;
+ if (t_type)
+ apt12_cdb[2] |= 0x10;
+ if (t_dir)
+ apt12_cdb[2] |= 0x8;
+ if (byte_block)
+ apt12_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt12_cdb, cdb_len, DEF_TIMEOUT, inBuff,
+ NULL /* doutp */, ID_RESPONSE_LEN, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ break;
+ default:
+ pr2serr("%s: bad cdb_len=%d\n", __func__, cdb_len);
+ return -1;
+ }
+ if (0 == res) {
+ ok = true;
+ if (verbose > 2)
+ pr2serr("command completed with SCSI GOOD status\n");
+ } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
+ if (verbose > 1) {
+ pr2serr("ATA pass-through:\n");
+ sg_print_sense(NULL, sense_buffer, sb_sz,
+ ((verbose > 2) ? 1 : 0));
+ }
+ if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
+ switch (ssh.sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d) not supported\n",
+ cdb_len);
+ } else {
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), bad field in cdb\n",
+ cdb_len);
+ }
+ return ret;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_RECOVERED_ERROR:
+ if ((0x0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ if (0x72 == ssh.response_code) {
+ if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
+ if (verbose)
+ pr2serr("did not find ATA Return (sense) "
+ "Descriptor\n");
+ return SG_LIB_CAT_RECOVERED;
+ }
+ got_ard = true;
+ break;
+ } else if (0x70 == ssh.response_code) {
+ got_fixsense = true;
+ break;
+ } else {
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), unexpected "
+ "response_code=0x%x\n", ssh.response_code,
+ cdb_len);
+ return SG_LIB_CAT_RECOVERED;
+ }
+ } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
+ return SG_LIB_CAT_RECOVERED;
+ else {
+ if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
+ break;
+ return SG_LIB_CAT_SENSE;
+ }
+ case SPC_SK_UNIT_ATTENTION:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), Unit Attention detected\n",
+ cdb_len);
+ return SG_LIB_CAT_UNIT_ATTENTION;
+ case SPC_SK_NOT_READY:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), device not ready\n",
+ cdb_len);
+ return SG_LIB_CAT_NOT_READY;
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_HARDWARE_ERROR:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), medium or hardware "
+ "error\n", cdb_len);
+ return SG_LIB_CAT_MEDIUM_HARD;
+ case SPC_SK_ABORTED_COMMAND:
+ if (0x10 == ssh.asc) {
+ pr2serr("Aborted command: protection information\n");
+ return SG_LIB_CAT_PROTECTION;
+ } else {
+ pr2serr("Aborted command: try again with%s '-p' option\n",
+ (do_packet ? "out" : ""));
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ case SPC_SK_DATA_PROTECT:
+ pr2serr("ATA PASS-THROUGH (%d): data protect, read only "
+ "media?\n", cdb_len);
+ return SG_LIB_CAT_DATA_PROTECT;
+ default:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), some sense data, use "
+ "'-v' for more information\n", cdb_len);
+ return SG_LIB_CAT_SENSE;
+ }
+ } else {
+ pr2serr("CHECK CONDITION without response code ??\n");
+ return SG_LIB_CAT_SENSE;
+ }
+ if (0x72 != (sense_buffer[0] & 0x7f)) {
+ pr2serr("expected descriptor sense format, response code=0x%x\n",
+ sense_buffer[0]);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else if (res > 0) {
+ if (SAM_STAT_RESERVATION_CONFLICT == res) {
+ pr2serr("SCSI status: RESERVATION CONFLICT\n");
+ return SG_LIB_CAT_RES_CONFLICT;
+ } else {
+ pr2serr("Unexpected SCSI status=0x%x\n", res);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ pr2serr("ATA pass-through (%d) failed\n", cdb_len);
+ if (verbose < 2)
+ pr2serr(" try adding '-v' for more information\n");
+ return -1;
+ }
+
+ if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
+ pr2serr("Seem to have got ATA Result Descriptor but it was not "
+ "indicated\n");
+ if (got_ard) {
+ if (ata_return_desc[3] & 0x4) {
+ pr2serr("error indication in returned FIS: aborted command\n");
+ pr2serr(" try again with%s '-p' option\n",
+ (do_packet ? "out" : ""));
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ ok = true;
+ }
+ if (got_fixsense) {
+ if (0x4 & sense_buffer[3]) { /* Error is MSB of Info field */
+ pr2serr("error indication in returned FIS: aborted command\n");
+ pr2serr(" try again with%s '-p' option\n",
+ (do_packet ? "out" : ""));
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ ok = true;
+ }
+
+ if (ok) { /* output result if it is available */
+ if (do_raw)
+ dStrRaw(inBuff, 512);
+ else if (0 == do_hex) {
+ if (do_ident) {
+ usp = (const unsigned short *)inBuff;
+ ull = 0;
+ for (j = 0; j < 4; ++j) {
+ if (j > 0)
+ ull <<= 16;
+ ull |= usp[108 + j];
+ }
+ printf("0x%016" PRIx64 "\n", ull);
+ } else {
+ printf("Response for IDENTIFY %sDEVICE ATA command:\n",
+ (do_packet ? "PACKET " : ""));
+ dWordHex((const unsigned short *)inBuff, 256, 0,
+ sg_is_big_endian());
+ }
+ } else if (1 == do_hex)
+ hex2stdout(inBuff, 512, 0);
+ else if (2 == do_hex)
+ dWordHex((const unsigned short *)inBuff, 256, 0,
+ sg_is_big_endian());
+ else if (3 == do_hex) /* '-HHH' suitable for "hdparm --Istdin" */
+ dWordHex((const unsigned short *)inBuff, 256, -2,
+ sg_is_big_endian());
+ else /* '-HHHH' hex bytes only */
+ hex2stdout(inBuff, 512, -1);
+ }
+ return 0;
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool do_packet = false;
+ bool do_ident = false;
+ bool do_raw = false;
+ bool o_readonly = false;
+ bool ck_cond = false; /* set to true to read register(s) back */
+ bool extend = false; /* set to true to send 48 bit LBA with command */
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, res;
+ int sg_fd = -1;
+ int cdb_len = SAT_ATA_PASS_THROUGH16_LEN;
+ int do_hex = 0;
+ int verbose = 0;
+ int ret = 0;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "cehHil:prRvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ ck_cond = true;
+ break;
+ case 'e':
+ extend = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ do_ident = true;
+ break;
+ case 'l':
+ cdb_len = sg_get_num(optarg);
+ switch (cdb_len) {
+ case 12:
+ case 16:
+ case 32:
+ break;
+ default:
+ pr2serr("argument to '--len' should be 12, 16 or 32\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ do_packet = true;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ o_readonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return 1;
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if ((sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose)) < 0) {
+ if (verbose)
+ pr2serr("error opening file: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ ret = do_identify_dev(sg_fd, do_packet, cdb_len, ck_cond, extend,
+ do_ident, do_hex, do_raw, verbose);
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_sat_identify failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sat_phy_event.c b/src/sg_sat_phy_event.c
new file mode 100644
index 00000000..b7e9d53d
--- /dev/null
+++ b/src/sg_sat_phy_event.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.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"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.15 20220425";
+
+/* This program uses a ATA PASS-THROUGH SCSI command. This usage is
+ * defined in the SCSI to ATA Translation (SAT) drafts and standards.
+ * See https://www.t10.org for drafts. SAT is a standard: SAT ANSI INCITS
+ * 431-2007 (draft prior to that is sat-r09.pdf). SAT-2 is also a
+ * standard: SAT-2 ANSI INCITS 465-2010 and the draft prior to that is
+ * sat2r09.pdf . The SAT-3 project has started and the most recent draft
+ * is sat3r01.pdf .
+ */
+
+/* This program uses a ATA PASS-THROUGH (16 or 12) SCSI command defined
+ * by SAT to package an ATA READ LOG EXT (2Fh) command to fetch
+ * log page 11h. That page contains SATA phy event counters.
+ * For ATA READ LOG EXT command see ATA-8/ACS at www.t13.org .
+ * For SATA phy counter definitions see SATA 2.5 .
+ *
+ * Invocation: see the usage() function below
+ */
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_PASS_THROUGH12 0xa1 /* clashes with MMC BLANK command */
+#define SAT_ATA_PASS_THROUGH12_LEN 12
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
+
+#define ATA_READ_LOG_EXT 0x2f
+#define SATA_PHY_EVENT_LPAGE 0x11
+#define READ_LOG_EXT_RESPONSE_LEN 512
+
+#define DEF_TIMEOUT 20
+
+#define EBUFF_SZ 256
+
+static struct option long_options[] = {
+ {"ck_cond", no_argument, 0, 'c'},
+ {"ck-cond", no_argument, 0, 'c'},
+ {"extend", no_argument, 0, 'e'},
+ {"hex", no_argument, 0, 'H'},
+ {"ignore", no_argument, 0, 'i'},
+ {"len", no_argument, 0, 'l'},
+ {"raw", no_argument, 0, 'r'},
+ {"reset", no_argument, 0, 'R'},
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct phy_event_t {
+ int id;
+ const char * desc;
+};
+
+static struct phy_event_t phy_event_arr[] = { /* SATA 2.5 section 13.7.2 */
+ {0x1, "Command failed and ICRC error bit set in Error register"}, /* M */
+ {0x2, "R_ERR(p) response for data FIS"},
+ {0x3, "R_ERR(p) response for device-to-host data FIS"},
+ {0x4, "R_ERR(p) response for host-to-device data FIS"},
+ {0x5, "R_ERR(p) response for non-data FIS"},
+ {0x6, "R_ERR(p) response for device-to-host non-data FIS"},
+ {0x7, "R_ERR(p) response for host-to-device non-data FIS"},
+ {0x8, "Device-to-host non-data FIS retries"},
+ {0x9, "Transition from drive PHYRDY to drive PHYRDYn"},
+ {0xa, "Signature device-to-host register FISes due to COMRESET"}, /* M */
+ {0xb, "CRC errors within host-to-device FIS"},
+ {0xd, "non CRC errors within host-to-device FIS"},
+ {0xf, "R_ERR(p) response for host-to-device data FIS, CRC"},
+ {0x10, "R_ERR(p) response for host-to-device data FIS, non-CRC"},
+ {0x12, "R_ERR(p) response for host-to-device non-data FIS, CRC"},
+ {0x13, "R_ERR(p) response for host-to-device non-data FIS, non-CRC"},
+ {0xc00, "PM: host-to-device non-data FIS, R_ERR(p) due to collision"},
+ {0xc01, "PM: signature register - device-to-host FISes"},
+ {0xc02, "PM: corrupts CRC propagation of device-to-host FISes"},
+ {0x0, NULL}, /* end marker */ /* M(andatory) */
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_sat_phy_event [--ck_cond] [--extend] [--help] [--hex] "
+ "[--ignore]\n"
+ " [--len=16|12] [--raw] [--reset] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --ck_cond|-c sets ck_cond bit in cdb (def: 0)\n"
+ " --extend|-e sets extend bit in cdb (def: 0)\n"
+ " --help|-h print this usage message then exit\n"
+ " --hex|-H output response in hex bytes, use twice for\n"
+ " hex words\n"
+ " --ignore|-i ignore identifier names, output id value "
+ "instead\n"
+ " --len=16|12 | -l 16|12 cdb length: 16 or 12 bytes "
+ "(default: 16)\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --reset|-R reset counters (after read)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n\n"
+ "Sends an ATA READ LOG EXT command via a SAT pass through to "
+ "fetch\nlog page 11h which contains SATA phy event counters\n");
+}
+
+static const char *
+find_phy_desc(int id)
+{
+ const struct phy_event_t * pep;
+
+ for (pep = phy_event_arr; pep->desc; ++pep) {
+ if ((id & 0xfff) == pep->id)
+ return pep->desc;
+ }
+ return NULL;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k =0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* ATA READ LOG EXT command [2Fh, PIO data-in] */
+/* N.B. "log_addr" is the log page number, "page_in_log" is usually 0 */
+static int
+do_read_log_ext(int sg_fd, int log_addr, int page_in_log, int feature,
+ int blk_count, void * resp, int mx_resp_len, int cdb_len,
+ bool ck_cond, bool extend, int do_hex, bool do_raw,
+ int verbose)
+{
+ /* Following for ATA READ/WRITE MULTIPLE (EXT) cmds, normally 0 */
+#if 0
+ bool t_type = false;/* false -> 512 byte LBs, true -> device's LB size */
+#endif
+ bool t_dir = true; /* false -> to device, 1 -> from device */
+ bool byte_block = true; /* false -> bytes, true -> 512 byte blocks (if
+ t_type=false) */
+ bool got_ard = false; /* got ATA result descriptor */
+ bool ok;
+ int res, ret;
+ int multiple_count = 0;
+ int protocol = 4; /* PIO data-in */
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ int resid = 0;
+ int sb_sz;
+ struct sg_scsi_sense_hdr ssh;
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
+ {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+
+ sb_sz = sizeof(sense_buffer);
+ ok = false;
+ if (SAT_ATA_PASS_THROUGH16_LEN == cdb_len) {
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[3] = (feature >> 8) & 0xff; /* feature(15:8) */
+ apt_cdb[4] = feature & 0xff; /* feature(7:0) */
+ apt_cdb[5] = (blk_count >> 8) & 0xff; /* sector_count(15:8) */
+ apt_cdb[6] = blk_count & 0xff; /* sector_count(7:0) */
+ apt_cdb[8] = log_addr & 0xff; /* lba_low(7:0) == LBA(7:0) */
+ apt_cdb[9] = (page_in_log >> 8) & 0xff;
+ /* lba_mid(15:8) == LBA(39:32) */
+ apt_cdb[10] = page_in_log & 0xff; /* lba_mid(7:0) == LBA(15:8) */
+ apt_cdb[14] = ATA_READ_LOG_EXT;
+ apt_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ if (extend)
+ apt_cdb[1] |= 0x1;
+ apt_cdb[2] = t_length;
+ if (ck_cond)
+ apt_cdb[2] |= 0x20;
+#if 0
+ if (t_type)
+ apt_cdb[2] |= 0x10;
+#endif
+ if (t_dir)
+ apt_cdb[2] |= 0x8;
+ if (byte_block)
+ apt_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt_cdb, cdb_len, DEF_TIMEOUT, resp,
+ NULL /* doutp */, mx_resp_len, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ } else {
+ /* Prepare ATA PASS-THROUGH COMMAND (12) command */
+ apt12_cdb[3] = feature & 0xff; /* feature(7:0) */
+ apt12_cdb[4] = blk_count & 0xff; /* sector_count(7:0) */
+ apt12_cdb[5] = log_addr & 0xff; /* lba_low(7:0) == LBA(7:0) */
+ apt12_cdb[6] = page_in_log & 0xff; /* lba_mid(7:0) == LBA(15:8) */
+ apt12_cdb[9] = ATA_READ_LOG_EXT;
+ apt12_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ apt12_cdb[2] = t_length;
+ if (ck_cond)
+ apt12_cdb[2] |= 0x20;
+#if 0
+ if (t_type)
+ apt12_cdb[2] |= 0x10;
+#endif
+ if (t_dir)
+ apt12_cdb[2] |= 0x8;
+ if (byte_block)
+ apt12_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt12_cdb, cdb_len, DEF_TIMEOUT, resp,
+ NULL /* doutp */, mx_resp_len, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ }
+ if (0 == res) {
+ ok = true;
+ if (verbose > 2)
+ pr2serr("command completed with SCSI GOOD status\n");
+ } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
+ if (verbose > 1) {
+ pr2serr("ATA pass through:\n");
+ sg_print_sense(NULL, sense_buffer, sb_sz,
+ ((verbose > 2) ? 1 : 0));
+ }
+ if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
+ switch (ssh.sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d) not supported\n",
+ cdb_len);
+ } else {
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), bad field in cdb\n",
+ cdb_len);
+ }
+ return ret;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_RECOVERED_ERROR:
+ if ((0x0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
+ if (verbose)
+ pr2serr("did not find ATA Return (sense) "
+ "Descriptor\n");
+ return SG_LIB_CAT_RECOVERED;
+ }
+ got_ard = true;
+ break;
+ } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
+ return SG_LIB_CAT_RECOVERED;
+ else {
+ if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
+ break;
+ return SG_LIB_CAT_SENSE;
+ }
+ case SPC_SK_UNIT_ATTENTION:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), Unit Attention detected\n",
+ cdb_len);
+ return SG_LIB_CAT_UNIT_ATTENTION;
+ case SPC_SK_NOT_READY:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), device not ready\n",
+ cdb_len);
+ return SG_LIB_CAT_NOT_READY;
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_HARDWARE_ERROR:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), medium or hardware "
+ "error\n", cdb_len);
+ return SG_LIB_CAT_MEDIUM_HARD;
+ case SPC_SK_ABORTED_COMMAND:
+ if (0x10 == ssh.asc) {
+ pr2serr("Aborted command: protection information\n");
+ return SG_LIB_CAT_PROTECTION;
+ } else {
+ pr2serr("Aborted command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ case SPC_SK_DATA_PROTECT:
+ pr2serr("ATA PASS-THROUGH (%d): data protect, read only "
+ "media?\n", cdb_len);
+ return SG_LIB_CAT_DATA_PROTECT;
+ default:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), some sense data, use "
+ "'-v' for more information\n", cdb_len);
+ return SG_LIB_CAT_SENSE;
+ }
+ } else {
+ pr2serr("CHECK CONDITION without response code ??\n");
+ return SG_LIB_CAT_SENSE;
+ }
+ if (0x72 != (sense_buffer[0] & 0x7f)) {
+ pr2serr("expected descriptor sense format, response code=0x%x\n",
+ sense_buffer[0]);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else if (res > 0) {
+ if (SAM_STAT_RESERVATION_CONFLICT == res) {
+ pr2serr("SCSI status: RESERVATION CONFLICT\n");
+ return SG_LIB_CAT_RES_CONFLICT;
+ } else {
+ pr2serr("Unexpected SCSI status=0x%x\n", res);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ pr2serr("ATA pass through (%d) failed\n", cdb_len);
+ if (verbose < 2)
+ pr2serr(" try adding '-v' for more information\n");
+ return -1;
+ }
+
+ if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
+ pr2serr("Seem to have got ATA Result Descriptor but it was not "
+ "indicated\n");
+ if (got_ard) {
+ if (ata_return_desc[3] & 0x4) {
+ pr2serr("error indication in returned FIS: aborted command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ ok = true;
+ }
+
+ if (ok) { /* output result if ok and --hex or --raw given */
+ if (do_raw)
+ dStrRaw((const uint8_t *)resp, mx_resp_len);
+ else if (1 == do_hex)
+ hex2stdout((const uint8_t *)resp, mx_resp_len, 0);
+ else if (do_hex > 1)
+ dWordHex((const unsigned short *)resp, mx_resp_len / 2, 0,
+ sg_is_big_endian());
+ }
+ return 0;
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool ck_cond = false; /* set to true to read register(s) back */
+ bool extend = false;
+ bool ignore = false;
+ bool raw = false;
+ bool reset = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, c, k, j, res, id, len, vendor, err;
+ char * device_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t inBuff[READ_LOG_EXT_RESPONSE_LEN];
+ int cdb_len = 16;
+ int hex = 0;
+ int verbose = 0;
+ int ret = 0;
+ uint64_t ull;
+ const char * cp;
+
+ memset(inBuff, 0, sizeof(inBuff));
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "cehHil:rRvV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ ck_cond = true;
+ break;
+ case 'e':
+ extend = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ exit(0);
+ case 'H':
+ ++hex;
+ break;
+ case 'i':
+ ignore = true;
+ break;
+ case 'l':
+ cdb_len = sg_get_num(optarg);
+ if (! ((cdb_len == 12) || (cdb_len == 16))) {
+ pr2serr("argument to '--len' should be 12 or 16\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r':
+ raw = true;
+ break;
+ case 'R':
+ reset = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+ if (0 == device_name) {
+ pr2serr("no DEVICE name detected\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if ((sg_fd = open(device_name, O_RDWR)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_sat_phy_event: error opening file: %s", device_name);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ ret = do_read_log_ext(sg_fd, SATA_PHY_EVENT_LPAGE,
+ 0 /* page_in_log */,
+ (reset ? 1 : 0) /* feature */,
+ 1 /* blk_count */, inBuff,
+ READ_LOG_EXT_RESPONSE_LEN, cdb_len, ck_cond,
+ extend, hex, raw, verbose);
+
+ if ((0 == ret) && (0 == hex) && (! raw)) {
+ printf("SATA phy event counters:\n");
+ for (k = 4; k < 512; k += (len + 2)) {
+ id = (inBuff[k + 1] << 8) + inBuff[k];
+ if (0 == id)
+ break;
+ len = ((id >> 12) & 0x7) * 2;
+ vendor = !!(id & 0x8000);
+ id = id & 0xfff;
+ ull = 0;
+ for (j = len - 1; j >= 0; --j) {
+ if (j < (len - 1))
+ ull <<= 8;
+ ull |= inBuff[k + 2 + j];
+ }
+ cp = NULL;
+ if ((0 == vendor) && (! ignore))
+ cp = find_phy_desc(id);
+ if (cp)
+ printf(" %s: %" PRIu64 "\n", cp, ull);
+ else
+ printf(" id=0x%x, vendor=%d, data_len=%d, "
+ "val=%" PRIu64 "\n", id, vendor, len, ull);
+ }
+ }
+
+ res = close(sg_fd);
+ if (res < 0) {
+ err = errno;
+ pr2serr("close error: %s\n", safe_strerror(err));
+ if (0 == ret)
+ ret = sg_convert_errno(err);
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sat_read_gplog.c b/src/sg_sat_read_gplog.c
new file mode 100644
index 00000000..55c164eb
--- /dev/null
+++ b/src/sg_sat_read_gplog.c
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2014-2022 Hannes Reinecke, SUSE Linux GmbH.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.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_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* This program uses a ATA PASS-THROUGH SCSI command. This usage is
+ * defined in the SCSI to ATA Translation (SAT) drafts and standards.
+ * See https://www.t10.org for drafts. SAT is a standard: SAT ANSI INCITS
+ * 431-2007 (draft prior to that is sat-r09.pdf). SAT-2 is also a
+ * standard: SAT-2 ANSI INCITS 465-2010 and the draft prior to that is
+ * sat2r09.pdf . The SAT-3 project has started and the most recent draft
+ * is sat3r01.pdf .
+ */
+
+/* This program performs a ATA PASS-THROUGH (16) SCSI command in order
+ * to perform an ATA READ LOG EXT or ATA READ LOG DMA EXT command.
+ *
+ * See man page (sg_sat_read_gplog.8) for details.
+ */
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_PASS_THROUGH12 0xa1 /* clashes with MMC BLANK command */
+#define SAT_ATA_PASS_THROUGH12_LEN 12
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
+
+#define ATA_READ_LOG_EXT 0x2f
+#define ATA_READ_LOG_DMA_EXT 0x47
+
+#define DEF_TIMEOUT 20
+
+static const char * version_str = "1.23 20220917";
+
+struct opts_t {
+ bool ck_cond;
+ bool rdonly;
+ int cdb_len;
+ int count;
+ int hex;
+ int la; /* log address */
+ int pn; /* page number within log address */
+ int verbose;
+ const char * device_name;
+};
+
+static struct option long_options[] = {
+ {"count", required_argument, 0, 'c'},
+ {"ck_cond", no_argument, 0, 'C'},
+ {"ck-cond", no_argument, 0, 'C'},
+ {"dma", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"len", required_argument, 0, 'l'},
+ {"log", required_argument, 0, 'L'},
+ {"page", required_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_sat_read_gplog [--ck_cond] [--count=CO] [--dma] [--help]\n"
+ " [--hex] [--len=16|12] [--log=LA] "
+ "[--page=PN]\n"
+ " [--readonly] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --ck_cond | -C set ck_cond field in pass-through "
+ "(def: 0)\n"
+ " --count=CO | -c CO block count (def: 1)\n"
+ " --dma | -d Use READ LOG DMA EXT (def: READ LOG "
+ "EXT)\n"
+ " --help | -h output this usage message\n"
+ " --hex | -H output response in hex bytes, -HH "
+ "yields hex\n"
+ " words + ASCII (def), -HHH hex words "
+ "only\n"
+ " --len=16|12 | -l 16|12 cdb length: 16 or 12 bytes "
+ "(def: 16)\n"
+ " --log=LA | -L LA Log address to be read (def: 0)\n"
+ " --page=PN|-p PN Log page number within address (def: "
+ "0)\n"
+ " --readonly | -r open DEVICE read-only (def: "
+ "read-write)\n"
+ " --verbose | -v increase verbosity\n"
+ " recommended if DEVICE is ATA disk\n"
+ " --version | -V print version string and exit\n\n"
+ "Sends an ATA READ LOG EXT (or READ LOG DMA EXT) command via a "
+ "SAT pass\nthrough to fetch a General Purpose (GP) log page. Each "
+ "page is accessed\nvia a log address and then a page number "
+ "within that address: LA,PN .\n"
+ "By default the output is the response in hex (16 bit) words.\n"
+ );
+}
+
+static int
+do_read_gplog(int sg_fd, int ata_cmd, uint8_t * inbuff,
+ const struct opts_t * op)
+{
+ const bool extend = true;
+ const bool t_dir = true; /* false -> to device, true -> from device */
+ const bool byte_block = true;/* false -> bytes, true -> 512 byte blocks */
+ const bool t_type = false; /* false -> 512 byte blocks, true -> logical
+ sectors */
+ bool got_ard = false; /* got ATA result descriptor */
+ int res, ret;
+ int protocol;
+ int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */
+ int resid = 0;
+ int sb_sz;
+ struct sg_scsi_sense_hdr ssh;
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
+ {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ char cmd_name[32];
+
+ snprintf(cmd_name, sizeof(cmd_name), "ATA PASS-THROUGH (%d)",
+ op->cdb_len);
+ if (ata_cmd == ATA_READ_LOG_DMA_EXT) {
+ protocol = 6; /* DMA */
+ } else {
+ protocol = 4; /* PIO Data-In */
+ }
+ sb_sz = sizeof(sense_buffer);
+ memset(inbuff, 0, op->count * 512);
+ if (op->verbose > 1)
+ pr2serr("Building ATA READ LOG%s EXT command; la=0x%x, pn=0x%x\n",
+ ((ata_cmd == ATA_READ_LOG_DMA_EXT) ? " DMA" : ""), op->la,
+ op->pn);
+ if (op->cdb_len == 16) {
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[14] = ata_cmd;
+ sg_put_unaligned_be16((uint16_t)op->count, apt_cdb + 5);
+ apt_cdb[8] = op->la;
+ sg_put_unaligned_be16((uint16_t)op->pn, apt_cdb + 9);
+ apt_cdb[1] = (protocol << 1) | extend;
+ if (extend)
+ apt_cdb[1] |= 0x1;
+ apt_cdb[2] = t_length;
+ if (op->ck_cond)
+ apt_cdb[2] |= 0x20;
+ if (t_type)
+ apt_cdb[2] |= 0x10;
+ if (t_dir)
+ apt_cdb[2] |= 0x8;
+ if (byte_block)
+ apt_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt_cdb, op->cdb_len, DEF_TIMEOUT, inbuff,
+ NULL, op->count * 512, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, op->verbose);
+ } else {
+ /* Prepare ATA PASS-THROUGH COMMAND (12) command */
+ /* Cannot map upper 8 bits of the pn since no LBA (39:32) field */
+ apt12_cdb[9] = ata_cmd;
+ apt12_cdb[4] = op->count;
+ apt12_cdb[5] = op->la;
+ apt12_cdb[6] = op->pn & 0xff;
+ /* apt12_cdb[7] = (op->pn >> 8) & 0xff; */
+ apt12_cdb[1] = (protocol << 1);
+ apt12_cdb[2] = t_length;
+ if (op->ck_cond)
+ apt12_cdb[2] |= 0x20;
+ if (t_type)
+ apt12_cdb[2] |= 0x10;
+ if (t_dir)
+ apt12_cdb[2] |= 0x8;
+ if (byte_block)
+ apt12_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt12_cdb, op->cdb_len, DEF_TIMEOUT,
+ inbuff, NULL, op->count * 512, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, op->verbose);
+ }
+ if (0 == res) {
+ if (op->verbose > 2)
+ pr2serr("command completed with SCSI GOOD status\n");
+ if ((0 == op->hex) || (2 == op->hex))
+ dWordHex((const unsigned short *)inbuff, op->count * 256, 0,
+ sg_is_big_endian());
+ else if (1 == op->hex)
+ hex2stdout(inbuff, 512, 0);
+ else if (3 == op->hex) /* '-HHH' suitable for "hdparm --Istdin" */
+ dWordHex((const unsigned short *)inbuff, 256, -2,
+ sg_is_big_endian());
+ else /* '-HHHH' hex bytes only */
+ hex2stdout(inbuff, 512, -1);
+ } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
+ if (op->verbose > 1) {
+ pr2serr("ATA pass through:\n");
+ sg_print_sense(NULL, sense_buffer, sb_sz,
+ ((op->verbose > 2) ? 1 : 0));
+ }
+ if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
+ switch (ssh.sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ if (op->verbose < 2)
+ pr2serr("%s not supported\n", cmd_name);
+ } else {
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ if (op->verbose < 2)
+ pr2serr("%s, bad field in cdb\n", cmd_name);
+ }
+ return ret;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_RECOVERED_ERROR:
+ if ((0x0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
+ if (op->verbose)
+ pr2serr("did not find ATA Return (sense) "
+ "Descriptor\n");
+ return SG_LIB_CAT_RECOVERED;
+ }
+ got_ard = true;
+ break;
+ } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
+ return SG_LIB_CAT_RECOVERED;
+ else {
+ if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
+ break;
+ return SG_LIB_CAT_SENSE;
+ }
+ case SPC_SK_UNIT_ATTENTION:
+ if (op->verbose < 2)
+ pr2serr("%s, Unit Attention detected\n", cmd_name);
+ return SG_LIB_CAT_UNIT_ATTENTION;
+ case SPC_SK_NOT_READY:
+ if (op->verbose < 2)
+ pr2serr("%s, device not ready\n", cmd_name);
+ return SG_LIB_CAT_NOT_READY;
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_HARDWARE_ERROR:
+ if (op->verbose < 2)
+ pr2serr("%s, medium or hardware error\n", cmd_name);
+ return SG_LIB_CAT_MEDIUM_HARD;
+ case SPC_SK_ABORTED_COMMAND:
+ if (0x10 == ssh.asc) {
+ pr2serr("Aborted command: protection information\n");
+ return SG_LIB_CAT_PROTECTION;
+ } else {
+ pr2serr("Aborted command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ case SPC_SK_DATA_PROTECT:
+ pr2serr("%s: data protect, read only media?\n", cmd_name);
+ return SG_LIB_CAT_DATA_PROTECT;
+ default:
+ if (op->verbose < 2)
+ pr2serr("%s, some sense data, use '-v' for more "
+ "information\n", cmd_name);
+ return SG_LIB_CAT_SENSE;
+ }
+ } else {
+ pr2serr("CHECK CONDITION without response code ??\n");
+ return SG_LIB_CAT_SENSE;
+ }
+ if (0x72 != (sense_buffer[0] & 0x7f)) {
+ pr2serr("expected descriptor sense format, response "
+ "code=0x%x\n", sense_buffer[0]);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else if (res > 0) {
+ if (SAM_STAT_RESERVATION_CONFLICT == res) {
+ pr2serr("SCSI status: RESERVATION CONFLICT\n");
+ return SG_LIB_CAT_RES_CONFLICT;
+ } else {
+ pr2serr("Unexpected SCSI status=0x%x\n", res);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ pr2serr("%s failed\n", cmd_name);
+ if (op->verbose < 2)
+ pr2serr(" try adding '-v' for more information\n");
+ return -1;
+ }
+
+ if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
+ pr2serr("Seem to have got ATA Result Descriptor but it was not "
+ "indicated\n");
+ if (got_ard) {
+ if (ata_return_desc[3] & 0x4) {
+ pr2serr("error indication in returned FIS: aborted "
+ "command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, ret, res, n;
+ int sg_fd = -1;
+ int ata_cmd = ATA_READ_LOG_EXT;
+ uint8_t *inbuff = NULL;
+ uint8_t *free_inbuff = NULL;
+ struct opts_t opts;
+ struct opts_t * op;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->cdb_len = SAT_ATA_PASS_THROUGH16_LEN;
+ op->count = 1;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:CdhHl:L:p:rvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ op->count = sg_get_num(optarg);
+ if ((op->count < 1) || (op->count > 0xffff)) {
+ pr2serr("bad argument for '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'C':
+ op->ck_cond = true;
+ break;
+ case 'd':
+ ata_cmd = ATA_READ_LOG_DMA_EXT;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++op->hex;
+ break;
+ case 'l':
+ op->cdb_len = sg_get_num(optarg);
+ if (! ((op->cdb_len == 12) || (op->cdb_len == 16))) {
+ pr2serr("argument to '--len' should be 12 or 16\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'L':
+ op->la = sg_get_num(optarg);
+ if (op->la < 0 || op->la > 0xff) {
+ pr2serr("bad argument for '--log'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'p':
+ op->pn = sg_get_num(optarg);
+ if ((op->pn < 0) || (op->pn > 0xffff)) {
+ pr2serr("bad argument for '--page'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r':
+ op->rdonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ op->verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == op->device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return 1;
+ }
+
+ if ((op->count > 0xff) && (12 == op->cdb_len)) {
+ op->cdb_len = 16;
+ if (op->verbose)
+ pr2serr("Since count > 0xff, forcing cdb length to "
+ "16\n");
+ }
+
+ n = op->count * 512;
+ inbuff = (uint8_t *)sg_memalign(n, 0, &free_inbuff, op->verbose > 3);
+ if (!inbuff) {
+ pr2serr("Cannot allocate output buffer of size %d\n", n);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if ((sg_fd = sg_cmds_open_device(op->device_name, op->rdonly,
+ op->verbose)) < 0) {
+ if (op->verbose)
+ pr2serr("error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ ret = do_read_gplog(sg_fd, ata_cmd, inbuff, op);
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_sat_read_gplog failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ if (free_inbuff)
+ free(free_inbuff);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sat_set_features.c b/src/sg_sat_set_features.c
new file mode 100644
index 00000000..3a6712ab
--- /dev/null
+++ b/src/sg_sat_set_features.c
@@ -0,0 +1,463 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.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_pr2serr.h"
+
+/* This program uses a ATA PASS-THROUGH SCSI command. This usage is
+ * defined in the SCSI to ATA Translation (SAT) drafts and standards.
+ * See https://www.t10.org for drafts. SAT is a standard: SAT ANSI INCITS
+ * 431-2007 (draft prior to that is sat-r09.pdf). SAT-2 is also a
+ * standard: SAT-2 ANSI INCITS 465-2010 and the draft prior to that is
+ * sat2r09.pdf . The SAT-3 project has started and the most recent draft
+ * is sat3r01.pdf .
+ */
+
+/* This program performs a ATA PASS-THROUGH (16) SCSI command in order
+ * to perform an ATA SET FEATURES command.
+ *
+ * See man page (sg_sat_set_features.8) for details.
+ */
+
+#define SAT_ATA_PASS_THROUGH16 0x85
+#define SAT_ATA_PASS_THROUGH16_LEN 16
+#define SAT_ATA_PASS_THROUGH12 0xa1 /* clashes with MMC BLANK command */
+#define SAT_ATA_PASS_THROUGH12_LEN 12
+#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */
+#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
+
+#define ATA_SET_FEATURES 0xef
+
+#define DEF_TIMEOUT 20
+
+static const char * version_str = "1.19 20220425";
+
+static struct option long_options[] = {
+ {"count", required_argument, 0, 'c'},
+ {"ck_cond", no_argument, 0, 'C'},
+ {"ck-cond", no_argument, 0, 'C'},
+ {"extended", no_argument, 0, 'e'},
+ {"feature", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"len", required_argument, 0, 'l'},
+ {"lba", required_argument, 0, 'L'},
+ {"readonly", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_sat_set_features [--count=CO] [--ck_cond] [--extended] "
+ "[--feature=FEA]\n"
+ " [--help] [--lba=LBA] [--len=16|12] "
+ "[--readonly]\n"
+ " [--verbose] [--version] DEVICE\n"
+ " where:\n"
+ " --count=CO | -c CO count field contents (def: 0)\n"
+ " --ck_cond | -C set ck_cond field in pass-through "
+ "(def: 0)\n"
+ " --extended | -e enable extended lba values\n"
+ " --feature=FEA|-f FEA feature field contents\n"
+ " (def: 0 (which is reserved))\n"
+ " --help | -h output this usage message\n"
+ " --lba=LBA | -L LBA LBA field contents (def: 0)\n"
+ " meaning depends on sub-command "
+ "(feature)\n"
+ " --len=16|12 | -l 16|12 cdb length: 16 or 12 bytes "
+ "(def: 16)\n"
+ " --verbose | -v increase verbosity\n"
+ " --readonly | -r open DEVICE read-only (def: "
+ "read-write)\n"
+ " recommended if DEVICE is ATA disk\n"
+ " --version | -V print version string and exit\n\n"
+ "Sends an ATA SET FEATURES command via a SAT pass through.\n"
+ "Primary feature code is placed in '--feature=FEA' with "
+ "'--count=CO' and\n"
+ "'--lba=LBA' being auxiliaries for some features. The arguments "
+ "CO, FEA\n"
+ "and LBA are decimal unless prefixed by '0x' or have a trailing "
+ "'h'.\n"
+ "Example enabling write cache: 'sg_sat_set_feature --feature=2 "
+ "/dev/sdc'\n");
+}
+
+static int
+do_set_features(int sg_fd, int feature, int count, uint64_t lba,
+ int cdb_len, bool ck_cond, bool extend, int verbose)
+{
+ const bool t_type = false; /* false -> 512 byte blocks, true -> device's
+ LB size */
+ const bool t_dir = true; /* false -> to device, true -> from device */
+ const bool byte_block = true; /* false -> bytes, true -> 512 byte blocks
+ (if t_type=false) */
+ bool got_ard = false; /* got ATA result descriptor */
+ int res, ret;
+ /* Following for ATA READ/WRITE MULTIPLE (EXT) cmds, normally 0 */
+ int multiple_count = 0;
+ int protocol = 3; /* non-data */
+ int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */
+ int resid = 0;
+ int sb_sz;
+ struct sg_scsi_sense_hdr ssh;
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
+ uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
+ {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
+ {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+
+ sb_sz = sizeof(sense_buffer);
+ if (16 == cdb_len) {
+ /* Prepare ATA PASS-THROUGH COMMAND (16) command */
+ apt_cdb[14] = ATA_SET_FEATURES;
+ apt_cdb[4] = feature;
+ apt_cdb[6] = count;
+ apt_cdb[8] = lba & 0xff;
+ apt_cdb[10] = (lba >> 8) & 0xff;
+ apt_cdb[12] = (lba >> 16) & 0xff;
+ apt_cdb[7] = (lba >> 24) & 0xff;
+ apt_cdb[9] = (lba >> 32) & 0xff;
+ apt_cdb[11] = (lba >> 40) & 0xff;
+ apt_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ if (extend)
+ apt_cdb[1] |= 0x1;
+ apt_cdb[2] = t_length;
+ if (ck_cond)
+ apt_cdb[2] |= 0x20;
+ if (t_type)
+ apt_cdb[2] |= 0x10;
+ if (t_dir)
+ apt_cdb[2] |= 0x8;
+ if (byte_block)
+ apt_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt_cdb, cdb_len, DEF_TIMEOUT, NULL,
+ NULL /* doutp */, 0, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ } else {
+ /* Prepare ATA PASS-THROUGH COMMAND (12) command */
+ apt12_cdb[9] = ATA_SET_FEATURES;
+ apt12_cdb[3] = feature;
+ apt12_cdb[4] = count;
+ apt12_cdb[5] = lba & 0xff;
+ apt12_cdb[6] = (lba >> 8) & 0xff;
+ apt12_cdb[7] = (lba >> 16) & 0xff;
+ apt12_cdb[1] = (multiple_count << 5) | (protocol << 1);
+ apt12_cdb[2] = t_length;
+ if (ck_cond)
+ apt12_cdb[2] |= 0x20;
+ if (t_type)
+ apt12_cdb[2] |= 0x10;
+ if (t_dir)
+ apt12_cdb[2] |= 0x8;
+ if (byte_block)
+ apt12_cdb[2] |= 0x4;
+ res = sg_ll_ata_pt(sg_fd, apt12_cdb, cdb_len, DEF_TIMEOUT, NULL,
+ NULL /* doutp */, 0, sense_buffer,
+ sb_sz, ata_return_desc,
+ sizeof(ata_return_desc), &resid, verbose);
+ }
+ if (0 == res) {
+ if (verbose > 2)
+ pr2serr("command completed with SCSI GOOD status\n");
+ } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
+ if (verbose > 1) {
+ pr2serr("ATA pass through:\n");
+ sg_print_sense(NULL, sense_buffer, sb_sz,
+ ((verbose > 2) ? 1 : 0));
+ }
+ if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
+ switch (ssh.sense_key) {
+ case SPC_SK_ILLEGAL_REQUEST:
+ if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d) not supported\n",
+ cdb_len);
+ } else {
+ ret = SG_LIB_CAT_ILLEGAL_REQ;
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), bad field in cdb\n",
+ cdb_len);
+ }
+ return ret;
+ case SPC_SK_NO_SENSE:
+ case SPC_SK_RECOVERED_ERROR:
+ if ((0x0 == ssh.asc) &&
+ (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
+ if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
+ if (verbose)
+ pr2serr("did not find ATA Return (sense) "
+ "Descriptor\n");
+ return SG_LIB_CAT_RECOVERED;
+ }
+ got_ard = true;
+ break;
+ } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
+ return SG_LIB_CAT_RECOVERED;
+ else {
+ if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
+ break;
+ return SG_LIB_CAT_SENSE;
+ }
+ case SPC_SK_UNIT_ATTENTION:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), Unit Attention detected\n",
+ cdb_len);
+ return SG_LIB_CAT_UNIT_ATTENTION;
+ case SPC_SK_NOT_READY:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), device not ready\n",
+ cdb_len);
+ return SG_LIB_CAT_NOT_READY;
+ case SPC_SK_MEDIUM_ERROR:
+ case SPC_SK_HARDWARE_ERROR:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), medium or hardware "
+ "error\n", cdb_len);
+ return SG_LIB_CAT_MEDIUM_HARD;
+ case SPC_SK_ABORTED_COMMAND:
+ if (0x10 == ssh.asc) {
+ pr2serr("Aborted command: protection information\n");
+ return SG_LIB_CAT_PROTECTION;
+ } else {
+ pr2serr("Aborted command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ case SPC_SK_DATA_PROTECT:
+ pr2serr("ATA PASS-THROUGH (%d): data protect, read only "
+ "media?\n", cdb_len);
+ return SG_LIB_CAT_DATA_PROTECT;
+ default:
+ if (verbose < 2)
+ pr2serr("ATA PASS-THROUGH (%d), some sense data, use "
+ "'-v' for more information\n", cdb_len);
+ return SG_LIB_CAT_SENSE;
+ }
+ } else {
+ pr2serr("CHECK CONDITION without response code ??\n");
+ return SG_LIB_CAT_SENSE;
+ }
+ if (0x72 != (sense_buffer[0] & 0x7f)) {
+ pr2serr("expected descriptor sense format, response code=0x%x\n",
+ sense_buffer[0]);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else if (res > 0) {
+ if (SAM_STAT_RESERVATION_CONFLICT == res) {
+ pr2serr("SCSI status: RESERVATION CONFLICT\n");
+ return SG_LIB_CAT_RES_CONFLICT;
+ } else {
+ pr2serr("Unexpected SCSI status=0x%x\n", res);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ } else {
+ pr2serr("ATA pass through (%d) failed\n", cdb_len);
+ if (verbose < 2)
+ pr2serr(" try adding '-v' for more information\n");
+ return -1;
+ }
+
+ if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
+ pr2serr("Seem to have got ATA Result Descriptor but it was not "
+ "indicated\n");
+ if (got_ard) {
+ if (ata_return_desc[3] & 0x4) {
+ pr2serr("error indication in returned FIS: aborted command\n");
+ return SG_LIB_CAT_ABORTED_COMMAND;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool ck_cond = false;
+ bool extend = false;
+ bool rdonly = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, ret, res;
+ int sg_fd = -1;
+ int count = 0;
+ int feature = 0;
+ int verbose = 0;
+ int cdb_len = SAT_ATA_PASS_THROUGH16_LEN;
+ uint64_t lba = 0;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:Cef:hl:L:rvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ count = sg_get_num(optarg);
+ if ((count < 0) || (count > 255)) {
+ pr2serr("bad argument for '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'C':
+ ck_cond = true;
+ break;
+ case 'e':
+ extend = true;
+ break;
+ case 'f':
+ feature = sg_get_num(optarg);
+ if ((feature < 0) || (feature > 255)) {
+ pr2serr("bad argument for '--feature'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ cdb_len = sg_get_num(optarg);
+ if (! ((cdb_len == 12) || (cdb_len == 16))) {
+ pr2serr("argument to '--len' should be 12 or 16\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'L': /* up to 32 bits, allow for 48 bits (less -1) */
+ lba = sg_get_llnum(optarg);
+ if ((uint64_t)-1 == lba) {
+ pr2serr("bad argument for '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r':
+ rdonly = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return 1;
+ }
+
+ if (lba > 0xffffff) {
+ if (12 == cdb_len) {
+ cdb_len = 16;
+ if (verbose)
+ pr2serr("Since lba > 0xffffff, forcing cdb length to 16\n");
+ }
+ if (16 == cdb_len) {
+ if (! extend) {
+ extend = true;
+ if (verbose)
+ pr2serr("Since lba > 0xffffff, set extend bit\n");
+ }
+ }
+ }
+
+ if ((sg_fd = sg_cmds_open_device(device_name, rdonly, verbose)) < 0) {
+ if (verbose)
+ pr2serr("error opening file: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ ret = do_set_features(sg_fd, feature, count, lba, cdb_len, ck_cond,
+ extend, verbose);
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_sat_set_feature failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_scan_linux.c b/src/sg_scan_linux.c
new file mode 100644
index 00000000..0dddaa83
--- /dev/null
+++ b/src/sg_scan_linux.c
@@ -0,0 +1,629 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 1999 - 2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program scans the "sg" device space (ie actual + simulated SCSI
+ * generic devices). Optionally sg_scan can be given other device names
+ * to scan (in place of the sg devices).
+ * Options: -a alpha scan: scan /dev/sga,b,c, ....
+ * -i do SCSI inquiry on device (implies -w)
+ * -n numeric scan: scan /dev/sg0,1,2, ....
+ * -V output version string and exit
+ * -w open writable (new driver opens readable unless -i)
+ * -x extra information output
+ *
+ * By default this program will look for /dev/sg0 first (i.e. numeric scan)
+ *
+ * Note: This program is written to work under both the original and
+ * the new sg driver.
+ *
+ * F. Jansen - modification to extend beyond 26 sg devices.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <scsi/scsi_ioctl.h>
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "4.18 20220118";
+
+#define ME "sg_scan: "
+
+#define NUMERIC_SCAN_DEF true /* change to false to make alpha scan default */
+
+#define INQ_REPLY_LEN 36
+#define INQ_CMD_LEN 6
+#define MAX_ERRORS 4
+
+#define EBUFF_SZ 256
+#define FNAME_SZ 64
+#define PRESENT_ARRAY_SIZE 8192
+
+static const char * sysfs_sg_dir = "/sys/class/scsi_generic";
+static int * gen_index_arr;
+
+typedef struct my_scsi_idlun {
+/* why can't userland see this structure ??? */
+ int dev_id;
+ int host_unique_id;
+} My_scsi_idlun;
+
+typedef struct my_sg_scsi_id {
+ int host_no; /* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
+ int channel;
+ int scsi_id; /* scsi id of target device */
+ int lun;
+ int scsi_type; /* TYPE_... defined in scsi/scsi.h */
+ short h_cmd_per_lun;/* host (adapter) maximum commands per lun */
+ short d_queue_depth;/* device (or adapter) maximum queue length */
+ int unused1; /* probably find a good use, set 0 for now */
+ int unused2; /* ditto */
+} My_sg_scsi_id;
+
+int sg3_inq(int sg_fd, uint8_t * inqBuff, bool do_extra);
+int scsi_inq(int sg_fd, uint8_t * inqBuff);
+int try_ata_identity(const char * file_namep, int ata_fd, bool do_inq);
+
+static uint8_t inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+
+
+void usage()
+{
+ printf("Usage: sg_scan [-a] [-i] [-n] [-v] [-V] [-w] [-x] "
+ "[DEVICE]*\n");
+ printf(" where:\n");
+ printf(" -a do alpha scan (ie sga, sgb, sgc)\n");
+ printf(" -i do SCSI INQUIRY, output results\n");
+ printf(" -n do numeric scan (ie sg0, sg1...) [default]\n");
+ printf(" -v increase verbosity\n");
+ printf(" -V output version string then exit\n");
+ printf(" -w force open with read/write flag\n");
+ printf(" -x extra information output about queuing\n");
+ printf(" DEVICE name of device\n");
+}
+
+static int scandir_select(const struct dirent * s)
+{
+ int k;
+
+ if (1 == sscanf(s->d_name, "sg%d", &k)) {
+ if ((k >= 0) && (k < PRESENT_ARRAY_SIZE)) {
+ gen_index_arr[k] = 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int sysfs_sg_scan(const char * dir_name)
+{
+ struct dirent ** namelist;
+ int num, k;
+
+ num = scandir(dir_name, &namelist, scandir_select, NULL);
+ if (num < 0)
+ return -errno;
+ for (k = 0; k < num; ++k)
+ free(namelist[k]);
+ free(namelist);
+ return num;
+}
+
+void make_dev_name(char * fname, int k, bool do_numeric)
+{
+ char buff[FNAME_SZ];
+ int big,little;
+
+ strcpy(fname, "/dev/sg");
+ if (do_numeric) {
+ snprintf(buff, sizeof(buff), "%d", k);
+ strcat(fname, buff);
+ }
+ else {
+ if (k < 26) {
+ buff[0] = 'a' + (char)k;
+ buff[1] = '\0';
+ strcat(fname, buff);
+ }
+ else if (k <= 255) { /* assumes sequence goes x,y,z,aa,ab,ac etc */
+ big = k/26;
+ little = k - (26 * big);
+ big = big - 1;
+
+ buff[0] = 'a' + (char)big;
+ buff[1] = 'a' + (char)little;
+ buff[2] = '\0';
+ strcat(fname, buff);
+ }
+ else
+ strcat(fname, "xxxx");
+ }
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool do_extra = false;
+ bool do_inquiry = false;
+ bool do_numeric = NUMERIC_SCAN_DEF;
+ bool eacces_err = false;
+ bool has_file_args = false;
+ bool has_sysfs_sg = false;
+ bool jmp_out;
+ bool sg_ver3 = false;
+ bool sg_ver3_set = false;
+ bool writeable = false;
+ int sg_fd, res, k, j, f, plen;
+ int emul = -1;
+ int flags;
+ int host_no;
+ const int max_file_args = PRESENT_ARRAY_SIZE;
+ int num_errors = 0;
+ int num_silent = 0;
+ int verbose = 0;
+ char * file_namep;
+ const char * cp;
+ char fname[FNAME_SZ];
+ char ebuff[EBUFF_SZ];
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ My_scsi_idlun my_idlun;
+ struct stat a_stat;
+
+ if (NULL == (gen_index_arr =
+ (int *)calloc(max_file_args + 1, sizeof(int)))) {
+ printf(ME "Out of memory\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ strcpy(fname, "<null>");
+
+ for (k = 1, j = 0; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'a':
+ do_numeric = false;
+ break;
+ case 'h':
+ case '?':
+ printf("Scan sg device names and optionally do an "
+ "INQUIRY\n\n");
+ usage();
+ return 0;
+ case 'i':
+ do_inquiry = true;
+ break;
+ case 'n':
+ do_numeric = true;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr("Version string: %s\n", version_str);
+ exit(0);
+ case 'w':
+ writeable = true;
+ break;
+ case 'x':
+ do_extra = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ if (j < max_file_args) {
+ has_file_args = true;
+ gen_index_arr[j++] = k;
+ } else {
+ printf("Too many command line arguments\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+
+ if ((! has_file_args) && (stat(sysfs_sg_dir, &a_stat) >= 0) &&
+ (S_ISDIR(a_stat.st_mode)))
+ has_sysfs_sg = !! sysfs_sg_scan(sysfs_sg_dir);
+
+ flags = O_NONBLOCK | (writeable ? O_RDWR : O_RDONLY);
+
+ for (k = 0, res = 0, j = 0, sg_fd = -1;
+ (k < max_file_args) && (has_file_args || (num_errors < MAX_ERRORS));
+ ++k, res = ((sg_fd >= 0) ? close(sg_fd) : 0)) {
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "Error closing %s ", fname);
+ perror(ebuff);
+ return SG_LIB_FILE_ERROR;
+ }
+ if (has_file_args) {
+ if (gen_index_arr[j])
+ file_namep = argv[gen_index_arr[j++]];
+ else
+ break;
+ } else if (has_sysfs_sg) {
+ if (0 == gen_index_arr[k]) {
+ sg_fd = -1;
+ continue;
+ }
+ make_dev_name(fname, k, 1);
+ file_namep = fname;
+ } else {
+ make_dev_name(fname, k, do_numeric);
+ file_namep = fname;
+ }
+
+ sg_fd = open(file_namep, flags);
+ if (sg_fd < 0) {
+ if (EBUSY == errno) {
+ printf("%s: device busy (O_EXCL lock), skipping\n",
+ file_namep);
+ continue;
+ }
+ else if ((ENODEV == errno) || (ENOENT == errno) ||
+ (ENXIO == errno)) {
+ if (verbose)
+ pr2serr("Unable to open: %s, errno=%d\n", file_namep,
+ errno);
+ ++num_errors;
+ ++num_silent;
+ continue;
+ }
+ else {
+ if (EACCES == errno)
+ eacces_err = true;
+ snprintf(ebuff, EBUFF_SZ, ME "Error opening %s ", file_namep);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ }
+ res = ioctl(sg_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun);
+ if (res < 0) {
+ res = try_ata_identity(file_namep, sg_fd, do_inquiry);
+ if (res == 0)
+ continue;
+ snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi+ata "
+ "ioctl, skip", file_namep);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ res = ioctl(sg_fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi "
+ "ioctl(2), skip", file_namep);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ res = ioctl(sg_fd, SG_EMULATED_HOST, &emul);
+ if (res < 0)
+ emul = -1;
+ printf("%s: scsi%d channel=%d id=%d lun=%d", file_namep, host_no,
+ (my_idlun.dev_id >> 16) & 0xff, my_idlun.dev_id & 0xff,
+ (my_idlun.dev_id >> 8) & 0xff);
+ if (1 == emul)
+ printf(" [em]");
+#if 0
+ printf(", huid=%d", my_idlun.host_unique_id);
+#endif
+ if (! has_file_args) {
+ My_sg_scsi_id m_id; /* compatible with sg_scsi_id_t in sg.h */
+
+ res = ioctl(sg_fd, SG_GET_SCSI_ID, &m_id);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "device %s failed "
+ "SG_GET_SCSI_ID ioctl(4), skip", file_namep);
+ perror(ebuff);
+ ++num_errors;
+ continue;
+ }
+ /* printf(" type=%d", m_id.scsi_type); */
+ if (do_extra)
+ printf(" cmd_per_lun=%hd queue_depth=%hd\n",
+ m_id.h_cmd_per_lun, m_id.d_queue_depth);
+ else
+ printf("\n");
+ }
+ else
+ printf("\n");
+ if (do_inquiry) {
+ if (! sg_ver3_set) {
+ sg_ver3 = false;
+ sg_ver3_set = true;
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &f) >= 0) &&
+ (f >= 30000))
+ sg_ver3 = true;
+ }
+ if (sg_ver3) {
+ res = sg3_inq(sg_fd, inqBuff, do_extra);
+ if (res)
+ ++num_errors;
+ }
+ }
+ }
+ if ((num_errors >= MAX_ERRORS) && (num_silent < num_errors) &&
+ (! has_file_args)) {
+ printf("Stopping because there are too many error\n");
+ if (eacces_err)
+ printf(" root access may be required\n");
+ }
+ return 0;
+}
+
+int sg3_inq(int sg_fd, uint8_t * inqBuff, bool do_extra)
+{
+ bool ok;
+ int err, sg_io;
+ uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr SG_C_CPP_ZERO_INIT;
+
+ memset(inqBuff, 0, INQ_REPLY_LEN);
+ inqBuff[0] = 0x7f;
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = INQ_REPLY_LEN;
+ io_hdr.dxferp = inqBuff;
+ io_hdr.cmdp = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ ok = true;
+ sg_io = 0;
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ if ((err = scsi_inq(sg_fd, inqBuff)) < 0) {
+ perror(ME "Inquiry SG_IO + SCSI_IOCTL_SEND_COMMAND ioctl error");
+ return 1;
+ } else if (err) {
+ printf(ME "SCSI_IOCTL_SEND_COMMAND ioctl error=0x%x\n", err);
+ return 1;
+ }
+ } else {
+ sg_io = 1;
+ /* now for the error processing */
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("Inquiry, continuing", &io_hdr, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ ok = false;
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, true);
+ break;
+ }
+ }
+
+ if (ok) { /* output result if it is available */
+ char * p = (char *)inqBuff;
+
+ printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
+ printf("[rmb=%d cmdq=%d pqual=%d pdev=0x%x] ",
+ !!(p[1] & 0x80), !!(p[7] & 2), (p[0] & 0xe0) >> 5,
+ (p[0] & PDT_MASK));
+ if (do_extra && sg_io)
+ printf("dur=%ums\n", io_hdr.duration);
+ else
+ printf("\n");
+ }
+ return 0;
+}
+
+struct lscsi_ioctl_command {
+ unsigned int inlen; /* _excluding_ scsi command length */
+ unsigned int outlen;
+ uint8_t data[1]; /* was 0 but that's not ISO C!! */
+ /* on input, scsi command starts here then opt. data */
+};
+
+/* fallback INQUIRY using scsi mid-level's SCSI_IOCTL_SEND_COMMAND ioctl */
+int scsi_inq(int sg_fd, uint8_t * inqBuff)
+{
+ int res;
+ uint8_t buff[1024];
+ struct lscsi_ioctl_command * sicp = (struct lscsi_ioctl_command *)buff;
+
+ memset(buff, 0, sizeof(buff));
+ sicp->inlen = 0;
+ sicp->outlen = INQ_REPLY_LEN;
+ memcpy(sicp->data, inq_cdb, INQ_CMD_LEN);
+ res = ioctl(sg_fd, SCSI_IOCTL_SEND_COMMAND, sicp);
+ if (0 == res)
+ memcpy(inqBuff, sicp->data, INQ_REPLY_LEN);
+ return res;
+}
+
+/* Following code permits ATA IDENTIFY commands to be performed on
+ ATA non "Packet Interface" devices (e.g. ATA disks).
+ GPL-ed code borrowed from smartmontools (smartmontools.sf.net).
+ Copyright (C) 2002-4 Bruce Allen
+ <smartmontools-support@lists.sourceforge.net>
+ */
+#ifndef ATA_IDENTIFY_DEVICE
+#define ATA_IDENTIFY_DEVICE 0xec
+#endif
+#ifndef HDIO_DRIVE_CMD
+#define HDIO_DRIVE_CMD 0x031f
+#endif
+
+/* Needed parts of the ATA DRIVE IDENTIFY Structure. Those labeled
+ * word* are NOT used.
+ */
+struct ata_identify_device {
+ unsigned short words000_009[10];
+ uint8_t serial_no[20];
+ unsigned short words020_022[3];
+ uint8_t fw_rev[8];
+ uint8_t model[40];
+ unsigned short words047_079[33];
+ unsigned short major_rev_num;
+ unsigned short minor_rev_num;
+ unsigned short command_set_1;
+ unsigned short command_set_2;
+ unsigned short command_set_extension;
+ unsigned short cfs_enable_1;
+ unsigned short word086;
+ unsigned short csf_default;
+ unsigned short words088_255[168];
+};
+
+/* Copies n bytes (or n-1 if n is odd) from in to out, but swaps adjacents
+ * bytes.
+ */
+void swapbytes(char *out, const char *in, size_t n)
+{
+ size_t k;
+
+ if (n > 1) {
+ for (k = 0; k < (n - 1); k += 2) {
+ out[k] = in[k + 1];
+ out[k + 1] = in[k];
+ }
+ }
+}
+
+/* Copies in to out, but removes leading and trailing whitespace. */
+void trim(char *out, const char *in)
+{
+ int k, first, last, num;
+
+ /* Find the first non-space character (maybe none). */
+ first = -1;
+ for (k = 0; in[k]; k++) {
+ if (! isspace((int)in[k])) {
+ first = k;
+ break;
+ }
+ }
+
+ if (first == -1) {
+ /* There are no non-space characters. */
+ out[0] = '\0';
+ return;
+ }
+
+ /* Find the last non-space character. */
+ for (k = strlen(in) - 1; k >= first && isspace((int)in[k]); k--)
+ ;
+ last = k;
+ num = last - first + 1;
+ for (k = 0; k < num; ++k)
+ out[k] = in[first + k];
+ out[num] = '\0';
+}
+
+/* Convenience function for formatting strings from ata_identify_device */
+void formatdriveidstring(char *out, const char *in, int n)
+{
+ char tmp[65];
+
+ n = n > 64 ? 64 : n;
+ swapbytes(tmp, in, n);
+ tmp[n] = '\0';
+ trim(out, tmp);
+}
+
+/* Function for printing ASCII byte-swapped strings, skipping white
+ * space. Please note that this is needed on both big- and
+ * little-endian hardware.
+ */
+void printswap(char *output, char *in, unsigned int n)
+{
+ formatdriveidstring(output, in, n);
+ if (*output)
+ printf("%.*s ", (int)n, output);
+ else
+ printf("%.*s ", (int)n, "[No Information Found]\n");
+}
+
+#define ATA_IDENTIFY_BUFF_SZ sizeof(struct ata_identify_device)
+#define HDIO_DRIVE_CMD_OFFSET 4
+
+int ata_command_interface(int device, char *data)
+{
+ uint8_t buff[ATA_IDENTIFY_BUFF_SZ + HDIO_DRIVE_CMD_OFFSET];
+ int retval;
+
+ buff[0] = ATA_IDENTIFY_DEVICE;
+ buff[3] = 1;
+ /* We are now doing the HDIO_DRIVE_CMD type ioctl. */
+ if ((retval = ioctl(device, HDIO_DRIVE_CMD, buff)))
+ return retval;
+
+ /* if the command returns data, copy it back */
+ memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
+ return 0;
+}
+
+int try_ata_identity(const char * file_namep, int ata_fd, bool do_inq)
+{
+ struct ata_identify_device ata_ident;
+ char model[64];
+ char serial[64];
+ char firm[64];
+ int res;
+
+ res = ata_command_interface(ata_fd, (char *)&ata_ident);
+ if (res)
+ return res;
+ printf("%s: ATA device\n", file_namep);
+ if (do_inq) {
+ printf(" ");
+ printswap(model, (char *)ata_ident.model, 40);
+ printswap(serial, (char *)ata_ident.serial_no, 20);
+ printswap(firm, (char *)ata_ident.fw_rev, 8);
+ printf("\n");
+ }
+ return res;
+}
diff --git a/src/sg_scan_win32.c b/src/sg_scan_win32.c
new file mode 100644
index 00000000..d39212c7
--- /dev/null
+++ b/src/sg_scan_win32.c
@@ -0,0 +1,733 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * This utility shows the relationship between various device names and
+ * volumes in Windows OSes (Windows 2000, 2003, XP and Vista). There is
+ * an optional scsi adapter scan.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_pr2serr.h"
+
+#ifdef _WIN32_WINNT
+ #if _WIN32_WINNT < 0x0602
+ #undef _WIN32_WINNT
+ #define _WIN32_WINNT 0x0602
+ #endif
+#else
+#define _WIN32_WINNT 0x0602
+/* claim its W8 */
+#endif
+
+#include "sg_pt_win32.h"
+
+static const char * version_str = "1.23 (win32) 20220127";
+
+#define MAX_SCSI_ELEMS 4096
+#define MAX_ADAPTER_NUM 256
+#define MAX_PHYSICALDRIVE_NUM 2048
+#define MAX_CDROM_NUM 512
+#define MAX_TAPE_NUM 512
+#define MAX_HOLE_COUNT 16
+#define MAX_GET_INQUIRY_DATA_SZ (32 * 1024)
+
+
+union STORAGE_DEVICE_DESCRIPTOR_DATA {
+ STORAGE_DEVICE_DESCRIPTOR desc;
+ char raw[256];
+};
+
+union STORAGE_DEVICE_UID_DATA {
+ STORAGE_DEVICE_UNIQUE_IDENTIFIER desc;
+ char raw[1060];
+};
+
+struct storage_elem {
+ char name[32];
+ char volume_letters[32];
+ bool qp_descriptor_valid;
+ bool qp_uid_valid;
+ union STORAGE_DEVICE_DESCRIPTOR_DATA qp_descriptor;
+ union STORAGE_DEVICE_UID_DATA qp_uid;
+};
+
+
+static struct storage_elem * storage_arr;
+static uint8_t * free_storage_arr;
+static int next_unused_elem = 0;
+static int verbose = 0;
+
+static struct option long_options[] = {
+ {"bus", no_argument, 0, 'b'},
+ {"help", no_argument, 0, 'h'},
+ {"letter", required_argument, 0, 'l'},
+ {"verbose", no_argument, 0, 'v'},
+ {"scsi", no_argument, 0, 's'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_scan [--bus] [--help] [--letter=VL] [--scsi] "
+ "[--verbose] [--version]\n");
+ pr2serr(" --bus|-b output bus type\n"
+ " --help|-h output this usage message then exit\n"
+ " --letter=VL|-l VL volume letter (e.g. 'F' for F:) "
+ "to match\n"
+ " --scsi|-s used once: show SCSI adapters (tuple) "
+ "scan after\n"
+ " device scan; default: show no "
+ "adapters;\n"
+ " used twice: show only adapters\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Scan for storage and related device names\n");
+}
+
+static char *
+get_err_str(DWORD err, int max_b_len, char * b)
+{
+ char * cp;
+ struct sg_pt_base * tmp_p = construct_scsi_pt_obj();
+
+ if ((NULL == b) || (max_b_len < 2)) {
+ if (b && (max_b_len > 0))
+ b[0] = '\0';
+ return b;
+ }
+ if (NULL == tmp_p) {
+ snprintf(b, max_b_len, "%s: construct_scsi_pt_obj() failed\n",
+ __func__);
+
+ return b;
+ }
+ set_scsi_pt_transport_err(tmp_p, (int)err);
+ cp = get_scsi_pt_transport_err_str(tmp_p, max_b_len, b);
+ destruct_scsi_pt_obj(tmp_p);
+ return cp;
+}
+
+static const char *
+get_bus_type(int bt)
+{
+ switch (bt)
+ {
+ case BusTypeUnknown:
+ return "Unkno";
+ case BusTypeScsi:
+ return "Scsi ";
+ case BusTypeAtapi:
+ return "Atapi";
+ case BusTypeAta:
+ return "Ata ";
+ case BusType1394:
+ return "1394 ";
+ case BusTypeSsa:
+ return "Ssa ";
+ case BusTypeFibre:
+ return "Fibre";
+ case BusTypeUsb:
+ return "Usb ";
+ case BusTypeRAID:
+ return "RAID ";
+ case BusTypeiScsi:
+ return "iScsi";
+ case BusTypeSas:
+ return "Sas ";
+ case BusTypeSata:
+ return "Sata ";
+ case BusTypeSd:
+ return "Sd ";
+ case BusTypeMmc:
+ return "Mmc ";
+ case BusTypeVirtual:
+ return "Virt ";
+ case BusTypeFileBackedVirtual:
+ return "FBVir";
+#ifdef BusTypeSpaces
+ case BusTypeSpaces:
+#else
+ case 0x10:
+#endif
+ return "Spaces";
+#ifdef BusTypeNvme
+ case BusTypeNvme:
+#else
+ case 0x11:
+#endif
+ return "NVMe ";
+#ifdef BusTypeSCM
+ case BusTypeSCM:
+#else
+ case 0x12:
+#endif
+ return "SCM ";
+#ifdef BusTypeUfs
+ case BusTypeUfs:
+#else
+ case 0x13:
+#endif
+ return "Ufs ";
+ case 0x14:
+ return "Max ";
+ case 0x7f:
+ return "Max Reserved";
+ default:
+ return "_unkn";
+ }
+}
+
+static int
+query_dev_property(HANDLE hdevice,
+ union STORAGE_DEVICE_DESCRIPTOR_DATA * data)
+{
+ DWORD num_out, err;
+ char b[256];
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty,
+ PropertyStandardQuery, {0} };
+
+ memset(data, 0, sizeof(*data));
+ if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), data, sizeof(*data),
+ &num_out, NULL)) {
+ if (verbose > 2) {
+ err = GetLastError();
+ pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(Devprop) failed, "
+ "Error=%u %s\n", (unsigned int)err,
+ get_err_str(err, sizeof(b), b));
+ }
+ return -ENOSYS;
+ }
+
+ if (verbose > 3)
+ pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevProp) num_out=%u\n",
+ (unsigned int)num_out);
+ return 0;
+}
+
+static int
+query_dev_uid(HANDLE hdevice, union STORAGE_DEVICE_UID_DATA * data)
+{
+ DWORD num_out, err;
+ char b[256];
+ STORAGE_PROPERTY_QUERY query = {StorageDeviceUniqueIdProperty,
+ PropertyStandardQuery, {0} };
+
+ memset(data, 0, sizeof(*data));
+ num_out = 0;
+ query.QueryType = PropertyExistsQuery;
+ if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), NULL, 0, &num_out, NULL)) {
+ if (verbose > 2) {
+ err = GetLastError();
+ pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid(exists)) failed, "
+ "Error=%u %s\n", (unsigned int)err,
+ get_err_str(err, sizeof(b), b));
+ }
+ if (verbose > 3)
+ pr2serr(" num_out=%u\n", (unsigned int)num_out);
+ /* interpret any error to mean this property doesn't exist */
+ return 0;
+ }
+
+ query.QueryType = PropertyStandardQuery;
+ if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), data, sizeof(*data),
+ &num_out, NULL)) {
+ if (verbose > 2) {
+ err = GetLastError();
+ pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid) failed, Error=%u "
+ "%s\n", (unsigned int)err,
+ get_err_str(err, sizeof(b), b));
+ }
+ return -ENOSYS;
+ }
+ if (verbose > 3)
+ pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid) num_out=%u\n",
+ (unsigned int)num_out);
+ return 0;
+}
+
+/* Updates storage_arr based on sep. Returns 1 if update occurred, 0 if
+ * no update occurred. */
+static int
+check_devices(const struct storage_elem * sep)
+{
+ int k, j;
+ struct storage_elem * sarr = storage_arr;
+
+ for (k = 0; k < next_unused_elem; ++k, ++sarr) {
+ if ('\0' == sarr->name[0])
+ continue;
+ if (sep->qp_uid_valid && sarr->qp_uid_valid) {
+ if (0 == memcmp(&sep->qp_uid, &sarr->qp_uid,
+ sizeof(sep->qp_uid))) {
+ for (j = 0; j < (int)sizeof(sep->volume_letters); ++j) {
+ if ('\0' == sarr->volume_letters[j]) {
+ sarr->volume_letters[j] = sep->name[0];
+ break;
+ }
+ }
+ return 1;
+ }
+ } else if (sep->qp_descriptor_valid && sarr->qp_descriptor_valid) {
+ if (0 == memcmp(&sep->qp_descriptor, &sarr->qp_descriptor,
+ sizeof(sep->qp_descriptor))) {
+ for (j = 0; j < (int)sizeof(sep->volume_letters); ++j) {
+ if ('\0' == sarr->volume_letters[j]) {
+ sarr->volume_letters[j] = sep->name[0];
+ break;
+ }
+ }
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+enum_scsi_adapters(void)
+{
+ int k, j;
+ int hole_count = 0;
+ HANDLE fh;
+ ULONG dummy;
+ DWORD err = 0;
+ BYTE bus;
+ BOOL success;
+ char adapter_name[64];
+ char * inq_dbp;
+ uint8_t * free_inq_dbp = NULL;
+ PSCSI_ADAPTER_BUS_INFO ai;
+ char b[256];
+
+ inq_dbp = (char *)sg_memalign(MAX_GET_INQUIRY_DATA_SZ, 0, &free_inq_dbp,
+ false);
+ if (NULL == inq_dbp) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ MAX_GET_INQUIRY_DATA_SZ);
+ return sg_convert_errno(ENOMEM);
+ }
+
+ for (k = 0; k < MAX_ADAPTER_NUM; ++k) {
+ snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\SCSI%d:", k);
+ fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ if (fh != INVALID_HANDLE_VALUE) {
+ hole_count = 0;
+ success = DeviceIoControl(fh, IOCTL_SCSI_GET_INQUIRY_DATA, NULL,
+ 0, inq_dbp, MAX_GET_INQUIRY_DATA_SZ,
+ &dummy, NULL);
+ if (success) {
+ PSCSI_BUS_DATA pbd;
+ PSCSI_INQUIRY_DATA pid;
+ int num_lus, off;
+
+ ai = (PSCSI_ADAPTER_BUS_INFO)inq_dbp;
+ for (bus = 0; bus < ai->NumberOfBusses; bus++) {
+ pbd = ai->BusData + bus;
+ num_lus = pbd->NumberOfLogicalUnits;
+ off = pbd->InquiryDataOffset;
+ for (j = 0; j < num_lus; ++j) {
+ if ((off < (int)sizeof(SCSI_ADAPTER_BUS_INFO)) ||
+ (off > (MAX_GET_INQUIRY_DATA_SZ -
+ (int)sizeof(SCSI_INQUIRY_DATA))))
+ break;
+ pid = (PSCSI_INQUIRY_DATA)(inq_dbp + off);
+ snprintf(b, sizeof(b) - 1, "SCSI%d:%d,%d,%d ", k,
+ pid->PathId, pid->TargetId, pid->Lun);
+ printf("%-15s", b);
+ snprintf(b, sizeof(b) - 1, "claimed=%d pdt=%xh %s ",
+ pid->DeviceClaimed,
+ pid->InquiryData[0] % PDT_MASK,
+ ((0 == pid->InquiryData[4]) ? "dubious" :
+ ""));
+ printf("%-26s", b);
+ printf("%.8s %.16s %.4s\n", pid->InquiryData + 8,
+ pid->InquiryData + 16, pid->InquiryData + 32);
+ off = pid->NextInquiryDataOffset;
+ }
+ }
+ } else {
+ err = GetLastError();
+ pr2serr("%s: IOCTL_SCSI_GET_INQUIRY_DATA failed err=%u\n\t%s",
+ adapter_name, (unsigned int)err,
+ get_err_str(err, sizeof(b), b));
+ err = SG_LIB_WINDOWS_ERR;
+ }
+ CloseHandle(fh);
+ } else {
+ err = GetLastError();
+ if (ERROR_SHARING_VIOLATION == err)
+ pr2serr("%s: in use by other process (sharing violation "
+ "[34])\n", adapter_name);
+ else if (verbose > 3)
+ pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
+ (unsigned int)err, get_err_str(err, sizeof(b), b));
+ if (++hole_count >= MAX_HOLE_COUNT)
+ break;
+ /* hope problem is local to this adapter so continue to next */
+ }
+ }
+ if (free_inq_dbp)
+ free(free_inq_dbp);
+ return 0;
+}
+
+static int
+enum_volumes(char letter)
+{
+ int k;
+ HANDLE fh;
+ char adapter_name[64];
+ struct storage_elem tmp_se;
+
+ if (verbose > 2)
+ pr2serr("%s: enter\n", __FUNCTION__ );
+ for (k = 0; k < 24; ++k) {
+ memset(&tmp_se, 0, sizeof(tmp_se));
+ snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\%c:", 'C' + k);
+ tmp_se.name[0] = 'C' + k;
+ fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ if (fh != INVALID_HANDLE_VALUE) {
+ if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
+ pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
+ else
+ tmp_se.qp_descriptor_valid = true;
+ if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
+ if (verbose > 2)
+ pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
+ } else
+ tmp_se.qp_uid_valid = true;
+ if (('\0' == letter) || (letter == tmp_se.name[0]))
+ check_devices(&tmp_se);
+ CloseHandle(fh);
+ }
+ }
+ return 0;
+}
+
+static int
+enum_pds(void)
+{
+ int k;
+ int hole_count = 0;
+ HANDLE fh;
+ DWORD err;
+ char adapter_name[64];
+ char b[256];
+ struct storage_elem tmp_se;
+
+ if (verbose > 2)
+ pr2serr("%s: enter\n", __FUNCTION__ );
+ for (k = 0; k < MAX_PHYSICALDRIVE_NUM; ++k) {
+ memset(&tmp_se, 0, sizeof(tmp_se));
+ snprintf(adapter_name, sizeof (adapter_name),
+ "\\\\.\\PhysicalDrive%d", k);
+ snprintf(tmp_se.name, sizeof(tmp_se.name), "PD%d", k);
+ fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ if (fh != INVALID_HANDLE_VALUE) {
+ if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
+ pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
+ else
+ tmp_se.qp_descriptor_valid = true;
+ if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
+ if (verbose > 2)
+ pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
+ } else
+ tmp_se.qp_uid_valid = true;
+ hole_count = 0;
+ memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
+ CloseHandle(fh);
+ } else {
+ err = GetLastError();
+ if ((0 == k) && (ERROR_ACCESS_DENIED == err))
+ pr2serr("Access denied on %s, may need Administrator\n",
+ adapter_name);
+ if (ERROR_SHARING_VIOLATION == err)
+ pr2serr("%s: in use by other process (sharing violation "
+ "[34])\n", adapter_name);
+ else if (verbose > 3)
+ pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
+ (unsigned int)err, get_err_str(err, sizeof(b), b));
+ if (++hole_count >= MAX_HOLE_COUNT)
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+enum_cdroms(void)
+{
+ int k;
+ int hole_count = 0;
+ HANDLE fh;
+ DWORD err;
+ char adapter_name[64];
+ char b[256];
+ struct storage_elem tmp_se;
+
+ if (verbose > 2)
+ pr2serr("%s: enter\n", __FUNCTION__ );
+ for (k = 0; k < MAX_CDROM_NUM; ++k) {
+ memset(&tmp_se, 0, sizeof(tmp_se));
+ snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\CDROM%d", k);
+ snprintf(tmp_se.name, sizeof(tmp_se.name), "CDROM%d", k);
+ fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ if (fh != INVALID_HANDLE_VALUE) {
+ if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
+ pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
+ else
+ tmp_se.qp_descriptor_valid = true;
+ if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
+ if (verbose > 2)
+ pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
+ } else
+ tmp_se.qp_uid_valid = true;
+ hole_count = 0;
+ memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
+ CloseHandle(fh);
+ } else {
+ err = GetLastError();
+ if (ERROR_SHARING_VIOLATION == err)
+ pr2serr("%s: in use by other process (sharing violation "
+ "[34])\n", adapter_name);
+ else if (verbose > 3)
+ pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
+ (unsigned int)err, get_err_str(err, sizeof(b), b));
+ if (++hole_count >= MAX_HOLE_COUNT)
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+enum_tapes(void)
+{
+ int k;
+ int hole_count = 0;
+ HANDLE fh;
+ DWORD err;
+ char adapter_name[64];
+ char b[256];
+ struct storage_elem tmp_se;
+
+ if (verbose > 2)
+ pr2serr("%s: enter\n", __FUNCTION__ );
+ for (k = 0; k < MAX_TAPE_NUM; ++k) {
+ memset(&tmp_se, 0, sizeof(tmp_se));
+ snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\TAPE%d", k);
+ snprintf(tmp_se.name, sizeof(tmp_se.name), "TAPE%d", k);
+ fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, 0, NULL);
+ if (fh != INVALID_HANDLE_VALUE) {
+ if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
+ pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
+ else
+ tmp_se.qp_descriptor_valid = true;
+ if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
+ if (verbose > 2)
+ pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
+ } else
+ tmp_se.qp_uid_valid = true;
+ hole_count = 0;
+ memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
+ CloseHandle(fh);
+ } else {
+ err = GetLastError();
+ if (ERROR_SHARING_VIOLATION == err)
+ pr2serr("%s: in use by other process (sharing violation "
+ "[34])\n", adapter_name);
+ else if (verbose > 3)
+ pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
+ (unsigned int)err, get_err_str(err, sizeof(b), b));
+ if (++hole_count >= MAX_HOLE_COUNT)
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+sg_do_wscan(char letter, bool show_bt, int scsi_scan)
+{
+ int k, j, n;
+ struct storage_elem * sp;
+
+ if (scsi_scan < 2) {
+ k = enum_pds();
+ if (k)
+ return k;
+ k = enum_cdroms();
+ if (k)
+ return k;
+ k = enum_tapes();
+ if (k)
+ return k;
+ k = enum_volumes(letter);
+ if (k)
+ return k;
+
+ for (k = 0; k < next_unused_elem; ++k) {
+ sp = storage_arr + k;
+ if ('\0' == sp->name[0])
+ continue;
+ printf("%-7s ", sp->name);
+ n = strlen(sp->volume_letters);
+ if (0 == n)
+ printf(" ");
+ else if (1 == n)
+ printf("[%s] ", sp->volume_letters);
+ else if (2 == n)
+ printf("[%s] ", sp->volume_letters);
+ else if (3 == n)
+ printf("[%s] ", sp->volume_letters);
+ else if (4 == n)
+ printf("[%s] ", sp->volume_letters);
+ else
+ printf("[%4s+] ", sp->volume_letters);
+ if (sp->qp_descriptor_valid) {
+ if (show_bt)
+ printf("<%s> ",
+ get_bus_type(sp->qp_descriptor.desc.BusType));
+ j = sp->qp_descriptor.desc.VendorIdOffset;
+ if (j > 0)
+ printf("%s ", sp->qp_descriptor.raw + j);
+ j = sp->qp_descriptor.desc.ProductIdOffset;
+ if (j > 0)
+ printf("%s ", sp->qp_descriptor.raw + j);
+ j = sp->qp_descriptor.desc.ProductRevisionOffset;
+ if (j > 0)
+ printf("%s ", sp->qp_descriptor.raw + j);
+ j = sp->qp_descriptor.desc.SerialNumberOffset;
+ if (j > 0)
+ printf("%s", sp->qp_descriptor.raw + j);
+ printf("\n");
+ if (verbose > 2)
+ hex2stderr((const uint8_t *)sp->qp_descriptor.raw, 144, 0);
+ } else
+ printf("\n");
+ if ((verbose > 3) && sp->qp_uid_valid) {
+ printf(" UID valid, in hex:\n");
+ hex2stderr((const uint8_t *)sp->qp_uid.raw,
+ sizeof(sp->qp_uid.raw), 0);
+ }
+ }
+ }
+
+ if (scsi_scan) {
+ if (scsi_scan < 2)
+ printf("\n");
+ enum_scsi_adapters();
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool show_bt = false;
+ int c, ret;
+ int vol_letter = 0;
+ int scsi_scan = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bhHl:svV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ show_bt = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ vol_letter = toupper(optarg[0]);
+ if ((vol_letter < 'C') || (vol_letter > 'Z')) {
+ pr2serr("'--letter=' expects a letter in the 'C' to 'Z' "
+ "range\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ ++scsi_scan;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ storage_arr = (struct storage_elem *)
+ sg_memalign(sizeof(struct storage_elem) * MAX_SCSI_ELEMS, 0,
+ &free_storage_arr, false);
+ if (storage_arr) {
+ ret = sg_do_wscan(vol_letter, show_bt, scsi_scan);
+ if (free_storage_arr)
+ free(free_storage_arr);
+ } else {
+ pr2serr("Failed to allocate storage_arr (%d bytes) on heap\n",
+ (int)(sizeof(struct storage_elem) * MAX_SCSI_ELEMS));
+ ret = sg_convert_errno(ENOMEM);
+ }
+ return ret;
+}
diff --git a/src/sg_seek.c b/src/sg_seek.c
new file mode 100644
index 00000000..fb647639
--- /dev/null
+++ b/src/sg_seek.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 2018-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+#include <time.h>
+#elif defined(HAVE_GETTIMEOFDAY)
+#include <time.h>
+#include <sys/time.h>
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/*
+ * This program issues one or more SCSI SEEK(10), PRE-FETCH(10) or
+ * PRE-FETCH(16) commands. Both PRE-FETCH commands are current and appear
+ * in the most recent SBC-4 draft (sbc4r15.pdf at time of writing) while
+ * SEEK(10) has been obsolete since SBC-2 (2004). Currently more hard disks
+ * and SSDs support SEEK(10) than PRE-FETCH. It is even unclear what
+ * SEEK(10) means (defined in SBC-1 as moving the hard disk heads to the
+ * track containing the given LBA) for a SSD. But if the manufacturers'
+ * support it, then it must have a use, presumably to speed the next access
+ * to that LBA ...
+ */
+
+static const char * version_str = "1.08 20200115";
+
+#define BACKGROUND_CONTROL_SA 0x15
+
+#define CMD_ABORT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"10", no_argument, 0, 'T'},
+ {"count", required_argument, 0, 'c'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"immed", no_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"num-blocks", required_argument, 0, 'n'},
+ {"num_blocks", required_argument, 0, 'n'},
+ {"pre-fetch", no_argument, 0, 'p'},
+ {"pre_fetch", no_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'r'},
+ {"skip", required_argument, 0, 's'},
+ {"time", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrap-offset", required_argument, 0, 'w'},
+ {"wrap_offset", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_seek [--10] [--count=NC] [--grpnum=GN] [--help] [--immed]\n"
+ " [--lba=LBA] [--num-blocks=NUM] [--pre-fetch] "
+ "[--readonly]\n"
+ " [--skip=SB] [--time] [--verbose] [--version]\n"
+ " [--wrap-offset=WO] DEVICE\n");
+ pr2serr(" where:\n"
+ " --10|-T do PRE-FETCH(10) command (def: "
+ "SEEK(10), or\n"
+ " PRE-FETCH(16) if --pre-fetch also "
+ "given)\n"
+ " --count=NC|-c NC NC is number of commands to execute "
+ "(def: 1)\n"
+ " --grpnum=GN|-g GN GN is group number to place in "
+ "PRE-FETCH\n"
+ " cdb; 0 to 63 (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --immed|-i set IMMED bit in PRE-FETCH command\n"
+ " --lba=LBA|-l LBA starting Logical Block Address (LBA) "
+ "(def: 0)\n"
+ " --num-blocks=NUM|-n NUM number of blocks to cache (for "
+ "PRE-FETCH)\n"
+ " (def: 1). Ignored by "
+ "SEEK(10)\n");
+ pr2serr(" --pre-fetch|-p do PRE-FETCH command, 16 byte variant if "
+ "--10 not\n"
+ " given (def: do SEEK(10))\n"
+ " --readonly|-r open DEVICE read-only (if supported)\n"
+ " --skip=SB|-s SB when NC>1 skip SB blocks to next LBA "
+ "(def: 1)\n"
+ " --time|-t time the command(s) and if NC>1 show "
+ "usecs/command\n"
+ " (def: don't time)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --wrap-offset=WO|-w WO if SB>0 and WO>0 then if "
+ "LBAn>LBA+WO\n"
+ " then reset LBAn back to LBA (def: 0)\n\n"
+ "Performs SCSI SEEK(10), PRE-FETCH(10) or PRE-FETCH(16) "
+ "command(s).If no\noptions are given does one SEEK(10) command "
+ "with an LBA of 0 . If NC>1\nthen a tally is kept of successes, "
+ "'condition-met's and errors that is\nprinted on completion. "
+ "'condition-met' is from PRE-FETCH when NUM blocks\nfit in "
+ "the DEVICE's cache.\n"
+ );
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool cdb10 = false;
+ bool count_given = false;
+ bool do_time = false;
+ bool immed = false;
+ bool prefetch = false;
+ bool readonly = false;
+ bool start_tm_valid = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c;
+ int sg_fd = -1;
+ int first_err = 0;
+ int last_err = 0;
+ int ret = 0;
+ int verbose = 0;
+ uint32_t count = 1;
+ int32_t l;
+ uint32_t grpnum = 0;
+ uint32_t k;
+ uint32_t num_cond_met = 0;
+ uint32_t num_err = 0;
+ uint32_t num_good = 0;
+ uint32_t numblocks = 1;
+ uint32_t skip = 1;
+ uint32_t wrap_offs = 0;
+ int64_t ll;
+ int64_t elapsed_usecs = 0;
+ uint64_t lba = 0;
+ uint64_t lba_n;
+ const char * device_name = NULL;
+ const char * cdb_name = NULL;
+ char b[64];
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ struct timespec start_tm, end_tm;
+#elif defined(HAVE_GETTIMEOFDAY)
+ struct timeval start_tm, end_tm;
+#endif
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:g:hil:n:prs:tTvVw:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ l = sg_get_num(optarg);
+ if (l < 0) {
+ pr2serr("--count= unable to decode argument, want 0 or "
+ "higher\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ count = (uint32_t)l;
+ count_given = true;
+ break;
+ case 'g':
+ l = sg_get_num(optarg);
+ if ((l > 63) || (l < 0)) {
+ pr2serr("--grpnum= expect argument in range 0 to 63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ grpnum = (uint32_t)l;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ immed = true;
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("--lba= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba = (uint64_t)ll;
+ break;
+ case 'n':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--num= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ numblocks = (uint32_t)l;
+ break;
+ case 'p':
+ prefetch = true;
+ break;
+ case 'r':
+ readonly = true;
+ break;
+ case 's':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--skip= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ skip = (uint32_t)l;
+ break;
+ case 't':
+ do_time = true;
+ break;
+ case 'T':
+ cdb10 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'w':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--wrap-offset= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ wrap_offs = (uint32_t)l;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (prefetch) {
+ if (cdb10)
+ cdb_name = "Pre-fetch(10)";
+ else
+ cdb_name = "Pre-fetch(16)";
+ } else
+ cdb_name = "Seek(10)";
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s %s\n", device_name, cdb_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_nsec = 0;
+ if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
+ start_tm_valid = true;
+ else
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+#else
+ start_tm_valid = false;
+#endif
+
+ for (k = 0, lba_n = lba; k < count; ++k, lba_n += skip) {
+ if (wrap_offs && (lba_n > lba) && ((lba_n - lba) > wrap_offs))
+ lba_n = lba;
+ res = sg_ll_pre_fetch_x(sg_fd, ! prefetch, ! cdb10, immed, lba_n,
+ numblocks, grpnum, 0, (verbose > 0), verbose);
+ ret = res; /* last command executed sets exit status */
+ if (SG_LIB_CAT_CONDITION_MET == res)
+ ++num_cond_met;
+ else if (res) {
+ ++num_err;
+ if (0 == first_err)
+ first_err = res;
+ last_err = res;
+ } else
+ ++num_good;
+ }
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if ((count > 0) && start_tm_valid &&
+ (start_tm.tv_sec || start_tm.tv_nsec)) {
+ int err;
+
+ res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
+ if (res < 0) {
+ err = errno;
+ perror("clock_gettime");
+ if (EINVAL == err)
+ pr2serr("clock_gettime(CLOCK_MONOTONIC) not supported\n");
+ }
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ /* Note that (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
+ elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if ((count > 0) && start_tm_valid &&
+ (start_tm.tv_sec || start_tm.tv_usec)) {
+ gettimeofday(&end_tm, NULL);
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
+ }
+#endif
+
+ if (elapsed_usecs > 0) {
+ if (elapsed_usecs > 1000000)
+ snprintf(b, sizeof(b), " (over %d seconds)",
+ (int)elapsed_usecs / 1000000);
+ else
+ b[0] = '\0';
+ printf("Elapsed time: %" PRId64 " microseconds%s, per command time: "
+ "%" PRId64 "\n", elapsed_usecs, b, elapsed_usecs / count);
+ }
+
+ if (count_given && verbose_given)
+ printf("Command count=%u, number of condition_mets=%u, number of "
+ "goods=%u\n", count, num_cond_met, num_good);
+ if (first_err) {
+ bool printed;
+
+ printf(" number of errors=%d\n", num_err);
+ printf(" first error");
+ printed = sg_if_can2stdout(": ", first_err);
+ if (! printed)
+ printf(" code: %d\n", first_err);
+ if (num_err > 1) {
+ printf(" last error");
+ printed = sg_if_can2stdout(": ", last_err);
+ if (! printed)
+ printf(" code: %d\n", last_err);
+ }
+ }
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ const char * e_str = (SG_LIB_CAT_CONDITION_MET == ret) ?
+ "sg_seek: " : "sg_seek: failed";
+
+ if (! sg_if_can2stderr(e_str, ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_senddiag.c b/src/sg_senddiag.c
new file mode 100644
index 00000000..7e82dd47
--- /dev/null
+++ b/src/sg_senddiag.c
@@ -0,0 +1,971 @@
+/*
+ * A utility program originally written for the Linux OS SCSI subsystem
+ * Copyright (C) 2003-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ This program issues the SCSI SEND DIAGNOSTIC command and in one case
+ the SCSI RECEIVE DIAGNOSTIC command to list supported diagnostic pages.
+*/
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#if SG_LIB_WIN32
+#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */
+#endif
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "0.65 20220128";
+
+#define ME "sg_senddiag: "
+
+#define DEF_ALLOC_LEN (1024 * 4)
+
+static struct option long_options[] = {
+ {"doff", no_argument, 0, 'd'},
+ {"extdur", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"list", no_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"page", required_argument, 0, 'P'},
+ {"pf", no_argument, 0, 'p'},
+ {"raw", required_argument, 0, 'r'},
+ {"selftest", required_argument, 0, 's'},
+ {"test", no_argument, 0, 't'},
+ {"timeout", required_argument, 0, 'T'},
+ {"uoff", no_argument, 0, 'u'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_deftest;
+ bool do_doff;
+ bool do_extdur;
+ bool do_list;
+ bool do_pf;
+ bool do_raw;
+ bool do_uoff;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_help;
+ int do_hex;
+ int maxlen;
+ int page_code;
+ int do_selftest;
+ int timeout;
+ int verbose;
+ const char * device_name;
+ const char * raw_arg;
+};
+
+
+static void
+usage()
+{
+ printf("Usage: sg_senddiag [--doff] [--extdur] [--help] [--hex] "
+ "[--list]\n"
+ " [--maxlen=LEN] [--page=PG] [--pf] "
+ "[--raw=H,H...]\n"
+ " [--selftest=ST] [--test] [--timeout=SECS] "
+ "[--uoff]\n"
+ " [--verbose] [--version] [DEVICE]\n"
+ " where:\n"
+ " --doff|-d device online (def: 0, only with '--test')\n"
+ " --extdur|-e duration of an extended self-test (from mode "
+ "page 0xa)\n"
+ " --help|-h print usage message then exit\n"
+ " --hex|-H output RDR in hex; twice: plus ASCII; thrice: "
+ "suitable\n"
+ " for '--raw=-' with later invocation\n"
+ " --list|-l list supported page codes (with or without "
+ "DEVICE)\n"
+ " --maxlen=LEN|-m LEN parameter list length or maximum "
+ "allocation\n"
+ " length (default: 4096 bytes)\n"
+ " --page=PG|-P PG do RECEIVE DIAGNOSTIC RESULTS only, set "
+ "PCV\n"
+ " --pf|-p set PF bit (def: 0)\n"
+ " --raw=H,H...|-r H,H... sequence of hex bytes to form "
+ "diag page to send\n"
+ " --raw=-|-r - read stdin for sequence of bytes to send\n"
+ " --selftest=ST|-s ST self-test code, default: 0 "
+ "(inactive)\n"
+ " 1->background short, 2->background "
+ "extended\n"
+ " 4->abort test\n"
+ " 5->foreground short, 6->foreground "
+ "extended\n"
+ " --test|-t default self-test\n"
+ " --timeout=SECS|-T SECS timeout for foreground self tests\n"
+ " unit: second (def: 7200 seconds)\n"
+ " --uoff|-u unit offline (def: 0, only with '--test')\n"
+ " --verbose|-v increase verbosity\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V output version string then exit\n\n"
+ "Performs a SCSI SEND DIAGNOSTIC (and/or a RECEIVE DIAGNOSTIC "
+ "RESULTS) command\n"
+ );
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_senddiag [-doff] [-e] [-h] [-H] [-l] [-pf]"
+ " [-raw=H,H...]\n"
+ " [-s=SF] [-t] [-T=SECS] [-uoff] [-v] [-V] "
+ "[DEVICE]\n"
+ " where:\n"
+ " -doff device online (def: 0, only with '-t')\n"
+ " -e duration of an extended self-test (from mode page "
+ "0xa)\n"
+ " -h output in hex\n"
+ " -H output in hex (same as '-h')\n"
+ " -l list supported page codes\n"
+ " -pf set PF bit (def: 0)\n"
+ " -raw=H,H... sequence of bytes to form diag page to "
+ "send\n"
+ " -raw=- read stdin for sequence of bytes to send\n"
+ " -s=SF self-test code (def: 0)\n"
+ " 1->background short, 2->background extended,"
+ " 4->abort test\n"
+ " 5->foreground short, 6->foreground extended\n"
+ " -t default self-test\n"
+ " -T SECS timeout for foreground self tests\n"
+ " -uoff unit offline (def: 0, only with '-t')\n"
+ " -v increase verbosity (print issued SCSI cmds)\n"
+ " -V output version string\n"
+ " -N|--new use new interface\n"
+ " -? output this usage message\n\n"
+ "Performs a SCSI SEND DIAGNOSTIC (and/or a RECEIVE DIAGNOSTIC "
+ "RESULTS) command\n"
+ );
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "dehHlm:NOpP:r:s:tT:uvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ op->do_doff = true;
+ break;
+ case 'e':
+ op->do_extdur = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ op->do_list = true;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("bad argument to '--maxlen=' or greater than 65535 "
+ "[0xffff]\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->maxlen = n;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ op->do_pf = true;
+ break;
+ case 'P':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xff)) {
+ pr2serr("bad argument to '--page=' or greater than 255 "
+ "[0xff]\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->page_code = n;
+ break;
+ case 'r':
+ op->raw_arg = optarg;
+ op->do_raw = true;
+ break;
+ case 's':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 7)) {
+ pr2serr("bad argument to '--selftest='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_selftest = n;
+ break;
+ case 't':
+ op->do_deftest = true;
+ break;
+ case 'T':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--timeout=SECS'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->timeout = n;
+ break;
+ case 'u':
+ op->do_uoff = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num, n;
+ unsigned int u;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'd':
+ if (0 == strncmp("doff", cp, 4)) {
+ op->do_doff = true;
+ cp += 3;
+ plen -= 3;
+ } else
+ jmp_out = true;
+ break;
+ case 'e':
+ op->do_extdur = true;
+ break;
+ case 'h':
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ op->do_list = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'p':
+ if (0 == strncmp("pf", cp, 2)) {
+ op->do_pf = true;
+ ++cp;
+ --plen;
+ } else
+ jmp_out = true;
+ break;
+ case 't':
+ op->do_deftest = true;
+ break;
+ case 'u':
+ if (0 == strncmp("uoff", cp, 4)) {
+ op->do_uoff = true;
+ cp += 3;
+ plen -= 3;
+ } else
+ jmp_out = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case '?':
+ ++op->do_help;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("raw=", cp, 4)) {
+ op->raw_arg = cp + 4;
+ op->do_raw = true;
+ } else if (0 == strncmp("s=", cp, 2)) {
+ num = sscanf(cp + 2, "%x", &u);
+ if ((1 != num) || (u > 7)) {
+ printf("Bad page code after '-s=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_selftest = u;
+ } else if (0 == strncmp("T=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &n);
+ if ((1 != num) || (n < 0)) {
+ printf("Bad page code after '-T=SECS' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->timeout = n;
+ } else if (0 == strncmp("-old", cp, 5))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+/* Return of 0 -> success, otherwise see sg_ll_send_diag() */
+static int
+do_senddiag(int sg_fd, int sf_code, bool pf_bit, bool sf_bit,
+ bool devofl_bit, bool unitofl_bit, void * outgoing_pg,
+ int outgoing_len, int tmout, bool noisy, int verbose)
+{
+ int long_duration = 0;
+
+ if ((0 == sf_bit) && ((5 == sf_code) || (6 == sf_code))) {
+ /* foreground self-tests */
+ if (tmout <= 0)
+ long_duration = 1;
+ else
+ long_duration = tmout;
+ }
+ return sg_ll_send_diag(sg_fd, sf_code, pf_bit, sf_bit, devofl_bit,
+ unitofl_bit, long_duration, outgoing_pg,
+ outgoing_len, noisy, verbose);
+}
+
+/* Get expected extended self-test time from mode page 0xa (for '-e') */
+static int
+do_modes_0a(int sg_fd, void * resp, int mx_resp_len, bool mode6, bool noisy,
+ int verbose)
+{
+ int res;
+ int resid = 0;
+
+ if (mode6)
+ res = sg_ll_mode_sense6(sg_fd, true /* dbd */, false /* pc */,
+ 0xa /* page */, false, resp, mx_resp_len,
+ noisy, verbose);
+ else
+ res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, true /* dbd */,
+ false, 0xa, false, resp, mx_resp_len,
+ 0, &resid, noisy, verbose);
+ if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Mode sense (%s): %s\n", (mode6 ? "6" : "10"), b);
+ } else {
+ mx_resp_len -= resid;
+ if (mx_resp_len < 4) {
+ pr2serr("%s: response length (%d) too small (resid=%d)\n",
+ __func__, mx_resp_len, resid);
+ res = SG_LIB_WILD_RESID;
+ }
+ }
+ return res;
+}
+
+/* Read hex numbers from command line (comma separated list) or from */
+/* stdin (one per line, comma separated list or space separated list). */
+/* Returns 0 if ok, or 1 if error. */
+static int
+build_diag_page(const char * inp, uint8_t * mp_arr, int * mp_arr_len,
+ int max_arr_len)
+{
+ int in_len, k, j, m;
+ unsigned int h;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == mp_arr) ||
+ (NULL == mp_arr_len))
+ return 1;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *mp_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ bool split_line;
+ int off = 0;
+ char line[512];
+ char carry_over[4];
+
+ carry_over[0] = 0;
+ for (j = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), stdin))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit((uint8_t)line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%x", &h))
+ mp_arr[off - 1] = h; /* back up and overwrite */
+ else {
+ pr2serr("build_diag_page: carry_over error ['%s'] "
+ "around line %d\n", carry_over, j + 1);
+ return 1;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("build_diag_page: syntax error at line %d, pos %d\n",
+ j + 1, m + k + 1);
+ return 1;
+ }
+ for (k = 0; k < 1024; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("build_diag_page: hex number larger than "
+ "0xff in line %d, pos %d\n", j + 1,
+ (int)(lcp - line + 1));
+ return 1;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2serr("build_diag_page: array length exceeded\n");
+ return 1;
+ }
+ mp_arr[off + k] = h;
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("build_diag_page: error in line %d, at pos %d\n",
+ j + 1, (int)(lcp - line + 1));
+ return 1;
+ }
+ }
+ off += (k + 1);
+ }
+ *mp_arr_len = off;
+ } else { /* hex string on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("build_diag_page: error at pos %d\n", k + 1);
+ return 1;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("build_diag_page: hex number larger than 0xff at "
+ "pos %d\n", (int)(lcp - inp + 1));
+ return 1;
+ }
+ mp_arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("build_diag_page: error at pos %d\n",
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+ *mp_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("build_diag_page: array length exceeded\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+struct page_code_desc {
+ int page_code;
+ const char * desc;
+};
+static struct page_code_desc pc_desc_arr[] = {
+ {0x0, "Supported diagnostic pages"},
+ {0x1, "Configuration (SES)"},
+ {0x2, "Enclosure status/control (SES)"},
+ {0x3, "Help text (SES)"},
+ {0x4, "String In/Out (SES)"},
+ {0x5, "Threshold In/Out (SES)"},
+ {0x6, "Array Status/Control (SES, obsolete)"},
+ {0x7, "Element descriptor (SES)"},
+ {0x8, "Short enclosure status (SES)"},
+ {0x9, "Enclosure busy (SES-2)"},
+ {0xa, "Additional (device) element status (SES-2)"},
+ {0xb, "Subenclosure help text (SES-2)"},
+ {0xc, "Subenclosure string In/Out (SES-2)"},
+ {0xd, "Supported SES diagnostic pages (SES-2)"},
+ {0xe, "Download microcode diagnostic pages (SES-2)"},
+ {0xf, "Subenclosure nickname diagnostic pages (SES-2)"},
+ {0x3f, "Protocol specific (SAS transport)"},
+ {0x40, "Translate address (direct access)"},
+ {0x41, "Device status (direct access)"},
+ {0x42, "Rebuild assist (direct access)"}, /* sbc3r31 */
+};
+
+static const char *
+find_page_code_desc(int page_num)
+{
+ int k;
+ int num = SG_ARRAY_SIZE(pc_desc_arr);
+ const struct page_code_desc * pcdp = &pc_desc_arr[0];
+
+ for (k = 0; k < num; ++k, ++pcdp) {
+ if (page_num == pcdp->page_code)
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ return NULL;
+ }
+ return NULL;
+}
+
+static void
+list_page_codes()
+{
+ int k;
+ int num = SG_ARRAY_SIZE(pc_desc_arr);
+ const struct page_code_desc * pcdp = &pc_desc_arr[0];
+
+ printf("Page_Code Description\n");
+ for (k = 0; k < num; ++k, ++pcdp)
+ printf(" 0x%02x %s\n", pcdp->page_code,
+ (pcdp->desc ? pcdp->desc : "<unknown>"));
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int k, num, rsp_len, res, rsp_buff_size, pg, bd_len, resid, vb;
+ int sg_fd = -1;
+ int read_in_len = 0;
+ int ret = 0;
+ struct opts_t opts;
+ struct opts_t * op;
+ uint8_t * rsp_buff = NULL;
+ uint8_t * free_rsp_buff = NULL;
+ const char * cp;
+ uint8_t * read_in = NULL;
+ uint8_t * free_read_in = NULL;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->maxlen = DEF_ALLOC_LEN;
+ op->page_code = -1;
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ rsp_buff_size = op->maxlen;
+
+ if (NULL == op->device_name) {
+ if (op->do_list) {
+ list_page_codes();
+ return 0;
+ }
+ pr2serr("No DEVICE argument given\n\n");
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ vb = op->verbose;
+ if (op->do_raw) {
+ read_in = sg_memalign(op->maxlen, 0, &free_read_in, vb > 3);
+ if (NULL == read_in) {
+ pr2serr("unable to allocate %d bytes\n", op->maxlen);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (build_diag_page(op->raw_arg, read_in, &read_in_len, op->maxlen)) {
+ if (op->opt_new) {
+ printf("Bad sequence after '--raw=' option\n");
+ usage();
+ } else {
+ printf("Bad sequence after '-raw=' option\n");
+ usage_old();
+ }
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ }
+
+ if ((op->do_doff || op->do_uoff) && (! op->do_deftest)) {
+ if (op->opt_new) {
+ printf("setting --doff or --uoff only useful when -t is set\n");
+ usage();
+ } else {
+ printf("setting -doff or -uoff only useful when -t is set\n");
+ usage_old();
+ }
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if ((op->do_selftest > 0) && op->do_deftest) {
+ if (op->opt_new) {
+ printf("either set --selftest=SF or --test (not both)\n");
+ usage();
+ } else {
+ printf("either set -s=SF or -t (not both)\n");
+ usage_old();
+ }
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (op->do_raw) {
+ if ((op->do_selftest > 0) || op->do_deftest || op->do_extdur ||
+ op->do_list) {
+ if (op->opt_new) {
+ printf("'--raw=' cannot be used with self-tests, '-e' or "
+ "'-l'\n");
+ usage();
+ } else {
+ printf("'-raw=' cannot be used with self-tests, '-e' or "
+ "'-l'\n");
+ usage_old();
+ }
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (! op->do_pf) {
+ if (op->opt_new)
+ printf(">>> warning, '--pf' probably should be used with "
+ "'--raw='\n");
+ else
+ printf(">>> warning, '-pf' probably should be used with "
+ "'-raw='\n");
+ }
+ }
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (vb > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ if (op->maxlen >= 16384)
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+ if ((sg_fd = sg_cmds_open_device(op->device_name, false /* rw */, vb)) <
+ 0) {
+ if (vb)
+ pr2serr(ME "error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ rsp_buff = sg_memalign(op->maxlen, 0, &free_rsp_buff, vb > 3);
+ if (NULL == rsp_buff) {
+ pr2serr("unable to allocate %d bytes (2)\n", op->maxlen);
+ ret = SG_LIB_CAT_OTHER;
+ goto close_fini;
+ }
+ if (op->do_extdur) { /* fetch Extended self-test time from Control
+ * mode page with Mode Sense(10) command*/
+ res = do_modes_0a(sg_fd, rsp_buff, 32, false /* mode6 */,
+ true /* noisy */, vb);
+ if (0 == res) {
+ /* Mode sense(10) response, step over any block descriptors */
+ num = sg_msense_calc_length(rsp_buff, 32, false, &bd_len);
+ num -= (8 /* MS(10) header length */ + bd_len);
+ if (num >= 0xc) {
+ int secs = sg_get_unaligned_be16(rsp_buff + 8 + bd_len + 10);
+
+ if (0xffff == secs) {
+ if (op->verbose > 1)
+ printf("Expected extended self-test duration's value "
+ "[65535] indicates the\nsimilarly named field "
+ "in the Extended Inquiry VPD page should be "
+ "used\n");
+ } else {
+#ifdef SG_LIB_MINGW
+ printf("Expected extended self-test duration=%d seconds "
+ "(%g minutes)\n", secs, secs / 60.0);
+#else
+ printf("Expected extended self-test duration=%d seconds "
+ "(%.2f minutes)\n", secs, secs / 60.0);
+#endif
+ }
+ } else
+ printf("Extended self-test duration not available\n");
+ } else {
+ ret = res;
+ printf("Extended self-test duration (mode page 0xa) failed\n");
+ goto err_out9;
+ }
+ } else if (op->do_list || (op->page_code >= 0x0)) {
+ pg = op->page_code;
+ if (pg < 0)
+ res = do_senddiag(sg_fd, 0, true /* pf */, false, false, false,
+ rsp_buff, 4, op->timeout, 1, vb);
+ else
+ res = 0;
+ if (0 == res) {
+ resid = 0;
+ if (0 == sg_ll_receive_diag_v2(sg_fd, (pg >= 0x0),
+ ((pg >= 0x0) ? pg : 0), rsp_buff,
+ rsp_buff_size, 0, &resid,
+ true, vb)) {
+ rsp_buff_size -= resid;
+ if (rsp_buff_size < 4) {
+ pr2serr("RD resid (%d) indicates response too small "
+ "(lem=%d)\n", resid, rsp_buff_size);
+ goto err_out;
+ }
+ rsp_len = sg_get_unaligned_be16(rsp_buff + 2) + 4;
+ rsp_len= (rsp_len < rsp_buff_size) ? rsp_len : rsp_buff_size;
+ if (op->do_hex > 1)
+ hex2stdout(rsp_buff, rsp_len,
+ (2 == op->do_hex) ? 0 : -1);
+ else if (pg < 0x1) {
+ printf("Supported diagnostic pages response:\n");
+ if (op->do_hex)
+ hex2stdout(rsp_buff, rsp_len, 1);
+ else {
+ for (k = 0; k < (rsp_len - 4); ++k) {
+ pg = rsp_buff[k + 4];
+ cp = find_page_code_desc(pg);
+ if (NULL == cp)
+ cp = (pg < 0x80) ? "<unknown>" :
+ "<vendor specific>";
+ printf(" 0x%02x %s\n", pg, cp);
+ }
+ }
+ } else {
+ cp = find_page_code_desc(pg);
+ if (cp)
+ printf("%s diagnostic page [0x%x] response in "
+ "hex:\n", cp, pg);
+ else
+ printf("diagnostic page 0x%x response in hex:\n", pg);
+ hex2stdout(rsp_buff, rsp_len, 1);
+ }
+ } else {
+ ret = res;
+ pr2serr("RECEIVE DIAGNOSTIC RESULTS command failed\n");
+ goto err_out9;
+ }
+ } else {
+ ret = res;
+ goto err_out;
+ }
+ } else if (op->do_raw) {
+ res = do_senddiag(sg_fd, 0, op->do_pf, false, false, false, read_in,
+ read_in_len, op->timeout, 1, vb);
+ if (res) {
+ ret = res;
+ goto err_out;
+ }
+ } else {
+ res = do_senddiag(sg_fd, op->do_selftest, op->do_pf, op->do_deftest,
+ op->do_doff, op->do_uoff, NULL, 0, op->timeout, 1,
+ vb);
+ if (0 == res) {
+ if ((5 == op->do_selftest) || (6 == op->do_selftest))
+ printf("Foreground self-test returned GOOD status\n");
+ else if (op->do_deftest && (! op->do_doff) && (! op->do_uoff))
+ printf("Default self-test returned GOOD status\n");
+ } else {
+ ret = res;
+ goto err_out;
+ }
+ }
+ goto close_fini;
+
+err_out:
+ if (SG_LIB_CAT_UNIT_ATTENTION == res)
+ pr2serr("SEND DIAGNOSTIC, unit attention\n");
+ else if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("SEND DIAGNOSTIC, aborted command\n");
+ else if (SG_LIB_CAT_NOT_READY == res)
+ pr2serr("SEND DIAGNOSTIC, device not ready\n");
+ else
+ pr2serr("SEND DIAGNOSTIC command, failed\n");
+err_out9:
+ if (vb < 2)
+ pr2serr(" try again with '-vv' for more information\n");
+close_fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+fini:
+ if (free_read_in)
+ free(free_read_in);
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if (0 == vb) {
+ if (! sg_if_can2stderr("sg_senddiag failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_ses.c b/src/sg_ses.c
new file mode 100644
index 00000000..6ac26e8b
--- /dev/null
+++ b/src/sg_ses.c
@@ -0,0 +1,5986 @@
+/*
+ * Copyright (c) 2004-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.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"
+#include "sg_unaligned.h"
+#include "sg_pt.h"
+#include "sg_pr2serr.h"
+
+/*
+ * This program issues SCSI SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS
+ * commands tailored for SES (enclosure) devices.
+ */
+
+static const char * version_str = "2.58 20220813"; /* ses4r04 */
+
+#define MX_ALLOC_LEN ((64 * 1024) - 4) /* max allowable for big enclosures */
+#define MX_ELEM_HDR 1024
+#define REQUEST_SENSE_RESP_SZ 252
+#define DATA_IN_OFF 4
+#define MIN_MAXLEN 16
+#define MIN_DATA_IN_SZ 8192 /* use max(MIN_DATA_IN_SZ, op->maxlen) for
+ * the size of data_arr */
+#define MX_DATA_IN_LINES (16 * 1024)
+#define MX_JOIN_ROWS 520 /* element index fields in dpages are only 8
+ * bit, and index 0xff (255) is sometimes used
+ * for 'not applicable'. However this limit
+ * can bypassed with sub-enclosure numbers.
+ * So try higher figure. */
+#define MX_DATA_IN_DESCS 32
+#define NUM_ACTIVE_ET_AESP_ARR 32
+
+#define TEMPERAT_OFF 20 /* 8 bits represents -19 C to +235 C */
+ /* value of 0 (would imply -20 C) reserved */
+
+/* Send Diagnostic and Receive Diagnostic Results page codes */
+/* Sometimes referred to as "dpage"s in code comments */
+#define SUPPORTED_DPC 0x0
+#define CONFIGURATION_DPC 0x1
+#define ENC_CONTROL_DPC 0x2
+#define ENC_STATUS_DPC 0x2
+#define HELP_TEXT_DPC 0x3
+#define STRING_DPC 0x4
+#define THRESHOLD_DPC 0x5
+#define ARRAY_CONTROL_DPC 0x6 /* obsolete, last seen ses-r08b.pdf */
+#define ARRAY_STATUS_DPC 0x6 /* obsolete */
+#define ELEM_DESC_DPC 0x7
+#define SHORT_ENC_STATUS_DPC 0x8
+#define ENC_BUSY_DPC 0x9
+#define ADD_ELEM_STATUS_DPC 0xa /* Additional Element Status dpage code */
+#define SUBENC_HELP_TEXT_DPC 0xb
+#define SUBENC_STRING_DPC 0xc
+#define SUPPORTED_SES_DPC 0xd /* should be 0x1 <= dpc <= 0x2f */
+#define DOWNLOAD_MICROCODE_DPC 0xe
+#define SUBENC_NICKNAME_DPC 0xf
+#define ALL_DPC 0xff
+
+/* Element Type codes */
+#define UNSPECIFIED_ETC 0x0
+#define DEVICE_ETC 0x1
+#define POWER_SUPPLY_ETC 0x2
+#define COOLING_ETC 0x3
+#define TEMPERATURE_ETC 0x4
+#define DOOR_ETC 0x5 /* prior to ses3r05 was DOOR_LOCK_ETC */
+#define AUD_ALARM_ETC 0x6
+#define ENC_SCELECTR_ETC 0x7 /* Enclosure services controller electronics */
+#define SCC_CELECTR_ETC 0x8 /* SCC: SCSI Controller Commands (e.g. RAID
+ * controller). SCC Controller Elecronics */
+#define NV_CACHE_ETC 0x9
+#define INV_OP_REASON_ETC 0xa
+#define UI_POWER_SUPPLY_ETC 0xb
+#define DISPLAY_ETC 0xc
+#define KEY_PAD_ETC 0xd
+#define ENCLOSURE_ETC 0xe
+#define SCSI_PORT_TRAN_ETC 0xf
+#define LANGUAGE_ETC 0x10
+#define COMM_PORT_ETC 0x11
+#define VOLT_SENSOR_ETC 0x12
+#define CURR_SENSOR_ETC 0x13
+#define SCSI_TPORT_ETC 0x14
+#define SCSI_IPORT_ETC 0x15
+#define SIMPLE_SUBENC_ETC 0x16
+#define ARRAY_DEV_ETC 0x17
+#define SAS_EXPANDER_ETC 0x18
+#define SAS_CONNECTOR_ETC 0x19
+#define LAST_ETC SAS_CONNECTOR_ETC /* adjust as necessary */
+
+#define TPROTO_PCIE_PS_NVME 1 /* NVMe regarded as subset of PCIe */
+#define NUM_ETC (LAST_ETC + 1)
+
+#define DEF_CLEAR_VAL 0
+#define DEF_SET_VAL 1
+
+
+struct element_type_t {
+ int elem_type_code;
+ const char * abbrev;
+ const char * desc;
+};
+
+#define CGS_CL_ARR_MAX_SZ 8
+#define CGS_STR_MAX_SZ 80
+
+enum cgs_select_t {CLEAR_OPT, GET_OPT, SET_OPT};
+
+struct cgs_cl_t {
+ enum cgs_select_t cgs_sel;
+ bool last_cs; /* true only for last --clear= or --set= */
+ char cgs_str[CGS_STR_MAX_SZ];
+};
+
+struct opts_t {
+ bool byte1_given; /* true if -b B1 or --byte1=B1 given */
+ bool do_control; /* want to write to DEVICE */
+ bool do_data; /* flag if --data= option has been used */
+ bool do_list;
+ bool do_status; /* want to read from DEVICE (or user data) */
+ bool eiioe_auto; /* Element Index Includes Overall (status) Element */
+ bool eiioe_force;
+ bool ind_given; /* '--index=...' or '-I ...' */
+ bool inner_hex;
+ bool many_dpages; /* user supplied data has more than one dpage */
+ bool mask_ign; /* element read-mask-modify-write actions */
+ bool o_readonly;
+ bool page_code_given; /* or suitable abbreviation */
+ bool quiet; /* exit status unaltered by --quiet */
+ bool seid_given;
+ bool verbose_given;
+ bool version_given;
+ bool warn;
+ int byte1; /* (origin 0 so second byte) in Control dpage */
+ int dev_slot_num;
+ int do_filter;
+ int do_help;
+ int do_hex;
+ int do_join; /* relational join of Enclosure status, Element
+ descriptor and Additional element status dpages.
+ Use twice to add Threshold in dpage to join. */
+ int do_raw;
+ int enumerate;
+ int ind_th; /* type header index, set by build_type_desc_hdr_arr() */
+ int ind_indiv; /* individual element index; -1 for overall */
+ int ind_indiv_last; /* if > ind_indiv then [ind_indiv..ind_indiv_last] */
+ int ind_et_inst; /* ETs can have multiple type header instances */
+ int maxlen;
+ int seid;
+ int page_code; /* recognised abbreviations converted to dpage num */
+ int verbose;
+ int num_cgs; /* number of --clear-, --get= and --set= options */
+ int mx_arr_len; /* allocated size of data_arr */
+ int arr_len; /* valid bytes in data_arr */
+ uint8_t * data_arr;
+ uint8_t * free_data_arr;
+ const char * desc_name;
+ const char * dev_name;
+ const struct element_type_t * ind_etp;
+ const char * index_str;
+ const char * nickname_str;
+ struct cgs_cl_t cgs_cl_arr[CGS_CL_ARR_MAX_SZ];
+ uint8_t sas_addr[8]; /* Big endian byte sequence */
+};
+
+struct diag_page_code {
+ int page_code;
+ const char * desc;
+};
+
+struct diag_page_abbrev {
+ const char * abbrev;
+ int page_code;
+};
+
+/* The Configuration diagnostic page contains one or more of these. The
+ * elements of the Enclosure Control/Status and Threshold In/ Out page follow
+ * this format. The additional element status page is closely related to
+ * this format (with some element types and all overall elements excluded). */
+struct type_desc_hdr_t {
+ uint8_t etype; /* element type code (0: unspecified) */
+ uint8_t num_elements; /* number of possible elements, excluding
+ * overall element */
+ uint8_t se_id; /* subenclosure id (0 for primary enclosure) */
+ uint8_t txt_len; /* type descriptor text length; (unused) */
+};
+
+/* A SQL-like join of the Enclosure Status, Threshold In and Additional
+ * Element Status pages based of the format indicated in the Configuration
+ * page. Note that the array of these struct instances is built such that
+ * the array index is equal to the 'ei_ioe' (element index that includes
+ * overall elements). */
+struct join_row_t { /* this struct is 72 bytes long on Intel "64" bit arch */
+ int th_i; /* type header index (origin 0) */
+ int indiv_i; /* individual (element) index, -1 for overall
+ * instance, otherwise origin 0 */
+ uint8_t etype; /* element type */
+ uint8_t se_id; /* subenclosure id (0 for primary enclosure) */
+ int ei_eoe; /* element index referring to Enclosure status dpage
+ * descriptors, origin 0 and excludes overall
+ * elements, -1 for not applicable. As defined by
+ * SES-2 standard for the AES descriptor, EIP=1 */
+ int ei_aess; /* subset of ei_eoe that only includes elements of
+ * these types: excludes DEVICE_ETC, ARRAY_DEV_ETC,
+ * SAS_EXPANDER_ETC, SCSI_IPORT_ETC, SCSI_TPORT_ETC
+ * and ENC_SCELECTR_ETC. -1 for not applicable */
+ /* following point into Element Descriptor, Enclosure Status, Threshold
+ * In and Additional element status diagnostic pages. enc_statp only
+ * NULL beyond last, other pointers can be NULL . */
+ const uint8_t * elem_descp;
+ uint8_t * enc_statp; /* NULL indicates past last */
+ uint8_t * thresh_inp;
+ const uint8_t * ae_statp;
+ int dev_slot_num; /* if not available, set to -1 */
+ uint8_t sas_addr[8]; /* big endian, if not available, set to 0 */
+};
+
+enum fj_select_t {FJ_IOE, FJ_EOE, FJ_AESS, FJ_SAS_CON};
+
+/* Instance ('tes' in main() ) holds a type_desc_hdr_t array potentially with
+ the matching join array if present. */
+struct th_es_t {
+ const struct type_desc_hdr_t * th_base;
+ int num_ths; /* items in array pointed to by th_base */
+ struct join_row_t * j_base;
+ int num_j_rows;
+ int num_j_eoe;
+};
+
+/* Representation of <acronym>[=<value>] or
+ * <start_byte>:<start_bit>[:<num_bits>][=<value>]. Associated with
+ * --clear=, --get= or --set= option. */
+struct tuple_acronym_val {
+ const char * acron;
+ const char * val_str;
+ enum cgs_select_t cgs_sel; /* indicates --clear=, --get= or --set= */
+ int start_byte; /* -1 indicates no start_byte */
+ int start_bit;
+ int num_bits;
+ int64_t val;
+};
+
+/* Mapping from <acronym> to <start_byte>:<start_bit>:<num_bits> for a
+ * given element type. Table of known acronyms made from these elements. */
+struct acronym2tuple {
+ const char * acron; /* element name or acronym, NULL for past end */
+ int etype; /* -1 for all element types */
+ int start_byte; /* origin 0, normally 0 to 3 */
+ int start_bit; /* 7 (MSbit or leftmost in SES drafts) to 0 (LSbit) */
+ int num_bits; /* usually 1, maximum is 64 */
+ const char * info; /* optional, set to NULL if not used */
+};
+
+/* Structure for holding (sub-)enclosure information found in the
+ * Configuration diagnostic page. */
+struct enclosure_info {
+ int have_info;
+ int rel_esp_id; /* relative enclosure services process id (origin 1) */
+ int num_esp; /* number of enclosure services processes */
+ uint8_t enc_log_id[8]; /* 8 byte NAA */
+ uint8_t enc_vendor_id[8]; /* may differ from INQUIRY response */
+ uint8_t product_id[16]; /* may differ from INQUIRY response */
+ uint8_t product_rev_level[4]; /* may differ from INQUIRY response */
+};
+
+/* When --status is given with --data= the file contents may contain more
+ * than one dpage to be decoded. */
+struct data_in_desc_t {
+ bool in_use;
+ int page_code;
+ int offset; /* byte offset from op->data_arr + DATA_IN_OFF */
+ int dp_len; /* byte length of this diagnostic page */
+};
+
+
+/* Join array has four "element index"ing strategies:
+ * [1] based on all descriptors in the Enclosure Status (ES) dpage
+ * [2] based on the non-overall descriptors in the ES dpage
+ * [3] based on the non-overall descriptors of these element types
+ * in the ES dpage: DEVICE_ETC, ARRAY_DEV_ETC, SAS_EXPANDER_ETC,
+ * SCSI_IPORT_ETC, SCSI_TPORT_ETC and ENC_SCELECTR_ETC.
+ * [4] based on the non-overall descriptors of the SAS_CONNECTOR_ETC
+ * element type
+ *
+ * The indexes are all origin 0 with the maximum index being one less then
+ * the number of status descriptors in the ES dpage. Table of supported
+ * permutations follows:
+ *
+ * ==========|===============================================================
+ * Algorithm | Indexes | Notes
+ * |Element|Connector element|Other element|
+ * ==========|=======|=================|=============|=======================
+ * [A] | [2] | [4] | [3] | SES-2, OR
+ * [A] | [2] | [4] | [3] | SES-3,EIIOE=0
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [B] | [1] | [1] | [1] | SES-3, EIIOE=1
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [C] | [2] | [2] | [2] | SES-3, EIIOE=2
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [D] | [2] | [1] | [1] | SES-3, EIIOE=3
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [E] | [1] | [4] | [3] | EIIOE=0 and
+ * | | | | --eiioe=force, OR
+ * [E] | [1] | [4] | [3] | {HP JBOD} EIIOE=0 and
+ * | | | | --eiioe=auto and
+ * | | | | AES[desc_0].ei==1 .
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [F] | [2->3]| [4] | [3] | "broken_ei" when any
+ * | | | | of AES[*].ei invalid
+ * | | | | using strategy [2]
+ * ----------|-------|-----------------|-------------|-----------------------
+ * [Z] | - | [4] | [3] | EIP=0, implicit
+ * | | | | element index of [3]
+ * ==========================================================================
+ *
+ *
+ */
+static struct join_row_t join_arr[MX_JOIN_ROWS];
+static struct join_row_t * join_arr_lastp = join_arr + MX_JOIN_ROWS - 1;
+static bool join_done = false;
+
+static struct type_desc_hdr_t type_desc_hdr_arr[MX_ELEM_HDR];
+static int type_desc_hdr_count = 0;
+static uint8_t * config_dp_resp = NULL;
+static uint8_t * free_config_dp_resp = NULL;
+static int config_dp_resp_len;
+
+static struct data_in_desc_t data_in_desc_arr[MX_DATA_IN_DESCS];
+
+/* Large buffers on heap, aligned to page size and zeroed */
+static uint8_t * enc_stat_rsp;
+static uint8_t * elem_desc_rsp;
+static uint8_t * add_elem_rsp;
+static uint8_t * threshold_rsp;
+
+static unsigned enc_stat_rsp_sz;
+static unsigned elem_desc_rsp_sz;
+static unsigned add_elem_rsp_sz;
+static unsigned threshold_rsp_sz;
+
+static int enc_stat_rsp_len;
+static int elem_desc_rsp_len;
+static int add_elem_rsp_len;
+static int threshold_rsp_len;
+
+
+/* Diagnostic page names, control and/or status (in and/or out) */
+static struct diag_page_code dpc_arr[] = {
+ {SUPPORTED_DPC, "Supported Diagnostic Pages"}, /* 0 */
+ {CONFIGURATION_DPC, "Configuration (SES)"},
+ {ENC_STATUS_DPC, "Enclosure Status/Control (SES)"},
+ {HELP_TEXT_DPC, "Help Text (SES)"},
+ {STRING_DPC, "String In/Out (SES)"},
+ {THRESHOLD_DPC, "Threshold In/Out (SES)"},
+ {ARRAY_STATUS_DPC, "Array Status/Control (SES, obsolete)"},
+ {ELEM_DESC_DPC, "Element Descriptor (SES)"},
+ {SHORT_ENC_STATUS_DPC, "Short Enclosure Status (SES)"}, /* 8 */
+ {ENC_BUSY_DPC, "Enclosure Busy (SES-2)"},
+ {ADD_ELEM_STATUS_DPC, "Additional Element Status (SES-2)"},
+ {SUBENC_HELP_TEXT_DPC, "Subenclosure Help Text (SES-2)"},
+ {SUBENC_STRING_DPC, "Subenclosure String In/Out (SES-2)"},
+ {SUPPORTED_SES_DPC, "Supported SES Diagnostic Pages (SES-2)"},
+ {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"},
+ {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"},
+ {0x3f, "Protocol Specific (SAS transport)"},
+ {0x40, "Translate Address (SBC)"},
+ {0x41, "Device Status (SBC)"},
+ {0x42, "Rebuild Assist (SBC)"}, /* sbc3r31 */
+ {ALL_DPC, "All SES diagnostic pages output (sg_ses)"},
+ {-1, NULL},
+};
+
+/* Diagnostic page names, for status (or in) pages */
+static struct diag_page_code in_dpc_arr[] = {
+ {SUPPORTED_DPC, "Supported Diagnostic Pages"}, /* 0 */
+ {CONFIGURATION_DPC, "Configuration (SES)"},
+ {ENC_STATUS_DPC, "Enclosure Status (SES)"},
+ {HELP_TEXT_DPC, "Help Text (SES)"},
+ {STRING_DPC, "String In (SES)"},
+ {THRESHOLD_DPC, "Threshold In (SES)"},
+ {ARRAY_STATUS_DPC, "Array Status (SES, obsolete)"},
+ {ELEM_DESC_DPC, "Element Descriptor (SES)"},
+ {SHORT_ENC_STATUS_DPC, "Short Enclosure Status (SES)"}, /* 8 */
+ {ENC_BUSY_DPC, "Enclosure Busy (SES-2)"},
+ {ADD_ELEM_STATUS_DPC, "Additional Element Status (SES-2)"},
+ {SUBENC_HELP_TEXT_DPC, "Subenclosure Help Text (SES-2)"},
+ {SUBENC_STRING_DPC, "Subenclosure String In (SES-2)"},
+ {SUPPORTED_SES_DPC, "Supported SES Diagnostic Pages (SES-2)"},
+ {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"},
+ {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"},
+ {0x3f, "Protocol Specific (SAS transport)"},
+ {0x40, "Translate Address (SBC)"},
+ {0x41, "Device Status (SBC)"},
+ {0x42, "Rebuild Assist Input (SBC)"},
+ {-1, NULL},
+};
+
+/* Diagnostic page names, for control (or out) pages */
+static struct diag_page_code out_dpc_arr[] = {
+ {SUPPORTED_DPC, "?? [Supported Diagnostic Pages]"}, /* 0 */
+ {CONFIGURATION_DPC, "?? [Configuration (SES)]"},
+ {ENC_CONTROL_DPC, "Enclosure Control (SES)"},
+ {HELP_TEXT_DPC, "Help Text (SES)"},
+ {STRING_DPC, "String Out (SES)"},
+ {THRESHOLD_DPC, "Threshold Out (SES)"},
+ {ARRAY_CONTROL_DPC, "Array Control (SES, obsolete)"},
+ {ELEM_DESC_DPC, "?? [Element Descriptor (SES)]"},
+ {SHORT_ENC_STATUS_DPC, "?? [Short Enclosure Status (SES)]"}, /* 8 */
+ {ENC_BUSY_DPC, "?? [Enclosure Busy (SES-2)]"},
+ {ADD_ELEM_STATUS_DPC, "?? [Additional Element Status (SES-2)]"},
+ {SUBENC_HELP_TEXT_DPC, "?? [Subenclosure Help Text (SES-2)]"},
+ {SUBENC_STRING_DPC, "Subenclosure String Out (SES-2)"},
+ {SUPPORTED_SES_DPC, "?? [Supported SES Diagnostic Pages (SES-2)]"},
+ {DOWNLOAD_MICROCODE_DPC, "Download Microcode (SES-2)"},
+ {SUBENC_NICKNAME_DPC, "Subenclosure Nickname (SES-2)"},
+ {0x3f, "Protocol Specific (SAS transport)"},
+ {0x40, "Translate Address (SBC)"},
+ {0x41, "Device Status (SBC)"},
+ {0x42, "Rebuild Assist Output (SBC)"},
+ {-1, NULL},
+};
+
+static struct diag_page_abbrev dp_abbrev[] = {
+ {"ac", ARRAY_CONTROL_DPC},
+ {"aes", ADD_ELEM_STATUS_DPC},
+ {"all", ALL_DPC},
+ {"as", ARRAY_STATUS_DPC},
+ {"cf", CONFIGURATION_DPC},
+ {"dm", DOWNLOAD_MICROCODE_DPC},
+ {"eb", ENC_BUSY_DPC},
+ {"ec", ENC_CONTROL_DPC},
+ {"ed", ELEM_DESC_DPC},
+ {"es", ENC_STATUS_DPC},
+ {"ht", HELP_TEXT_DPC},
+ {"sdp", SUPPORTED_DPC},
+ {"ses", SHORT_ENC_STATUS_DPC},
+ {"sht", SUBENC_HELP_TEXT_DPC},
+ {"snic", SUBENC_NICKNAME_DPC},
+ {"ssp", SUPPORTED_SES_DPC},
+ {"sstr", SUBENC_STRING_DPC},
+ {"str", STRING_DPC},
+ {"th", THRESHOLD_DPC},
+ {NULL, -999},
+};
+
+/* Names of element types used by the Enclosure Control/Status diagnostic
+ * page. */
+static struct element_type_t element_type_arr[] = {
+ {UNSPECIFIED_ETC, "un", "Unspecified"},
+ {DEVICE_ETC, "dev", "Device slot"},
+ {POWER_SUPPLY_ETC, "ps", "Power supply"},
+ {COOLING_ETC, "coo", "Cooling"},
+ {TEMPERATURE_ETC, "ts", "Temperature sensor"},
+ {DOOR_ETC, "do", "Door"}, /* prior to ses3r05 was 'dl' (for Door Lock)
+ but the "Lock" has been dropped */
+ {AUD_ALARM_ETC, "aa", "Audible alarm"},
+ {ENC_SCELECTR_ETC, "esc", "Enclosure services controller electronics"},
+ {SCC_CELECTR_ETC, "sce", "SCC controller electronics"},
+ {NV_CACHE_ETC, "nc", "Nonvolatile cache"},
+ {INV_OP_REASON_ETC, "ior", "Invalid operation reason"},
+ {UI_POWER_SUPPLY_ETC, "ups", "Uninterruptible power supply"},
+ {DISPLAY_ETC, "dis", "Display"},
+ {KEY_PAD_ETC, "kpe", "Key pad entry"},
+ {ENCLOSURE_ETC, "enc", "Enclosure"},
+ {SCSI_PORT_TRAN_ETC, "sp", "SCSI port/transceiver"},
+ {LANGUAGE_ETC, "lan", "Language"},
+ {COMM_PORT_ETC, "cp", "Communication port"},
+ {VOLT_SENSOR_ETC, "vs", "Voltage sensor"},
+ {CURR_SENSOR_ETC, "cs", "Current sensor"},
+ {SCSI_TPORT_ETC, "stp", "SCSI target port"},
+ {SCSI_IPORT_ETC, "sip", "SCSI initiator port"},
+ {SIMPLE_SUBENC_ETC, "ss", "Simple subenclosure"},
+ {ARRAY_DEV_ETC, "arr", "Array device slot"},
+ {SAS_EXPANDER_ETC, "sse", "SAS expander"},
+ {SAS_CONNECTOR_ETC, "ssc", "SAS connector"},
+ {-1, NULL, NULL},
+};
+
+static struct element_type_t element_type_by_code =
+ {0, NULL, "element type code form"};
+
+/* Many control element names below have "RQST" in front in drafts.
+ These are for the Enclosure Control/Status diagnostic page */
+static struct acronym2tuple ecs_a2t_arr[] = {
+ /* acron element_type start_byte start_bit num_bits */
+ {"ac_fail", UI_POWER_SUPPLY_ETC, 2, 4, 1, NULL},
+ {"ac_hi", UI_POWER_SUPPLY_ETC, 2, 6, 1, NULL},
+ {"ac_lo", UI_POWER_SUPPLY_ETC, 2, 7, 1, NULL},
+ {"ac_qual", UI_POWER_SUPPLY_ETC, 2, 5, 1, NULL},
+ {"active", DEVICE_ETC, 2, 7, 1, NULL}, /* for control only */
+ {"active", ARRAY_DEV_ETC, 2, 7, 1, NULL}, /* for control only */
+ {"batt_fail", UI_POWER_SUPPLY_ETC, 3, 1, 1, NULL},
+ {"bpf", UI_POWER_SUPPLY_ETC, 3, 0, 1, NULL},
+ {"bypa", DEVICE_ETC, 3, 3, 1, "bypass port A"},
+ {"bypa", ARRAY_DEV_ETC, 3, 3, 1, "bypass port A"},
+ {"bypb", DEVICE_ETC, 3, 2, 1, "bypass port B"},
+ {"bypb", ARRAY_DEV_ETC, 3, 2, 1, "bypass port B"},
+ {"conscheck", ARRAY_DEV_ETC, 1, 4, 1, "consistency check"},
+ {"ctr_link", SAS_CONNECTOR_ETC, 2, 7, 8, "connector physical link"},
+ {"ctr_type", SAS_CONNECTOR_ETC, 1, 6, 7, "connector type"},
+ {"current", CURR_SENSOR_ETC, 2, 7, 16, "current in centiamps"},
+ {"dc_fail", UI_POWER_SUPPLY_ETC, 2, 3, 1, NULL},
+ {"disable", -1, 0, 5, 1, NULL}, /* -1 is for all element types */
+ {"disable_elm", SCSI_PORT_TRAN_ETC, 3, 4, 1, "disable port/transceiver"},
+ {"disable_elm", COMM_PORT_ETC, 3, 0, 1, "disable communication port"},
+ {"devoff", DEVICE_ETC, 3, 4, 1, NULL}, /* device off */
+ {"devoff", ARRAY_DEV_ETC, 3, 4, 1, NULL},
+ {"disp_mode", DISPLAY_ETC, 1, 1, 2, NULL},
+ {"disp_char", DISPLAY_ETC, 2, 7, 16, NULL},
+ {"dnr", ARRAY_DEV_ETC, 2, 6, 1, "do not remove"},
+ {"dnr", COOLING_ETC, 1, 6, 1, "do not remove"},
+ {"dnr", DEVICE_ETC, 2, 6, 1, "do not remove"},
+ {"dnr", ENC_SCELECTR_ETC, 1, 5, 1, "do not remove"},
+ {"dnr", POWER_SUPPLY_ETC, 1, 6, 1, "do not remove"},
+ {"dnr", UI_POWER_SUPPLY_ETC, 3, 3, 1, "do not remove"},
+ {"enable", SCSI_IPORT_ETC, 3, 0, 1, NULL},
+ {"enable", SCSI_TPORT_ETC, 3, 0, 1, NULL},
+ {"fail", AUD_ALARM_ETC, 1, 6, 1, NULL},
+ {"fail", COMM_PORT_ETC, 1, 7, 1, NULL},
+ {"fail", COOLING_ETC, 3, 6, 1, NULL},
+ {"fail", CURR_SENSOR_ETC, 3, 6, 1, NULL},
+ {"fail", DISPLAY_ETC, 1, 6, 1, NULL},
+ {"fail", DOOR_ETC, 1, 6, 1, NULL},
+ {"fail", ENC_SCELECTR_ETC, 1, 6, 1, NULL},
+ {"fail", KEY_PAD_ETC, 1, 6, 1, NULL},
+ {"fail", NV_CACHE_ETC, 3, 6, 1, NULL},
+ {"fail", POWER_SUPPLY_ETC, 3, 6, 1, NULL},
+ {"fail", SAS_CONNECTOR_ETC, 3, 6, 1, NULL},
+ {"fail", SAS_EXPANDER_ETC, 1, 6, 1, NULL},
+ {"fail", SCC_CELECTR_ETC, 3, 6, 1, NULL},
+ {"fail", SCSI_IPORT_ETC, 1, 6, 1, NULL},
+ {"fail", SCSI_PORT_TRAN_ETC, 1, 6, 1, NULL},
+ {"fail", SCSI_TPORT_ETC, 1, 6, 1, NULL},
+ {"fail", SIMPLE_SUBENC_ETC, 1, 6, 1, NULL},
+ {"fail", TEMPERATURE_ETC, 3, 6, 1, NULL},
+ {"fail", UI_POWER_SUPPLY_ETC, 3, 6, 1, NULL},
+ {"fail", VOLT_SENSOR_ETC, 1, 6, 1, NULL},
+ {"failure_ind", ENCLOSURE_ETC, 2, 1, 1, NULL},
+ {"failure", ENCLOSURE_ETC, 3, 1, 1, NULL},
+ {"fault", DEVICE_ETC, 3, 5, 1, NULL},
+ {"fault", ARRAY_DEV_ETC, 3, 5, 1, NULL},
+ {"hotspare", ARRAY_DEV_ETC, 1, 5, 1, NULL},
+ {"hotswap", COOLING_ETC, 3, 7, 1, NULL},
+ {"hotswap", ENC_SCELECTR_ETC, 3, 7, 1, NULL}, /* status only */
+ {"hw_reset", ENC_SCELECTR_ETC, 1, 2, 1, "hardware reset"}, /* 18-047r1 */
+ {"ident", DEVICE_ETC, 2, 1, 1, "flash LED"},
+ {"ident", ARRAY_DEV_ETC, 2, 1, 1, "flash LED"},
+ {"ident", POWER_SUPPLY_ETC, 1, 7, 1, "flash LED"},
+ {"ident", COMM_PORT_ETC, 1, 7, 1, "flash LED"},
+ {"ident", COOLING_ETC, 1, 7, 1, "flash LED"},
+ {"ident", CURR_SENSOR_ETC, 1, 7, 1, "flash LED"},
+ {"ident", DISPLAY_ETC, 1, 7, 1, "flash LED"},
+ {"ident", DOOR_ETC, 1, 7, 1, "flash LED"},
+ {"ident", ENC_SCELECTR_ETC, 1, 7, 1, "flash LED"},
+ {"ident", ENCLOSURE_ETC, 1, 7, 1, "flash LED"},
+ {"ident", KEY_PAD_ETC, 1, 7, 1, "flash LED"},
+ {"ident", LANGUAGE_ETC, 1, 7, 1, "flash LED"},
+ {"ident", AUD_ALARM_ETC, 1, 7, 1, NULL},
+ {"ident", NV_CACHE_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SAS_CONNECTOR_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SAS_EXPANDER_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SCC_CELECTR_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SCSI_IPORT_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SCSI_PORT_TRAN_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SCSI_TPORT_ETC, 1, 7, 1, "flash LED"},
+ {"ident", SIMPLE_SUBENC_ETC, 1, 7, 1, "flash LED"},
+ {"ident", TEMPERATURE_ETC, 1, 7, 1, "flash LED"},
+ {"ident", UI_POWER_SUPPLY_ETC, 3, 7, 1, "flash LED"},
+ {"ident", VOLT_SENSOR_ETC, 1, 7, 1, "flash LED"},
+ {"incritarray", ARRAY_DEV_ETC, 1, 3, 1, NULL},
+ {"infailedarray", ARRAY_DEV_ETC, 1, 2, 1, NULL},
+ {"info", AUD_ALARM_ETC, 3, 3, 1, "emits warning tone when set"},
+ {"insert", DEVICE_ETC, 2, 3, 1, NULL},
+ {"insert", ARRAY_DEV_ETC, 2, 3, 1, NULL},
+ {"intf_fail", UI_POWER_SUPPLY_ETC, 2, 0, 1, NULL},
+ {"language", LANGUAGE_ETC, 2, 7, 16, "language code"},
+ {"locate", DEVICE_ETC, 2, 1, 1, "flash LED"},
+ {"locate", ARRAY_DEV_ETC, 2, 1, 1, "flash LED"},
+ {"locate", POWER_SUPPLY_ETC, 1, 7, 1, "flash LED"},
+ {"locate", COMM_PORT_ETC, 1, 7, 1, "flash LED"},
+ {"locate", COOLING_ETC, 1, 7, 1, "flash LED"},
+ {"locate", CURR_SENSOR_ETC, 1, 7, 1, "flash LED"},
+ {"locate", DISPLAY_ETC, 1, 7, 1, "flash LED"},
+ {"locate", DOOR_ETC, 1, 7, 1, "flash LED"},
+ {"locate", ENC_SCELECTR_ETC, 1, 7, 1, "flash LED"},
+ {"locate", ENCLOSURE_ETC, 1, 7, 1, "flash LED"},
+ {"locate", KEY_PAD_ETC, 1, 7, 1, "flash LED"},
+ {"locate", LANGUAGE_ETC, 1, 7, 1, "flash LED"},
+ {"locate", AUD_ALARM_ETC, 1, 7, 1, NULL},
+ {"locate", NV_CACHE_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SAS_CONNECTOR_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SAS_EXPANDER_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SCC_CELECTR_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SCSI_IPORT_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SCSI_PORT_TRAN_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SCSI_TPORT_ETC, 1, 7, 1, "flash LED"},
+ {"locate", SIMPLE_SUBENC_ETC, 1, 7, 1, "flash LED"},
+ {"locate", TEMPERATURE_ETC, 1, 7, 1, "flash LED"},
+ {"locate", UI_POWER_SUPPLY_ETC, 3, 7, 1, "flash LED"},
+ {"locate", VOLT_SENSOR_ETC, 1, 7, 1, "flash LED"},
+ {"lol", SCSI_PORT_TRAN_ETC, 3, 1, 1, "Loss of Link"},
+ {"mated", SAS_CONNECTOR_ETC, 3, 7, 1, NULL},
+ {"missing", DEVICE_ETC, 2, 4, 1, NULL},
+ {"missing", ARRAY_DEV_ETC, 2, 4, 1, NULL},
+ {"mute", AUD_ALARM_ETC, 3, 6, 1, "control only: mute the alarm"},
+ {"muted", AUD_ALARM_ETC, 3, 6, 1, "status only: alarm is muted"},
+ {"off", POWER_SUPPLY_ETC, 3, 4, 1, "Not providing power"},
+ {"off", COOLING_ETC, 3, 4, 1, "Not providing cooling"},
+ {"offset_temp", TEMPERATURE_ETC, 1, 5, 6, "Offset for reference "
+ "temperature"},
+ {"ok", ARRAY_DEV_ETC, 1, 7, 1, NULL},
+ {"on", COOLING_ETC, 3, 5, 1, NULL},
+ {"on", POWER_SUPPLY_ETC, 3, 5, 1, "0: turn (remain) off; 1: turn on"},
+ {"open", DOOR_ETC, 3, 1, 1, NULL},
+ {"overcurrent", CURR_SENSOR_ETC, 1, 1, 1, "overcurrent"},
+ {"overcurrent", POWER_SUPPLY_ETC, 2, 1, 1, "DC overcurrent"},
+ {"overcurrent", SAS_CONNECTOR_ETC, 3, 5, 1, NULL}, /* added ses3r07 */
+ {"overcurrent_warn", CURR_SENSOR_ETC, 1, 3, 1, "overcurrent warning"},
+ {"overtemp_fail", TEMPERATURE_ETC, 3, 3, 1, "Overtemperature failure"},
+ {"overtemp_warn", TEMPERATURE_ETC, 3, 2, 1, "Overtemperature warning"},
+ {"overvoltage", POWER_SUPPLY_ETC, 2, 3, 1, "DC overvoltage"},
+ {"overvoltage", VOLT_SENSOR_ETC, 1, 1, 1, "overvoltage"},
+ {"overvoltage_warn", POWER_SUPPLY_ETC, 1, 3, 1, "DC overvoltage warning"},
+ {"pow_cycle", ENCLOSURE_ETC, 2, 7, 2,
+ "0: no; 1: start in pow_c_delay minutes; 2: cancel"},
+ {"pow_c_delay", ENCLOSURE_ETC, 2, 5, 6,
+ "delay in minutes before starting power cycle (max: 60)"},
+ {"pow_c_duration", ENCLOSURE_ETC, 3, 7, 6,
+ "0: power off, restore within 1 minute; <=60: restore within that many "
+ "minutes; 63: power off, wait for manual power on"},
+ /* slightly different in Enclosure status element */
+ {"pow_c_time", ENCLOSURE_ETC, 2, 7, 6,
+ "time in minutes remaining until starting power cycle; 0: not "
+ "scheduled; <=60: scheduled in that many minutes; 63: in zero minutes"},
+ {"prdfail", -1, 0, 6, 1, "predict failure"},
+ {"rebuildremap", ARRAY_DEV_ETC, 1, 1, 1, NULL},
+ {"remove", DEVICE_ETC, 2, 2, 1, NULL},
+ {"remove", ARRAY_DEV_ETC, 2, 2, 1, NULL},
+ {"remind", AUD_ALARM_ETC, 3, 4, 1, NULL},
+ {"report", ENC_SCELECTR_ETC, 2, 0, 1, NULL}, /* status only */
+ {"report", SCC_CELECTR_ETC, 2, 0, 1, NULL},
+ {"report", SCSI_IPORT_ETC, 2, 0, 1, NULL},
+ {"report", SCSI_TPORT_ETC, 2, 0, 1, NULL},
+ {"rqst_mute", AUD_ALARM_ETC, 3, 7, 1,
+ "status only: alarm was manually muted"},
+ {"rqst_override", TEMPERATURE_ETC, 3, 7, 1, "Request(ed) override"},
+ {"rrabort", ARRAY_DEV_ETC, 1, 0, 1, "rebuild/remap abort"},
+ {"rsvddevice", ARRAY_DEV_ETC, 1, 6, 1, "reserved device"},
+ {"select_element", ENC_SCELECTR_ETC, 2, 0, 1, NULL}, /* control */
+ {"short_stat", SIMPLE_SUBENC_ETC, 3, 7, 8, "short enclosure status"},
+ {"size", NV_CACHE_ETC, 2, 7, 16, NULL},
+ {"speed_act", COOLING_ETC, 1, 2, 11, "actual speed (rpm / 10)"},
+ {"speed_code", COOLING_ETC, 3, 2, 3,
+ "0: leave; 1: lowest... 7: highest"},
+ {"size_mult", NV_CACHE_ETC, 1, 1, 2, NULL},
+ {"swap", -1, 0, 4, 1, NULL}, /* Reset swap */
+ {"sw_reset", ENC_SCELECTR_ETC, 1, 3, 1, "software reset"},/* 18-047r1 */
+ {"temp", TEMPERATURE_ETC, 2, 7, 8, "(Requested) temperature"},
+ {"unlock", DOOR_ETC, 3, 0, 1, NULL},
+ {"undertemp_fail", TEMPERATURE_ETC, 3, 1, 1, "Undertemperature failure"},
+ {"undertemp_warn", TEMPERATURE_ETC, 3, 0, 1, "Undertemperature warning"},
+ {"undervoltage", POWER_SUPPLY_ETC, 2, 2, 1, "DC undervoltage"},
+ {"undervoltage", VOLT_SENSOR_ETC, 1, 0, 1, "undervoltage"},
+ {"undervoltage_warn", POWER_SUPPLY_ETC, 1, 2, 1,
+ "DC undervoltage warning"},
+ {"ups_fail", UI_POWER_SUPPLY_ETC, 2, 2, 1, NULL},
+ {"urgency", AUD_ALARM_ETC, 3, 3, 4, NULL}, /* Tone urgency control bits */
+ {"voltage", VOLT_SENSOR_ETC, 2, 7, 16, "voltage in centivolts"},
+ {"warning", UI_POWER_SUPPLY_ETC, 2, 1, 1, NULL},
+ {"warning", ENCLOSURE_ETC, 3, 0, 1, NULL},
+ {"warning_ind", ENCLOSURE_ETC, 2, 0, 1, NULL},
+ {"xmit_fail", SCSI_PORT_TRAN_ETC, 3, 0, 1, "Transmitter failure"},
+ {NULL, 0, 0, 0, 0, NULL},
+};
+
+/* These are for the Threshold in/out diagnostic page */
+static struct acronym2tuple th_a2t_arr[] = {
+ {"high_crit", -1, 0, 7, 8, NULL},
+ {"high_warn", -1, 1, 7, 8, NULL},
+ {"low_crit", -1, 2, 7, 8, NULL},
+ {"low_warn", -1, 3, 7, 8, NULL},
+ {NULL, 0, 0, 0, 0, NULL},
+};
+
+/* These are for the Additional element status diagnostic page for SAS with
+ * the EIP bit set. First phy only. Index from start of AES descriptor */
+static struct acronym2tuple ae_sas_a2t_arr[] = {
+ {"at_sas_addr", -1, 12, 7, 64, NULL}, /* best viewed with --hex --get= */
+ /* typically this is the expander's SAS address */
+ {"dev_type", -1, 8, 6, 3, "1: SAS/SATA dev, 2: expander"},
+ {"dsn", -1, 7, 7, 8, "device slot number (255: none)"},
+ {"num_phys", -1, 4, 7, 8, "number of phys"},
+ {"phy_id", -1, 28, 7, 8, NULL},
+ {"sas_addr", -1, 20, 7, 64, NULL}, /* should be disk or tape ... */
+ {"exp_sas_addr", -1, 8, 7, 64, NULL}, /* expander address */
+ {"sata_dev", -1, 11, 0, 1, NULL},
+ {"sata_port_sel", -1, 11, 7, 1, NULL},
+ {"smp_init", -1, 10, 1, 1, NULL},
+ {"smp_targ", -1, 11, 1, 1, NULL},
+ {"ssp_init", -1, 10, 3, 1, NULL},
+ {"ssp_targ", -1, 11, 3, 1, NULL},
+ {"stp_init", -1, 10, 2, 1, NULL},
+ {"stp_targ", -1, 11, 2, 1, NULL},
+ {NULL, 0, 0, 0, 0, NULL},
+};
+
+/* Boolean array of element types of interest to the Additional Element
+ * Status page. Indexed by element type (0 <= et < 32). */
+static bool active_et_aesp_arr[NUM_ACTIVE_ET_AESP_ARR] = {
+ false, true /* dev */, false, false,
+ false, false, false, true /* esce */,
+ false, false, false, false,
+ false, false, false, false,
+ false, false, false, false,
+ true /* starg */, true /* sinit */, false, true /* arr */,
+ true /* sas exp */, false, false, false,
+ false, false, false, false,
+};
+
+/* Command line long option names with corresponding short letter. */
+static struct option long_options[] = {
+ {"all", no_argument, 0, 'a'},
+ {"ALL", no_argument, 0, 'z'},
+ {"byte1", required_argument, 0, 'b'},
+ {"clear", required_argument, 0, 'C'},
+ {"control", no_argument, 0, 'c'},
+ {"data", required_argument, 0, 'd'},
+ {"descriptor", required_argument, 0, 'D'},
+ {"dev-slot-num", required_argument, 0, 'x'},
+ {"dev_slot_num", required_argument, 0, 'x'},
+ {"dsn", required_argument, 0, 'x'},
+ {"eiioe", required_argument, 0, 'E'},
+ {"enumerate", no_argument, 0, 'e'},
+ {"filter", no_argument, 0, 'f'},
+ {"get", required_argument, 0, 'G'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"index", required_argument, 0, 'I'},
+ {"inhex", required_argument, 0, 'X'},
+ {"inner-hex", no_argument, 0, 'i'},
+ {"inner_hex", no_argument, 0, 'i'},
+ {"join", no_argument, 0, 'j'},
+ {"list", no_argument, 0, 'l'},
+ {"nickid", required_argument, 0, 'N'},
+ {"nickname", required_argument, 0, 'n'},
+ {"mask", required_argument, 0, 'M'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"page", required_argument, 0, 'p'},
+ {"quiet", no_argument, 0, 'q'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"sas-addr", required_argument, 0, 'A'},
+ {"sas_addr", required_argument, 0, 'A'},
+ {"set", required_argument, 0, 'S'},
+ {"status", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"warn", no_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+/* For overzealous SES device servers that don't like some status elements
+ * sent back as control elements. This table is as per ses3r06. */
+static uint8_t ses3_element_cmask_arr[NUM_ETC][4] = {
+ /* Element type code (ETC) names; comment */
+ {0x40, 0xff, 0xff, 0xff}, /* [0] unspecified */
+ {0x40, 0, 0x4e, 0x3c}, /* DEVICE */
+ {0x40, 0x80, 0, 0x60}, /* POWER_SUPPLY */
+ {0x40, 0x80, 0, 0x60}, /* COOLING; requested speed as is unless */
+ {0x40, 0xc0, 0, 0}, /* TEMPERATURE */
+ {0x40, 0xc0, 0, 0x1}, /* DOOR */
+ {0x40, 0xc0, 0, 0x5f}, /* AUD_ALARM */
+ {0x40, 0xc0, 0x1, 0}, /* ENC_SCELECTR_ETC */
+ {0x40, 0xc0, 0, 0}, /* SCC_CELECTR */
+ {0x40, 0xc0, 0, 0}, /* NV_CACHE */
+ {0x40, 0, 0, 0}, /* [10] INV_OP_REASON */
+ {0x40, 0, 0, 0xc0}, /* UI_POWER_SUPPLY */
+ {0x40, 0xc0, 0xff, 0xff}, /* DISPLAY */
+ {0x40, 0xc3, 0, 0}, /* KEY_PAD */
+ {0x40, 0x80, 0, 0xff}, /* ENCLOSURE */
+ {0x40, 0xc0, 0, 0x10}, /* SCSI_PORT_TRAN */
+ {0x40, 0x80, 0xff, 0xff}, /* LANGUAGE */
+ {0x40, 0xc0, 0, 0x1}, /* COMM_PORT */
+ {0x40, 0xc0, 0, 0}, /* VOLT_SENSOR */
+ {0x40, 0xc0, 0, 0}, /* CURR_SENSOR */
+ {0x40, 0xc0, 0, 0x1}, /* [20] SCSI_TPORT */
+ {0x40, 0xc0, 0, 0x1}, /* SCSI_IPORT */
+ {0x40, 0xc0, 0, 0}, /* SIMPLE_SUBENC */
+ {0x40, 0xff, 0x4e, 0x3c}, /* ARRAY */
+ {0x40, 0xc0, 0, 0}, /* SAS_EXPANDER */
+ {0x40, 0x80, 0, 0x40}, /* SAS_CONNECTOR */
+};
+
+
+static int read_hex(const char * inp, uint8_t * arr, int mx_arr_len,
+ int * arr_len, bool in_hex, bool may_gave_at, int verb);
+static int strcase_eq(const char * s1p, const char * s2p);
+static void enumerate_diag_pages(void);
+static bool saddr_non_zero(const uint8_t * bp);
+static const char * find_in_diag_page_desc(int page_num);
+
+
+static void
+usage(int help_num)
+{
+ if (2 != help_num) {
+ pr2serr(
+ "Usage: sg_ses [--all] [--ALL] [--descriptor=DES] "
+ "[--dev-slot-num=SN]\n"
+ " [--eiioe=A_F] [--filter] [--get=STR] "
+ "[--hex]\n"
+ " [--index=IIA | =TIA,II] [--inner-hex] [--join] "
+ "[--maxlen=LEN]\n"
+ " [--page=PG] [--quiet] [--raw] [--readonly] "
+ "[--sas-addr=SA]\n"
+ " [--status] [--verbose] [--warn] DEVICE\n\n"
+ " sg_ses --control [--byte1=B1] [--clear=STR] "
+ "[--data=H,H...]\n"
+ " [--descriptor=DES] [--dev-slot-num=SN] "
+ "[--index=IIA | =TIA,II]\n"
+ " [--inhex=FN] [--mask] [--maxlen=LEN] "
+ "[--nickid=SEID]\n"
+ " [--nickname=SEN] [--page=PG] [--sas-addr=SA] "
+ "[--set=STR]\n"
+ " [--verbose] DEVICE\n\n"
+ " sg_ses --data=@FN --status [-rr] [<most options from "
+ "first form>]\n"
+ " sg_ses --inhex=FN --status [-rr] [<most options from "
+ "first form>]\n\n"
+ " sg_ses [--enumerate] [--help] [--index=IIA] [--list] "
+ "[--version]\n\n"
+ );
+ if ((help_num < 1) || (help_num > 2)) {
+ pr2serr("Or the corresponding short option usage: \n"
+ " sg_ses [-a] [-D DES] [-x SN] [-E A_F] [-f] [-G STR] "
+ "[-H] [-I IIA|TIA,II]\n"
+ " [-i] [-j] [-m LEN] [-p PG] [-q] [-r] [-R] "
+ "[-A SA] [-s] [-v] [-w]\n"
+ " DEVICE\n\n"
+ " sg_ses [-b B1] [-C STR] [-c] [-d H,H...] [-D DES] "
+ "[-x SN] [-I IIA|TIA,II]\n"
+ " [-M] [-m LEN] [-N SEID] [-n SEN] [-p PG] "
+ "[-A SA] [-S STR]\n"
+ " [-v] DEVICE\n\n"
+ " sg_ses -d @FN -s [-rr] [<most options from first "
+ "form>]\n"
+ " sg_ses -X FN -s [-rr] [<most options from first "
+ "form>]\n\n"
+ " sg_ses [-e] [-h] [-I IIA] [-l] [-V]\n"
+ );
+ pr2serr("\nFor help use '-h' one or more times.\n");
+ return;
+ }
+ pr2serr(
+ " where the main options are:\n"
+ " --all|-a show (almost) all status pages (same "
+ "as --join)\n"
+ " --clear=STR|-C STR clear field by acronym or position\n"
+ " --control|-c send control information (def: fetch "
+ "status)\n"
+ " --descriptor=DES|-D DES descriptor name (for indexing)\n"
+ " --dev-slot-num=SN|--dsn=SN|-x SN device slot number "
+ "(for indexing)\n"
+ " --filter|-f filter out enclosure status flags that "
+ "are clear\n"
+ " use twice for status=okay entries "
+ "only\n"
+ " --get=STR|-G STR get value of field by acronym or "
+ "position\n"
+ " --help|-h print out usage message, use twice for "
+ "additional\n"
+ " --index=IIA|-I IIA individual index ('-1' for overall) "
+ "or element\n"
+ " type abbreviation (e.g. 'arr'). A "
+ "range may be\n"
+ " given for the individual index "
+ "(e.g. '2-5')\n"
+ " --index=TIA,II|-I TIA,II comma separated pair: TIA is "
+ "type header\n"
+ " index or element type "
+ "abbreviation;\n"
+ " II is individual index ('-1' "
+ "for overall)\n"
+ );
+ pr2serr(
+ " --join|-j group Enclosure Status, Element "
+ "Descriptor\n"
+ " and Additional Element Status pages. "
+ "Use twice\n"
+ " to add Threshold In page\n"
+ " --page=PG|-p PG diagnostic page code (abbreviation "
+ "or number)\n"
+ " (def: 'ssp' [0x0] (supported diagnostic "
+ "pages))\n"
+ " --sas-addr=SA|-A SA SAS address in hex (for indexing)\n"
+ " --set=STR|-S STR set value of field by acronym or "
+ "position\n"
+ " --status|-s fetch status information (default "
+ "action)\n\n"
+ "First usage above is for fetching pages or fields from a SCSI "
+ "enclosure.\nThe second usage is for changing a page or field in "
+ "an enclosure. The\n'--clear=', '--get=' and '--set=' options "
+ "can appear multiple times.\nUse '-hh' for more help, including "
+ "the options not explained above.\n");
+ } else { /* for '-hh' or '--help --help' */
+ pr2serr(
+ " where the remaining sg_ses options are:\n"
+ " --ALL|-z same as --all twice (adds thresholds)\n"
+ " --byte1=B1|-b B1 byte 1 (2nd byte) of control page set "
+ "to B1\n"
+ " --data=H,H...|-d H,H... string of ASCII hex bytes to "
+ "send as a\n"
+ " control page or decode as a "
+ "status page\n"
+ " --data=- | -d - fetch string of ASCII hex bytes from "
+ "stdin\n"
+ " --data=@FN | -d @FN fetch string of ASCII hex bytes from "
+ "file: FN\n"
+ " --eiioe=A_F|-E A_F A_F is either 'auto' or 'force'. "
+ "'force' acts\n"
+ " as if EIIOE field is 1, 'auto' tries "
+ "to guess\n"
+ " --enumerate|-e enumerate page names + element types "
+ "(ignore\n"
+ " DEVICE). Use twice for clear,get,set "
+ "acronyms\n"
+ " --hex|-H print page response (or field) in hex\n"
+ " --inhex=FN|-X FN alternate form of --data=@FN\n"
+ " --inner-hex|-i print innermost level of a"
+ " status page in hex\n"
+ " --list|-l same as '--enumerate' option\n"
+ " --mask|-M ignore status element mask in modify "
+ "actions\n"
+ " (e.g.--set= and --clear=) (def: apply "
+ "mask)\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " --nickid=SEID|-N SEID SEID is subenclosure identifier "
+ "(def: 0)\n"
+ " used to specify which nickname to "
+ "change\n"
+ " --nickname=SEN|-n SEN SEN is new subenclosure nickname\n"
+ " --quiet|-q suppress some output messages\n"
+ " --raw|-r print status page in ASCII hex suitable "
+ "for '-d';\n"
+ " when used twice outputs page in binary "
+ "to stdout\n"
+ " --readonly|-R open DEVICE read-only (def: "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --warn|-w warn about join (and other) issues\n\n"
+ "If no options are given then DEVICE's supported diagnostic "
+ "pages are\nlisted. STR can be '<start_byte>:<start_bit>"
+ "[:<num_bits>][=<val>]'\nor '<acronym>[=val]'. Element type "
+ "abbreviations may be followed by a\nnumber (e.g. 'ps1' is "
+ "the second power supply element type). Use\n'sg_ses -e' and "
+ "'sg_ses -ee' for more information.\n\n"
+ );
+ pr2serr(
+ "Low level indexing can be done with one of the two '--index=' "
+ "options.\nAlternatively, medium level indexing can be done "
+ "with either the\n'--descriptor=', 'dev-slot-num=' or "
+ "'--sas-addr=' options. Support for\nthe medium level options "
+ "in the SES device is itself optional.\n"
+ );
+ }
+}
+
+/* Return 0 for okay, else an error */
+static int
+parse_index(struct opts_t *op)
+{
+ int n, n2;
+ const char * cp;
+ char * mallcp;
+ char * c2p;
+ const struct element_type_t * etp;
+ char b[64];
+ const int blen = sizeof(b);
+
+ op->ind_given = true;
+ n2 = 0;
+ if ((cp = strchr(op->index_str, ','))) {
+ /* decode number following comma */
+ if (0 == strcmp("-1", cp + 1))
+ n = -1;
+ else {
+ const char * cc3p;
+
+ n = sg_get_num_nomult(cp + 1);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad argument to '--index=', after comma expect "
+ "number from -1 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((cc3p = strchr(cp + 1, '-'))) {
+ n2 = sg_get_num_nomult(cc3p + 1);
+ if ((n2 < n) || (n2 > 255)) {
+ pr2serr("bad argument to '--index', after '-' expect "
+ "number from -%d to 255\n", n);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+ op->ind_indiv = n;
+ if (n2 > 0)
+ op->ind_indiv_last = n2;
+ n = cp - op->index_str;
+ if (n >= (blen - 1)) {
+ pr2serr("bad argument to '--index', string prior to comma too "
+ "long\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else { /* no comma found in index_str */
+ n = strlen(op->index_str);
+ if (n >= (blen - 1)) {
+ pr2serr("bad argument to '--index', string too long\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ snprintf(b, blen, "%.*s", n, op->index_str);
+ if (0 == strcmp("-1", b)) {
+ if (cp) {
+ pr2serr("bad argument to '--index', unexpected '-1' type header "
+ "index\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->ind_th = 0;
+ op->ind_indiv = -1;
+ } else if (isdigit((uint8_t)b[0])) {
+ n = sg_get_num_nomult(b);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad numeric argument to '--index', expect number from 0 "
+ "to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (cp) /* argument to left of comma */
+ op->ind_th = n;
+ else { /* no comma found, so 'n' is ind_indiv */
+ op->ind_th = 0;
+ op->ind_indiv = n;
+ if ((c2p = strchr(b, '-'))) {
+ n2 = sg_get_num_nomult(c2p + 1);
+ if ((n2 < n) || (n2 > 255)) {
+ pr2serr("bad argument to '--index', after '-' expect "
+ "number from -%d to 255\n", n);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ op->ind_indiv_last = n2;
+ }
+ } else if ('_' == b[0]) { /* leading "_" prefixes element type code */
+ if ((c2p = strchr(b + 1, '_')))
+ *c2p = '\0'; /* subsequent "_" prefixes e.t. index */
+ n = sg_get_num_nomult(b + 1);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad element type code for '--index', expect value from "
+ "0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ element_type_by_code.elem_type_code = n;
+ mallcp = (char *)malloc(8); /* willfully forget about freeing this */
+ if (NULL == mallcp)
+ return sg_convert_errno(ENOMEM);
+ mallcp[0] = '_';
+ snprintf(mallcp + 1, 6, "%d", n);
+ element_type_by_code.abbrev = mallcp;
+ if (c2p) {
+ n = sg_get_num_nomult(c2p + 1);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad element type code <num> for '--index', expect "
+ "<num> from 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->ind_et_inst = n;
+ }
+ op->ind_etp = &element_type_by_code;
+ if (NULL == cp)
+ op->ind_indiv = -1;
+ } else { /* element type abbreviation perhaps followed by <num> */
+ int b_len = strlen(b);
+
+ for (etp = element_type_arr; etp->desc; ++etp) {
+ n = strlen(etp->abbrev);
+ if ((n == b_len) && (0 == strncmp(b, etp->abbrev, n)))
+ break;
+ }
+ if (NULL == etp->desc) {
+ pr2serr("bad element type abbreviation [%s] for '--index'\n"
+ "use '--enumerate' to see possibles\n", b);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (b_len > n) {
+ n = sg_get_num_nomult(b + n);
+ if ((n < 0) || (n > 255)) {
+ pr2serr("bad element type abbreviation <num> for '--index', "
+ "expect <num> from 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->ind_et_inst = n;
+ }
+ op->ind_etp = etp;
+ if (NULL == cp)
+ op->ind_indiv = -1;
+ }
+ if (op->verbose > 1) {
+ if (op->ind_etp)
+ pr2serr(" element type abbreviation: %s, etp_num=%d, "
+ "individual index=%d\n", op->ind_etp->abbrev,
+ op->ind_et_inst, op->ind_indiv);
+ else
+ pr2serr(" type header index=%d, individual index=%d\n",
+ op->ind_th, op->ind_indiv);
+ }
+ return 0;
+}
+
+
+/* command line process, options and arguments. Returns 0 if ok. */
+static int
+parse_cmd_line(struct opts_t *op, int argc, char *argv[])
+{
+ int c, j, n, d_len, ret;
+ const char * data_arg = NULL;
+ const char * inhex_arg = NULL;
+ uint64_t saddr;
+ const char * cp;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aA:b:cC:d:D:eE:fG:hHiI:jln:N:m:Mp:qrRs"
+ "S:vVwx:z", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a': /* --all is synonym for --join */
+ ++op->do_join;
+ break;
+ case 'A': /* SAS address, assumed to be hex */
+ cp = optarg;
+ if ((strlen(optarg) > 2) && ('X' == toupper((uint8_t)optarg[1])))
+ cp = optarg + 2;
+ if (1 != sscanf(cp, "%" SCNx64 "", &saddr)) {
+ pr2serr("bad argument to '--sas-addr=SA'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ sg_put_unaligned_be64(saddr, op->sas_addr + 0);
+ if (sg_all_ffs(op->sas_addr, 8)) {
+ pr2serr("error decoding '--sas-addr=SA' argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'b':
+ op->byte1 = sg_get_num_nomult(optarg);
+ if ((op->byte1 < 0) || (op->byte1 > 255)) {
+ pr2serr("bad argument to '--byte1=B1' (0 to 255 "
+ "inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->byte1_given = true;
+ break;
+ case 'c':
+ op->do_control = true;
+ break;
+ case 'C':
+ if (strlen(optarg) >= CGS_STR_MAX_SZ) {
+ pr2serr("--clear= option too long (max %d characters)\n",
+ CGS_STR_MAX_SZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->num_cgs < CGS_CL_ARR_MAX_SZ) {
+ op->cgs_cl_arr[op->num_cgs].cgs_sel = CLEAR_OPT;
+ strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg);
+ ++op->num_cgs;
+ } else {
+ pr2serr("Too many --clear=, --get= and --set= options "
+ "(max: %d)\n", CGS_CL_ARR_MAX_SZ);
+ return SG_LIB_CONTRADICT;
+ }
+ break;
+ case 'd':
+ data_arg = optarg;
+ op->do_data = true;
+ break;
+ case 'D':
+ op->desc_name = optarg;
+ break;
+ case 'e':
+ ++op->enumerate;
+ break;
+ case 'E':
+ if (0 == strcmp("auto", optarg))
+ op->eiioe_auto = true;
+ else if (0 == strcmp("force", optarg))
+ op->eiioe_force = true;
+ else {
+ pr2serr("--eiioe option expects 'auto' or 'force' as an "
+ "argument\n");
+ return SG_LIB_CONTRADICT;
+ }
+ break;
+ case 'f':
+ ++op->do_filter;
+ break;
+ case 'G':
+ if (strlen(optarg) >= CGS_STR_MAX_SZ) {
+ pr2serr("--get= option too long (max %d characters)\n",
+ CGS_STR_MAX_SZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->num_cgs < CGS_CL_ARR_MAX_SZ) {
+ op->cgs_cl_arr[op->num_cgs].cgs_sel = GET_OPT;
+ strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg);
+ ++op->num_cgs;
+ } else {
+ pr2serr("Too many --clear=, --get= and --set= options "
+ "(max: %d)\n", CGS_CL_ARR_MAX_SZ);
+ return SG_LIB_CONTRADICT;
+ }
+ break;
+ case 'h':
+ ++op->do_help;
+ break;
+ case '?':
+ pr2serr("\n");
+ usage(0);
+ return SG_LIB_SYNTAX_ERROR;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ op->inner_hex = true;
+ break;
+ case 'I':
+ op->index_str = optarg;
+ break;
+ case 'j':
+ ++op->do_join;
+ break;
+ case 'l':
+ op->do_list = true;
+ break;
+ case 'n':
+ op->nickname_str = optarg;
+ break;
+ case 'N':
+ op->seid = sg_get_num_nomult(optarg);
+ if ((op->seid < 0) || (op->seid > 255)) {
+ pr2serr("bad argument to '--nickid=SEID' (0 to 255 "
+ "inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->seid_given = true;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 65535)) {
+ pr2serr("bad argument to '--maxlen=LEN' (0 to 65535 "
+ "inclusive expected)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == n)
+ op->maxlen = MX_ALLOC_LEN;
+ else if (n < MIN_MAXLEN) {
+ pr2serr("Warning: --maxlen=LEN less than %d ignored\n",
+ MIN_MAXLEN);
+ op->maxlen = MX_ALLOC_LEN;
+ } else
+ op->maxlen = n;
+ break;
+ case 'M':
+ op->mask_ign = true;
+ break;
+ case 'p':
+ if (isdigit((uint8_t)optarg[0])) {
+ op->page_code = sg_get_num_nomult(optarg);
+ if ((op->page_code < 0) || (op->page_code > 255)) {
+ pr2serr("bad argument to '--page=PG' (0 to 255 "
+ "inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ const struct diag_page_abbrev * ap;
+
+ for (ap = dp_abbrev; ap->abbrev; ++ap) {
+ if (strcase_eq(ap->abbrev, optarg)) {
+ op->page_code = ap->page_code;
+ break;
+ }
+ }
+ if (NULL == ap->abbrev) {
+ pr2serr("'--page=PG' argument abbreviation \"%s\" not "
+ "found\nHere are the choices:\n", optarg);
+ enumerate_diag_pages();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ op->page_code_given = true;
+ break;
+ case 'q':
+ op->quiet = true;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 's':
+ op->do_status = true;
+ break;
+ case 'S':
+ if (strlen(optarg) >= CGS_STR_MAX_SZ) {
+ pr2serr("--set= option too long (max %d characters)\n",
+ CGS_STR_MAX_SZ);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->num_cgs < CGS_CL_ARR_MAX_SZ) {
+ op->cgs_cl_arr[op->num_cgs].cgs_sel = SET_OPT;
+ strcpy(op->cgs_cl_arr[op->num_cgs].cgs_str, optarg);
+ ++op->num_cgs;
+ } else {
+ pr2serr("Too many --clear=, --get= and --set= options "
+ "(max: %d)\n", CGS_CL_ARR_MAX_SZ);
+ return SG_LIB_CONTRADICT;
+ }
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ return 0;
+ case 'w':
+ op->warn = true;
+ break;
+ case 'x':
+ op->dev_slot_num = sg_get_num_nomult(optarg);
+ if ((op->dev_slot_num < 0) || (op->dev_slot_num > 255)) {
+ pr2serr("bad argument to '--dev-slot-num' (0 to 255 "
+ "inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'X': /* --inhex=FN for compatibility with other utils */
+ inhex_arg = optarg;
+ op->do_data = true;
+ break;
+ case 'z': /* --ALL and -z are synonyms for '--join --join' */
+ /* -A already used for --sas-addr=SA shortened form */
+ op->do_join += 2;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ goto err_help;
+ }
+ }
+ if (op->do_help)
+ return 0;
+ if (optind < argc) {
+ if (NULL == op->dev_name) {
+ op->dev_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ goto err_help;
+ }
+ }
+ op->mx_arr_len = (op->maxlen > MIN_DATA_IN_SZ) ? op->maxlen :
+ MIN_DATA_IN_SZ;
+ op->data_arr = sg_memalign(op->mx_arr_len, 0 /* page aligned */,
+ &op->free_data_arr, false);
+ if (NULL == op->data_arr) {
+ pr2serr("unable to allocate %u bytes on heap\n", op->mx_arr_len);
+ return sg_convert_errno(ENOMEM);
+ }
+ if (data_arg || inhex_arg) {
+ if (inhex_arg) {
+ data_arg = inhex_arg;
+ if (read_hex(data_arg, op->data_arr + DATA_IN_OFF,
+ op->mx_arr_len - DATA_IN_OFF, &op->arr_len,
+ (op->do_raw < 2), false, op->verbose)) {
+ pr2serr("bad argument, expect '--inhex=FN' or '--inhex=-'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ if (read_hex(data_arg, op->data_arr + DATA_IN_OFF,
+ op->mx_arr_len - DATA_IN_OFF, &op->arr_len,
+ (op->do_raw < 2), true, op->verbose)) {
+ pr2serr("bad argument, expect '--data=H,H...', '--data=-' or "
+ "'--data=@FN'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ op->do_raw = 0;
+ /* struct data_in_desc_t stuff does not apply when --control */
+ if (op->do_status && (op->arr_len > 3)) {
+ int off;
+ int pc = 0;
+ const uint8_t * bp = op->data_arr + DATA_IN_OFF;
+ struct data_in_desc_t * didp = data_in_desc_arr;
+
+ d_len = sg_get_unaligned_be16(bp + 2) + 4;
+ for (n = 0, off = 0; n < MX_DATA_IN_DESCS; ++n, ++didp) {
+ didp->in_use = true;
+ pc = bp[0];
+ didp->page_code = pc;
+ didp->offset = off;
+ didp->dp_len = d_len;
+ off += d_len;
+ if ((off + 3) < op->arr_len) {
+ bp += d_len;
+ d_len = sg_get_unaligned_be16(bp + 2) + 4;
+ } else {
+ ++n;
+ break;
+ }
+ }
+ if (1 == n) {
+ op->page_code_given = true;
+ op->page_code = pc;
+ } else /* n must be > 1 */
+ op->many_dpages = true;
+
+ if (op->verbose > 3) {
+ int k;
+ char b[128];
+
+ for (didp = data_in_desc_arr, k = 0; k < n; ++k, ++didp) {
+ if ((cp = find_in_diag_page_desc(didp->page_code)))
+ snprintf(b, sizeof(b), "%s dpage", cp);
+ else
+ snprintf(b, sizeof(b), "dpage 0x%x", didp->page_code);
+ pr2serr("%s found, offset %d, dp_len=%d\n", b,
+ didp->offset, didp->dp_len);
+ }
+ }
+ }
+ }
+ if (op->do_join && op->do_control) {
+ pr2serr("cannot have '--join' and '--control'\n");
+ goto err_help;
+ }
+ if (op->index_str) {
+ ret = parse_index(op);
+ if (ret) {
+ pr2serr(" For more information use '--help'\n");
+ return ret;
+ }
+ }
+ if (op->desc_name || (op->dev_slot_num >= 0) ||
+ saddr_non_zero(op->sas_addr)) {
+ if (op->ind_given) {
+ pr2serr("cannot have --index with either --descriptor, "
+ "--dev-slot-num or --sas-addr\n");
+ goto err_help;
+ }
+ if (((!! op->desc_name) + (op->dev_slot_num >= 0) +
+ saddr_non_zero(op->sas_addr)) > 1) {
+ pr2serr("can only have one of --descriptor, "
+ "--dev-slot-num and --sas-addr\n");
+ goto err_help;
+ }
+ if ((0 == op->do_join) && (! op->do_control) &&
+ (0 == op->num_cgs) && (! op->page_code_given)) {
+ ++op->do_join; /* implicit --join */
+ if (op->verbose)
+ pr2serr("process as if --join option is set\n");
+ }
+ }
+ if (op->ind_given) {
+ if ((0 == op->do_join) && (! op->do_control) &&
+ (0 == op->num_cgs) && (! op->page_code_given)) {
+ op->page_code_given = true;
+ op->page_code = ENC_STATUS_DPC; /* implicit status page */
+ if (op->verbose)
+ pr2serr("assume --page=2 (es) option is set\n");
+ }
+ }
+ if (op->do_list || op->enumerate)
+ return 0;
+
+ if (op->do_control && op->do_status) {
+ pr2serr("cannot have both '--control' and '--status'\n");
+ goto err_help;
+ } else if (op->do_control) {
+ if (op->nickname_str || op->seid_given)
+ ;
+ else if (! op->do_data) {
+ pr2serr("need to give '--data' in control mode\n");
+ goto err_help;
+ }
+ } else if (! op->do_status) {
+ if (op->do_data) {
+ pr2serr("when user data given, require '--control' or "
+ "'--status' option\n");
+ goto err_help;
+ }
+ op->do_status = true; /* default to receiving status pages */
+ } else if (op->do_status && op->do_data && op->dev_name) {
+ pr2serr(">>> Warning: device name (%s) will be ignored\n",
+ op->dev_name);
+ op->dev_name = NULL; /* quash device name */
+ }
+
+ if (op->nickname_str) {
+ if (! op->do_control) {
+ pr2serr("since '--nickname=' implies control mode, require "
+ "'--control' as well\n");
+ goto err_help;
+ }
+ if (op->page_code_given) {
+ if (SUBENC_NICKNAME_DPC != op->page_code) {
+ pr2serr("since '--nickname=' assume or expect "
+ "'--page=snic'\n");
+ goto err_help;
+ }
+ } else
+ op->page_code = SUBENC_NICKNAME_DPC;
+ } else if (op->seid_given) {
+ pr2serr("'--nickid=' must be used together with '--nickname='\n");
+ goto err_help;
+
+ }
+ if ((op->verbose > 4) && saddr_non_zero(op->sas_addr)) {
+ pr2serr(" SAS address (in hex): ");
+ for (j = 0; j < 8; ++j)
+ pr2serr("%02x", op->sas_addr[j]);
+ pr2serr("\n");
+ }
+
+ if ((! (op->do_data && op->do_status)) && (NULL == op->dev_name)) {
+ pr2serr("missing DEVICE name!\n\n");
+ goto err_help;
+ }
+ return 0;
+
+err_help:
+ if (op->verbose) {
+ pr2serr("\n");
+ usage(0);
+ }
+ return SG_LIB_SYNTAX_ERROR;
+}
+
+/* Parse clear/get/set string, writes output to '*tavp'. Uses 'buff' for
+ * scratch area. Returns 0 on success, else -1. */
+static int
+parse_cgs_str(char * buff, struct tuple_acronym_val * tavp)
+{
+ char * esp;
+ char * colp;
+ unsigned int ui;
+
+ tavp->acron = NULL;
+ tavp->val_str = NULL;
+ tavp->start_byte = -1;
+ tavp->num_bits = 1;
+ if ((esp = strchr(buff, '='))) {
+ tavp->val_str = esp + 1;
+ *esp = '\0';
+ if (0 == strcmp("-1", esp + 1))
+ tavp->val = -1;
+ else {
+ tavp->val = sg_get_llnum_nomult(esp + 1);
+ if (-1 == tavp->val) {
+ pr2serr("unable to decode: %s value\n", esp + 1);
+ pr2serr(" expected: <acronym>[=<val>]\n");
+ return -1;
+ }
+ }
+ }
+ if (isalpha((uint8_t)buff[0]))
+ tavp->acron = buff;
+ else {
+ char * cp;
+
+ colp = strchr(buff, ':');
+ if ((NULL == colp) || (buff == colp))
+ return -1;
+ *colp = '\0';
+ if (('0' == buff[0]) && ('X' == toupper((uint8_t)buff[1]))) {
+ if (1 != sscanf(buff + 2, "%x", &ui))
+ return -1;
+ tavp->start_byte = ui;
+ } else if ('H' == toupper((uint8_t)*(colp - 1))) {
+ if (1 != sscanf(buff, "%x", &ui))
+ return -1;
+ tavp->start_byte = ui;
+ } else {
+ if (1 != sscanf(buff, "%d", &tavp->start_byte))
+ return -1;
+ }
+ if ((tavp->start_byte < 0) || (tavp->start_byte > 127)) {
+ pr2serr("<start_byte> needs to be between 0 and 127\n");
+ return -1;
+ }
+ cp = colp + 1;
+ colp = strchr(cp, ':');
+ if (cp == colp)
+ return -1;
+ if (colp)
+ *colp = '\0';
+ if (1 != sscanf(cp, "%d", &tavp->start_bit))
+ return -1;
+ if ((tavp->start_bit < 0) || (tavp->start_bit > 7)) {
+ pr2serr("<start_bit> needs to be between 0 and 7\n");
+ return -1;
+ }
+ if (colp) {
+ if (1 != sscanf(colp + 1, "%d", &tavp->num_bits))
+ return -1;
+ }
+ if ((tavp->num_bits < 1) || (tavp->num_bits > 64)) {
+ pr2serr("<num_bits> needs to be between 1 and 64\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Fetch diagnostic page name (control or out). Returns NULL if not found. */
+static const char *
+find_out_diag_page_desc(int page_num)
+{
+ const struct diag_page_code * pcdp;
+
+ for (pcdp = out_dpc_arr; pcdp->desc; ++pcdp) {
+ if (page_num == pcdp->page_code)
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ return NULL;
+ }
+ return NULL;
+}
+
+static bool
+match_ind_indiv(int index, const struct opts_t * op)
+{
+ if (index == op->ind_indiv)
+ return true;
+ if (op->ind_indiv_last > op->ind_indiv) {
+ if ((index > op->ind_indiv) && (index <= op->ind_indiv_last))
+ return true;
+ }
+ return false;
+}
+
+#if 0
+static bool
+match_last_ind_indiv(int index, const struct opts_t * op)
+{
+ if (op->ind_indiv_last >= op->ind_indiv)
+ return (index == op->ind_indiv_last);
+ return (index == op->ind_indiv);
+}
+#endif
+
+/* Return of 0 -> success, SG_LIB_CAT_* positive values or -1 -> other
+ * failures */
+static int
+do_senddiag(struct sg_pt_base * ptvp, void * outgoing_pg, int outgoing_len,
+ bool noisy, int verbose)
+{
+ int ret;
+
+ if (outgoing_pg && (verbose > 2)) {
+ int page_num = ((const char *)outgoing_pg)[0];
+ const char * cp = find_out_diag_page_desc(page_num);
+
+ if (cp)
+ pr2serr(" Send diagnostic command page name: %s\n", cp);
+ else
+ pr2serr(" Send diagnostic command page number: 0x%x\n",
+ page_num);
+ }
+ ret = sg_ll_send_diag_pt(ptvp, 0 /* sf_code */, true /* pf_bit */,
+ false /* sf_bit */, false /* devofl_bit */,
+ false /* unitofl_bit */, 0 /* long_duration */,
+ outgoing_pg, outgoing_len, noisy, verbose);
+ clear_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Fetch diagnostic page name (status and/or control). Returns NULL if not
+ * found. */
+static const char *
+find_diag_page_desc(int page_num)
+{
+ const struct diag_page_code * pcdp;
+
+ for (pcdp = dpc_arr; pcdp->desc; ++pcdp) {
+ if (page_num == pcdp->page_code)
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ return NULL;
+ }
+ return NULL;
+}
+
+/* Fetch diagnostic page name (status or in). Returns NULL if not found. */
+static const char *
+find_in_diag_page_desc(int page_num)
+{
+ const struct diag_page_code * pcdp;
+
+ for (pcdp = in_dpc_arr; pcdp->desc; ++pcdp) {
+ if (page_num == pcdp->page_code)
+ return pcdp->desc;
+ else if (page_num < pcdp->page_code)
+ return NULL;
+ }
+ return NULL;
+}
+
+/* Fetch element type name. Returns NULL if not found. */
+static char *
+etype_str(int elem_type_code, char * b, int mlen_b)
+{
+ const struct element_type_t * etp;
+ int len;
+
+ if ((NULL == b) || (mlen_b < 1))
+ return b;
+ for (etp = element_type_arr; etp->desc; ++etp) {
+ if (elem_type_code == etp->elem_type_code) {
+ len = strlen(etp->desc);
+ if (len < mlen_b)
+ strcpy(b, etp->desc);
+ else {
+ strncpy(b, etp->desc, mlen_b - 1);
+ b[mlen_b - 1] = '\0';
+ }
+ return b;
+ } else if (elem_type_code < etp->elem_type_code)
+ break;
+ }
+ if (elem_type_code < 0x80)
+ snprintf(b, mlen_b - 1, "[0x%x]", elem_type_code);
+ else
+ snprintf(b, mlen_b - 1, "vendor specific [0x%x]", elem_type_code);
+ b[mlen_b - 1] = '\0';
+ return b;
+}
+
+/* Returns true if el_type (element type) is of interest to the Additional
+ * Element Status page. Otherwise return false. */
+static bool
+is_et_used_by_aes(int el_type)
+{
+ if ((el_type >= 0) && (el_type < NUM_ACTIVE_ET_AESP_ARR))
+ return active_et_aesp_arr[el_type];
+ else
+ return false;
+}
+
+#if 0
+static struct join_row_t *
+find_join_row(struct th_es_t * tesp, int index, enum fj_select_t sel)
+{
+ int k;
+ struct join_row_t * jrp = tesp->j_base;
+
+ if (index < 0)
+ return NULL;
+ switch (sel) {
+ case FJ_IOE: /* index includes overall element */
+ if (index >= tesp->num_j_rows)
+ return NULL;
+ return jrp + index;
+ case FJ_EOE: /* index excludes overall element */
+ if (index >= tesp->num_j_eoe)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (index == jrp->ei_eoe)
+ return jrp;
+ }
+ return NULL;
+ case FJ_AESS: /* index includes only AES listed element types */
+ if (index >= tesp->num_j_eoe)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (index == jrp->ei_aess)
+ return jrp;
+ }
+ return NULL;
+ case FJ_SAS_CON: /* index on non-overall SAS connector etype */
+ if (index >= tesp->num_j_rows)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (SAS_CONNECTOR_ETC == jrp->etype) {
+ if (index == jrp->indiv_i)
+ return jrp;
+ }
+ }
+ return NULL;
+ default:
+ pr2serr("%s: bad selector: %d\n", __func__, (int)sel);
+ return NULL;
+ }
+}
+#endif
+
+static const struct join_row_t *
+find_join_row_cnst(const struct th_es_t * tesp, int index,
+ enum fj_select_t sel)
+{
+ int k;
+ const struct join_row_t * jrp = tesp->j_base;
+
+ if (index < 0)
+ return NULL;
+ switch (sel) {
+ case FJ_IOE: /* index includes overall element */
+ if (index >= tesp->num_j_rows)
+ return NULL;
+ return jrp + index;
+ case FJ_EOE: /* index excludes overall element */
+ if (index >= tesp->num_j_eoe)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (index == jrp->ei_eoe)
+ return jrp;
+ }
+ return NULL;
+ case FJ_AESS: /* index includes only AES listed element types */
+ if (index >= tesp->num_j_eoe)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (index == jrp->ei_aess)
+ return jrp;
+ }
+ return NULL;
+ case FJ_SAS_CON: /* index on non-overall SAS connector etype */
+ if (index >= tesp->num_j_rows)
+ return NULL;
+ for (k = 0; k < tesp->num_j_rows; ++k, ++jrp) {
+ if (SAS_CONNECTOR_ETC == jrp->etype) {
+ if (index == jrp->indiv_i)
+ return jrp;
+ }
+ }
+ return NULL;
+ default:
+ pr2serr("%s: bad selector: %d\n", __func__, (int)sel);
+ return NULL;
+ }
+}
+
+/* Return of 0 -> success, SG_LIB_CAT_* positive values or -2 if response
+ * had bad format, -1 -> other failures */
+static int
+do_rec_diag(struct sg_pt_base * ptvp, int page_code, uint8_t * rsp_buff,
+ int rsp_buff_size, struct opts_t * op, int * rsp_lenp)
+{
+ int k, d_len, rsp_len, res;
+ int resid = 0;
+ int vb = op->verbose;
+ const char * cp;
+ char b[80];
+ char bb[120];
+ static const char * rdr = "Receive diagnostic results";
+
+ memset(rsp_buff, 0, rsp_buff_size);
+ if (rsp_lenp)
+ *rsp_lenp = 0;
+ if ((cp = find_in_diag_page_desc(page_code)))
+ snprintf(bb, sizeof(bb), "%s dpage", cp);
+ else
+ snprintf(bb, sizeof(bb), "dpage 0x%x", page_code);
+ cp = bb;
+
+ if (op->data_arr && op->do_data) { /* user provided data */
+ /* N.B. First 4 bytes in data_arr are not used, user data was read in
+ * starting at byte offset 4 */
+ bool found = false;
+ int off = 0;
+ const uint8_t * bp = op->data_arr + DATA_IN_OFF;
+ const struct data_in_desc_t * didp = data_in_desc_arr;
+
+ for (k = 0, d_len = 0; k < MX_DATA_IN_DESCS; ++k, ++didp) {
+ if (! didp->in_use)
+ break;
+ if (page_code == didp->page_code) {
+ off = didp->offset;
+ d_len = didp->dp_len;
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ memcpy(rsp_buff, bp + off, d_len);
+ else {
+ if (vb)
+ pr2serr("%s: %s not found in user data\n", __func__, cp);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ cp = find_in_diag_page_desc(page_code);
+ if (vb > 2) {
+ pr2serr(" %s: response data from user", rdr);
+ if (3 == vb) {
+ pr2serr("%s:\n", (d_len > 256 ? ", first 256 bytes" : ""));
+ hex2stderr(rsp_buff, (d_len > 256 ? 256 : d_len), -1);
+ } else {
+ pr2serr(":\n");
+ hex2stderr(rsp_buff, d_len, 0);
+ }
+ }
+ res = 0;
+ resid = rsp_buff_size - d_len;
+ goto decode; /* step over the device access */
+ }
+ if (vb > 1)
+ pr2serr(" %s command for %s\n", rdr, cp);
+ res = sg_ll_receive_diag_pt(ptvp, true /* pcv */, page_code, rsp_buff,
+ rsp_buff_size, 0 /* default timeout */,
+ &resid, ! op->quiet, vb);
+ clear_scsi_pt_obj(ptvp);
+decode:
+ if (0 == res) {
+ rsp_len = sg_get_unaligned_be16(rsp_buff + 2) + 4;
+ if (rsp_len > rsp_buff_size) {
+ if (rsp_buff_size > 8) /* tried to get more than header */
+ pr2serr("<<< warning response buffer too small [was %d but "
+ "need %d]>>>\n", rsp_buff_size, rsp_len);
+ if (resid > 0)
+ rsp_buff_size -= resid;
+ } else if (resid > 0)
+ rsp_buff_size -= resid;
+ rsp_len = (rsp_len < rsp_buff_size) ? rsp_len : rsp_buff_size;
+ if (rsp_len < 0) {
+ pr2serr("<<< warning: resid=%d too large, implies negative "
+ "reply length: %d\n", resid, rsp_len);
+ rsp_len = 0;
+ }
+ if (rsp_lenp)
+ *rsp_lenp = rsp_len;
+ if ((rsp_len > 1) && (page_code != rsp_buff[0])) {
+ if ((0x9 == rsp_buff[0]) && (1 & rsp_buff[1])) {
+ pr2serr("Enclosure busy, try again later\n");
+ if (op->do_hex)
+ hex2stderr(rsp_buff, rsp_len, 0);
+ } else if (0x8 == rsp_buff[0]) {
+ pr2serr("Enclosure only supports Short Enclosure Status: "
+ "0x%x\n", rsp_buff[1]);
+ } else {
+ pr2serr("Invalid response, wanted page code: 0x%x but got "
+ "0x%x\n", page_code, rsp_buff[0]);
+ hex2stderr(rsp_buff, rsp_len, 0);
+ }
+ return -2;
+ }
+ return 0;
+ } else if (vb) {
+ pr2serr("Attempt to fetch %s failed\n", cp);
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr(" %s\n", b);
+ }
+ return res;
+}
+
+#if 1
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+#else
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int res, err;
+
+ if (len > 0) {
+ res = write(fileno(stdout), str, len);
+ if (res < 0) {
+ err = errno;
+ pr2serr("%s: write to stdout failed: %s [%d]\n", __func__,
+ strerror(err), err);
+ }
+ }
+}
+
+#endif
+
+/* CONFIGURATION_DPC [0x1]
+ * Display Configuration diagnostic page. */
+static void
+configuration_sdg(const uint8_t * resp, int resp_len)
+{
+ int j, k, el, num_subs, sum_elem_types;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const uint8_t * text_bp;
+ char b[64];
+
+ printf("Configuration diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */
+ sum_elem_types = 0;
+ last_bp = resp + resp_len - 1;
+ printf(" number of secondary subenclosures: %d\n",
+ num_subs - 1);
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ printf(" enclosure descriptor list\n");
+ for (k = 0; k < num_subs; ++k, bp += el) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ el = bp[3] + 4;
+ sum_elem_types += bp[2];
+ printf(" Subenclosure identifier: %d%s\n", bp[1],
+ (bp[1] ? "" : " [primary]"));
+ printf(" relative ES process id: %d, number of ES processes"
+ ": %d\n", ((bp[0] & 0x70) >> 4), (bp[0] & 0x7));
+ printf(" number of type descriptor headers: %d\n", bp[2]);
+ if (el < 40) {
+ pr2serr(" enc descriptor len=%d ??\n", el);
+ continue;
+ }
+ printf(" enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", bp[4 + j]);
+ printf("\n enclosure vendor: %.8s product: %.16s rev: %.4s\n",
+ bp + 12, bp + 20, bp + 36);
+ if (el > 40) {
+ char bb[1024];
+
+ printf(" vendor-specific data:\n");
+ hex2str(bp + 40, el - 40, " ", 0, sizeof(bb), bb);
+ printf("%s\n", bb);
+ }
+ }
+ /* printf("\n"); */
+ printf(" type descriptor header and text list\n");
+ text_bp = bp + (sum_elem_types * 4);
+ for (k = 0; k < sum_elem_types; ++k, bp += 4) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ printf(" Element type: %s, subenclosure id: %d\n",
+ etype_str(bp[0], b, sizeof(b)), bp[2]);
+ printf(" number of possible elements: %d\n", bp[1]);
+ if (bp[3] > 0) {
+ if (text_bp > last_bp)
+ goto truncated;
+ printf(" text: %.*s\n", bp[3], text_bp);
+ text_bp += bp[3];
+ }
+ }
+ return;
+truncated:
+ pr2serr(" <<<ses_configuration_sdg: response too short>>>\n");
+ return;
+}
+
+/* CONFIGURATION_DPC [0x1] read and used to build array pointed to by
+ * 'tdhp' with no more than 'max_elems' elements. If 'generationp' is non
+ * NULL then writes generation code where it points. if 'primary_ip" is
+ * non NULL the writes rimary enclosure info where it points.
+ * Returns total number of type descriptor headers written to 'tdhp' or -1
+ * if there is a problem */
+static int
+build_type_desc_hdr_arr(struct sg_pt_base * ptvp,
+ struct type_desc_hdr_t * tdhp, int max_elems,
+ uint32_t * generationp,
+ struct enclosure_info * primary_ip,
+ struct opts_t * op)
+{
+ int resp_len, k, el, num_subs, sum_type_dheaders, res, n;
+ int ret = 0;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+
+ if (NULL == config_dp_resp) {
+ config_dp_resp = sg_memalign(op->maxlen, 0, &free_config_dp_resp,
+ false);
+ if (NULL == config_dp_resp) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->maxlen);
+ ret = -1;
+ goto the_end;
+ }
+ res = do_rec_diag(ptvp, CONFIGURATION_DPC, config_dp_resp, op->maxlen,
+ op, &resp_len);
+ if (res) {
+ pr2serr("%s: couldn't read config page, res=%d\n", __func__, res);
+ ret = -1;
+ free(free_config_dp_resp);
+ free_config_dp_resp = NULL;
+ goto the_end;
+ }
+ if (resp_len < 4) {
+ ret = -1;
+ free(free_config_dp_resp);
+ free_config_dp_resp = NULL;
+ goto the_end;
+ }
+ config_dp_resp_len = resp_len;
+ } else
+ resp_len = config_dp_resp_len;
+
+ num_subs = config_dp_resp[1] + 1;
+ sum_type_dheaders = 0;
+ last_bp = config_dp_resp + resp_len - 1;
+ gen_code = sg_get_unaligned_be32(config_dp_resp + 4);
+ if (generationp)
+ *generationp = gen_code;
+ bp = config_dp_resp + 8;
+ for (k = 0; k < num_subs; ++k, bp += el) {
+ if ((bp + 3) > last_bp)
+ goto p_truncated;
+ el = bp[3] + 4;
+ sum_type_dheaders += bp[2];
+ if (el < 40) {
+ pr2serr("%s: short enc descriptor len=%d ??\n", __func__, el);
+ continue;
+ }
+ if ((0 == k) && primary_ip) {
+ ++primary_ip->have_info;
+ primary_ip->rel_esp_id = (bp[0] & 0x70) >> 4;
+ primary_ip->num_esp = (bp[0] & 0x7);
+ memcpy(primary_ip->enc_log_id, bp + 4, 8);
+ memcpy(primary_ip->enc_vendor_id, bp + 12, 8);
+ memcpy(primary_ip->product_id, bp + 20, 16);
+ memcpy(primary_ip->product_rev_level, bp + 36, 4);
+ }
+ }
+ for (k = 0; k < sum_type_dheaders; ++k, bp += 4) {
+ if ((bp + 3) > last_bp)
+ goto p_truncated;
+ if (k >= max_elems) {
+ pr2serr("%s: too many elements\n", __func__);
+ ret = -1;
+ goto the_end;
+ }
+ tdhp[k].etype = bp[0];
+ tdhp[k].num_elements = bp[1];
+ tdhp[k].se_id = bp[2];
+ tdhp[k].txt_len = bp[3];
+ }
+ if (op->ind_given && op->ind_etp) {
+ n = op->ind_et_inst;
+ for (k = 0; k < sum_type_dheaders; ++k) {
+ if (op->ind_etp->elem_type_code == tdhp[k].etype) {
+ if (0 == n)
+ break;
+ else
+ --n;
+ }
+ }
+ if (k < sum_type_dheaders)
+ op->ind_th = k;
+ else {
+ if (op->ind_et_inst)
+ pr2serr("%s: unable to find element type '%s%d'\n", __func__,
+ op->ind_etp->abbrev, op->ind_et_inst);
+ else
+ pr2serr("%s: unable to find element type '%s'\n", __func__,
+ op->ind_etp->abbrev);
+ ret = -1;
+ goto the_end;
+ }
+ }
+ ret = sum_type_dheaders;
+ goto the_end;
+
+p_truncated:
+ pr2serr("%s: config too short\n", __func__);
+ ret = -1;
+
+the_end:
+ if (0 == ret)
+ ++type_desc_hdr_count;
+ return ret;
+}
+
+static char *
+find_sas_connector_type(int conn_type, bool abridged, char * buff,
+ int buff_len)
+{
+ switch (conn_type) {
+ case 0x0:
+ snprintf(buff, buff_len, "No information");
+ break;
+ case 0x1:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS 4x");
+ else
+ snprintf(buff, buff_len, "SAS 4x receptacle (SFF-8470) "
+ "[max 4 phys]");
+ break;
+ case 0x2:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS 4x");
+ else
+ snprintf(buff, buff_len, "Mini SAS 4x receptacle (SFF-8088) "
+ "[max 4 phys]");
+ break;
+ case 0x3:
+ if (abridged)
+ snprintf(buff, buff_len, "QSFP+");
+ else
+ snprintf(buff, buff_len, "QSFP+ receptacle (SFF-8436) "
+ "[max 4 phys]");
+ break;
+ case 0x4:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS 4x active");
+ else
+ snprintf(buff, buff_len, "Mini SAS 4x active receptacle "
+ "(SFF-8088) [max 4 phys]");
+ break;
+ case 0x5:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 4x");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 4x receptacle (SFF-8644) "
+ "[max 4 phys]");
+ break;
+ case 0x6:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 8x");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 8x receptacle (SFF-8644) "
+ "[max 8 phys]");
+ break;
+ case 0x7:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 16x");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 16x receptacle (SFF-8644) "
+ "[max 16 phys]");
+ break;
+ case 0xf:
+ snprintf(buff, buff_len, "Vendor specific");
+ break;
+ case 0x10:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS 4i");
+ else
+ snprintf(buff, buff_len, "SAS 4i plug (SFF-8484) [max 4 phys]");
+ break;
+ case 0x11:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS 4i");
+ else
+ snprintf(buff, buff_len, "Mini SAS 4i receptacle (SFF-8087) "
+ "[max 4 phys]");
+ break;
+ case 0x12:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 4i");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 4i receptacle (SFF-8643) "
+ "[max 4 phys]");
+ break;
+ case 0x13:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 8i");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 8i receptacle (SFF-8643) "
+ "[max 8 phys]");
+ break;
+ case 0x14:
+ if (abridged)
+ snprintf(buff, buff_len, "Mini SAS HD 16i");
+ else
+ snprintf(buff, buff_len, "Mini SAS HD 16i receptacle (SFF-8643) "
+ "[max 16 phys]");
+ break;
+ case 0x15:
+ if (abridged)
+ snprintf(buff, buff_len, "SlimSAS 4i"); /* was "SAS SlimLine" */
+ else
+ snprintf(buff, buff_len, "SlimSAS 4i (SFF-8654) [max 4 phys]");
+ break;
+ case 0x16:
+ if (abridged)
+ snprintf(buff, buff_len, "SlimSAS 8i"); /* was "SAS SlimLine" */
+ else
+ snprintf(buff, buff_len, "SlimSAS 8i (SFF-8654) [max 8 phys]");
+ break;
+ case 0x17:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS MiniLink 4i");
+ else
+ snprintf(buff, buff_len, "SAS MiniLink 4i (SFF-8612) "
+ "[max 4 phys]");
+ break;
+ case 0x18:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS MiniLink 8i");
+ else
+ snprintf(buff, buff_len, "SAS MiniLink 8i (SFF-8612) "
+ "[max 8 phys]");
+ break;
+ case 0x20:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS Drive backplane");
+ else
+ snprintf(buff, buff_len, "SAS Drive backplane receptacle "
+ "(SFF-8482) [max 2 phys]");
+ break;
+ case 0x21:
+ if (abridged)
+ snprintf(buff, buff_len, "SATA host plug");
+ else
+ snprintf(buff, buff_len, "SATA host plug [max 1 phy]");
+ break;
+ case 0x22:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS Drive plug");
+ else
+ snprintf(buff, buff_len, "SAS Drive plug (SFF-8482) "
+ "[max 2 phys]");
+ break;
+ case 0x23:
+ if (abridged)
+ snprintf(buff, buff_len, "SATA device plug");
+ else
+ snprintf(buff, buff_len, "SATA device plug [max 1 phy]");
+ break;
+ case 0x24:
+ if (abridged)
+ snprintf(buff, buff_len, "Micro SAS receptacle");
+ else
+ snprintf(buff, buff_len, "Micro SAS receptacle [max 2 phys]");
+ break;
+ case 0x25:
+ if (abridged)
+ snprintf(buff, buff_len, "Micro SATA device plug");
+ else
+ snprintf(buff, buff_len, "Micro SATA device plug [max 1 phy]");
+ break;
+ case 0x26:
+ if (abridged)
+ snprintf(buff, buff_len, "Micro SAS plug");
+ else
+ snprintf(buff, buff_len, "Micro SAS plug (SFF-8486) [max 2 "
+ "phys]");
+ break;
+ case 0x27:
+ if (abridged)
+ snprintf(buff, buff_len, "Micro SAS/SATA plug");
+ else
+ snprintf(buff, buff_len, "Micro SAS/SATA plug (SFF-8486) "
+ "[max 2 phys]");
+ break;
+ case 0x28:
+ if (abridged)
+ snprintf(buff, buff_len, "12 Gb/s SAS drive backplane");
+ else
+ snprintf(buff, buff_len, "12 Gb/s SAS drive backplane receptacle "
+ "(SFF-8680) [max 2 phys]");
+ break;
+ case 0x29:
+ if (abridged)
+ snprintf(buff, buff_len, "12 Gb/s SAS drive plug");
+ else
+ snprintf(buff, buff_len, "12 Gb/s SAS drive plug (SFF-8680) "
+ "[max 2 phys]");
+ break;
+ case 0x2a:
+ if (abridged)
+ snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x receptacle");
+ else
+ snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded "
+ "receptacle (SFF-8639)");
+ break;
+ case 0x2b:
+ if (abridged)
+ snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x plug");
+ else
+ snprintf(buff, buff_len, "Multifunction 12 Gb/s 6x unshielded "
+ "plug (SFF-8639)");
+ break;
+ case 0x2c:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS MultiLink Drive backplane "
+ "receptacle");
+ else
+ snprintf(buff, buff_len, "SAS MultiLink Drive backplane "
+ "receptacle (SFF-8630)");
+ break;
+ case 0x2d:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS MultiLink Drive backplane plug");
+ else
+ snprintf(buff, buff_len, "SAS MultiLink Drive backplane plug "
+ "(SFF-8630)");
+ break;
+ case 0x2e:
+ if (abridged)
+ snprintf(buff, buff_len, "Reserved");
+ else
+ snprintf(buff, buff_len, "Reserved for internal connectors to "
+ "end device");
+ break;
+ case 0x2f:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS virtual connector");
+ else
+ snprintf(buff, buff_len, "SAS virtual connector [max 1 phy]");
+ break;
+ case 0x3f:
+ if (abridged)
+ snprintf(buff, buff_len, "VS internal connector");
+ else
+ snprintf(buff, buff_len, "Vendor specific internal connector");
+ break;
+ case 0x40:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS high density drive backplane "
+ "receptacle");
+ else
+ snprintf(buff, buff_len, "SAS high density drive backplane "
+ "receptacle (SFF-8631) [max 8 phys]");
+ break;
+ case 0x41:
+ if (abridged)
+ snprintf(buff, buff_len, "SAS high density drive backplane "
+ "plug");
+ else
+ snprintf(buff, buff_len, "SAS high density drive backplane "
+ "plug (SFF-8631) [max 8 phys]");
+ break;
+ default:
+ if (conn_type < 0x10)
+ snprintf(buff, buff_len, "unknown external connector type: 0x%x",
+ conn_type);
+ else if (conn_type < 0x20)
+ snprintf(buff, buff_len, "unknown internal wide connector type: "
+ "0x%x", conn_type);
+ else if (conn_type < 0x3f)
+ snprintf(buff, buff_len, "reserved for internal connector, "
+ "type: 0x%x", conn_type);
+ else if (conn_type < 0x70)
+ snprintf(buff, buff_len, "reserved connector type: 0x%x",
+ conn_type);
+ else if (conn_type < 0x80)
+ snprintf(buff, buff_len, "vendor specific connector type: 0x%x",
+ conn_type);
+ else /* conn_type is a 7 bit field, so this is impossible */
+ snprintf(buff, buff_len, "unexpected connector type: 0x%x",
+ conn_type);
+ break;
+ }
+ return buff;
+}
+
+/* 'Fan speed factor' new in ses4r04 */
+static int
+calc_fan_speed(int fan_speed_factor, int actual_fan_speed)
+{
+ switch (fan_speed_factor) {
+ case 0:
+ return actual_fan_speed * 10;
+ case 1:
+ return (actual_fan_speed * 10) + 20480;
+ case 2:
+ return actual_fan_speed * 100;
+ default:
+ break;
+ }
+ return -1; /* something is wrong */
+}
+
+static const char * elem_status_code_desc[] = {
+ "Unsupported", "OK", "Critical", "Noncritical",
+ "Unrecoverable", "Not installed", "Unknown", "Not available",
+ "No access allowed", "reserved [9]", "reserved [10]", "reserved [11]",
+ "reserved [12]", "reserved [13]", "reserved [14]", "reserved [15]",
+};
+
+static const char * actual_speed_desc[] = {
+ "stopped", "at lowest speed", "at second lowest speed",
+ "at third lowest speed", "at intermediate speed",
+ "at third highest speed", "at second highest speed", "at highest speed"
+};
+
+static const char * nv_cache_unit[] = {
+ "Bytes", "KiB", "MiB", "GiB"
+};
+
+static const char * invop_type_desc[] = {
+ "SEND DIAGNOSTIC page code error", "SEND DIAGNOSTIC page format error",
+ "Reserved", "Vendor specific error"
+};
+
+static void
+enc_status_helper(const char * pad, const uint8_t * statp, int etype,
+ bool abridged, const struct opts_t * op)
+{
+ int res, a, b, ct, bblen;
+ bool nofilter = ! op->do_filter;
+ char bb[128];
+
+
+ if (op->inner_hex) {
+ printf("%s%02x %02x %02x %02x\n", pad, statp[0], statp[1], statp[2],
+ statp[3]);
+ return;
+ }
+ if (! abridged)
+ printf("%sPredicted failure=%d, Disabled=%d, Swap=%d, status: %s\n",
+ pad, !!(statp[0] & 0x40), !!(statp[0] & 0x20),
+ !!(statp[0] & 0x10), elem_status_code_desc[statp[0] & 0xf]);
+ switch (etype) { /* element types */
+ case UNSPECIFIED_ETC:
+ if (op->verbose)
+ printf("%sstatus in hex: %02x %02x %02x %02x\n",
+ pad, statp[0], statp[1], statp[2], statp[3]);
+ break;
+ case DEVICE_ETC:
+ if (ARRAY_STATUS_DPC == op->page_code) { /* obsolete after SES-1 */
+ if (nofilter || (0xf0 & statp[1]))
+ printf("%sOK=%d, Reserved device=%d, Hot spare=%d, Cons "
+ "check=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[1] & 0x20),
+ !!(statp[1] & 0x10));
+ if (nofilter || (0xf & statp[1]))
+ printf("%sIn crit array=%d, In failed array=%d, Rebuild/"
+ "remap=%d, R/R abort=%d\n", pad, !!(statp[1] & 0x8),
+ !!(statp[1] & 0x4), !!(statp[1] & 0x2),
+ !!(statp[1] & 0x1));
+ if (nofilter || ((0x46 & statp[2]) || (0x8 & statp[3])))
+ printf("%sDo not remove=%d, RMV=%d, Ident=%d, Enable bypass "
+ "A=%d\n", pad, !!(statp[2] & 0x40), !!(statp[2] & 0x4),
+ !!(statp[2] & 0x2), !!(statp[3] & 0x8));
+ if (nofilter || (0x7 & statp[3]))
+ printf("%sEnable bypass B=%d, Bypass A enabled=%d, Bypass B "
+ "enabled=%d\n", pad, !!(statp[3] & 0x4),
+ !!(statp[3] & 0x2), !!(statp[3] & 0x1));
+ break;
+ }
+ printf("%sSlot address: %d\n", pad, statp[1]);
+ if (nofilter || (0xe0 & statp[2]))
+ printf("%sApp client bypassed A=%d, Do not remove=%d, Enc "
+ "bypassed A=%d\n", pad, !!(statp[2] & 0x80),
+ !!(statp[2] & 0x40), !!(statp[2] & 0x20));
+ if (nofilter || (0x1c & statp[2]))
+ printf("%sEnc bypassed B=%d, Ready to insert=%d, RMV=%d, Ident="
+ "%d\n", pad, !!(statp[2] & 0x10), !!(statp[2] & 0x8),
+ !!(statp[2] & 0x4), !!(statp[2] & 0x2));
+ if (nofilter || ((1 & statp[2]) || (0xe0 & statp[3])))
+ printf("%sReport=%d, App client bypassed B=%d, Fault sensed=%d, "
+ "Fault requested=%d\n", pad, !!(statp[2] & 0x1),
+ !!(statp[3] & 0x80), !!(statp[3] & 0x40),
+ !!(statp[3] & 0x20));
+ if (nofilter || (0x1e & statp[3]))
+ printf("%sDevice off=%d, Bypassed A=%d, Bypassed B=%d, Device "
+ "bypassed A=%d\n", pad, !!(statp[3] & 0x10),
+ !!(statp[3] & 0x8), !!(statp[3] & 0x4), !!(statp[3] & 0x2));
+ if (nofilter || (0x1 & statp[3]))
+ printf("%sDevice bypassed B=%d\n", pad, !!(statp[3] & 0x1));
+ break;
+ case POWER_SUPPLY_ETC:
+ if (nofilter || ((0xc0 & statp[1]) || (0xc & statp[2]))) {
+ printf("%sIdent=%d, Do not remove=%d, DC overvoltage=%d, "
+ "DC undervoltage=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[2] & 0x8),
+ !!(statp[2] & 0x4));
+ }
+ if (nofilter || ((0x2 & statp[2]) || (0xf0 & statp[3])))
+ printf("%sDC overcurrent=%d, Hot swap=%d, Fail=%d, Requested "
+ "on=%d, Off=%d\n", pad, !!(statp[2] & 0x2),
+ !!(statp[3] & 0x80), !!(statp[3] & 0x40),
+ !!(statp[3] & 0x20), !!(statp[3] & 0x10));
+ if (nofilter || (0xf & statp[3]))
+ printf("%sOvertmp fail=%d, Temperature warn=%d, AC fail=%d, "
+ "DC fail=%d\n", pad, !!(statp[3] & 0x8),
+ !!(statp[3] & 0x4), !!(statp[3] & 0x2),
+ !!(statp[3] & 0x1));
+ break;
+ case COOLING_ETC:
+ if (nofilter || ((0xc0 & statp[1]) || (0xf0 & statp[3])))
+ printf("%sIdent=%d, Do not remove=%d, Hot swap=%d, Fail=%d, "
+ "Requested on=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[3] & 0x80),
+ !!(statp[3] & 0x40), !!(statp[3] & 0x20));
+ printf("%sOff=%d, Actual speed=%d rpm, Fan %s\n", pad,
+ !!(statp[3] & 0x10),
+ calc_fan_speed((statp[1] >> 3) & 0x3,
+ ((0x7 & statp[1]) << 8) + statp[2]),
+ actual_speed_desc[7 & statp[3]]);
+ if (op->verbose > 1) /* show real field values */
+ printf("%s [Fan_speed_factor=%d, Actual_fan_speed=%d]\n",
+ pad, (statp[1] >> 3) & 0x3,
+ ((0x7 & statp[1]) << 8) + statp[2]);
+ break;
+ case TEMPERATURE_ETC: /* temperature sensor */
+ if (nofilter || ((0xc0 & statp[1]) || (0xf & statp[3]))) {
+ printf("%sIdent=%d, Fail=%d, OT failure=%d, OT warning=%d, "
+ "UT failure=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[3] & 0x8),
+ !!(statp[3] & 0x4), !!(statp[3] & 0x2));
+ printf("%sUT warning=%d\n", pad, !!(statp[3] & 0x1));
+ }
+ if (statp[2])
+ printf("%sTemperature=%d C\n", pad,
+ (int)statp[2] - TEMPERAT_OFF);
+ else
+ printf("%sTemperature: <reserved>\n", pad);
+ break;
+ case DOOR_ETC: /* OPEN field added in ses3r05 */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Open=%d, Unlock=%d\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[3] & 0x2), !!(statp[3] & 0x1));
+ break;
+ case AUD_ALARM_ETC: /* audible alarm */
+ if (nofilter || ((0xc0 & statp[1]) || (0xd0 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Request mute=%d, Mute=%d, "
+ "Remind=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[3] & 0x80),
+ !!(statp[3] & 0x40), !!(statp[3] & 0x10));
+ if (nofilter || (0xf & statp[3]))
+ printf("%sTone indicator: Info=%d, Non-crit=%d, Crit=%d, "
+ "Unrecov=%d\n", pad, !!(statp[3] & 0x8), !!(statp[3] & 0x4),
+ !!(statp[3] & 0x2), !!(statp[3] & 0x1));
+ break;
+ case ENC_SCELECTR_ETC: /* enclosure services controller electronics */
+ if (nofilter || (0xe0 & statp[1]) || (0x1 & statp[2]) ||
+ (0x80 & statp[3]))
+ printf("%sIdent=%d, Fail=%d, Do not remove=%d, Report=%d, "
+ "Hot swap=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[1] & 0x20),
+ !!(statp[2] & 0x1), !!(statp[3] & 0x80));
+ break;
+ case SCC_CELECTR_ETC: /* SCC controller electronics */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2])))
+ printf("%sIdent=%d, Fail=%d, Report=%d\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[2] & 0x1));
+ break;
+ case NV_CACHE_ETC: /* Non volatile cache */
+ res = sg_get_unaligned_be16(statp + 2);
+ printf("%sIdent=%d, Fail=%d, Size multiplier=%d, Non volatile cache "
+ "size=0x%x\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ (statp[1] & 0x3), res);
+ printf("%sHence non volatile cache size: %d %s\n", pad, res,
+ nv_cache_unit[statp[1] & 0x3]);
+ break;
+ case INV_OP_REASON_ETC: /* Invalid operation reason */
+ res = ((statp[1] >> 6) & 3);
+ printf("%sInvop type=%d %s\n", pad, res, invop_type_desc[res]);
+ switch (res) {
+ case 0:
+ printf("%sPage not supported=%d\n", pad, (statp[1] & 1));
+ break;
+ case 1:
+ printf("%sByte offset=%d, bit number=%d\n", pad,
+ sg_get_unaligned_be16(statp + 2), (statp[1] & 7));
+ break;
+ case 2:
+ case 3:
+ printf("%slast 3 bytes (hex): %02x %02x %02x\n", pad, statp[1],
+ statp[2], statp[3]);
+ break;
+ }
+ break;
+ case UI_POWER_SUPPLY_ETC: /* Uninterruptible power supply */
+ if (0 == statp[1])
+ printf("%sBattery status: discharged or unknown\n", pad);
+ else if (255 == statp[1])
+ printf("%sBattery status: 255 or more minutes remaining\n", pad);
+ else
+ printf("%sBattery status: %d minutes remaining\n", pad, statp[1]);
+ if (nofilter || (0xf8 & statp[2]))
+ printf("%sAC low=%d, AC high=%d, AC qual=%d, AC fail=%d, DC fail="
+ "%d\n", pad, !!(statp[2] & 0x80), !!(statp[2] & 0x40),
+ !!(statp[2] & 0x20), !!(statp[2] & 0x10),
+ !!(statp[2] & 0x8));
+ if (nofilter || ((0x7 & statp[2]) || (0xe3 & statp[3]))) {
+ printf("%sUPS fail=%d, Warn=%d, Intf fail=%d, Ident=%d, Fail=%d, "
+ "Do not remove=%d\n", pad, !!(statp[2] & 0x4),
+ !!(statp[2] & 0x2), !!(statp[2] & 0x1),
+ !!(statp[3] & 0x80), !!(statp[3] & 0x40),
+ !!(statp[3] & 0x20));
+ printf("%sBatt fail=%d, BPF=%d\n", pad, !!(statp[3] & 0x2),
+ !!(statp[3] & 0x1));
+ }
+ break;
+ case DISPLAY_ETC: /* Display (ses2r15) */
+ if (nofilter || (0xc0 & statp[1])) {
+ int dms = statp[1] & 0x3;
+
+ printf("%sIdent=%d, Fail=%d, Display mode status=%d", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40), dms);
+ if ((1 == dms) || (2 == dms)) {
+ uint16_t dcs = sg_get_unaligned_be16(statp + 2);
+
+ printf(", Display character status=0x%x", dcs);
+ if (statp[2] && (0 == statp[3]))
+ printf(" ['%c']", statp[2]);
+ }
+ printf("\n");
+ }
+ break;
+ case KEY_PAD_ETC: /* Key pad entry */
+ if (nofilter || (0xc0 & statp[1]))
+ printf("%sIdent=%d, Fail=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40));
+ break;
+ case ENCLOSURE_ETC:
+ a = ((statp[2] >> 2) & 0x3f);
+ if (nofilter || ((0x80 & statp[1]) || a || (0x2 & statp[2])))
+ printf("%sIdent=%d, Time until power cycle=%d, "
+ "Failure indication=%d\n", pad, !!(statp[1] & 0x80),
+ a, !!(statp[2] & 0x2));
+ b = ((statp[3] >> 2) & 0x3f);
+ if (nofilter || (0x1 & statp[2]) || a || b)
+ printf("%sWarning indication=%d, Requested power off "
+ "duration=%d\n", pad, !!(statp[2] & 0x1), b);
+ if (nofilter || (0x3 & statp[3]))
+ printf("%sFailure requested=%d, Warning requested=%d\n",
+ pad, !!(statp[3] & 0x2), !!(statp[3] & 0x1));
+ break;
+ case SCSI_PORT_TRAN_ETC: /* SCSI port/transceiver */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) ||
+ (0x13 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Report=%d, Disabled=%d, Loss of "
+ "link=%d, Xmit fail=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[2] & 0x1),
+ !!(statp[3] & 0x10), !!(statp[3] & 0x2),
+ !!(statp[3] & 0x1));
+ break;
+ case LANGUAGE_ETC:
+ printf("%sIdent=%d, Language code: %.2s\n", pad, !!(statp[1] & 0x80),
+ statp + 2);
+ break;
+ case COMM_PORT_ETC: /* Communication port */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Disabled=%d\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[3] & 0x1));
+ break;
+ case VOLT_SENSOR_ETC: /* Voltage sensor */
+ if (nofilter || (0xcf & statp[1])) {
+ printf("%sIdent=%d, Fail=%d, Warn Over=%d, Warn Under=%d, "
+ "Crit Over=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40), !!(statp[1] & 0x8),
+ !!(statp[1] & 0x4), !!(statp[1] & 0x2));
+ printf("%sCrit Under=%d\n", pad, !!(statp[1] & 0x1));
+ }
+#ifdef SG_LIB_MINGW
+ printf("%sVoltage: %g volts\n", pad,
+ ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0));
+#else
+ printf("%sVoltage: %.2f volts\n", pad,
+ ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0));
+#endif
+ break;
+ case CURR_SENSOR_ETC: /* Current sensor */
+ if (nofilter || (0xca & statp[1]))
+ printf("%sIdent=%d, Fail=%d, Warn Over=%d, Crit Over=%d\n",
+ pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[1] & 0x8), !!(statp[1] & 0x2));
+#ifdef SG_LIB_MINGW
+ printf("%sCurrent: %g amps\n", pad,
+ ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0));
+#else
+ printf("%sCurrent: %.2f amps\n", pad,
+ ((int)(short)sg_get_unaligned_be16(statp + 2) / 100.0));
+#endif
+ break;
+ case SCSI_TPORT_ETC: /* SCSI target port */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) ||
+ (0x1 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Report=%d, Enabled=%d\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[2] & 0x1), !!(statp[3] & 0x1));
+ break;
+ case SCSI_IPORT_ETC: /* SCSI initiator port */
+ if (nofilter || ((0xc0 & statp[1]) || (0x1 & statp[2]) ||
+ (0x1 & statp[3])))
+ printf("%sIdent=%d, Fail=%d, Report=%d, Enabled=%d\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[2] & 0x1), !!(statp[3] & 0x1));
+ break;
+ case SIMPLE_SUBENC_ETC: /* Simple subenclosure */
+ printf("%sIdent=%d, Fail=%d, Short enclosure status: 0x%x\n", pad,
+ !!(statp[1] & 0x80), !!(statp[1] & 0x40), statp[3]);
+ break;
+ case ARRAY_DEV_ETC: /* Array device */
+ if (nofilter || (0xf0 & statp[1]))
+ printf("%sOK=%d, Reserved device=%d, Hot spare=%d, Cons check="
+ "%d\n", pad, !!(statp[1] & 0x80), !!(statp[1] & 0x40),
+ !!(statp[1] & 0x20), !!(statp[1] & 0x10));
+ if (nofilter || (0xf & statp[1]))
+ printf("%sIn crit array=%d, In failed array=%d, Rebuild/remap=%d"
+ ", R/R abort=%d\n", pad, !!(statp[1] & 0x8),
+ !!(statp[1] & 0x4), !!(statp[1] & 0x2),
+ !!(statp[1] & 0x1));
+ if (nofilter || (0xf0 & statp[2]))
+ printf("%sApp client bypass A=%d, Do not remove=%d, Enc bypass "
+ "A=%d, Enc bypass B=%d\n", pad, !!(statp[2] & 0x80),
+ !!(statp[2] & 0x40), !!(statp[2] & 0x20),
+ !!(statp[2] & 0x10));
+ if (nofilter || (0xf & statp[2]))
+ printf("%sReady to insert=%d, RMV=%d, Ident=%d, Report=%d\n",
+ pad, !!(statp[2] & 0x8), !!(statp[2] & 0x4),
+ !!(statp[2] & 0x2), !!(statp[2] & 0x1));
+ if (nofilter || (0xf0 & statp[3]))
+ printf("%sApp client bypass B=%d, Fault sensed=%d, Fault reqstd="
+ "%d, Device off=%d\n", pad, !!(statp[3] & 0x80),
+ !!(statp[3] & 0x40), !!(statp[3] & 0x20),
+ !!(statp[3] & 0x10));
+ if (nofilter || (0xf & statp[3]))
+ printf("%sBypassed A=%d, Bypassed B=%d, Dev bypassed A=%d, "
+ "Dev bypassed B=%d\n",
+ pad, !!(statp[3] & 0x8), !!(statp[3] & 0x4),
+ !!(statp[3] & 0x2), !!(statp[3] & 0x1));
+ break;
+ case SAS_EXPANDER_ETC:
+ printf("%sIdent=%d, Fail=%d\n", pad, !!(statp[1] & 0x80),
+ !!(statp[1] & 0x40));
+ break;
+ case SAS_CONNECTOR_ETC: /* OC (overcurrent) added in ses3r07 */
+ ct = (statp[1] & 0x7f);
+ bblen = sizeof(bb);
+ if (abridged)
+ printf("%s%s, pl=%d", pad,
+ find_sas_connector_type(ct, true, bb, bblen), statp[2]);
+ else {
+ printf("%sIdent=%d, %s\n", pad, !!(statp[1] & 0x80),
+ find_sas_connector_type(ct, false, bb, bblen));
+ /* Mated added in ses3r10 */
+ printf("%sConnector physical link=0x%x, Mated=%d, Fail=%d, "
+ "OC=%d\n", pad, statp[2], !!(statp[3] & 0x80),
+ !!(statp[3] & 0x40), !!(statp[3] & 0x20));
+ }
+ break;
+ default:
+ if (etype < 0x80)
+ printf("%sUnknown element type, status in hex: %02x %02x %02x "
+ "%02x\n", pad, statp[0], statp[1], statp[2], statp[3]);
+ else
+ printf("%sVendor specific element type, status in hex: %02x "
+ "%02x %02x %02x\n", pad, statp[0], statp[1], statp[2],
+ statp[3]);
+ break;
+ }
+}
+
+/* ENC_STATUS_DPC [0x2]
+ * Display enclosure status diagnostic page. */
+static void
+enc_status_dp(const struct th_es_t * tesp, uint32_t ref_gen_code,
+ const uint8_t * resp, int resp_len,
+ const struct opts_t * op)
+{
+ int j, k;
+ uint32_t gen_code;
+ bool got1, match_ind_th;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const struct type_desc_hdr_t * tdhp = tesp->th_base;
+ char b[64];
+
+ printf("Enclosure Status diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ printf(" INVOP=%d, INFO=%d, NON-CRIT=%d, CRIT=%d, UNRECOV=%d\n",
+ !!(resp[1] & 0x10), !!(resp[1] & 0x8), !!(resp[1] & 0x4),
+ !!(resp[1] & 0x2), !!(resp[1] & 0x1));
+ last_bp = resp + resp_len - 1;
+ if (resp_len < 8)
+ goto truncated;
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%x\n", gen_code);
+ if (ref_gen_code != gen_code) {
+ pr2serr(" <<state of enclosure changed, please try again>>\n");
+ return;
+ }
+ printf(" status descriptor list\n");
+ bp = resp + 8;
+ for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ match_ind_th = (op->ind_given && (k == op->ind_th));
+ if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) {
+ printf(" Element type: %s, subenclosure id: %d [ti=%d]\n",
+ etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k);
+ printf(" Overall descriptor:\n");
+ enc_status_helper(" ", bp, tdhp->etype, false, op);
+ got1 = true;
+ }
+ for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) {
+ if (op->ind_given) {
+ if ((! match_ind_th) || (-1 == op->ind_indiv) ||
+ (! match_ind_indiv(j, op)))
+ continue;
+ }
+ printf(" Element %d descriptor:\n", j);
+ enc_status_helper(" ", bp, tdhp->etype, false, op);
+ got1 = true;
+ }
+ }
+ if (op->ind_given && (! got1)) {
+ printf(" >>> no match on --index=%d,%d", op->ind_th,
+ op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d\n", op->ind_indiv_last);
+ else
+ printf("\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<enc: response too short>>>\n");
+ return;
+}
+
+/* ARRAY_STATUS_DPC [0x6]
+ * Display array status diagnostic page. */
+static void
+array_status_dp(const struct th_es_t * tesp, uint32_t ref_gen_code,
+ const uint8_t * resp, int resp_len,
+ const struct opts_t * op)
+{
+ int j, k;
+ uint32_t gen_code;
+ bool got1, match_ind_th;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const struct type_desc_hdr_t * tdhp = tesp->th_base;
+ char b[64];
+
+ printf("Array Status diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ printf(" INVOP=%d, INFO=%d, NON-CRIT=%d, CRIT=%d, UNRECOV=%d\n",
+ !!(resp[1] & 0x10), !!(resp[1] & 0x8), !!(resp[1] & 0x4),
+ !!(resp[1] & 0x2), !!(resp[1] & 0x1));
+ last_bp = resp + resp_len - 1;
+ if (resp_len < 8)
+ goto truncated;
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%x\n", gen_code);
+ if (ref_gen_code != gen_code) {
+ pr2serr(" <<state of enclosure changed, please try again>>\n");
+ return;
+ }
+ printf(" status descriptor list\n");
+ bp = resp + 8;
+ for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ match_ind_th = (op->ind_given && (k == op->ind_th));
+ if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) {
+ printf(" Element type: %s, subenclosure id: %d [ti=%d]\n",
+ etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k);
+ printf(" Overall descriptor:\n");
+ enc_status_helper(" ", bp, tdhp->etype, false, op);
+ got1 = true;
+ }
+ for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) {
+ if (op->ind_given) {
+ if ((! match_ind_th) || (-1 == op->ind_indiv) ||
+ (! match_ind_indiv(j, op)))
+ continue;
+ }
+ printf(" Element %d descriptor:\n", j);
+ enc_status_helper(" ", bp, tdhp->etype, false, op);
+ got1 = true;
+ }
+ }
+ if (op->ind_given && (! got1)) {
+ printf(" >>> no match on --index=%d,%d", op->ind_th,
+ op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d\n", op->ind_indiv_last);
+ else
+ printf("\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<arr: response too short>>>\n");
+ return;
+}
+
+static char *
+reserved_or_num(char * buff, int buff_len, int num, int reserve_num)
+{
+ if (num == reserve_num)
+ strncpy(buff, "<res>", buff_len);
+ else
+ snprintf(buff, buff_len, "%d", num);
+ if (buff_len > 0)
+ buff[buff_len - 1] = '\0';
+ return buff;
+}
+
+static void
+threshold_helper(const char * header, const char * pad,
+ const uint8_t *tp, int etype,
+ const struct opts_t * op)
+{
+ char b[128];
+ char b2[128];
+
+ if (op->inner_hex) {
+ if (header)
+ printf("%s", header);
+ printf("%s%02x %02x %02x %02x\n", pad, tp[0], tp[1], tp[2], tp[3]);
+ return;
+ }
+ switch (etype) {
+ case 0x4: /*temperature */
+ if (header)
+ printf("%s", header);
+ printf("%shigh critical=%s, high warning=%s", pad,
+ reserved_or_num(b, 128, tp[0] - TEMPERAT_OFF, -TEMPERAT_OFF),
+ reserved_or_num(b2, 128, tp[1] - TEMPERAT_OFF, -TEMPERAT_OFF));
+ if (op->do_filter && (0 == tp[2]) && (0 == tp[3])) {
+ printf(" (in Celsius)\n");
+ break;
+ }
+ printf("\n%slow warning=%s, low critical=%s (in Celsius)\n", pad,
+ reserved_or_num(b, 128, tp[2] - TEMPERAT_OFF, -TEMPERAT_OFF),
+ reserved_or_num(b2, 128, tp[3] - TEMPERAT_OFF, -TEMPERAT_OFF));
+ break;
+ case 0xb: /* UPS */
+ if (header)
+ printf("%s", header);
+ if (0 == tp[2])
+ strcpy(b, "<vendor>");
+ else
+ snprintf(b, sizeof(b), "%d", tp[2]);
+ printf("%slow warning=%s, ", pad, b);
+ if (0 == tp[3])
+ strcpy(b, "<vendor>");
+ else
+ snprintf(b, sizeof(b), "%d", tp[3]);
+ printf("low critical=%s (in minutes)\n", b);
+ break;
+ case 0x12: /* voltage */
+ if (header)
+ printf("%s", header);
+#ifdef SG_LIB_MINGW
+ printf("%shigh critical=%g %%, high warning=%g %% (above nominal "
+ "voltage)\n", pad, 0.5 * tp[0], 0.5 * tp[1]);
+ printf("%slow warning=%g %%, low critical=%g %% (below nominal "
+ "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]);
+#else
+ printf("%shigh critical=%.1f %%, high warning=%.1f %% (above nominal "
+ "voltage)\n", pad, 0.5 * tp[0], 0.5 * tp[1]);
+ printf("%slow warning=%.1f %%, low critical=%.1f %% (below nominal "
+ "voltage)\n", pad, 0.5 * tp[2], 0.5 * tp[3]);
+#endif
+ break;
+ case 0x13: /* current */
+ if (header)
+ printf("%s", header);
+#ifdef SG_LIB_MINGW
+ printf("%shigh critical=%g %%, high warning=%g %%", pad,
+ 0.5 * tp[0], 0.5 * tp[1]);
+#else
+ printf("%shigh critical=%.1f %%, high warning=%.1f %%", pad,
+ 0.5 * tp[0], 0.5 * tp[1]);
+#endif
+ printf(" (above nominal current)\n");
+ break;
+ default:
+ if (op->verbose) {
+ if (header)
+ printf("%s", header);
+ printf("%s<< no thresholds for this element type >>\n", pad);
+ }
+ break;
+ }
+}
+
+/* THRESHOLD_DPC [0x5] */
+static void
+threshold_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code,
+ const uint8_t * resp, int resp_len,
+ const struct opts_t * op)
+{
+ int j, k;
+ uint32_t gen_code;
+ bool got1, match_ind_th;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const struct type_desc_hdr_t * tdhp = tesp->th_base;
+ char b[64];
+
+ printf("Threshold In diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ printf(" INVOP=%d\n", !!(resp[1] & 0x10));
+ last_bp = resp + resp_len - 1;
+ if (resp_len < 8)
+ goto truncated;
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ if (ref_gen_code != gen_code) {
+ pr2serr(" <<state of enclosure changed, please try again>>\n");
+ return;
+ }
+ printf(" Threshold status descriptor list\n");
+ bp = resp + 8;
+ for (k = 0, got1 = false; k < tesp->num_ths; ++k, ++tdhp) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ match_ind_th = (op->ind_given && (k == op->ind_th));
+ if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) {
+ printf(" Element type: %s, subenclosure id: %d [ti=%d]\n",
+ etype_str(tdhp->etype, b, sizeof(b)), tdhp->se_id, k);
+ threshold_helper(" Overall descriptor:\n", " ", bp,
+ tdhp->etype, op);
+ got1 = true;
+ }
+ for (bp += 4, j = 0; j < tdhp->num_elements; ++j, bp += 4) {
+ if (op->ind_given) {
+ if ((! match_ind_th) || (-1 == op->ind_indiv) ||
+ (! match_ind_indiv(j, op)))
+ continue;
+ }
+ snprintf(b, sizeof(b), " Element %d descriptor:\n", j);
+ threshold_helper(b, " ", bp, tdhp->etype, op);
+ got1 = true;
+ }
+ }
+ if (op->ind_given && (! got1)) {
+ printf(" >>> no match on --index=%d,%d", op->ind_th,
+ op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d\n", op->ind_indiv_last);
+ else
+ printf("\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<thresh: response too short>>>\n");
+ return;
+}
+
+/* ELEM_DESC_DPC [0x7]
+ * This page essentially contains names of overall and individual
+ * elements. */
+static void
+element_desc_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code,
+ const uint8_t * resp, int resp_len,
+ const struct opts_t * op)
+{
+ int j, k, desc_len;
+ uint32_t gen_code;
+ bool got1, match_ind_th;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const struct type_desc_hdr_t * tp;
+ char b[64];
+
+ printf("Element Descriptor In diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ last_bp = resp + resp_len - 1;
+ if (resp_len < 8)
+ goto truncated;
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ if (ref_gen_code != gen_code) {
+ pr2serr(" <<state of enclosure changed, please try again>>\n");
+ return;
+ }
+ printf(" element descriptor list (grouped by type):\n");
+ bp = resp + 8;
+ got1 = false;
+ for (k = 0, tp = tesp->th_base; k < tesp->num_ths; ++k, ++tp) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ desc_len = sg_get_unaligned_be16(bp + 2) + 4;
+ match_ind_th = (op->ind_given && (k == op->ind_th));
+ if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) {
+ printf(" Element type: %s, subenclosure id: %d [ti=%d]\n",
+ etype_str(tp->etype, b, sizeof(b)), tp->se_id, k);
+ if (desc_len > 4)
+ printf(" Overall descriptor: %.*s\n", desc_len - 4,
+ bp + 4);
+ else
+ printf(" Overall descriptor: <empty>\n");
+ got1 = true;
+ }
+ for (bp += desc_len, j = 0; j < tp->num_elements;
+ ++j, bp += desc_len) {
+ desc_len = sg_get_unaligned_be16(bp + 2) + 4;
+ if (op->ind_given) {
+ if ((! match_ind_th) || (-1 == op->ind_indiv) ||
+ (! match_ind_indiv(j, op)))
+ continue;
+ }
+ if (desc_len > 4)
+ printf(" Element %d descriptor: %.*s\n", j,
+ desc_len - 4, bp + 4);
+ else
+ printf(" Element %d descriptor: <empty>\n", j);
+ got1 = true;
+ }
+ }
+ if (op->ind_given && (! got1)) {
+ printf(" >>> no match on --index=%d,%d", op->ind_th,
+ op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d\n", op->ind_indiv_last);
+ else
+ printf("\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<element: response too short>>>\n");
+ return;
+}
+
+static bool
+saddr_non_zero(const uint8_t * bp)
+{
+ return ! sg_all_zeros(bp, 8);
+}
+
+static const char * sas_device_type[] = {
+ "no SAS device attached", /* but might be SATA device */
+ "end device",
+ "expander device", /* in SAS-1.1 this was a "edge expander device */
+ "expander device (fanout, SAS-1.1)", /* marked obsolete in SAS-2 */
+ "reserved [4]", "reserved [5]", "reserved [6]", "reserved [7]"
+};
+
+static void
+additional_elem_sas(const char * pad, const uint8_t * ae_bp, int etype,
+ const struct th_es_t * tesp, const struct opts_t * op)
+{
+ int phys, j, m, n, desc_type, eiioe, eip_offset;
+ bool nofilter = ! op->do_filter;
+ bool eip;
+ const struct join_row_t * jrp;
+ const uint8_t * aep;
+ const uint8_t * ed_bp;
+ const char * cp;
+ char b[64];
+
+ eip = !!(0x10 & ae_bp[0]);
+ eiioe = eip ? (0x3 & ae_bp[2]) : 0;
+ eip_offset = eip ? 2 : 0;
+ desc_type = (ae_bp[3 + eip_offset] >> 6) & 0x3;
+ if (op->verbose > 1)
+ printf("%sdescriptor_type: %d\n", pad, desc_type);
+ if (0 == desc_type) {
+ phys = ae_bp[2 + eip_offset];
+ printf("%snumber of phys: %d, not all phys: %d", pad, phys,
+ ae_bp[3 + eip_offset] & 1);
+ if (eip_offset)
+ printf(", device slot number: %d", ae_bp[5 + eip_offset]);
+ printf("\n");
+ aep = ae_bp + 4 + eip_offset + eip_offset;
+ for (j = 0; j < phys; ++j, aep += 28) {
+ bool print_sas_addr = false;
+ bool saddr_nz;
+
+ printf("%sphy index: %d\n", pad, j);
+ printf("%s SAS device type: %s\n", pad,
+ sas_device_type[(0x70 & aep[0]) >> 4]);
+ if (nofilter || (0xe & aep[2]))
+ printf("%s initiator port for:%s%s%s\n", pad,
+ ((aep[2] & 8) ? " SSP" : ""),
+ ((aep[2] & 4) ? " STP" : ""),
+ ((aep[2] & 2) ? " SMP" : ""));
+ if (nofilter || (0x8f & aep[3]))
+ printf("%s target port for:%s%s%s%s%s\n", pad,
+ ((aep[3] & 0x80) ? " SATA_port_selector" : ""),
+ ((aep[3] & 8) ? " SSP" : ""),
+ ((aep[3] & 4) ? " STP" : ""),
+ ((aep[3] & 2) ? " SMP" : ""),
+ ((aep[3] & 1) ? " SATA_device" : ""));
+ saddr_nz = saddr_non_zero(aep + 4);
+ if (nofilter || saddr_nz) {
+ print_sas_addr = true;
+ printf("%s attached SAS address: 0x", pad);
+ if (saddr_nz) {
+ for (m = 0; m < 8; ++m)
+ printf("%02x", aep[4 + m]);
+ } else
+ printf("0");
+ }
+ saddr_nz = saddr_non_zero(aep + 12);
+ if (nofilter || saddr_nz) {
+ print_sas_addr = true;
+ printf("\n%s SAS address: 0x", pad);
+ if (saddr_nz) {
+ for (m = 0; m < 8; ++m)
+ printf("%02x", aep[12 + m]);
+ } else
+ printf("0");
+ }
+ if (print_sas_addr)
+ printf("\n%s phy identifier: 0x%x\n", pad, aep[20]);
+ }
+ } else if (1 == desc_type) {
+ phys = ae_bp[2 + eip_offset];
+ if (SAS_EXPANDER_ETC == etype) {
+ printf("%snumber of phys: %d\n", pad, phys);
+ printf("%sSAS address: 0x", pad);
+ for (m = 0; m < 8; ++m)
+ printf("%02x", ae_bp[6 + eip_offset + m]);
+ printf("\n%sAttached connector; other_element pairs:\n", pad);
+ aep = ae_bp + 14 + eip_offset;
+ for (j = 0; j < phys; ++j, aep += 2) {
+ printf("%s [%d] ", pad, j);
+ m = aep[0]; /* connector element index */
+ if (0xff == m)
+ printf("no connector");
+ else {
+ if (tesp->j_base) {
+ if (0 == eiioe)
+ jrp = find_join_row_cnst(tesp, m, FJ_SAS_CON);
+ else if ((1 == eiioe) || (3 == eiioe))
+ jrp = find_join_row_cnst(tesp, m, FJ_IOE);
+ else
+ jrp = find_join_row_cnst(tesp, m, FJ_EOE);
+ if ((NULL == jrp) || (NULL == jrp->enc_statp) ||
+ (SAS_CONNECTOR_ETC != jrp->etype))
+ printf("broken [conn_idx=%d]", m);
+ else {
+ enc_status_helper("", jrp->enc_statp, jrp->etype,
+ true, op);
+ printf(" [%d]", jrp->indiv_i);
+ }
+ } else
+ printf("connector ei: %d", m);
+ }
+ m = aep[1]; /* other element index */
+ if (0xff != m) {
+ printf("; ");
+ if (tesp->j_base) {
+
+ if (0 == eiioe)
+ jrp = find_join_row_cnst(tesp, m, FJ_AESS);
+ else if ((1 == eiioe) || (3 == eiioe))
+ jrp = find_join_row_cnst(tesp, m, FJ_IOE);
+ else
+ jrp = find_join_row_cnst(tesp, m, FJ_EOE);
+ if (NULL == jrp)
+ printf("broken [oth_elem_idx=%d]", m);
+ else if (jrp->elem_descp) {
+ cp = etype_str(jrp->etype, b, sizeof(b));
+ ed_bp = jrp->elem_descp;
+ n = sg_get_unaligned_be16(ed_bp + 2);
+ if (n > 0)
+ printf("%.*s [%d,%d] etype: %s", n,
+ (const char *)(ed_bp + 4),
+ jrp->th_i, jrp->indiv_i, cp);
+ else
+ printf("[%d,%d] etype: %s", jrp->th_i,
+ jrp->indiv_i, cp);
+ } else {
+ cp = etype_str(jrp->etype, b, sizeof(b));
+ printf("[%d,%d] etype: %s", jrp->th_i,
+ jrp->indiv_i, cp);
+ }
+ } else
+ printf("other ei: %d", m);
+ }
+ printf("\n");
+ }
+ } else if ((SCSI_TPORT_ETC == etype) ||
+ (SCSI_IPORT_ETC == etype) ||
+ (ENC_SCELECTR_ETC == etype)) {
+ printf("%snumber of phys: %d\n", pad, phys);
+ aep = ae_bp + 6 + eip_offset;
+ for (j = 0; j < phys; ++j, aep += 12) {
+ printf("%sphy index: %d\n", pad, j);
+ printf("%s phy_id: 0x%x\n", pad, aep[0]);
+ printf("%s ", pad);
+ m = aep[2]; /* connector element index */
+ if (0xff == m)
+ printf("no connector");
+ else {
+ if (tesp->j_base) {
+ if (0 == eiioe)
+ jrp = find_join_row_cnst(tesp, m, FJ_SAS_CON);
+ else if ((1 == eiioe) || (3 == eiioe))
+ jrp = find_join_row_cnst(tesp, m, FJ_IOE);
+ else
+ jrp = find_join_row_cnst(tesp, m, FJ_EOE);
+ if ((NULL == jrp) || (NULL == jrp->enc_statp) ||
+ (SAS_CONNECTOR_ETC != jrp->etype))
+ printf("broken [conn_idx=%d]", m);
+ else {
+ enc_status_helper("", jrp->enc_statp, jrp->etype,
+ true, op);
+ printf(" [%d]", jrp->indiv_i);
+ }
+ } else
+ printf("connector ei: %d", m);
+ }
+ m = aep[3]; /* other element index */
+ if (0xff != m) {
+ printf("; ");
+ if (tesp->j_base) {
+ if (0 == eiioe)
+ jrp = find_join_row_cnst(tesp, m, FJ_AESS);
+ else if ((1 == eiioe) || (3 == eiioe))
+ jrp = find_join_row_cnst(tesp, m, FJ_IOE);
+ else
+ jrp = find_join_row_cnst(tesp, m, FJ_EOE);
+ if (NULL == jrp)
+ printf("broken [oth_elem_idx=%d]", m);
+ else if (jrp->elem_descp) {
+ cp = etype_str(jrp->etype, b, sizeof(b));
+ ed_bp = jrp->elem_descp;
+ n = sg_get_unaligned_be16(ed_bp + 2);
+ if (n > 0)
+ printf("%.*s [%d,%d] etype: %s", n,
+ (const char *)(ed_bp + 4),
+ jrp->th_i, jrp->indiv_i, cp);
+ else
+ printf("[%d,%d] etype: %s", jrp->th_i,
+ jrp->indiv_i, cp);
+ } else {
+ cp = etype_str(jrp->etype, b, sizeof(b));
+ printf("[%d,%d] etype: %s", jrp->th_i,
+ jrp->indiv_i, cp);
+ }
+ } else
+ printf("other ei: %d", m);
+ }
+ printf("\n");
+ printf("%s SAS address: 0x", pad);
+ for (m = 0; m < 8; ++m)
+ printf("%02x", aep[4 + m]);
+ printf("\n");
+ } /* end_for: loop over phys in SCSI initiator, target */
+ } else
+ printf("%sunrecognised element type [%d] for desc_type "
+ "1\n", pad, etype);
+ } else
+ printf("%sunrecognised descriptor type [%d]\n", pad, desc_type);
+}
+
+static void
+additional_elem_helper(const char * pad, const uint8_t * ae_bp,
+ int len, int etype, const struct th_es_t * tesp,
+ const struct opts_t * op)
+{
+ int ports, phys, j, m, eip_offset, pcie_pt;
+ bool eip;
+ uint16_t pcie_vid;
+ const uint8_t * aep;
+ char b[64];
+
+ if (op->inner_hex) {
+ for (j = 0; j < len; ++j) {
+ if (0 == (j % 16))
+ printf("%s%s", ((0 == j) ? "" : "\n"), pad);
+ printf("%02x ", ae_bp[j]);
+ }
+ printf("\n");
+ return;
+ }
+ eip = !!(0x10 & ae_bp[0]);
+ eip_offset = eip ? 2 : 0;
+ switch (0xf & ae_bp[0]) { /* switch on protocol identifier */
+ case TPROTO_FCP:
+ printf("%sTransport protocol: FCP\n", pad);
+ if (len < (12 + eip_offset))
+ break;
+ ports = ae_bp[2 + eip_offset];
+ printf("%snumber of ports: %d\n", pad, ports);
+ printf("%snode_name: ", pad);
+ for (m = 0; m < 8; ++m)
+ printf("%02x", ae_bp[6 + eip_offset + m]);
+ if (eip_offset)
+ printf(", device slot number: %d", ae_bp[5 + eip_offset]);
+ printf("\n");
+ aep = ae_bp + 14 + eip_offset;
+ for (j = 0; j < ports; ++j, aep += 16) {
+ printf("%s port index: %d, port loop position: %d, port "
+ "bypass reason: 0x%x\n", pad, j, aep[0], aep[1]);
+ printf("%srequested hard address: %d, n_port identifier: "
+ "%02x%02x%02x\n", pad, aep[4], aep[5],
+ aep[6], aep[7]);
+ printf("%s n_port name: ", pad);
+ for (m = 0; m < 8; ++m)
+ printf("%02x", aep[8 + m]);
+ printf("\n");
+ }
+ break;
+ case TPROTO_SAS:
+ printf("%sTransport protocol: SAS\n", pad);
+ if (len < (4 + eip_offset))
+ break;
+ additional_elem_sas(pad, ae_bp, etype, tesp, op);
+ break;
+ case TPROTO_PCIE: /* added in ses3r08; contains little endian fields */
+ printf("%sTransport protocol: PCIe\n", pad);
+ if (0 == eip_offset) {
+ printf("%sfor this protocol EIP must be set (it isn't)\n", pad);
+ break;
+ }
+ if (len < 6)
+ break;
+ pcie_pt = (ae_bp[5] >> 5) & 0x7;
+ if (TPROTO_PCIE_PS_NVME == pcie_pt)
+ printf("%sPCIe protocol type: NVMe\n", pad);
+ else { /* no others currently defined */
+ printf("%sTransport protocol: PCIe subprotocol=0x%x not "
+ "decoded\n", pad, pcie_pt);
+ if (op->verbose)
+ hex2stdout(ae_bp, len, 0);
+ break;
+ }
+ phys = ae_bp[4];
+ printf("%snumber of ports: %d, not all ports: %d", pad, phys,
+ ae_bp[5] & 1);
+ printf(", device slot number: %d\n", ae_bp[7]);
+
+ pcie_vid = sg_get_unaligned_le16(ae_bp + 10); /* N.B. LE */
+ printf("%sPCIe vendor id: 0x%" PRIx16 "%s\n", pad, pcie_vid,
+ (0xffff == pcie_vid) ? " (not reported)" : "");
+ printf("%sserial number: %.20s\n", pad, ae_bp + 12);
+ printf("%smodel number: %.40s\n", pad, ae_bp + 32);
+ aep = ae_bp + 72;
+ for (j = 0; j < phys; ++j, aep += 8) {
+ bool psn_valid = !!(0x4 & aep[0]);
+ bool bdf_valid = !!(0x2 & aep[0]);
+ bool cid_valid = !!(0x1 & aep[0]);
+
+ printf("%sport index: %d\n", pad, j);
+ printf("%s PSN_VALID=%d, BDF_VALID=%d, CID_VALID=%d\n", pad,
+ (int)psn_valid, (int)bdf_valid, (int)cid_valid);
+ if (cid_valid) /* N.B. little endian */
+ printf("%s controller id: 0x%" PRIx16 "\n", pad,
+ sg_get_unaligned_le16(aep + 1)); /* N.B. LEndian */
+ if (bdf_valid)
+ printf("%s bus number: 0x%x, device number: 0x%x, "
+ "function number: 0x%x\n", pad, aep[4],
+ (aep[5] >> 3) & 0x1f, 0x7 & aep[5]);
+ if (psn_valid) /* little endian, top 3 bits assumed zero */
+ printf("%s physical slot number: 0x%" PRIx16 "\n", pad,
+ 0x1fff & sg_get_unaligned_le16(aep + 6)); /* N.B. LE */
+ }
+ break;
+ default:
+ printf("%sTransport protocol: %s not decoded\n", pad,
+ sg_get_trans_proto_str((0xf & ae_bp[0]), sizeof(b), b));
+ if (op->verbose)
+ hex2stdout(ae_bp, len, 0);
+ break;
+ }
+}
+
+/* ADD_ELEM_STATUS_DPC [0xa] Additional Element Status dpage
+ * Previously called "Device element status descriptor". Changed "device"
+ * to "additional" to allow for SAS expander and SATA devices */
+static void
+additional_elem_sdg(const struct th_es_t * tesp, uint32_t ref_gen_code,
+ const uint8_t * resp, int resp_len,
+ const struct opts_t * op)
+{
+ int j, k, desc_len, etype, el_num, ind, elem_count, ei, eiioe, num_elems;
+ int fake_ei;
+ uint32_t gen_code;
+ bool eip, invalid, match_ind_th, my_eiioe_force, skip;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const struct type_desc_hdr_t * tp = tesp->th_base;
+ char b[64];
+
+ printf("Additional element status diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ last_bp = resp + resp_len - 1;
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ if (ref_gen_code != gen_code) {
+ pr2serr(" <<state of enclosure changed, please try again>>\n");
+ return;
+ }
+ printf(" additional element status descriptor list\n");
+ bp = resp + 8;
+ my_eiioe_force = op->eiioe_force;
+ for (k = 0, elem_count = 0; k < tesp->num_ths; ++k, ++tp) {
+ fake_ei = -1;
+ etype = tp->etype;
+ num_elems = tp->num_elements;
+ if (! is_et_used_by_aes(etype)) {
+ elem_count += num_elems;
+ continue; /* skip if not element type of interest */
+ }
+ if ((bp + 1) > last_bp)
+ goto truncated;
+
+ eip = !! (bp[0] & 0x10);
+ if (eip) { /* do bounds check on the element index */
+ ei = bp[3];
+ skip = false;
+ if ((0 == k) && op->eiioe_auto && (1 == ei)) {
+ /* heuristic: if first AES descriptor has EIP set and its
+ * element index equal to 1, then act as if the EIIOE field
+ * is one. */
+ my_eiioe_force = true;
+ }
+ eiioe = (0x3 & bp[2]);
+ if (my_eiioe_force && (0 == eiioe))
+ eiioe = 1;
+ if (1 == eiioe) {
+ if ((ei < (elem_count + k)) ||
+ (ei > (elem_count + k + num_elems))) {
+ elem_count += num_elems;
+ skip = true;
+ }
+ } else {
+ if ((ei < elem_count) || (ei > elem_count + num_elems)) {
+ if ((0 == ei) && (TPROTO_SAS == (0xf & bp[0])) &&
+ (1 == (bp[5] >> 6))) {
+ /* heuristic (hack) for Areca 8028 */
+ fake_ei = elem_count;
+ if (op->verbose > 2)
+ pr2serr("%s: hack, bad ei=%d, fake_ei=%d\n",
+ __func__, ei, fake_ei);
+ ei = fake_ei;
+ } else {
+ elem_count += num_elems;
+ skip = true;
+ }
+ }
+ }
+ if (skip) {
+ if (op->verbose > 2)
+ pr2serr("skipping etype=0x%x, k=%d due to "
+ "element_index=%d bounds\n effective eiioe=%d, "
+ "elem_count=%d, num_elems=%d\n", etype, k,
+ ei, eiioe, elem_count, num_elems);
+ continue;
+ }
+ }
+ match_ind_th = (op->ind_given && (k == op->ind_th));
+ if ((! op->ind_given) || (match_ind_th && (-1 == op->ind_indiv))) {
+ printf(" Element type: %s, subenclosure id: %d [ti=%d]\n",
+ etype_str(etype, b, sizeof(b)), tp->se_id, k);
+ }
+ el_num = 0;
+ for (j = 0; j < num_elems; ++j, bp += desc_len, ++el_num) {
+ invalid = !!(bp[0] & 0x80);
+ desc_len = bp[1] + 2;
+ eip = !!(bp[0] & 0x10);
+ eiioe = eip ? (0x3 & bp[2]) : 0;
+ if (fake_ei >= 0)
+ ind = fake_ei;
+ else
+ ind = eip ? bp[3] : el_num;
+ if (op->ind_given) {
+ if ((! match_ind_th) || (-1 == op->ind_indiv) ||
+ (! match_ind_indiv(el_num, op)))
+ continue;
+ }
+ if (eip)
+ printf(" Element index: %d eiioe=%d%s\n", ind, eiioe,
+ (((0 != eiioe) && my_eiioe_force) ?
+ " but overridden" : ""));
+ else
+ printf(" Element %d descriptor\n", ind);
+ if (invalid && (! op->inner_hex))
+ printf(" flagged as invalid (no further "
+ "information)\n");
+ else
+ additional_elem_helper(" ", bp, desc_len, etype,
+ tesp, op);
+ }
+ elem_count += tp->num_elements;
+ } /* end_for: loop over type descriptor headers */
+ return;
+truncated:
+ pr2serr(" <<<additional: response too short>>>\n");
+ return;
+}
+
+/* SUBENC_HELP_TEXT_DPC [0xb] */
+static void
+subenc_help_sdg(const uint8_t * resp, int resp_len)
+{
+ int k, el, num_subs;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+
+ printf("Subenclosure help text diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */
+ last_bp = resp + resp_len - 1;
+ printf(" number of secondary subenclosures: %d\n", num_subs - 1);
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ for (k = 0; k < num_subs; ++k, bp += el) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ el = sg_get_unaligned_be16(bp + 2) + 4;
+ printf(" subenclosure identifier: %d\n", bp[1]);
+ if (el > 4)
+ printf(" %.*s\n", el - 4, bp + 4);
+ else
+ printf(" <empty>\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<subenc: response too short>>>\n");
+ return;
+}
+
+/* SUBENC_STRING_DPC [0xc] */
+static void
+subenc_string_sdg(const uint8_t * resp, int resp_len)
+{
+ int k, el, num_subs;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+
+ printf("Subenclosure string in diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */
+ last_bp = resp + resp_len - 1;
+ printf(" number of secondary subenclosures: %d\n", num_subs - 1);
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ for (k = 0; k < num_subs; ++k, bp += el) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ el = sg_get_unaligned_be16(bp + 2) + 4;
+ printf(" subenclosure identifier: %d\n", bp[1]);
+ if (el > 4) {
+ char bb[1024];
+
+ hex2str(bp + 40, el - 40, " ", 0, sizeof(bb), bb);
+ printf("%s\n", bb);
+ } else
+ printf(" <empty>\n");
+ }
+ return;
+truncated:
+ pr2serr(" <<<subence str: response too short>>>\n");
+ return;
+}
+
+/* SUBENC_NICKNAME_DPC [0xf] */
+static void
+subenc_nickname_sdg(const uint8_t * resp, int resp_len)
+{
+ int k, el, num_subs;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+
+ printf("Subenclosure nickname status diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */
+ last_bp = resp + resp_len - 1;
+ printf(" number of secondary subenclosures: %d\n", num_subs - 1);
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ el = 40;
+ for (k = 0; k < num_subs; ++k, bp += el) {
+ if ((bp + el - 1) > last_bp)
+ goto truncated;
+ printf(" subenclosure identifier: %d\n", bp[1]);
+ printf(" nickname status: 0x%x\n", bp[2]);
+ printf(" nickname additional status: 0x%x\n", bp[3]);
+ printf(" nickname language code: %.2s\n", bp + 6);
+ printf(" nickname: %.*s\n", 32, bp + 8);
+ }
+ return;
+truncated:
+ pr2serr(" <<<subence str: response too short>>>\n");
+ return;
+}
+
+/* SUPPORTED_SES_DPC [0xd] */
+static void
+supported_pages_sdg(const char * leadin, const uint8_t * resp,
+ int resp_len)
+{
+ int k, code, prev;
+ bool got1;
+ const struct diag_page_abbrev * ap;
+
+ printf("%s:\n", leadin);
+ for (k = 0, prev = 0; k < (resp_len - 4); ++k, prev = code) {
+ const char * cp;
+
+ code = resp[k + 4];
+ if (code < prev)
+ break; /* assume to be padding at end */
+ cp = find_diag_page_desc(code);
+ if (cp) {
+ printf(" %s [", cp);
+ for (ap = dp_abbrev, got1 = false; ap->abbrev; ++ap) {
+ if (ap->page_code == code) {
+ printf("%s%s", (got1 ? "," : ""), ap->abbrev);
+ got1 = true;
+ }
+ }
+ printf("] [0x%x]\n", code);
+ } else
+ printf(" <unknown> [0x%x]\n", code);
+ }
+}
+
+/* An array of Download microcode status field values and descriptions */
+static struct diag_page_code mc_status_arr[] = {
+ {0x0, "No download microcode operation in progress"},
+ {0x1, "Download in progress, awaiting more"},
+ {0x2, "Download complete, updating non-volatile storage"},
+ {0x3, "Updating non-volatile storage with deferred microcode"},
+ {0x10, "Complete, no error, starting now"},
+ {0x11, "Complete, no error, start after hard reset or power cycle"},
+ {0x12, "Complete, no error, start after power cycle"},
+ {0x13, "Complete, no error, start after activate_mc, hard reset or "
+ "power cycle"},
+ {0x80, "Error, discarded, see additional status"},
+ {0x81, "Error, discarded, image error"},
+ {0x82, "Timeout, discarded"},
+ {0x83, "Internal error, need new microcode before reset"},
+ {0x84, "Internal error, need new microcode, reset safe"},
+ {0x85, "Unexpected activate_mc received"},
+ {0x1000, NULL},
+};
+
+static const char *
+get_mc_status(uint8_t status_val)
+{
+ const struct diag_page_code * mcsp;
+
+ for (mcsp = mc_status_arr; mcsp->desc; ++mcsp) {
+ if (status_val == mcsp->page_code)
+ return mcsp->desc;
+ }
+ return "";
+}
+
+/* DOWNLOAD_MICROCODE_DPC [0xe] */
+static void
+download_code_sdg(const uint8_t * resp, int resp_len)
+{
+ int k, num_subs;
+ uint32_t gen_code;
+ const uint8_t * bp;
+ const uint8_t * last_bp;
+ const char * cp;
+
+ printf("Download microcode status diagnostic page:\n");
+ if (resp_len < 4)
+ goto truncated;
+ num_subs = resp[1] + 1; /* number of subenclosures (add 1 for primary) */
+ last_bp = resp + resp_len - 1;
+ printf(" number of secondary subenclosures: %d\n", num_subs - 1);
+ gen_code = sg_get_unaligned_be32(resp + 4);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ for (k = 0; k < num_subs; ++k, bp += 16) {
+ if ((bp + 3) > last_bp)
+ goto truncated;
+ cp = (0 == bp[1]) ? " [primary]" : "";
+ printf(" subenclosure identifier: %d%s\n", bp[1], cp);
+ cp = get_mc_status(bp[2]);
+ if (strlen(cp) > 0) {
+ printf(" download microcode status: %s [0x%x]\n", cp, bp[2]);
+ printf(" download microcode additional status: 0x%x\n",
+ bp[3]);
+ } else
+ printf(" download microcode status: 0x%x [additional "
+ "status: 0x%x]\n", bp[2], bp[3]);
+ printf(" download microcode maximum size: %d bytes\n",
+ sg_get_unaligned_be32(bp + 4));
+ printf(" download microcode expected buffer id: 0x%x\n", bp[11]);
+ printf(" download microcode expected buffer id offset: %d\n",
+ sg_get_unaligned_be32(bp + 12));
+ }
+ return;
+truncated:
+ pr2serr(" <<<download: response too short>>>\n");
+ return;
+}
+
+/* Reads hex data from command line, stdin or a file when in_hex is true.
+ * Reads binary from stdin or file when in_hex is false. Returns 0 on
+ * success, 1 otherwise. If inp is a file and may_have_at, then the
+ * first character is skipped to get filename (since it should be '@'). */
+static int
+read_hex(const char * inp, uint8_t * arr, int mx_arr_len, int * arr_len,
+ bool in_hex, bool may_have_at, int vb)
+{
+ bool has_stdin, split_line;
+ int in_len, k, j, m, off, off_fn;
+ unsigned int h;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+ char line[512];
+ char carry_over[4];
+ FILE * fp = NULL;
+
+ if ((NULL == inp) || (NULL == arr) || (NULL == arr_len))
+ return 1;
+ off_fn = may_have_at ? 1 : 0;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len) {
+ *arr_len = 0;
+ return 0;
+ }
+ has_stdin = ((1 == in_len) && ('-' == inp[0]));
+
+ if (! in_hex) { /* binary, assume its not on the command line, */
+ int fd; /* that leaves stdin or a file (pipe) */
+ struct stat a_stat;
+
+ if (has_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(inp + off_fn, O_RDONLY);
+ if (fd < 0) {
+ pr2serr("unable to open binary file %s: %s\n", inp + off_fn,
+ safe_strerror(errno));
+ return 1;
+ }
+ }
+ k = read(fd, arr, mx_arr_len);
+ if (k <= 0) {
+ if (0 == k)
+ pr2serr("read 0 bytes from binary file %s\n", inp + off_fn);
+ else
+ pr2serr("read from binary file %s: %s\n", inp + off_fn,
+ safe_strerror(errno));
+ if (! has_stdin)
+ close(fd);
+ return 1;
+ }
+ if ((0 == fstat(fd, &a_stat)) && S_ISFIFO(a_stat.st_mode)) {
+ /* pipe; keep reading till error or 0 read */
+ while (k < mx_arr_len) {
+ m = read(fd, arr + k, mx_arr_len - k);
+ if (0 == m)
+ break;
+ if (m < 0) {
+ pr2serr("read from binary pipe %s: %s\n", inp + off_fn,
+ safe_strerror(errno));
+ if (! has_stdin)
+ close(fd);
+ return 1;
+ }
+ k += m;
+ }
+ }
+ *arr_len = k;
+ if (! has_stdin)
+ close(fd);
+ return 0;
+ }
+ if (has_stdin || (! may_have_at) || ('@' == inp[0])) {
+ /* read hex from stdin or file */
+ if (has_stdin)
+ fp = stdin;
+ else {
+ fp = fopen(inp + off_fn, "r");
+ if (NULL == fp) {
+ pr2serr("%s: unable to open file: %s\n", __func__,
+ inp + off_fn);
+ return 1;
+ }
+ }
+ carry_over[0] = 0;
+ for (j = 0, off = 0; j < MX_DATA_IN_LINES; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit((uint8_t)line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%x", &h))
+ arr[off - 1] = h; /* back up and overwrite */
+ else {
+ pr2serr("%s: carry_over error ['%s'] around line "
+ "%d\n", __func__, carry_over, j + 1);
+ goto err_with_fp;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+ if (in_len != k) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__,
+ j + 1, m + k + 1);
+ if (vb > 2)
+ pr2serr("first 40 characters of line: %.40s\n", line);
+ goto err_with_fp;
+ }
+ for (k = 0; k < (mx_arr_len - off); ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff in line %d, "
+ "pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ if (vb > 2)
+ pr2serr("first 40 characters of line: %.40s\n",
+ line);
+ goto err_with_fp;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ arr[off + k] = h;
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ pr2serr("%s: error in line %d, at pos %d\n", __func__,
+ j + 1, (int)(lcp - line + 1));
+ if (vb > 2)
+ pr2serr("first 40 characters of line: %.40s\n", line);
+ goto err_with_fp;
+ }
+ }
+ off += k + 1;
+ if (off >= mx_arr_len)
+ break;
+ }
+ *arr_len = off;
+ } else { /* hex string on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ goto err_with_fp;
+ }
+ for (k = 0; k < mx_arr_len; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ goto err_with_fp;
+ }
+ arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ goto err_with_fp;
+ }
+ }
+ *arr_len = k + 1;
+ }
+ if (vb > 3) {
+ pr2serr("%s: user provided data:\n", __func__);
+ hex2stderr(arr, *arr_len, 0);
+ }
+ if (fp && (fp != stdin))
+ fclose(fp);
+ return 0;
+
+err_with_fp:
+ if (fp && (fp != stdin))
+ fclose(fp);
+ return 1;
+}
+
+static int
+process_status_dpage(struct sg_pt_base * ptvp, int page_code, uint8_t * resp,
+ int resp_len, struct opts_t * op)
+{
+ int j, num_ths;
+ int ret = 0;
+ uint32_t ref_gen_code;
+ const char * cp;
+ struct enclosure_info primary_info;
+ struct th_es_t tes;
+ struct th_es_t * tesp;
+ char bb[120];
+
+ tesp = &tes;
+ memset(tesp, 0, sizeof(tes));
+ if ((cp = find_in_diag_page_desc(page_code)))
+ snprintf(bb, sizeof(bb), "%s dpage", cp);
+ else
+ snprintf(bb, sizeof(bb), "dpage 0x%x", page_code);
+ cp = bb;
+ if (op->do_raw) {
+ if (1 == op->do_raw)
+ hex2stdout(resp + 4, resp_len - 4, -1);
+ else {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0)
+ perror("sg_set_binary_mode");
+ dStrRaw(resp, resp_len);
+ }
+ goto fini;
+ } else if (op->do_hex) {
+ if (op->do_hex > 2) {
+ if (op->do_hex > 3) {
+ if (4 == op->do_hex)
+ printf("\n# %s:\n", cp);
+ else
+ printf("\n# %s [0x%x]:\n", cp, page_code);
+ }
+ hex2stdout(resp, resp_len, -1);
+ } else {
+ printf("# Response in hex for %s:\n", cp);
+ hex2stdout(resp, resp_len, (2 == op->do_hex));
+ }
+ goto fini;
+ }
+
+ memset(&primary_info, 0, sizeof(primary_info));
+ switch (page_code) {
+ case SUPPORTED_DPC:
+ supported_pages_sdg("Supported diagnostic pages", resp, resp_len);
+ break;
+ case CONFIGURATION_DPC:
+ configuration_sdg(resp, resp_len);
+ break;
+ case ENC_STATUS_DPC:
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr,
+ MX_ELEM_HDR, &ref_gen_code,
+ &primary_info, op);
+ if (num_ths < 0) {
+ ret = num_ths;
+ goto fini;
+ }
+ if ((1 == type_desc_hdr_count) && primary_info.have_info) {
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ enc_status_dp(tesp, ref_gen_code, resp, resp_len, op);
+ break;
+ case ARRAY_STATUS_DPC:
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr,
+ MX_ELEM_HDR, &ref_gen_code,
+ &primary_info, op);
+ if (num_ths < 0) {
+ ret = num_ths;
+ goto fini;
+ }
+ if ((1 == type_desc_hdr_count) && primary_info.have_info) {
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ array_status_dp(tesp, ref_gen_code, resp, resp_len, op);
+ break;
+ case HELP_TEXT_DPC:
+ printf("Help text diagnostic page (for primary "
+ "subenclosure):\n");
+ if (resp_len > 4)
+ printf(" %.*s\n", resp_len - 4, resp + 4);
+ else
+ printf(" <empty>\n");
+ break;
+ case STRING_DPC:
+ printf("String In diagnostic page (for primary "
+ "subenclosure):\n");
+ if (resp_len > 4)
+ hex2stdout(resp + 4, resp_len - 4, 0);
+ else
+ printf(" <empty>\n");
+ break;
+ case THRESHOLD_DPC:
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr,
+ MX_ELEM_HDR, &ref_gen_code,
+ &primary_info, op);
+ if (num_ths < 0) {
+ ret = num_ths;
+ goto fini;
+ }
+ if ((1 == type_desc_hdr_count) && primary_info.have_info) {
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ threshold_sdg(tesp, ref_gen_code, resp, resp_len, op);
+ break;
+ case ELEM_DESC_DPC:
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr,
+ MX_ELEM_HDR, &ref_gen_code,
+ &primary_info, op);
+ if (num_ths < 0) {
+ ret = num_ths;
+ goto fini;
+ }
+ if ((1 == type_desc_hdr_count) && primary_info.have_info) {
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ element_desc_sdg(tesp, ref_gen_code, resp, resp_len, op);
+ break;
+ case SHORT_ENC_STATUS_DPC:
+ printf("Short enclosure status diagnostic page, "
+ "status=0x%x\n", resp[1]);
+ break;
+ case ENC_BUSY_DPC:
+ printf("Enclosure Busy diagnostic page, "
+ "busy=%d [vendor specific=0x%x]\n",
+ resp[1] & 1, (resp[1] >> 1) & 0xff);
+ break;
+ case ADD_ELEM_STATUS_DPC:
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr,
+ MX_ELEM_HDR, &ref_gen_code,
+ &primary_info, op);
+ if (num_ths < 0) {
+ ret = num_ths;
+ goto fini;
+ }
+ if (primary_info.have_info) {
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ additional_elem_sdg(tesp, ref_gen_code, resp, resp_len, op);
+ break;
+ case SUBENC_HELP_TEXT_DPC:
+ subenc_help_sdg(resp, resp_len);
+ break;
+ case SUBENC_STRING_DPC:
+ subenc_string_sdg(resp, resp_len);
+ break;
+ case SUPPORTED_SES_DPC:
+ supported_pages_sdg("Supported SES diagnostic pages", resp,
+ resp_len);
+ break;
+ case DOWNLOAD_MICROCODE_DPC:
+ download_code_sdg(resp, resp_len);
+ break;
+ case SUBENC_NICKNAME_DPC:
+ subenc_nickname_sdg(resp, resp_len);
+ break;
+ default:
+ printf("Cannot decode response from diagnostic page: %s\n", cp);
+ hex2stdout(resp, resp_len, 0);
+ }
+
+fini:
+ return ret;
+}
+
+/* Display "status" page or pages (if op->page_code==0xff) . data-in from
+ * SES device or user provided (with --data= option). Return 0 for success */
+static int
+process_status_page_s(struct sg_pt_base * ptvp, struct opts_t * op)
+{
+ int page_code, ret, resp_len;
+ uint8_t * resp = NULL;
+ uint8_t * free_resp = NULL;
+
+ resp = sg_memalign(op->maxlen, 0, &free_resp, false);
+ if (NULL == resp) {
+ pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
+ op->maxlen);
+ ret = -1;
+ goto fini;
+ }
+ page_code = op->page_code;
+ if (ALL_DPC == page_code) {
+ int k, n;
+ uint8_t pc, prev;
+ uint8_t supp_dpg_arr[256];
+ const int s_arr_sz = sizeof(supp_dpg_arr);
+
+ memset(supp_dpg_arr, 0, s_arr_sz);
+ ret = do_rec_diag(ptvp, SUPPORTED_DPC, resp, op->maxlen, op,
+ &resp_len);
+ if (ret) /* SUPPORTED_DPC failed so try SUPPORTED_SES_DPC */
+ ret = do_rec_diag(ptvp, SUPPORTED_SES_DPC, resp, op->maxlen, op,
+ &resp_len);
+ if (ret)
+ goto fini;
+ for (n = 0, pc = 0; (n < s_arr_sz) && (n < (resp_len - 4)); ++n) {
+ prev = pc;
+ pc = resp[4 + n];
+ if (prev > pc) {
+ if (pc) { /* could be zero pad at end which is ok */
+ pr2serr("%s: Supported (SES) dpage seems corrupt, "
+ "should ascend\n", __func__);
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ break;
+ }
+ if (pc > 0x2f)
+ break;
+ supp_dpg_arr[n] = pc;
+ }
+ for (k = 0; k < n; ++k) {
+ page_code = supp_dpg_arr[k];
+ ret = do_rec_diag(ptvp, page_code, resp, op->maxlen, op,
+ &resp_len);
+ if (ret)
+ goto fini;
+ ret = process_status_dpage(ptvp, page_code, resp, resp_len, op);
+ }
+ } else { /* asking for a specific page code */
+ ret = do_rec_diag(ptvp, page_code, resp, op->maxlen, op, &resp_len);
+ if (ret)
+ goto fini;
+ ret = process_status_dpage(ptvp, page_code, resp, resp_len, op);
+ }
+
+fini:
+ if (free_resp)
+ free(free_resp);
+ return ret;
+}
+
+static void
+devslotnum_and_sasaddr(struct join_row_t * jrp, const uint8_t * ae_bp)
+{
+ if ((NULL == jrp) || (NULL == ae_bp) || (0 == (0x10 & ae_bp[0])))
+ return; /* sanity and expect EIP=1 */
+ switch (0xf & ae_bp[0]) {
+ case TPROTO_FCP:
+ jrp->dev_slot_num = ae_bp[7];
+ break;
+ case TPROTO_SAS:
+ if (0 == (0xc0 & ae_bp[5])) {
+ /* only for device slot and array device slot elements */
+ jrp->dev_slot_num = ae_bp[7];
+ if (ae_bp[4] > 0) { /* number of phys */
+ int m;
+
+ /* Use the first phy's "SAS ADDRESS" field */
+ for (m = 0; m < 8; ++m)
+ jrp->sas_addr[m] = ae_bp[(4 + 4 + 12) + m];
+ }
+ }
+ break;
+ case TPROTO_PCIE:
+ jrp->dev_slot_num = ae_bp[7];
+ break;
+ default:
+ ;
+ }
+}
+
+static const char *
+offset_str(long offset, bool in_hex, char * b, int blen)
+{
+ if (in_hex && (offset >= 0))
+ snprintf(b, blen, "0x%lx", offset);
+ else
+ snprintf(b, blen, "%ld", offset);
+ return b;
+}
+
+/* Returns broken_ei which is only true when EIP=1 and EIIOE=0 is overridden
+ * as outlined in join array description near the top of this file. */
+static bool
+join_aes_helper(const uint8_t * ae_bp, const uint8_t * ae_last_bp,
+ const struct th_es_t * tesp, const struct opts_t * op)
+{
+ int k, j, ei, eiioe, aes_i, hex, blen;
+ bool eip, broken_ei;
+ struct join_row_t * jrp;
+ struct join_row_t * jr2p;
+ const struct type_desc_hdr_t * tdhp = tesp->th_base;
+ char b[20];
+
+ jrp = tesp->j_base;
+ blen = sizeof(b);
+ hex = op->do_hex;
+ broken_ei = false;
+ /* loop over all type descriptor headers in the Configuration dpge */
+ for (k = 0, aes_i = 0; k < tesp->num_ths; ++k, ++tdhp) {
+ if (is_et_used_by_aes(tdhp->etype)) {
+ /* only consider element types that AES element are permiited
+ * to refer to, then loop over those number of elements */
+ for (j = 0; j < tdhp->num_elements;
+ ++j, ++aes_i, ae_bp += ae_bp[1] + 2) {
+ if ((ae_bp + 1) > ae_last_bp) {
+ if (op->verbose || op->warn)
+ pr2serr("warning: %s: off end of ae page\n",
+ __func__);
+ return broken_ei;
+ }
+ eip = !!(ae_bp[0] & 0x10); /* EIP == Element Index Present */
+ if (eip) {
+ eiioe = 0x3 & ae_bp[2];
+ if ((0 == eiioe) && op->eiioe_force)
+ eiioe = 1;
+ } else
+ eiioe = 0;
+ if (eip && (1 == eiioe)) { /* EIP and EIIOE=1 */
+ ei = ae_bp[3];
+ jr2p = tesp->j_base + ei;
+ if ((ei >= tesp->num_j_eoe) ||
+ (NULL == jr2p->enc_statp)) {
+ pr2serr("%s: oi=%d, ei=%d [num_eoe=%d], eiioe=1 "
+ "not in join_arr\n", __func__, k, ei,
+ tesp->num_j_eoe);
+ return broken_ei;
+ }
+ devslotnum_and_sasaddr(jr2p, ae_bp);
+ if (jr2p->ae_statp) {
+ if (op->warn || op->verbose) {
+ pr2serr("warning: aes slot already in use, "
+ "keep existing AES+%s\n\t",
+ offset_str(jr2p->ae_statp - add_elem_rsp,
+ hex, b, blen));
+ pr2serr("dropping AES+%s [length=%d, oi=%d, "
+ "ei=%d, aes_i=%d]\n",
+ offset_str(ae_bp - add_elem_rsp, hex, b,
+ blen),
+ ae_bp[1] + 2, k, ei, aes_i);
+ }
+ } else
+ jr2p->ae_statp = ae_bp;
+ } else if (eip && (0 == eiioe)) { /* SES-2 so be careful */
+ ei = ae_bp[3];
+try_again:
+ /* Check AES dpage descriptor ei is valid */
+ for (jr2p = tesp->j_base; jr2p->enc_statp; ++jr2p) {
+ if (broken_ei) {
+ if (ei == jr2p->ei_aess)
+ break;
+ } else {
+ if (ei == jr2p->ei_eoe)
+ break;
+ }
+ }
+ if (NULL == jr2p->enc_statp) {
+ pr2serr("warning: %s: oi=%d, ei=%d (broken_ei=%d) "
+ "not in join_arr\n", __func__, k, ei,
+ (int)broken_ei);
+ return broken_ei;
+ }
+ if (! is_et_used_by_aes(jr2p->etype)) {
+ /* unexpected element type so ... */
+ broken_ei = true;
+ goto try_again;
+ }
+ devslotnum_and_sasaddr(jr2p, ae_bp);
+ if (jr2p->ae_statp) {
+ /* 1 to 1 AES to ES mapping assumption violated */
+ if ((0 == ei) && (TPROTO_SAS == (0xf & ae_bp[0])) &&
+ (1 == (ae_bp[5] >> 6))) {
+ /* heuristic for (hack) Areca 8028 */
+ for (jr2p = tesp->j_base; jr2p->enc_statp;
+ ++jr2p) {
+ if ((-1 == jr2p->indiv_i) ||
+ (! is_et_used_by_aes(jr2p->etype)) ||
+ jr2p->ae_statp)
+ continue;
+ jr2p->ae_statp = ae_bp;
+ break;
+ }
+ if ((NULL == jr2p->enc_statp) &&
+ (op->warn || op->verbose))
+ pr2serr("warning2: dropping AES+%s [length="
+ "%d, oi=%d, ei=%d, aes_i=%d]\n",
+ offset_str(ae_bp - add_elem_rsp, hex,
+ b, blen),
+ ae_bp[1] + 2, k, ei, aes_i);
+ } else if (op->warn || op->verbose) {
+ pr2serr("warning3: aes slot already in use, "
+ "keep existing AES+%s\n\t",
+ offset_str(jr2p->ae_statp - add_elem_rsp,
+ hex, b, blen));
+ pr2serr("dropping AES+%s [length=%d, oi=%d, ei="
+ "%d, aes_i=%d]\n",
+ offset_str(ae_bp - add_elem_rsp, hex, b,
+ blen),
+ ae_bp[1] + 2, k, ei, aes_i);
+ }
+ } else
+ jr2p->ae_statp = ae_bp;
+ } else if (eip) { /* EIP and EIIOE=2,3 */
+ ei = ae_bp[3];
+ for (jr2p = tesp->j_base; jr2p->enc_statp; ++jr2p) {
+ if (ei == jr2p->ei_eoe)
+ break; /* good, found match on ei_eoe */
+ }
+ if (NULL == jr2p->enc_statp) {
+ pr2serr("warning: %s: oi=%d, ei=%d, not in "
+ "join_arr\n", __func__, k, ei);
+ return broken_ei;
+ }
+ if (! is_et_used_by_aes(jr2p->etype)) {
+ pr2serr("warning: %s: oi=%d, ei=%d, unexpected "
+ "element_type=0x%x\n", __func__, k, ei,
+ jr2p->etype);
+ return broken_ei;
+ }
+ devslotnum_and_sasaddr(jr2p, ae_bp);
+ if (jr2p->ae_statp) {
+ if (op->warn || op->verbose) {
+ pr2serr("warning3: aes slot already in use, "
+ "keep existing AES+%s\n\t",
+ offset_str(jr2p->ae_statp - add_elem_rsp,
+ hex, b, blen));
+ pr2serr("dropping AES+%s [length=%d, oi=%d, ei="
+ "%d, aes_i=%d]\n",
+ offset_str(ae_bp - add_elem_rsp, hex, b,
+ blen),
+ ae_bp[1] + 2, k, ei, aes_i);
+ }
+ } else
+ jr2p->ae_statp = ae_bp;
+ } else { /* EIP=0 */
+ /* step jrp over overall elements or those with
+ * jrp->ae_statp already used */
+ while (jrp->enc_statp && ((-1 == jrp->indiv_i) ||
+ jrp->ae_statp))
+ ++jrp;
+ if (NULL == jrp->enc_statp) {
+ pr2serr("warning: %s: join_arr has no space for "
+ "ae\n", __func__);
+ return broken_ei;
+ }
+ jrp->ae_statp = ae_bp;
+ ++jrp;
+ }
+ } /* end_for: loop over non-overall elements of the
+ * current type descriptor header */
+ } else { /* element type _not_ relevant to ae status */
+ /* step jrp over overall and individual elements */
+ for (j = 0; j <= tdhp->num_elements; ++j, ++jrp) {
+ if (NULL == jrp->enc_statp) {
+ pr2serr("warning: %s: join_arr has no space\n",
+ __func__);
+ return broken_ei;
+ }
+ }
+ }
+ } /* end_for: loop over type descriptor headers */
+ return broken_ei;
+}
+
+
+/* User output of join array */
+static void
+join_array_display(struct th_es_t * tesp, struct opts_t * op)
+{
+ bool got1, need_aes;
+ int k, j, blen, desc_len, dn_len;
+ const uint8_t * ae_bp;
+ const char * cp;
+ const uint8_t * ed_bp;
+ struct join_row_t * jrp;
+ uint8_t * t_bp;
+ char b[64];
+
+ blen = sizeof(b);
+ need_aes = (op->page_code_given &&
+ (ADD_ELEM_STATUS_DPC == op->page_code));
+ dn_len = op->desc_name ? (int)strlen(op->desc_name) : 0;
+ for (k = 0, jrp = tesp->j_base, got1 = false;
+ ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) {
+ if (op->ind_given) {
+ if (op->ind_th != jrp->th_i)
+ continue;
+ if (! match_ind_indiv(jrp->indiv_i, op))
+ continue;
+ }
+ if (need_aes && (NULL == jrp->ae_statp))
+ continue;
+ ed_bp = jrp->elem_descp;
+ if (op->desc_name) {
+ if (NULL == ed_bp)
+ continue;
+ desc_len = sg_get_unaligned_be16(ed_bp + 2);
+ /* some element descriptor strings have trailing NULLs and
+ * count them in their length; adjust */
+ while (desc_len && ('\0' == ed_bp[4 + desc_len - 1]))
+ --desc_len;
+ if (desc_len != dn_len)
+ continue;
+ if (0 != strncmp(op->desc_name, (const char *)(ed_bp + 4),
+ desc_len))
+ continue;
+ } else if (op->dev_slot_num >= 0) {
+ if (op->dev_slot_num != jrp->dev_slot_num)
+ continue;
+ } else if (saddr_non_zero(op->sas_addr)) {
+ for (j = 0; j < 8; ++j) {
+ if (op->sas_addr[j] != jrp->sas_addr[j])
+ break;
+ }
+ if (j < 8)
+ continue;
+ }
+ got1 = true;
+ if ((op->do_filter > 1) && (1 != (0xf & jrp->enc_statp[0])))
+ continue; /* when '-ff' and status!=OK, skip */
+ cp = etype_str(jrp->etype, b, blen);
+ if (ed_bp) {
+ desc_len = sg_get_unaligned_be16(ed_bp + 2) + 4;
+ if (desc_len > 4)
+ printf("%.*s [%d,%d] Element type: %s\n", desc_len - 4,
+ (const char *)(ed_bp + 4), jrp->th_i,
+ jrp->indiv_i, cp);
+ else
+ printf("[%d,%d] Element type: %s\n", jrp->th_i,
+ jrp->indiv_i, cp);
+ } else
+ printf("[%d,%d] Element type: %s\n", jrp->th_i,
+ jrp->indiv_i, cp);
+ printf(" Enclosure Status:\n");
+ enc_status_helper(" ", jrp->enc_statp, jrp->etype, false, op);
+ if (jrp->ae_statp) {
+ printf(" Additional Element Status:\n");
+ ae_bp = jrp->ae_statp;
+ desc_len = ae_bp[1] + 2;
+ additional_elem_helper(" ", ae_bp, desc_len, jrp->etype,
+ tesp, op);
+ }
+ if (jrp->thresh_inp) {
+ t_bp = jrp->thresh_inp;
+ threshold_helper(" Threshold In:\n", " ", t_bp, jrp->etype,
+ op);
+ }
+ }
+ if (! got1) {
+ if (op->ind_given) {
+ printf(" >>> no match on --index=%d,%d", op->ind_th,
+ op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d\n", op->ind_indiv_last);
+ else
+ printf("\n");
+ } else if (op->desc_name)
+ printf(" >>> no match on --descriptor=%s\n", op->desc_name);
+ else if (op->dev_slot_num >= 0)
+ printf(" >>> no match on --dev-slot-name=%d\n",
+ op->dev_slot_num);
+ else if (saddr_non_zero(op->sas_addr)) {
+ printf(" >>> no match on --sas-addr=0x");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", op->sas_addr[j]);
+ printf("\n");
+ }
+ }
+}
+
+/* This is for debugging, output to stderr */
+static void
+join_array_dump(struct th_es_t * tesp, int broken_ei, struct opts_t * op)
+{
+ int k, j, blen, hex;
+ int eiioe_count = 0;
+ int eip_count = 0;
+ struct join_row_t * jrp;
+ char b[64];
+
+ blen = sizeof(b);
+ hex = op->do_hex;
+ pr2serr("Dump of join array, each line is a row. Lines start with\n");
+ pr2serr("[<element_type>: <type_hdr_index>,<elem_ind_within>]\n");
+ pr2serr("'-1' indicates overall element or not applicable.\n");
+ jrp = tesp->j_base;
+ for (k = 0; ((k < MX_JOIN_ROWS) && jrp->enc_statp); ++k, ++jrp) {
+ pr2serr("[0x%x: %d,%d] ", jrp->etype, jrp->th_i, jrp->indiv_i);
+ if (jrp->se_id > 0)
+ pr2serr("se_id=%d ", jrp->se_id);
+ pr2serr("ei_ioe,_eoe,_aess=%s", offset_str(k, hex, b, blen));
+ pr2serr(",%s", offset_str(jrp->ei_eoe, hex, b, blen));
+ pr2serr(",%s", offset_str(jrp->ei_aess, hex, b, blen));
+ pr2serr(" dsn=%s", offset_str(jrp->dev_slot_num, hex, b, blen));
+ if (op->do_join > 2) {
+ pr2serr(" sa=0x");
+ if (saddr_non_zero(jrp->sas_addr)) {
+ for (j = 0; j < 8; ++j)
+ pr2serr("%02x", jrp->sas_addr[j]);
+ } else
+ pr2serr("0");
+ }
+ if (jrp->enc_statp)
+ pr2serr(" ES+%s", offset_str(jrp->enc_statp - enc_stat_rsp,
+ hex, b, blen));
+ if (jrp->elem_descp)
+ pr2serr(" ED+%s", offset_str(jrp->elem_descp - elem_desc_rsp,
+ hex, b, blen));
+ if (jrp->ae_statp) {
+ pr2serr(" AES+%s", offset_str(jrp->ae_statp - add_elem_rsp,
+ hex, b, blen));
+ if (jrp->ae_statp[0] & 0x10) {
+ ++eip_count;
+ if (jrp->ae_statp[2] & 0x3)
+ ++eiioe_count;
+ }
+ }
+ if (jrp->thresh_inp)
+ pr2serr(" TI+%s", offset_str(jrp->thresh_inp - threshold_rsp,
+ hex, b, blen));
+ pr2serr("\n");
+ }
+ pr2serr(">> ES len=%s, ", offset_str(enc_stat_rsp_len, hex, b, blen));
+ pr2serr("ED len=%s, ", offset_str(elem_desc_rsp_len, hex, b, blen));
+ pr2serr("AES len=%s, ", offset_str(add_elem_rsp_len, hex, b, blen));
+ pr2serr("TI len=%s\n", offset_str(threshold_rsp_len, hex, b, blen));
+ pr2serr(">> join_arr elements=%s, ", offset_str(k, hex, b, blen));
+ pr2serr("eip_count=%s, ", offset_str(eip_count, hex, b, blen));
+ pr2serr("eiioe_count=%s ", offset_str(eiioe_count, hex, b, blen));
+ pr2serr("broken_ei=%d\n", (int)broken_ei);
+}
+
+/* EIIOE juggling (standards + heuristics) for join with AES page */
+static void
+join_juggle_aes(struct th_es_t * tesp, uint8_t * es_bp, const uint8_t * ed_bp,
+ uint8_t * t_bp)
+{
+ int k, j, eoe, ei4aess;
+ struct join_row_t * jrp;
+ const struct type_desc_hdr_t * tdhp;
+
+ jrp = tesp->j_base;
+ tdhp = tesp->th_base;
+ for (k = 0, eoe = 0, ei4aess = 0; k < tesp->num_ths; ++k, ++tdhp) {
+ bool et_used_by_aes;
+
+ jrp->th_i = k;
+ jrp->indiv_i = -1;
+ jrp->etype = tdhp->etype;
+ jrp->ei_eoe = -1;
+ et_used_by_aes = is_et_used_by_aes(tdhp->etype);
+ jrp->ei_aess = -1;
+ jrp->se_id = tdhp->se_id;
+ /* check es_bp < es_last_bp still in range */
+ jrp->enc_statp = es_bp;
+ es_bp += 4;
+ jrp->elem_descp = ed_bp;
+ if (ed_bp)
+ ed_bp += sg_get_unaligned_be16(ed_bp + 2) + 4;
+ jrp->ae_statp = NULL;
+ jrp->thresh_inp = t_bp;
+ jrp->dev_slot_num = -1;
+ /* assume sas_addr[8] zeroed since it's static file scope */
+ if (t_bp)
+ t_bp += 4;
+ ++jrp;
+ for (j = 0; j < tdhp->num_elements; ++j, ++jrp) {
+ if (jrp >= join_arr_lastp)
+ break;
+ jrp->th_i = k;
+ jrp->indiv_i = j;
+ jrp->ei_eoe = eoe++;
+ if (et_used_by_aes)
+ jrp->ei_aess = ei4aess++;
+ else
+ jrp->ei_aess = -1;
+ jrp->etype = tdhp->etype;
+ jrp->se_id = tdhp->se_id;
+ jrp->enc_statp = es_bp;
+ es_bp += 4;
+ jrp->elem_descp = ed_bp;
+ if (ed_bp)
+ ed_bp += sg_get_unaligned_be16(ed_bp + 2) + 4;
+ jrp->thresh_inp = t_bp;
+ jrp->dev_slot_num = -1;
+ /* assume sas_addr[8] zeroed since it's static file scope */
+ if (t_bp)
+ t_bp += 4;
+ jrp->ae_statp = NULL;
+ ++tesp->num_j_eoe;
+ }
+ if (jrp >= join_arr_lastp) {
+ /* ++k; */
+ break; /* leave last row all zeros */
+ }
+ }
+ tesp->num_j_rows = jrp - tesp->j_base;
+}
+
+/* Fetch Configuration, Enclosure Status, Element Descriptor, Additional
+ * Element Status and optionally Threshold In pages, place in static arrays.
+ * Collate (join) overall and individual elements into the static join_arr[].
+ * When 'display' is true then the join_arr[] is output to stdout in a form
+ * suitable for end users. For debug purposes the join_arr[] is output to
+ * stderr when op->verbose > 3. Returns 0 for success, any other return value
+ * is an error. */
+static int
+join_work(struct sg_pt_base * ptvp, struct opts_t * op, bool display)
+{
+ bool broken_ei;
+ int res, num_ths, mlen;
+ uint32_t ref_gen_code, gen_code;
+ const uint8_t * ae_bp;
+ const uint8_t * ae_last_bp;
+ const char * enc_state_changed = " <<state of enclosure changed, "
+ "please try again>>\n";
+ uint8_t * es_bp;
+ const uint8_t * ed_bp;
+ uint8_t * t_bp;
+ struct th_es_t * tesp;
+ struct enclosure_info primary_info;
+ struct th_es_t tes;
+
+ memset(&primary_info, 0, sizeof(primary_info));
+ num_ths = build_type_desc_hdr_arr(ptvp, type_desc_hdr_arr, MX_ELEM_HDR,
+ &ref_gen_code, &primary_info, op);
+ if (num_ths < 0)
+ return num_ths;
+ tesp = &tes;
+ memset(tesp, 0, sizeof(tes));
+ tesp->th_base = type_desc_hdr_arr;
+ tesp->num_ths = num_ths;
+ if (display && primary_info.have_info) {
+ int j;
+
+ printf(" Primary enclosure logical identifier (hex): ");
+ for (j = 0; j < 8; ++j)
+ printf("%02x", primary_info.enc_log_id[j]);
+ printf("\n");
+ }
+ mlen = enc_stat_rsp_sz;
+ if (mlen > op->maxlen)
+ mlen = op->maxlen;
+ res = do_rec_diag(ptvp, ENC_STATUS_DPC, enc_stat_rsp, mlen, op,
+ &enc_stat_rsp_len);
+ if (res)
+ return res;
+ if (enc_stat_rsp_len < 8) {
+ pr2serr("Enclosure Status response too short\n");
+ return -1;
+ }
+ gen_code = sg_get_unaligned_be32(enc_stat_rsp + 4);
+ if (ref_gen_code != gen_code) {
+ pr2serr("%s", enc_state_changed);
+ return -1;
+ }
+ es_bp = enc_stat_rsp + 8;
+ /* es_last_bp = enc_stat_rsp + enc_stat_rsp_len - 1; */
+
+ mlen = elem_desc_rsp_sz;
+ if (mlen > op->maxlen)
+ mlen = op->maxlen;
+ res = do_rec_diag(ptvp, ELEM_DESC_DPC, elem_desc_rsp, mlen, op,
+ &elem_desc_rsp_len);
+ if (0 == res) {
+ if (elem_desc_rsp_len < 8) {
+ pr2serr("Element Descriptor response too short\n");
+ return -1;
+ }
+ gen_code = sg_get_unaligned_be32(elem_desc_rsp + 4);
+ if (ref_gen_code != gen_code) {
+ pr2serr("%s", enc_state_changed);
+ return -1;
+ }
+ ed_bp = elem_desc_rsp + 8;
+ /* ed_last_bp = elem_desc_rsp + elem_desc_rsp_len - 1; */
+ } else {
+ elem_desc_rsp_len = 0;
+ ed_bp = NULL;
+ res = 0;
+ if (op->verbose)
+ pr2serr(" Element Descriptor page not available\n");
+ }
+
+ /* check if we want to add the AES page to the join */
+ if (display || (ADD_ELEM_STATUS_DPC == op->page_code) ||
+ (op->dev_slot_num >= 0) || saddr_non_zero(op->sas_addr)) {
+ mlen = add_elem_rsp_sz;
+ if (mlen > op->maxlen)
+ mlen = op->maxlen;
+ res = do_rec_diag(ptvp, ADD_ELEM_STATUS_DPC, add_elem_rsp, mlen, op,
+ &add_elem_rsp_len);
+ if (0 == res) {
+ if (add_elem_rsp_len < 8) {
+ pr2serr("Additional Element Status response too short\n");
+ return -1;
+ }
+ gen_code = sg_get_unaligned_be32(add_elem_rsp + 4);
+ if (ref_gen_code != gen_code) {
+ pr2serr("%s", enc_state_changed);
+ return -1;
+ }
+ ae_bp = add_elem_rsp + 8;
+ ae_last_bp = add_elem_rsp + add_elem_rsp_len - 1;
+ if (op->eiioe_auto && (add_elem_rsp_len > 11)) {
+ /* heuristic: if first AES descriptor has EIP set and its
+ * EI equal to 1, then act as if the EIIOE field is 1. */
+ if ((ae_bp[0] & 0x10) && (1 == ae_bp[3]))
+ op->eiioe_force = true;
+ }
+ } else { /* unable to read AES dpage */
+ add_elem_rsp_len = 0;
+ ae_bp = NULL;
+ ae_last_bp = NULL;
+ res = 0;
+ if (op->verbose)
+ pr2serr(" Additional Element Status page not available\n");
+ }
+ } else {
+ ae_bp = NULL;
+ ae_last_bp = NULL;
+ }
+
+ if ((op->do_join > 1) ||
+ ((! display) && (THRESHOLD_DPC == op->page_code))) {
+ mlen = threshold_rsp_sz;
+ if (mlen > op->maxlen)
+ mlen = op->maxlen;
+ res = do_rec_diag(ptvp, THRESHOLD_DPC, threshold_rsp, mlen, op,
+ &threshold_rsp_len);
+ if (0 == res) {
+ if (threshold_rsp_len < 8) {
+ pr2serr("Threshold In response too short\n");
+ return -1;
+ }
+ gen_code = sg_get_unaligned_be32(threshold_rsp + 4);
+ if (ref_gen_code != gen_code) {
+ pr2serr("%s", enc_state_changed);
+ return -1;
+ }
+ t_bp = threshold_rsp + 8;
+ /* t_last_bp = threshold_rsp + threshold_rsp_len - 1; */
+ } else {
+ threshold_rsp_len = 0;
+ t_bp = NULL;
+ res = 0;
+ if (op->verbose)
+ pr2serr(" Threshold In page not available\n");
+ }
+ } else {
+ threshold_rsp_len = 0;
+ t_bp = NULL;
+ }
+
+
+ tesp->j_base = join_arr;
+ join_juggle_aes(tesp, es_bp, ed_bp, t_bp);
+
+ broken_ei = false;
+ if (ae_bp)
+ broken_ei = join_aes_helper(ae_bp, ae_last_bp, tesp, op);
+
+ if (op->verbose > 3)
+ join_array_dump(tesp, broken_ei, op);
+
+ join_done = true;
+ if (display) /* probably wanted join_arr[] built only */
+ join_array_display(tesp, op);
+
+ return res;
+
+}
+
+/* Returns 1 if strings equal (same length, characters same or only differ
+ * by case), else returns 0. Assumes 7 bit ASCII (English alphabet). */
+static int
+strcase_eq(const char * s1p, const char * s2p)
+{
+ int c1;
+
+ do {
+ int c2;
+
+ c1 = *s1p++;
+ c2 = *s2p++;
+ if (c1 != c2) {
+ if (c2 >= 'a')
+ c2 = toupper(c2);
+ else if (c1 >= 'a')
+ c1 = toupper(c1);
+ else
+ return 0;
+ if (c1 != c2)
+ return 0;
+ }
+ } while (c1);
+ return 1;
+}
+
+static bool
+is_acronym_in_status_ctl(const struct tuple_acronym_val * tavp)
+{
+ const struct acronym2tuple * ap;
+
+ for (ap = ecs_a2t_arr; ap->acron; ++ ap) {
+ if (strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ return ap->acron;
+}
+
+static bool
+is_acronym_in_threshold(const struct tuple_acronym_val * tavp)
+{
+ const struct acronym2tuple * ap;
+
+ for (ap = th_a2t_arr; ap->acron; ++ ap) {
+ if (strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ return ap->acron;
+}
+
+static bool
+is_acronym_in_additional(const struct tuple_acronym_val * tavp)
+{
+ const struct acronym2tuple * ap;
+
+ for (ap = ae_sas_a2t_arr; ap->acron; ++ ap) {
+ if (strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ return ap->acron;
+}
+
+/* ENC_STATUS_DPC ENC_CONTROL_DPC
+ * Do clear/get/set (cgs) on Enclosure Control/Status page. Return 0 for ok
+ * -2 for acronym not found, else -1 . */
+static int
+cgs_enc_ctl_stat(struct sg_pt_base * ptvp, struct join_row_t * jrp,
+ const struct tuple_acronym_val * tavp,
+ const struct opts_t * op, bool last)
+{
+ int s_byte, s_bit, n_bits;
+ const struct acronym2tuple * ap;
+
+ if (NULL == tavp->acron) {
+ s_byte = tavp->start_byte;
+ s_bit = tavp->start_bit;
+ n_bits = tavp->num_bits;
+ }
+ if (tavp->acron) {
+ for (ap = ecs_a2t_arr; ap->acron; ++ ap) {
+ if (((jrp->etype == ap->etype) || (-1 == ap->etype)) &&
+ strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ if (ap->acron) {
+ s_byte = ap->start_byte;
+ s_bit = ap->start_bit;
+ n_bits = ap->num_bits;
+ } else {
+ if (-1 != ap->etype) {
+ for (ap = ecs_a2t_arr; ap->acron; ++ap) {
+ if (0 == strcase_eq(tavp->acron, ap->acron)) {
+ pr2serr(">>> Found %s acronym but not for element "
+ "type %d\n", tavp->acron, jrp->etype);
+ break;
+ }
+ }
+ }
+ return -2;
+ }
+ }
+ if (op->verbose > 1)
+ pr2serr(" s_byte=%d, s_bit=%d, n_bits=%d\n", s_byte, s_bit, n_bits);
+ if (GET_OPT == tavp->cgs_sel) {
+ uint64_t ui = sg_get_big_endian(jrp->enc_statp + s_byte, s_bit,
+ n_bits);
+
+ if (op->do_hex)
+ printf("0x%" PRIx64 "\n", ui);
+ else
+ printf("%" PRId64 "\n", (int64_t)ui);
+ } else { /* --set or --clear */
+ int len;
+
+ if ((! op->mask_ign) && (jrp->etype < NUM_ETC)) {
+ int k;
+
+ if (op->verbose > 2)
+ pr2serr("Applying mask to element status [etc=%d] prior to "
+ "modify then write\n", jrp->etype);
+ for (k = 0; k < 4; ++k)
+ jrp->enc_statp[k] &= ses3_element_cmask_arr[jrp->etype][k];
+ } else
+ jrp->enc_statp[0] &= 0x40; /* keep PRDFAIL is set in byte 0 */
+ /* next we modify requested bit(s) */
+ sg_set_big_endian((uint64_t)tavp->val,
+ jrp->enc_statp + s_byte, s_bit, n_bits);
+ jrp->enc_statp[0] |= 0x80; /* set SELECT bit */
+ if (op->byte1_given)
+ enc_stat_rsp[1] = op->byte1;
+ len = sg_get_unaligned_be16(enc_stat_rsp + 2) + 4;
+ if (last) {
+ int ret = do_senddiag(ptvp, enc_stat_rsp, len, ! op->quiet,
+ op->verbose);
+
+ if (ret) {
+ pr2serr("couldn't send Enclosure Control page\n");
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* THRESHOLD_DPC
+ * Do clear/get/set (cgs) on Threshold In/Out page. Return 0 for ok,
+ * -2 for acronym not found, else -1 . */
+static int
+cgs_threshold(struct sg_pt_base * ptvp, const struct join_row_t * jrp,
+ const struct tuple_acronym_val * tavp,
+ const struct opts_t * op, bool last)
+{
+ int s_byte, s_bit, n_bits;
+ const struct acronym2tuple * ap;
+
+ if (NULL == jrp->thresh_inp) {
+ pr2serr("No Threshold In/Out element available\n");
+ return -1;
+ }
+ if (NULL == tavp->acron) {
+ s_byte = tavp->start_byte;
+ s_bit = tavp->start_bit;
+ n_bits = tavp->num_bits;
+ }
+ if (tavp->acron) {
+ for (ap = th_a2t_arr; ap->acron; ++ap) {
+ if (((jrp->etype == ap->etype) || (-1 == ap->etype)) &&
+ strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ if (ap->acron) {
+ s_byte = ap->start_byte;
+ s_bit = ap->start_bit;
+ n_bits = ap->num_bits;
+ } else
+ return -2;
+ }
+ if (GET_OPT == tavp->cgs_sel) {
+ uint64_t ui = sg_get_big_endian(jrp->thresh_inp + s_byte, s_bit,
+ n_bits);
+
+ if (op->do_hex)
+ printf("0x%" PRIx64 "\n", ui);
+ else
+ printf("%" PRId64 "\n", (int64_t)ui);
+ } else {
+ int len;
+
+ sg_set_big_endian((uint64_t)tavp->val,
+ jrp->thresh_inp + s_byte, s_bit, n_bits);
+ if (op->byte1_given)
+ threshold_rsp[1] = op->byte1;
+ len = sg_get_unaligned_be16(threshold_rsp + 2) + 4;
+ if (last) {
+ int ret = do_senddiag(ptvp, threshold_rsp, len, ! op->quiet,
+ op->verbose);
+
+ if (ret) {
+ pr2serr("couldn't send Threshold Out page\n");
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* ADD_ELEM_STATUS_DPC
+ * Do get (cgs) on Additional element status page. Return 0 for ok,
+ * -2 for acronym not found, else -1 . */
+static int
+cgs_additional_el(const struct join_row_t * jrp,
+ const struct tuple_acronym_val * tavp,
+ const struct opts_t * op)
+{
+ int s_byte, s_bit, n_bits;
+ const struct acronym2tuple * ap;
+
+ if (NULL == jrp->ae_statp) {
+ pr2serr("No additional element status element available\n");
+ return -1;
+ }
+ if (NULL == tavp->acron) {
+ s_byte = tavp->start_byte;
+ s_bit = tavp->start_bit;
+ n_bits = tavp->num_bits;
+ }
+ if (tavp->acron) {
+ for (ap = ae_sas_a2t_arr; ap->acron; ++ap) {
+ if (((jrp->etype == ap->etype) || (-1 == ap->etype)) &&
+ strcase_eq(tavp->acron, ap->acron))
+ break;
+ }
+ if (ap->acron) {
+ s_byte = ap->start_byte;
+ s_bit = ap->start_bit;
+ n_bits = ap->num_bits;
+ } else
+ return -2;
+ }
+ if (GET_OPT == tavp->cgs_sel) {
+ uint64_t ui = sg_get_big_endian(jrp->ae_statp + s_byte, s_bit,
+ n_bits);
+
+ if (op->do_hex)
+ printf("0x%" PRIx64 "\n", ui);
+ else
+ printf("%" PRId64 "\n", (int64_t)ui);
+ } else {
+ pr2serr("--clear and --set not available for Additional Element "
+ "Status page\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* Do --clear, --get or --set .
+ * Returns 0 for success, any other return value is an error. */
+static int
+ses_cgs(struct sg_pt_base * ptvp, const struct tuple_acronym_val * tavp,
+ struct opts_t * op, bool last)
+{
+ int ret, k, j, desc_len, dn_len;
+ bool found;
+ struct join_row_t * jrp;
+ const uint8_t * ed_bp;
+ char b[64];
+
+ if ((NULL == ptvp) && (GET_OPT != tavp->cgs_sel)) {
+ pr2serr("%s: --clear= and --set= only supported when DEVICE is "
+ "given\n", __func__);
+ return SG_LIB_CONTRADICT;
+ }
+ found = false;
+ if (NULL == tavp->acron) {
+ if (! op->page_code_given)
+ op->page_code = ENC_CONTROL_DPC;
+ found = true;
+ } else if (is_acronym_in_status_ctl(tavp)) {
+ if (op->page_code > 0) {
+ if (ENC_CONTROL_DPC != op->page_code)
+ goto inconsistent;
+ } else
+ op->page_code = ENC_CONTROL_DPC;
+ found = true;
+ } else if (is_acronym_in_threshold(tavp)) {
+ if (op->page_code > 0) {
+ if (THRESHOLD_DPC != op->page_code)
+ goto inconsistent;
+ } else
+ op->page_code = THRESHOLD_DPC;
+ found = true;
+ } else if (is_acronym_in_additional(tavp)) {
+ if (op->page_code > 0) {
+ if (ADD_ELEM_STATUS_DPC != op->page_code)
+ goto inconsistent;
+ } else
+ op->page_code = ADD_ELEM_STATUS_DPC;
+ found = true;
+ }
+ if (! found) {
+ pr2serr("acroynm %s not found (try '-ee' option)\n", tavp->acron);
+ return -1;
+ }
+ if (false == join_done) {
+ ret = join_work(ptvp, op, false);
+ if (ret)
+ return ret;
+ }
+ dn_len = op->desc_name ? (int)strlen(op->desc_name) : 0;
+ for (k = 0, jrp = join_arr; ((k < MX_JOIN_ROWS) && jrp->enc_statp);
+ ++k, ++jrp) {
+ if (op->ind_given) {
+ if (op->ind_th != jrp->th_i)
+ continue;
+ if (! match_ind_indiv(jrp->indiv_i, op))
+ continue;
+ } else if (op->desc_name) {
+ ed_bp = jrp->elem_descp;
+ if (NULL == ed_bp)
+ continue;
+ desc_len = sg_get_unaligned_be16(ed_bp + 2);
+ /* some element descriptor strings have trailing NULLs and
+ * count them; adjust */
+ while (desc_len && ('\0' == ed_bp[4 + desc_len - 1]))
+ --desc_len;
+ if (desc_len != dn_len)
+ continue;
+ if (0 != strncmp(op->desc_name, (const char *)(ed_bp + 4),
+ desc_len))
+ continue;
+ } else if (op->dev_slot_num >= 0) {
+ if (op->dev_slot_num != jrp->dev_slot_num)
+ continue;
+ } else if (saddr_non_zero(op->sas_addr)) {
+ for (j = 0; j < 8; ++j) {
+ if (op->sas_addr[j] != jrp->sas_addr[j])
+ break;
+ }
+ if (j < 8)
+ continue;
+ }
+ if (ENC_CONTROL_DPC == op->page_code)
+ ret = cgs_enc_ctl_stat(ptvp, jrp, tavp, op, last);
+ else if (THRESHOLD_DPC == op->page_code)
+ ret = cgs_threshold(ptvp, jrp, tavp, op, last);
+ else if (ADD_ELEM_STATUS_DPC == op->page_code)
+ ret = cgs_additional_el(jrp, tavp, op);
+ else {
+ pr2serr("page %s not supported for cgs\n",
+ etype_str(op->page_code, b, sizeof(b)));
+ ret = -1;
+ }
+ if (ret)
+ return ret;
+ if (op->ind_indiv_last <= op->ind_indiv)
+ break;
+ } /* end of loop over join array */
+ if ((k >= MX_JOIN_ROWS || (NULL == jrp->enc_statp))) {
+ if (op->desc_name)
+ pr2serr("descriptor name: %s not found (check the 'ed' page "
+ "[0x7])\n", op->desc_name);
+ else if (op->dev_slot_num >= 0)
+ pr2serr("device slot number: %d not found\n", op->dev_slot_num);
+ else if (saddr_non_zero(op->sas_addr))
+ pr2serr("SAS address not found\n");
+ else {
+ pr2serr("index: %d,%d", op->ind_th, op->ind_indiv);
+ if (op->ind_indiv_last > op->ind_indiv)
+ printf("-%d not found\n", op->ind_indiv_last);
+ else
+ printf(" not found\n");
+ }
+ return -1;
+ }
+ return 0;
+
+inconsistent:
+ pr2serr("acroynm %s inconsistent with page_code=0x%x\n", tavp->acron,
+ op->page_code);
+ return -1;
+}
+
+/* Called when '--nickname=SEN' given. First calls status page to fetch
+ * the generation code. Returns 0 for success, any other return value is
+ * an error. */
+static int
+ses_set_nickname(struct sg_pt_base * ptvp, struct opts_t * op)
+{
+ int res, len;
+ int resp_len = 0;
+ uint8_t b[64];
+ const int control_plen = 0x24;
+
+ if (NULL == ptvp) {
+ pr2serr("%s: ignored when no device name\n", __func__);
+ return 0;
+ }
+ memset(b, 0, sizeof(b));
+ /* Only after the generation code, offset 4 for 4 bytes */
+ res = do_rec_diag(ptvp, SUBENC_NICKNAME_DPC, b, 8, op, &resp_len);
+ if (res) {
+ pr2serr("%s: Subenclosure nickname status page, res=%d\n", __func__,
+ res);
+ return -1;
+ }
+ if (resp_len < 8) {
+ pr2serr("%s: Subenclosure nickname status page, response length too "
+ "short: %d\n", __func__, resp_len);
+ return -1;
+ }
+ if (op->verbose) {
+ uint32_t gc;
+
+ gc = sg_get_unaligned_be32(b + 4);
+ pr2serr("%s: generation code from status page: %" PRIu32 "\n",
+ __func__, gc);
+ }
+ b[0] = (uint8_t)SUBENC_NICKNAME_DPC; /* just in case */
+ b[1] = (uint8_t)op->seid;
+ sg_put_unaligned_be16((uint16_t)control_plen, b + 2);
+ len = strlen(op->nickname_str);
+ if (len > 32)
+ len = 32;
+ memcpy(b + 8, op->nickname_str, len);
+ return do_senddiag(ptvp, b, control_plen + 4, ! op->quiet,
+ op->verbose);
+}
+
+static void
+enumerate_diag_pages(void)
+{
+ bool got1;
+ const struct diag_page_code * pcdp;
+ const struct diag_page_abbrev * ap;
+
+ printf("Diagnostic pages, followed by abbreviation(s) then page code:\n");
+ for (pcdp = dpc_arr; pcdp->desc; ++pcdp) {
+ printf(" %s [", pcdp->desc);
+ for (ap = dp_abbrev, got1 = false; ap->abbrev; ++ap) {
+ if (ap->page_code == pcdp->page_code) {
+ printf("%s%s", (got1 ? "," : ""), ap->abbrev);
+ got1 = true;
+ }
+ }
+ printf("] [0x%x]\n", pcdp->page_code);
+ }
+}
+
+/* Output from --enumerate or --list option. Note that the output is
+ * different when the option is given twice. */
+static void
+enumerate_work(const struct opts_t * op)
+{
+ int num;
+
+ if (op->dev_name)
+ printf(">>> DEVICE %s ignored when --%s option given.\n",
+ op->dev_name, (op->do_list ? "list" : "enumerate"));
+ num = op->enumerate + (int)op->do_list;
+ if (num < 2) {
+ const struct element_type_t * etp;
+
+ enumerate_diag_pages();
+ printf("\nSES element type names, followed by abbreviation and "
+ "element type code:\n");
+ for (etp = element_type_arr; etp->desc; ++etp)
+ printf(" %s [%s] [0x%x]\n", etp->desc, etp->abbrev,
+ etp->elem_type_code);
+ } else {
+ bool given_et = false;
+ const struct acronym2tuple * ap;
+ const char * cp;
+ char a[160];
+ char b[64];
+ char bb[64];
+
+ /* command line has multiple --enumerate and/or --list options */
+ printf("--clear, --get, --set acronyms for Enclosure Status/Control "
+ "['es' or 'ec'] page");
+ if (op->ind_given && op->ind_etp &&
+ (cp = etype_str(op->ind_etp->elem_type_code, bb, sizeof(bb)))) {
+ printf("\n(element type: %s)", cp);
+ given_et = true;
+ }
+ printf(":\n");
+ for (ap = ecs_a2t_arr; ap->acron; ++ap) {
+ if (given_et && (op->ind_etp->elem_type_code != ap->etype))
+ continue;
+ cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b));
+ snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron,
+ (cp ? cp : "??"), ap->start_byte, ap->start_bit,
+ ap->num_bits);
+ if (ap->info)
+ printf("%-44s %s\n", a, ap->info);
+ else
+ printf("%s\n", a);
+ }
+ if (given_et)
+ return;
+ printf("\n--clear, --get, --set acronyms for Threshold In/Out "
+ "['th'] page:\n");
+ for (ap = th_a2t_arr; ap->acron; ++ap) {
+ cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b));
+ snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron,
+ (cp ? cp : "??"), ap->start_byte, ap->start_bit,
+ ap->num_bits);
+ if (ap->info)
+ printf("%-34s %s\n", a, ap->info);
+ else
+ printf("%s\n", a);
+ }
+ printf("\n--get acronyms for Additional Element Status ['aes'] page "
+ "(SAS EIP=1):\n");
+ for (ap = ae_sas_a2t_arr; ap->acron; ++ap) {
+ cp = (ap->etype < 0) ? "*" : etype_str(ap->etype, b, sizeof(b));
+ snprintf(a, sizeof(a), " %s [%s] [%d:%d:%d]", ap->acron,
+ (cp ? cp : "??"), ap->start_byte, ap->start_bit,
+ ap->num_bits);
+ if (ap->info)
+ printf("%-34s %s\n", a, ap->info);
+ else
+ printf("%s\n", a);
+ }
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool have_cgs = false;
+ int k, n, d_len, res, resid, vb;
+ int sg_fd = -1;
+ int pd_type = 0;
+ int ret = 0;
+ const char * cp;
+ struct opts_t opts;
+ struct opts_t * op;
+ struct tuple_acronym_val * tavp;
+ struct cgs_cl_t * cgs_clp;
+ uint8_t * free_enc_stat_rsp = NULL;
+ uint8_t * free_elem_desc_rsp = NULL;
+ uint8_t * free_add_elem_rsp = NULL;
+ uint8_t * free_threshold_rsp = NULL;
+ struct sg_pt_base * ptvp = NULL;
+ struct tuple_acronym_val tav_arr[CGS_CL_ARR_MAX_SZ];
+ char buff[128];
+ char b[128];
+
+ op = &opts;
+ memset(op, 0, sizeof(*op));
+ op->dev_slot_num = -1;
+ op->ind_indiv_last = -1;
+ op->maxlen = MX_ALLOC_LEN;
+ res = parse_cmd_line(op, argc, argv);
+ vb = op->verbose;
+ if (res) {
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto early_out;
+ }
+ if (op->do_help) {
+ usage(op->do_help);
+ goto early_out;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ goto early_out;
+ }
+
+ vb = op->verbose; /* may have changed */
+ if (op->enumerate || op->do_list) {
+ enumerate_work(op);
+ goto early_out;
+ }
+ enc_stat_rsp = sg_memalign(op->maxlen, 0, &free_enc_stat_rsp, false);
+ if (NULL == enc_stat_rsp) {
+ pr2serr("Unable to get heap for enc_stat_rsp\n");
+ goto err_out;
+ }
+ enc_stat_rsp_sz = op->maxlen;
+ elem_desc_rsp = sg_memalign(op->maxlen, 0, &free_elem_desc_rsp, false);
+ if (NULL == elem_desc_rsp) {
+ pr2serr("Unable to get heap for elem_desc_rsp\n");
+ goto err_out;
+ }
+ elem_desc_rsp_sz = op->maxlen;
+ add_elem_rsp = sg_memalign(op->maxlen, 0, &free_add_elem_rsp, false);
+ if (NULL == add_elem_rsp) {
+ pr2serr("Unable to get heap for add_elem_rsp\n");
+ goto err_out;
+ }
+ add_elem_rsp_sz = op->maxlen;
+ threshold_rsp = sg_memalign(op->maxlen, 0, &free_threshold_rsp, false);
+ if (NULL == threshold_rsp) {
+ pr2serr("Unable to get heap for threshold_rsp\n");
+ goto err_out;
+ }
+ threshold_rsp_sz = op->maxlen;
+
+ if (op->num_cgs) {
+ have_cgs = true;
+ if (op->page_code_given &&
+ ! ((ENC_STATUS_DPC == op->page_code) ||
+ (THRESHOLD_DPC == op->page_code) ||
+ (ADD_ELEM_STATUS_DPC == op->page_code))) {
+ pr2serr("--clear, --get or --set options only supported for the "
+ "Enclosure\nControl/Status, Threshold In/Out and "
+ "Additional Element Status pages\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (! (op->ind_given || op->desc_name || (op->dev_slot_num >= 0) ||
+ saddr_non_zero(op->sas_addr))) {
+ pr2serr("with --clear, --get or --set option need either\n "
+ "--index, --descriptor, --dev-slot-num or --sas-addr\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ for (k = 0, cgs_clp = op->cgs_cl_arr, tavp = tav_arr; k < op->num_cgs;
+ ++k, ++cgs_clp, ++tavp) {
+ if (parse_cgs_str(cgs_clp->cgs_str, tavp)) {
+ pr2serr("unable to decode STR argument to: %s\n",
+ cgs_clp->cgs_str);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if ((GET_OPT == cgs_clp->cgs_sel) && tavp->val_str)
+ pr2serr("--get option ignoring =<val> at the end of STR "
+ "argument\n");
+ if (NULL == tavp->val_str) {
+ if (CLEAR_OPT == cgs_clp->cgs_sel)
+ tavp->val = DEF_CLEAR_VAL;
+ if (SET_OPT == cgs_clp->cgs_sel)
+ tavp->val = DEF_SET_VAL;
+ }
+ if (!strcmp(cgs_clp->cgs_str, "sas_addr") &&
+ op->dev_slot_num < 0) {
+ pr2serr("--get=sas_addr requires --dev-slot-num. For "
+ "expander SAS address, use exp_sas_addr instead.\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ tavp->cgs_sel = cgs_clp->cgs_sel;
+ }
+ /* keep this descending for loop directly after ascending for loop */
+ for (--k, --cgs_clp; k >= 0; --k, --cgs_clp) {
+ if ((CLEAR_OPT == cgs_clp->cgs_sel) ||
+ (SET_OPT == cgs_clp->cgs_sel)) {
+ cgs_clp->last_cs = true;
+ break;
+ }
+ }
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (vb > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ if (op->maxlen >= 16384)
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+#if 0
+ pr2serr("Debug dump of input parameters:\n");
+ pr2serr(" index option given: %d, ind_th=%d, ind_indiv=%d, "
+ "ind_indiv_last=%d\n", op->ind_given, op->ind_th,
+ op->ind_indiv, op->ind_indiv_last);
+ pr2serr(" num_cgs=%d, contents:\n", op->num_cgs);
+ for (k = 0, tavp = tav_arr, cgs_clp = op->cgs_cl_arr;
+ k < op->num_cgs; ++k, ++tavp, ++cgs_clp) {
+ pr2serr(" k=%d, cgs_sel=%d, last_cs=%d, tavp=%p str: %s\n",
+ k, (int)cgs_clp->cgs_sel, (int)cgs_clp->last_cs, tavp,
+ cgs_clp->cgs_str);
+ }
+#endif
+
+ if (op->dev_name) {
+ sg_fd = sg_cmds_open_device(op->dev_name, op->o_readonly, vb);
+ if (sg_fd < 0) {
+ if (vb)
+ pr2serr("open error: %s: %s\n", op->dev_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto early_out;
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ if (NULL == ptvp) {
+ pr2serr("construct pt_base failed, probably out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ if (! (op->do_raw || have_cgs || (op->do_hex > 2))) {
+ uint8_t inq_rsp[36];
+
+ memset(inq_rsp, 0, sizeof(inq_rsp));
+ if ((ret = sg_ll_inquiry_pt(ptvp, false, 0, inq_rsp, 36,
+ 0, &resid, ! op->quiet, vb))) {
+ pr2serr("%s doesn't respond to a SCSI INQUIRY\n",
+ op->dev_name);
+ goto err_out;
+ } else {
+ if (resid > 0)
+ pr2serr("Short INQUIRY response, not looking good\n");
+ printf(" %.8s %.16s %.4s\n", inq_rsp + 8, inq_rsp + 16,
+ inq_rsp + 32);
+ pd_type = PDT_MASK & inq_rsp[0];
+ cp = sg_get_pdt_str(pd_type, sizeof(buff), buff);
+ if (0xd == pd_type) {
+ if (vb)
+ printf(" enclosure services device\n");
+ } else if (0x40 & inq_rsp[6])
+ printf(" %s device has EncServ bit set\n", cp);
+ else {
+ if (0 != memcmp("NVMe", inq_rsp + 8, 4))
+ printf(" %s device (not an enclosure)\n", cp);
+ }
+ }
+ clear_scsi_pt_obj(ptvp);
+ }
+ } else if (op->do_control) {
+ pr2serr("Cannot do SCSI Send diagnostic command without a DEVICE\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+ if (ptvp && pt_device_is_nvme(ptvp) && (enc_stat_rsp_sz > 4095)) {
+ /* Fetch VPD 0xde (vendor specific: sg3_utils) for Identify ctl */
+ ret = sg_ll_inquiry_pt(ptvp, true, 0xde, enc_stat_rsp, 4096, 0,
+ &resid, ! op->quiet, vb);
+ if (ret) {
+ if (vb)
+ pr2serr("Fetch VPD page 0xde (NVMe Identify ctl) failed, "
+ "continue\n");
+ } else if (resid > 0) {
+ if (vb)
+ pr2serr("VPD page 0xde (NVMe Identify ctl) less than 4096 "
+ "bytes, continue\n");
+ } else {
+ uint8_t nvmsr;
+ uint16_t oacs;
+
+ nvmsr = enc_stat_rsp[253];
+ oacs = sg_get_unaligned_le16(enc_stat_rsp + 256); /* N.B. LE */
+ if (vb > 3)
+ pr2serr("NVMe Identify ctl response: nvmsr=%u, oacs=0x%x\n",
+ nvmsr, oacs);
+ if (! ((0x2 & nvmsr) && (0x40 & oacs))) {
+ pr2serr(">>> Warning: A NVMe enclosure needs both the "
+ "enclosure bit and support for\n");
+ pr2serr(">>> MI Send+Receive commands bit set; current "
+ "state: %s, %s\n", (0x2 & nvmsr) ? "set" : "clear",
+ (0x40 & oacs) ? "set" : "clear");
+ }
+ }
+ clear_scsi_pt_obj(ptvp);
+ memset(enc_stat_rsp, 0, enc_stat_rsp_sz);
+ }
+#endif
+
+ if (ptvp) {
+ n = (enc_stat_rsp_sz < REQUEST_SENSE_RESP_SZ) ? enc_stat_rsp_sz :
+ REQUEST_SENSE_RESP_SZ;
+ ret = sg_ll_request_sense_pt(ptvp, false, enc_stat_rsp, n,
+ ! op->quiet, vb);
+ if (0 == ret) {
+ int sense_len = n - get_scsi_pt_resid(ptvp);
+ struct sg_scsi_sense_hdr ssh;
+
+ if ((sense_len > 7) && sg_scsi_normalize_sense(enc_stat_rsp,
+ sense_len, &ssh)) {
+ const char * aa_str = sg_get_asc_ascq_str(ssh.asc, ssh.ascq,
+ sizeof(b), b);
+
+ /* Ignore the possibility that multiple UAs queued up */
+ if (SPC_SK_UNIT_ATTENTION == ssh.sense_key)
+ pr2serr("Unit attention detected: %s\n ... continue\n",
+ aa_str);
+ else {
+ if (vb) {
+ pr2serr("Request Sense near startup detected "
+ "something:\n");
+ pr2serr(" Sense key: %s, additional: %s\n ... "
+ "continue\n",
+ sg_get_sense_key_str(ssh.sense_key,
+ sizeof(buff), buff), aa_str);
+ }
+ }
+ }
+ } else {
+ if (vb)
+ pr2serr("Request sense failed (res=%d), most likely "
+ " problems ahead\n", ret);
+ }
+ clear_scsi_pt_obj(ptvp);
+ memset(enc_stat_rsp, 0, enc_stat_rsp_sz);
+ }
+
+ if (op->nickname_str)
+ ret = ses_set_nickname(ptvp, op);
+ else if (have_cgs) {
+ for (k = 0, tavp = tav_arr, cgs_clp = op->cgs_cl_arr;
+ k < op->num_cgs; ++k, ++tavp, ++cgs_clp) {
+ ret = ses_cgs(ptvp, tavp, op, cgs_clp->last_cs);
+ if (ret)
+ break;
+ }
+ } else if (op->do_join)
+ ret = join_work(ptvp, op, true);
+ else if (op->do_status)
+ ret = process_status_page_s(ptvp, op);
+ else { /* control page requested */
+ op->data_arr[0] = op->page_code;
+ op->data_arr[1] = op->byte1;
+ d_len = op->arr_len + DATA_IN_OFF;
+ sg_put_unaligned_be16((uint16_t)op->arr_len, op->data_arr + 2);
+ switch (op->page_code) {
+ case ENC_CONTROL_DPC: /* Enclosure Control diagnostic page [0x2] */
+ printf("Sending Enclosure Control [0x%x] page, with page "
+ "length=%d bytes\n", op->page_code, op->arr_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Enclosure Control page\n");
+ goto err_out;
+ }
+ break;
+ case STRING_DPC: /* String Out diagnostic page [0x4] */
+ printf("Sending String Out [0x%x] page, with page length=%d "
+ "bytes\n", op->page_code, op->arr_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send String Out page\n");
+ goto err_out;
+ }
+ break;
+ case THRESHOLD_DPC: /* Threshold Out diagnostic page [0x5] */
+ printf("Sending Threshold Out [0x%x] page, with page length=%d "
+ "bytes\n", op->page_code, op->arr_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Threshold Out page\n");
+ goto err_out;
+ }
+ break;
+ case ARRAY_CONTROL_DPC: /* Array control diagnostic page [0x6] */
+ printf("Sending Array Control [0x%x] page, with page "
+ "length=%d bytes\n", op->page_code, op->arr_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Array Control page\n");
+ goto err_out;
+ }
+ break;
+ case SUBENC_STRING_DPC: /* Subenclosure String Out page [0xc] */
+ printf("Sending Subenclosure String Out [0x%x] page, with page "
+ "length=%d bytes\n", op->page_code, op->arr_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Subenclosure String Out page\n");
+ goto err_out;
+ }
+ break;
+ case DOWNLOAD_MICROCODE_DPC: /* Download Microcode Control [0xe] */
+ printf("Sending Download Microcode Control [0x%x] page, with "
+ "page length=%d bytes\n", op->page_code, d_len);
+ printf(" Perhaps it would be better to use the sg_ses_microcode "
+ "utility\n");
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Download Microcode Control page\n");
+ goto err_out;
+ }
+ break;
+ case SUBENC_NICKNAME_DPC: /* Subenclosure Nickname Control [0xf] */
+ printf("Sending Subenclosure Nickname Control [0x%x] page, with "
+ "page length=%d bytes\n", op->page_code, d_len);
+ ret = do_senddiag(ptvp, op->data_arr, d_len, ! op->quiet, vb);
+ if (ret) {
+ pr2serr("couldn't send Subenclosure Nickname Control page\n");
+ goto err_out;
+ }
+ break;
+ default:
+ pr2serr("Setting SES control page 0x%x not supported by this "
+ "utility\n", op->page_code);
+ pr2serr("That can be done with the sg_senddiag utility with its "
+ "'--raw=' option\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ break;
+ }
+ }
+
+err_out:
+ if (! op->do_status) {
+ sg_get_category_sense_str(ret, sizeof(b), b, vb);
+ pr2serr(" %s\n", b);
+ }
+ if (free_enc_stat_rsp)
+ free(free_enc_stat_rsp);
+ if (free_elem_desc_rsp)
+ free(free_elem_desc_rsp);
+ if (free_add_elem_rsp)
+ free(free_add_elem_rsp);
+ if (free_threshold_rsp)
+ free(free_threshold_rsp);
+
+early_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ if ((0 == vb) && (! op->quiet)) {
+ if (! sg_if_can2stderr("sg_ses failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ else if ((SG_LIB_SYNTAX_ERROR == ret) && (0 == vb))
+ pr2serr("Add '-h' to command line for usage information\n");
+ }
+ if (op->free_data_arr)
+ free(op->free_data_arr);
+ if (free_config_dp_resp)
+ free(free_config_dp_resp);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_ses_microcode.c b/src/sg_ses_microcode.c
new file mode 100644
index 00000000..a00b6d51
--- /dev/null
+++ b/src/sg_ses_microcode.c
@@ -0,0 +1,941 @@
+/*
+ * Copyright (c) 2014-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */
+#endif
+#endif
+
+/*
+ * This utility issues the SCSI SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC
+ * RESULTS commands in order to send microcode to the given SES device.
+ */
+
+static const char * version_str = "1.19 20210610"; /* ses4r02 */
+
+#define ME "sg_ses_microcode: "
+#define MAX_XFER_LEN (128 * 1024 * 1024)
+#define DEF_XFER_LEN (8 * 1024 * 1024)
+#define DEF_DIN_LEN (8 * 1024)
+#define EBUFF_SZ 256
+
+#define DPC_DOWNLOAD_MICROCODE 0xe
+
+struct opts_t {
+ bool dry_run;
+ bool ealsd;
+ bool mc_non;
+ bool bpw_then_activate;
+ bool mc_len_given;
+ int bpw; /* bytes per write, chunk size */
+ int mc_id;
+ int mc_len; /* --length=LEN */
+ int mc_mode;
+ int mc_offset; /* Buffer offset in SCSI commands */
+ int mc_skip; /* on FILE */
+ int mc_subenc;
+ int mc_tlen; /* --tlength=TLEN */
+ int verbose;
+};
+
+static struct option long_options[] = {
+ {"bpw", required_argument, 0, 'b'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"dry_run", no_argument, 0, 'd'},
+ {"ealsd", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"id", required_argument, 0, 'i'},
+ {"in", required_argument, 0, 'I'},
+ {"length", required_argument, 0, 'l'},
+ {"mode", required_argument, 0, 'm'},
+ {"non", no_argument, 0, 'N'},
+ {"offset", required_argument, 0, 'o'},
+ {"skip", required_argument, 0, 's'},
+ {"subenc", required_argument, 0, 'S'},
+ {"tlength", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+#define MODE_DNLD_STATUS 0
+#define MODE_DNLD_MC_OFFS 6
+#define MODE_DNLD_MC_OFFS_SAVE 7
+#define MODE_DNLD_MC_OFFS_DEFER 0x0E
+#define MODE_ACTIVATE_MC 0x0F
+#define MODE_ABORT_MC 0xFF /* actually reserved; any reserved
+ * value aborts a microcode download
+ * in progress */
+
+struct mode_s {
+ const char *mode_string;
+ int mode;
+ const char *comment;
+};
+
+static struct mode_s mode_arr[] = {
+ {"dmc_status", MODE_DNLD_STATUS, "report status of microcode "
+ "download"},
+ {"dmc_offs", MODE_DNLD_MC_OFFS, "download microcode with offsets "
+ "and activate"},
+ {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
+ "offsets, save and\n\t\t\t\tactivate"},
+ {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
+ "with offsets, save and\n\t\t\t\tdefer activation"},
+ {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
+ {"dmc_abort", MODE_ABORT_MC, "abort download microcode in progress"},
+ {NULL, 0, NULL},
+};
+
+/* An array of Download microcode status field values and descriptions.
+ * This table is a subset of one in sg_read_buffer for the read microcode
+ * status page. */
+static struct sg_lib_simple_value_name_t mc_status_arr[] = {
+ {0x0, "No download microcode operation in progress"},
+ {0x1, "Download in progress, awaiting more"},
+ {0x2, "Download complete, updating storage"},
+ {0x3, "Updating storage with deferred microcode"},
+ {0x10, "Complete, no error, starting now"},
+ {0x11, "Complete, no error, start after hard reset or power cycle"},
+ {0x12, "Complete, no error, start after power cycle"},
+ {0x13, "Complete, no error, start after activate_mc, hard reset or "
+ "power cycle"},
+ {0x80, "Error, discarded, see additional status"},
+ {0x81, "Error, discarded, image error"},
+ {0x82, "Timeout, discarded"},
+ {0x83, "Internal error, need new microcode before reset"},
+ {0x84, "Internal error, need new microcode, reset safe"},
+ {0x85, "Unexpected activate_mc received"},
+ {0x1000, NULL},
+};
+
+struct dout_buff_t {
+ uint8_t * doutp;
+ uint8_t * free_doutp;
+ int dout_len;
+};
+
+/* This dummy response is used when --dry-run skips the RECEIVE DIAGNOSTICS
+ * RESULTS command. Say maximum download MC size is 4 MB. Set generation
+ * code to 0 . */
+uint8_t dummy_rd_resp[] = {
+ 0xe, 3, 0, 68, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0x0, 0x40, 0x0, 0x0, 0, 0, 0, 0, 0x0, 0x0, 0x0, 0x0,
+ 0, 1, 0, 0, 0x0, 0x40, 0x0, 0x0, 0, 0, 0, 0, 0x0, 0x0, 0x0, 0x0,
+ 0, 2, 0, 0, 0x0, 0x40, 0x0, 0x0, 0, 0, 0, 0, 0x0, 0x0, 0x0, 0x0,
+ 0, 3, 0, 0, 0x0, 0x40, 0x0, 0x0, 0, 0, 0, 0, 0x0, 0x0, 0x0, 0x0,
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_ses_microcode [--bpw=CS] [--dry-run] [--ealsd] [--help] "
+ "[--id=ID]\n"
+ " [--in=FILE] [--length=LEN] [--mode=MO] "
+ "[--non]\n"
+ " [--offset=OFF] [--skip=SKIP] "
+ "[--subenc=SEID]\n"
+ " [--tlength=TLEN] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --bpw=CS|-b CS CS is chunk size: bytes per send "
+ "diagnostic\n"
+ " command (def: 0 -> as many as "
+ "possible)\n"
+ " can append ',act' to do activate "
+ "after last\n"
+ " --dry-run|-d skip SCSI commands, do everything "
+ "else\n"
+ " --ealsd|-e exit after last Send Diagnostic "
+ "command\n"
+ " --help|-h print out usage message then exit\n"
+ " --id=ID|-i ID buffer identifier (0 (default) to "
+ "255)\n"
+ " --in=FILE|-I FILE read from FILE ('-I -' read "
+ "from stdin)\n"
+ " --length=LEN|-l LEN length in bytes to send (def: "
+ "deduced from\n"
+ " FILE taking SKIP into account)\n"
+ " --mode=MO|-m MO download microcode mode, MO is "
+ "number or\n"
+ " acronym (def: 0 -> 'dmc_status')\n"
+ " --non|-N non-standard: bypass all receive "
+ "diagnostic\n"
+ " results commands except after check "
+ "condition\n"
+ " --offset=OFF|-o OFF buffer offset (unit: bytes, def: "
+ "0);\n"
+ " ignored if --bpw=CS given\n"
+ " --skip=SKIP|-s SKIP bytes in file FILE to skip before "
+ "reading\n"
+ " --subenc=SEID|-S SEID subenclosure identifier (def: 0 "
+ "(primary))\n"
+ " --tlength=TLEN|-t TLEN total length of firmware in "
+ "bytes\n"
+ " (def: 0). Only needed if "
+ "TLEN>LEN\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Does one or more SCSI SEND DIAGNOSTIC followed by RECEIVE "
+ "DIAGNOSTIC\nRESULTS command sequences in order to download "
+ "microcode. Use '-m xxx'\nto list available modes. With only "
+ "DEVICE given, the Download Microcode\nStatus dpage is output.\n"
+ );
+}
+
+static void
+print_modes(void)
+{
+ const struct mode_s * mp;
+
+ pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+ "or symbolic:\n");
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ pr2serr(" %3d [0x%02x] %-18s%s\n", mp->mode, mp->mode,
+ mp->mode_string, mp->comment);
+ }
+ pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
+ "microcode after a\nsuccessful multipart dmc_offs_defer mode "
+ "download.\n");
+}
+
+static const char *
+get_mc_status_str(uint8_t status_val)
+{
+ const struct sg_lib_simple_value_name_t * mcsp;
+
+ for (mcsp = mc_status_arr; mcsp->name; ++mcsp) {
+ if (status_val == mcsp->value)
+ return mcsp->name;
+ }
+ return "";
+}
+
+/* display DPC_DOWNLOAD_MICROCODE status dpage [0xe] */
+static void
+show_download_mc_sdg(const uint8_t * resp, int resp_len,
+ uint32_t gen_code)
+{
+ int k, num_subs, num;
+ const uint8_t * bp;
+ const char * cp;
+
+ printf("Download microcode status diagnostic page:\n");
+ if (resp_len < 8)
+ goto truncated;
+ num_subs = resp[1]; /* primary is additional one) */
+ num = (resp_len - 8) / 16;
+ if ((resp_len - 8) % 16)
+ pr2serr("Found %d Download microcode status descriptors, but there "
+ "is residual\n", num);
+ printf(" number of secondary subenclosures: %d\n", num_subs);
+ printf(" generation code: 0x%" PRIx32 "\n", gen_code);
+ bp = resp + 8;
+ for (k = 0; k < num; ++k, bp += 16) {
+ cp = (0 == bp[1]) ? " [primary]" : "";
+ printf(" subenclosure identifier: %d%s\n", bp[1], cp);
+ cp = get_mc_status_str(bp[2]);
+ if (strlen(cp) > 0) {
+ printf(" download microcode status: %s [0x%x]\n", cp, bp[2]);
+ printf(" download microcode additional status: 0x%x\n",
+ bp[3]);
+ } else
+ printf(" download microcode status: 0x%x [additional "
+ "status: 0x%x]\n", bp[2], bp[3]);
+ printf(" download microcode maximum size: %" PRIu32 " bytes\n",
+ sg_get_unaligned_be32(bp + 4));
+ printf(" download microcode expected buffer id: 0x%x\n", bp[11]);
+ printf(" download microcode expected buffer id offset: %" PRIu32
+ "\n", sg_get_unaligned_be32(bp + 12));
+ }
+ return;
+truncated:
+ pr2serr(" <<<download status: response too short>>>\n");
+ return;
+}
+
+static int
+send_then_receive(int sg_fd, uint32_t gen_code, int off_off,
+ const uint8_t * dmp, int dmp_len,
+ struct dout_buff_t * wp, uint8_t * dip,
+ int din_len, bool last, const struct opts_t * op)
+{
+ bool send_data = false;
+ int do_len, rem, res, rsp_len, k, n, num, mc_status, resid, act_len, verb;
+ int ret = 0;
+ uint32_t rec_gen_code;
+ const uint8_t * bp;
+ const char * cp;
+
+ verb = (op->verbose > 1) ? op->verbose - 1 : 0;
+ switch (op->mc_mode) {
+ case MODE_DNLD_MC_OFFS:
+ case MODE_DNLD_MC_OFFS_SAVE:
+ case MODE_DNLD_MC_OFFS_DEFER:
+ send_data = true;
+ do_len = 24 + dmp_len;
+ rem = do_len % 4;
+ if (rem)
+ do_len += (4 - rem);
+ break;
+ case MODE_ACTIVATE_MC:
+ case MODE_ABORT_MC:
+ do_len = 24;
+ break;
+ default:
+ pr2serr("%s: unexpected mc_mode=0x%x\n", __func__, op->mc_mode);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (do_len > wp->dout_len) {
+ if (wp->doutp)
+ free(wp->doutp);
+ wp->doutp = sg_memalign(do_len, 0, &wp->free_doutp, op->verbose > 3);
+ if (! wp->doutp) {
+ pr2serr("%s: unable to alloc %d bytes\n", __func__, do_len);
+ return SG_LIB_CAT_OTHER;
+ }
+ wp->dout_len = do_len;
+ } else
+ memset(wp->doutp, 0, do_len);
+ wp->doutp[0] = DPC_DOWNLOAD_MICROCODE;
+ wp->doutp[1] = op->mc_subenc;
+ sg_put_unaligned_be16(do_len - 4, wp->doutp + 2);
+ sg_put_unaligned_be32(gen_code, wp->doutp + 4);
+ wp->doutp[8] = op->mc_mode;
+ wp->doutp[11] = op->mc_id;
+ if (send_data)
+ sg_put_unaligned_be32(op->mc_offset + off_off, wp->doutp + 12);
+ sg_put_unaligned_be32(op->mc_tlen, wp->doutp + 16);
+ sg_put_unaligned_be32(dmp_len, wp->doutp + 20);
+ if (send_data && (dmp_len > 0))
+ memcpy(wp->doutp + 24, dmp, dmp_len);
+ if ((op->verbose > 2) || (op->dry_run && op->verbose)) {
+ pr2serr("send diag: sub-enc id=%u exp_gen=%u download_mc_code=%u "
+ "buff_id=%u\n", op->mc_subenc, gen_code, op->mc_mode,
+ op->mc_id);
+ pr2serr(" buff_off=%u image_len=%u this_mc_data_len=%u "
+ "dout_len=%u\n", op->mc_offset + off_off, op->mc_tlen,
+ dmp_len, do_len);
+ }
+ /* select long duration timeout (7200 seconds) */
+ if (op->dry_run) {
+ if (op->mc_subenc < 4) {
+ int s = op->mc_offset + off_off + dmp_len;
+
+ n = 8 + (op->mc_subenc * 16);
+ dummy_rd_resp[n + 11] = op->mc_id;
+ sg_put_unaligned_be32(((send_data && (! last)) ? s : 0),
+ dummy_rd_resp + n + 12);
+ if (MODE_ABORT_MC == op->mc_mode)
+ dummy_rd_resp[n + 2] = 0x80;
+ else if (MODE_ACTIVATE_MC == op->mc_mode)
+ dummy_rd_resp[n + 2] = 0x0; /* done */
+ else
+ dummy_rd_resp[n + 2] = (s >= op->mc_tlen) ? 0x13 : 0x1;
+ }
+ res = 0;
+ } else
+ res = sg_ll_send_diag(sg_fd, 0 /* st_code */, true /* pf */,
+ false /* st */, false /* devofl */,
+ false /* unitofl */, 1 /* long_duration */,
+ wp->doutp, do_len, true /* noisy */, verb);
+ if (op->mc_non) {
+ /* If non-standard, only call RDR after failed SD */
+ if (0 == res)
+ return 0;
+ /* If RDR error after SD error, prefer reporting SD error */
+ ret = res;
+ } else {
+ switch (op->mc_mode) {
+ case MODE_DNLD_MC_OFFS:
+ case MODE_DNLD_MC_OFFS_SAVE:
+ if (res)
+ return res;
+ else if (last) {
+ if (op->ealsd)
+ return 0; /* RDR after last may hit a device reset */
+ }
+ break;
+ case MODE_DNLD_MC_OFFS_DEFER:
+ if (res)
+ return res;
+ break;
+ case MODE_ACTIVATE_MC:
+ case MODE_ABORT_MC:
+ if (0 == res) {
+ if (op->ealsd)
+ return 0; /* RDR after this may hit a device reset */
+ }
+ /* SD has failed, so do a RDR but return SD's error */
+ ret = res;
+ break;
+ default:
+ pr2serr("%s: mc_mode=0x%x\n", __func__, op->mc_mode);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (op->dry_run) {
+ n = sizeof(dummy_rd_resp);
+ n = (n < din_len) ? n : din_len;
+ memcpy(dip, dummy_rd_resp, n);
+ resid = din_len - n;
+ res = 0;
+ } else
+ res = sg_ll_receive_diag_v2(sg_fd, true /* pcv */,
+ DPC_DOWNLOAD_MICROCODE, dip, din_len,
+ 0 /* default timeout */, &resid, true,
+ verb);
+ if (res)
+ return ret ? ret : res;
+ rsp_len = sg_get_unaligned_be16(dip + 2) + 4;
+ act_len = din_len - resid;
+ if (rsp_len > din_len) {
+ pr2serr("<<< warning response buffer too small [%d but need "
+ "%d]>>>\n", din_len, rsp_len);
+ rsp_len = din_len;
+ }
+ if (rsp_len > act_len) {
+ pr2serr("<<< warning response too short [actually got %d but need "
+ "%d]>>>\n", act_len, rsp_len);
+ rsp_len = act_len;
+ }
+ if (rsp_len < 8) {
+ pr2serr("Download microcode status dpage too short [%d]\n", rsp_len);
+ return ret ? ret : SG_LIB_CAT_OTHER;
+ }
+ rec_gen_code = sg_get_unaligned_be32(dip + 4);
+ if ((op->verbose > 2) || (op->dry_run && op->verbose)) {
+ n = 8 + (op->mc_subenc * 16);
+ pr2serr("rec diag: rsp_len=%d, num_sub-enc=%u rec_gen_code=%u "
+ "exp_buff_off=%u\n", rsp_len, dip[1],
+ sg_get_unaligned_be32(dip + 4),
+ sg_get_unaligned_be32(dip + n + 12));
+ }
+ if (rec_gen_code != gen_code)
+ pr2serr("gen_code changed from %" PRIu32 " to %" PRIu32
+ ", continuing but may fail\n", gen_code, rec_gen_code);
+ num = (rsp_len - 8) / 16;
+ if ((rsp_len - 8) % 16)
+ pr2serr("Found %d Download microcode status descriptors, but there "
+ "is residual\n", num);
+ bp = dip + 8;
+ for (k = 0; k < num; ++k, bp += 16) {
+ if ((unsigned int)op->mc_subenc == (unsigned int)bp[1]) {
+ mc_status = bp[2];
+ cp = get_mc_status_str(mc_status);
+ if ((mc_status >= 0x80) || op->verbose)
+ pr2serr("mc offset=%u: status: %s [0x%x, additional=0x%x]\n",
+ sg_get_unaligned_be32(bp + 12), cp, mc_status, bp[3]);
+ if (op->verbose > 1)
+ pr2serr(" subenc_id=%d, expected_buffer_id=%d, "
+ "expected_offset=0x%" PRIx32 "\n", bp[1], bp[11],
+ sg_get_unaligned_be32(bp + 12));
+ if (mc_status >= 0x80)
+ ret = ret ? ret : SG_LIB_CAT_OTHER;
+ }
+ }
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool last, got_stdin, is_reg;
+ bool want_file = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, len, k, n, rsp_len, resid, act_len, din_len, verb;
+ int sg_fd = -1;
+ int infd = -1;
+ int do_help = 0;
+ int ret = 0;
+ uint32_t gen_code = 0;
+ const char * device_name = NULL;
+ const char * file_name = NULL;
+ uint8_t * dmp = NULL;
+ uint8_t * dip = NULL;
+ uint8_t * free_dip = NULL;
+ char * cp;
+ char ebuff[EBUFF_SZ];
+ struct stat a_stat;
+ struct dout_buff_t dout;
+ struct opts_t opts;
+ struct opts_t * op;
+ const struct mode_s * mp;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ memset(&dout, 0, sizeof(dout));
+ din_len = DEF_DIN_LEN;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dehi:I:l:m:No:s:S:t:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ op->bpw = sg_get_num(optarg);
+ if (op->bpw < 0) {
+ pr2serr("argument to '--bpw' should be in a positive "
+ "number\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((cp = strchr(optarg, ','))) {
+ if (0 == strncmp("act", cp + 1, 3))
+ op->bpw_then_activate = true;
+ }
+ break;
+ case 'd':
+ op->dry_run = true;
+ break;
+ case 'e':
+ op->ealsd = true;
+ break;
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'i':
+ op->mc_id = sg_get_num_nomult(optarg);
+ if ((op->mc_id < 0) || (op->mc_id > 255)) {
+ pr2serr("argument to '--id' should be in the range 0 to "
+ "255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'I':
+ file_name = optarg;
+ break;
+ case 'l':
+ op->mc_len = sg_get_num(optarg);
+ if (op->mc_len < 0) {
+ pr2serr("bad argument to '--length'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->mc_len_given = true;
+ break;
+ case 'm':
+ if (isdigit((uint8_t)*optarg)) {
+ op->mc_mode = sg_get_num_nomult(optarg);
+ if ((op->mc_mode < 0) || (op->mc_mode > 255)) {
+ pr2serr("argument to '--mode' should be in the range 0 "
+ "to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ len = strlen(optarg);
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ if (0 == strncmp(mp->mode_string, optarg, len)) {
+ op->mc_mode = mp->mode;
+ break;
+ }
+ }
+ if (! mp->mode_string) {
+ print_modes();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ break;
+ case 'N':
+ op->mc_non = true;
+ break;
+ case 'o':
+ op->mc_offset = sg_get_num(optarg);
+ if (op->mc_offset < 0) {
+ pr2serr("bad argument to '--offset'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 != (op->mc_offset % 4)) {
+ pr2serr("'--offset' value needs to be a multiple of 4\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ op->mc_skip = sg_get_num(optarg);
+ if (op->mc_skip < 0) {
+ pr2serr("bad argument to '--skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'S':
+ op->mc_subenc = sg_get_num_nomult(optarg);
+ if ((op->mc_subenc < 0) || (op->mc_subenc > 255)) {
+ pr2serr("expected argument to '--subenc' to be 0 to 255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 't':
+ op->mc_tlen = sg_get_num(optarg);
+ if (op->mc_tlen < 0) {
+ pr2serr("bad argument to '--tlength'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (do_help) {
+ if (do_help > 1) {
+ usage();
+ pr2serr("\n");
+ print_modes();
+ } else
+ usage();
+ return 0;
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ op->verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ switch (op->mc_mode) {
+ case MODE_DNLD_MC_OFFS:
+ case MODE_DNLD_MC_OFFS_SAVE:
+ case MODE_DNLD_MC_OFFS_DEFER:
+ want_file = true;
+ break;
+ case MODE_DNLD_STATUS:
+ case MODE_ACTIVATE_MC:
+ case MODE_ABORT_MC:
+ want_file = false;
+ break;
+ default:
+ pr2serr("%s: mc_mode=0x%x, continue for now\n", __func__,
+ op->mc_mode);
+ break;
+ }
+
+ if ((op->mc_len > 0) && (op->bpw > op->mc_len)) {
+ pr2serr("trim chunk size (CS) to be the same as LEN\n");
+ op->bpw = op->mc_len;
+ }
+ if ((op->mc_offset > 0) && (op->bpw > 0)) {
+ op->mc_offset = 0;
+ pr2serr("WARNING: --offset= ignored (set back to 0) when --bpw= "
+ "argument given (and > 0)\n");
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (op->verbose > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, op->verbose);
+ if (sg_fd < 0) {
+ if (op->verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (file_name && (! want_file))
+ pr2serr("ignoring --in=FILE option\n");
+ else if (file_name) {
+ got_stdin = (0 == strcmp(file_name, "-"));
+ if (got_stdin)
+ infd = STDIN_FILENO;
+ else {
+ if ((infd = open(file_name, O_RDONLY)) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", file_name);
+ perror(ebuff);
+ goto fini;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ }
+ if ((0 == fstat(infd, &a_stat)) && S_ISREG(a_stat.st_mode)) {
+ is_reg = true;
+ if (0 == op->mc_len) {
+ if (op->mc_skip >= a_stat.st_size) {
+ pr2serr("skip exceeds file size of %d bytes\n",
+ (int)a_stat.st_size);
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ op->mc_len = (int)(a_stat.st_size) - op->mc_skip;
+ }
+ } else {
+ is_reg = false;
+ if (0 == op->mc_len)
+ op->mc_len = DEF_XFER_LEN;
+ }
+ if (op->mc_len > MAX_XFER_LEN) {
+ pr2serr("file size or requested length (%d) exceeds "
+ "MAX_XFER_LEN of %d bytes\n", op->mc_len,
+ MAX_XFER_LEN);
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ if (NULL == (dmp = (uint8_t *)malloc(op->mc_len))) {
+ pr2serr(ME "out of memory to hold microcode read from FILE\n");
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ /* Don't remember why this is preset to 0xff, from write_buffer */
+ memset(dmp, 0xff, op->mc_len);
+ if (op->mc_skip > 0) {
+ if (! is_reg) {
+ if (got_stdin)
+ pr2serr("Can't skip on stdin\n");
+ else
+ pr2serr(ME "not a 'regular' file so can't apply skip\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ if (lseek(infd, op->mc_skip, SEEK_SET) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to "
+ "required position on %s", file_name);
+ perror(ebuff);
+ goto fini;
+ }
+ }
+ res = read(infd, dmp, op->mc_len);
+ if (res < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+ file_name);
+ perror(ebuff);
+ goto fini;
+ }
+ if (res < op->mc_len) {
+ if (op->mc_len_given) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ op->mc_len, file_name, res);
+ pr2serr("pad with 0xff bytes and continue\n");
+ } else {
+ if (op->verbose) {
+ pr2serr("tried to read %d bytes from %s, got %d "
+ "bytes\n", op->mc_len, file_name, res);
+ pr2serr("will send %d bytes", res);
+ if ((op->bpw > 0) && (op->bpw < op->mc_len))
+ pr2serr(", %d bytes per WRITE BUFFER command\n",
+ op->bpw);
+ else
+ pr2serr("\n");
+ }
+ op->mc_len = res;
+ }
+ }
+ if (! got_stdin)
+ close(infd);
+ infd = -1;
+ } else if (want_file) {
+ pr2serr("need --in=FILE option with given mode\n");
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (op->mc_tlen < op->mc_len)
+ op->mc_tlen = op->mc_len;
+ if (op->mc_non && (MODE_DNLD_STATUS == op->mc_mode)) {
+ pr2serr("Do nothing because '--non' given so fetching the Download "
+ "microcode status\ndpage might be dangerous\n");
+ goto fini;
+ }
+
+ dip = sg_memalign(din_len, 0, &free_dip, op->verbose > 3);
+ if (NULL == dip) {
+ pr2serr(ME "out of memory (data-in buffer)\n");
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ verb = (op->verbose > 1) ? op->verbose - 1 : 0;
+ /* Fetch Download microcode status dpage for generation code ++ */
+ if (op->dry_run) {
+ n = sizeof(dummy_rd_resp);
+ n = (n < din_len) ? n : din_len;
+ memcpy(dip, dummy_rd_resp, n);
+ resid = din_len - n;
+ res = 0;
+ } else
+ res = sg_ll_receive_diag_v2(sg_fd, true /* pcv */,
+ DPC_DOWNLOAD_MICROCODE, dip, din_len,
+ 0 /*default timeout */, &resid, true,
+ verb);
+ if (0 == res) {
+ rsp_len = sg_get_unaligned_be16(dip + 2) + 4;
+ act_len = din_len - resid;
+ if (rsp_len > din_len) {
+ pr2serr("<<< warning response buffer too small [%d but need "
+ "%d]>>>\n", din_len, rsp_len);
+ rsp_len = din_len;
+ }
+ if (rsp_len > act_len) {
+ pr2serr("<<< warning response too short [actually got %d but "
+ "need %d]>>>\n", act_len, rsp_len);
+ rsp_len = act_len;
+ }
+ if (rsp_len < 8) {
+ pr2serr("Download microcode status dpage too short\n");
+ ret = SG_LIB_CAT_OTHER;
+ goto fini;
+ }
+ if ((op->verbose > 2) || (op->dry_run && op->verbose))
+ pr2serr("rec diag(ini): rsp_len=%d, num_sub-enc=%u "
+ "rec_gen_code=%u\n", rsp_len, dip[1],
+ sg_get_unaligned_be32(dip + 4));
+ } else {
+ ret = res;
+ goto fini;
+ }
+ gen_code = sg_get_unaligned_be32(dip + 4);
+
+ if (MODE_DNLD_STATUS == op->mc_mode) {
+ show_download_mc_sdg(dip, rsp_len, gen_code);
+ goto fini;
+ } else if (! want_file) { /* ACTIVATE and ABORT */
+ res = send_then_receive(sg_fd, gen_code, 0, NULL, 0, &dout, dip,
+ din_len, true, op);
+ ret = res;
+ goto fini;
+ }
+
+ res = 0;
+ if (op->bpw > 0) {
+ for (k = 0, last = false; k < op->mc_len; k += n) {
+ n = op->mc_len - k;
+ if (n > op->bpw)
+ n = op->bpw;
+ else
+ last = true;
+ if (op->verbose)
+ pr2serr("bpw loop: mode=0x%x, id=%d, off_off=%d, len=%d, "
+ "last=%d\n", op->mc_mode, op->mc_id, k, n, last);
+ res = send_then_receive(sg_fd, gen_code, k, dmp + k, n, &dout,
+ dip, din_len, last, op);
+ if (res)
+ break;
+ }
+ if (op->bpw_then_activate && (0 == res)) {
+ op->mc_mode = MODE_ACTIVATE_MC;
+ if (op->verbose)
+ pr2serr("sending Activate deferred microcode [0xf]\n");
+ res = send_then_receive(sg_fd, gen_code, 0, NULL, 0, &dout,
+ dip, din_len, true, op);
+ }
+ } else {
+ if (op->verbose)
+ pr2serr("single: mode=0x%x, id=%d, offset=%d, len=%d\n",
+ op->mc_mode, op->mc_id, op->mc_offset, op->mc_len);
+ res = send_then_receive(sg_fd, gen_code, 0, dmp, op->mc_len, &dout,
+ dip, din_len, true, op);
+ }
+ if (res)
+ ret = res;
+
+fini:
+ if ((infd >= 0) && (! got_stdin))
+ close(infd);
+ if (dmp)
+ free(dmp);
+ if (dout.free_doutp)
+ free(dout.free_doutp);
+ if (free_dip)
+ free(free_dip);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_ses_microcode failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_start.c b/src/sg_start.c
new file mode 100644
index 00000000..890b696a
--- /dev/null
+++ b/src/sg_start.c
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 1999-2020 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ Start/Stop parameter by Kurt Garloff, 6/2000
+ Sync cache parameter by Kurt Garloff, 1/2001
+ Guard block device answering sg's ioctls.
+ <dgilbert at interlog dot com> 12/2002
+ Convert to SG_IO ioctl so can use sg or block devices in 2.6.* 3/2003
+
+ This utility was written for the Linux 2.4 kernel series. It now
+ builds for the Linux 2.6 and 3 kernel series and various other
+ Operating Systems.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "0.67 20200930"; /* sbc3r14; mmc6r01a */
+
+static struct option long_options[] = {
+ {"eject", no_argument, 0, 'e'},
+ {"fl", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"immed", no_argument, 0, 'i'},
+ {"load", no_argument, 0, 'l'},
+ {"loej", no_argument, 0, 'L'},
+ {"mod", required_argument, 0, 'm'},
+ {"noflush", no_argument, 0, 'n'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"pc", required_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'r'},
+ {"start", no_argument, 0, 's'},
+ {"stop", no_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_eject;
+ bool do_immed;
+ bool do_load;
+ bool do_loej;
+ bool do_noflush;
+ bool do_readonly;
+ bool do_start;
+ bool do_stop;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_fl;
+ int do_help;
+ int do_mod;
+ int do_pc;
+ int verbose;
+ const char * device_name;
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_start [--eject] [--fl=FL] [--help] "
+ "[--immed] [--load] [--loej]\n"
+ " [--mod=PC_MOD] [--noflush] [--pc=PC] "
+ "[--readonly]\n"
+ " [--start] [--stop] [--verbose] "
+ "[--version] DEVICE\n"
+ " where:\n"
+ " --eject|-e stop unit then eject the medium\n"
+ " --fl=FL|-f FL format layer number (mmc5)\n"
+ " --help|-h print usage message then exit\n"
+ " --immed|-i device should return control after "
+ "receiving cdb,\n"
+ " default action is to wait until action "
+ "is complete\n"
+ " --load|-l load medium then start the unit\n"
+ " --loej|-L load or eject, corresponds to LOEJ bit "
+ "in cdb;\n"
+ " load when START bit also set, else "
+ "eject\n"
+ " --mod=PC_MOD|-m PC_MOD power condition modifier "
+ "(def: 0) (sbc)\n"
+ " --noflush|-n no flush prior to operation that limits "
+ "access (sbc)\n"
+ " --pc=PC|-p PC power condition: 0 (default) -> no "
+ "power condition,\n"
+ " 1 -> active, 2 -> idle, 3 -> standby, "
+ "5 -> sleep (mmc)\n"
+ " --readonly|-r open DEVICE read-only (def: read-write)\n"
+ " recommended if DEVICE is ATA disk\n"
+ " --start|-s start unit, corresponds to START bit "
+ "in cdb,\n"
+ " default (START=1) if no other options "
+ "given\n"
+ " --stop|-S stop unit (e.g. spin down disk)\n"
+ " --verbose|-v increase verbosity\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V print version string then exit\n\n"
+ " Example: 'sg_start --stop /dev/sdb' stops unit\n"
+ " 'sg_start --eject /dev/scd0' stops unit and "
+ "ejects medium\n\n"
+ "Performs a SCSI START STOP UNIT command\n"
+ );
+}
+
+static void
+usage_old()
+{
+ pr2serr("Usage: sg_start [0] [1] [--eject] [--fl=FL] "
+ "[-i] [--imm=0|1]\n"
+ " [--load] [--loej] [--mod=PC_MOD] "
+ "[--noflush] [--pc=PC]\n"
+ " [--readonly] [--start] [--stop] [-v] [-V]\n"
+ " DEVICE\n"
+ " where:\n"
+ " 0 stop unit (e.g. spin down a disk or a "
+ "cd/dvd)\n"
+ " 1 start unit (e.g. spin up a disk or a "
+ "cd/dvd)\n"
+ " --eject stop then eject the medium\n"
+ " --fl=FL format layer number (mmc5)\n"
+ " -i return immediately (same as '--imm=1')\n"
+ " --imm=0|1 0->await completion(def), 1->return "
+ "immediately\n"
+ " --load load then start the medium\n"
+ " --loej load the medium if '-start' option is "
+ "also given\n"
+ " or stop unit and eject\n"
+ " --mod=PC_MOD power condition modifier "
+ "(def: 0) (sbc)\n"
+ " --noflush no flush prior to operation that limits "
+ "access (sbc)\n"
+ " --pc=PC power condition (in hex, default 0 -> no "
+ "power condition)\n"
+ " 1 -> active, 2 -> idle, 3 -> standby, "
+ "5 -> sleep (mmc)\n"
+ " --readonly|-r open DEVICE read-only (def: read-write)\n"
+ " recommended if DEVICE is ATA disk\n"
+ " --start start unit (same as '1'), default "
+ "action\n"
+ " --stop stop unit (same as '0')\n"
+ " -v verbose (print out SCSI commands)\n"
+ " --new|-N use new interface\n"
+ " -V print version string then exit\n\n"
+ " Example: 'sg_start --stop /dev/sdb' stops unit\n"
+ " 'sg_start --eject /dev/scd0' stops unit and "
+ "ejects medium\n\n"
+ "Performs a SCSI START STOP UNIT command\n"
+ );
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n, err;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ef:hilLm:nNOp:rsSvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'e':
+ op->do_eject = true;
+ op->do_loej = true;
+ break;
+ case 'f':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 3)) {
+ pr2serr("bad argument to '--fl='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_loej = true;
+ op->do_start = true;
+ op->do_fl = n;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'i':
+ op->do_immed = true;
+ break;
+ case 'l':
+ op->do_load = true;
+ op->do_loej = true;
+ break;
+ case 'L':
+ op->do_loej = true;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 15)) {
+ pr2serr("bad argument to '--mod='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_mod = n;
+ break;
+ case 'n':
+ op->do_noflush = true;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 15)) {
+ pr2serr("bad argument to '--pc='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_pc = n;
+ break;
+ case 'r':
+ op->do_readonly = true;
+ break;
+ case 's':
+ op->do_start = true;
+ break;
+ case 'S':
+ op->do_stop = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ err = 0;
+ for (; optind < argc; ++optind) {
+ if (1 == strlen(argv[optind])) {
+ if (0 == strcmp("0", argv[optind])) {
+ op->do_stop = true;
+ continue;
+ } else if (0 == strcmp("1", argv[optind])) {
+ op->do_start = true;
+ continue;
+ }
+ }
+ if (NULL == op->device_name)
+ op->device_name = argv[optind];
+ else {
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ ++err;
+ }
+ }
+ if (err) {
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool ambigu = false;
+ bool jmp_out;
+ bool startstop = false;
+ bool startstop_set = false;
+ int k, plen, num;
+ unsigned int u;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0;
+ --plen, ++cp) {
+ switch (*cp) {
+ case 'i':
+ if ('\0' == *(cp + 1))
+ op->do_immed = true;
+ else
+ jmp_out = true;
+ break;
+ case 'r':
+ op->do_readonly = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case '-':
+ ++cp;
+ --plen;
+ jmp_out = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+
+ if (0 == strncmp(cp, "eject", 5)) {
+ op->do_loej = true;
+ if (startstop_set && startstop)
+ ambigu = true;
+ else {
+ startstop = false;
+ startstop_set = true;
+ }
+ } else if (0 == strncmp("fl=", cp, 3)) {
+ num = sscanf(cp + 3, "%x", &u);
+ if (1 != num) {
+ pr2serr("Bad value after 'fl=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ startstop = true;
+ startstop_set = true;
+ op->do_loej = true;
+ op->do_fl = u;
+ } else if (0 == strncmp("imm=", cp, 4)) {
+ num = sscanf(cp + 4, "%x", &u);
+ if ((1 != num) || (u > 1)) {
+ pr2serr("Bad value after 'imm=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_immed = !! u;
+ } else if (0 == strncmp(cp, "load", 4)) {
+ op->do_loej = true;
+ if (startstop_set && (! startstop))
+ ambigu = true;
+ else {
+ startstop = true;
+ startstop_set = true;
+ }
+ } else if (0 == strncmp(cp, "loej", 4))
+ op->do_loej = true;
+ else if (0 == strncmp("pc=", cp, 3)) {
+ num = sscanf(cp + 3, "%x", &u);
+ if ((1 != num) || (u > 15)) {
+ pr2serr("Bad value after after 'pc=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_pc = u;
+ } else if (0 == strncmp("mod=", cp, 4)) {
+ num = sscanf(cp + 3, "%x", &u);
+ if (1 != num) {
+ pr2serr("Bad value after 'mod=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_mod = u;
+ } else if (0 == strncmp(cp, "noflush", 7)) {
+ op->do_noflush = true;
+ } else if (0 == strncmp(cp, "start", 5)) {
+ if (startstop_set && (! startstop))
+ ambigu = true;
+ else {
+ startstop = true;
+ startstop_set = true;
+ }
+ } else if (0 == strncmp(cp, "stop", 4)) {
+ if (startstop_set && startstop)
+ ambigu = true;
+ else {
+ startstop = false;
+ startstop_set = true;
+ }
+ } else if (0 == strncmp(cp, "old", 3))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp("0", cp)) {
+ if (startstop_set && startstop)
+ ambigu = true;
+ else {
+ startstop = false;
+ startstop_set = true;
+ }
+ } else if (0 == strcmp("1", cp)) {
+ if (startstop_set && (! startstop))
+ ambigu = true;
+ else {
+ startstop = true;
+ startstop_set = true;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not "
+ "expecting: %s\n", op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (ambigu) {
+ pr2serr("please, only one of 0, 1, --eject, "
+ "--load, --start or --stop\n");
+ usage_old();
+ return SG_LIB_CONTRADICT;
+ } else if (startstop_set) {
+ if (startstop)
+ op->do_start = true;
+ else
+ op->do_stop = true;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int res;
+ int sg_fd = -1;
+ int ret = 0;
+ struct opts_t opts;
+ struct opts_t * op;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->do_fl = -1; /* only when >= 0 set FL bit */
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return res;
+ if (op->do_help) {
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ return 0;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (op->do_start && op->do_stop) {
+ pr2serr("Ambiguous to give both '--start' and '--stop'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->do_load && op->do_eject) {
+ pr2serr("Ambiguous to give both '--load' and '--eject'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->do_load)
+ op->do_start = true;
+ else if ((op->do_eject) || op->do_stop)
+ op->do_start = false;
+ else if (op->opt_new && op->do_loej && (! op->do_start))
+ op->do_start = true; /* --loej alone in new interface is load */
+ else if ((! op->do_loej) && (-1 == op->do_fl) && (0 == op->do_pc))
+ op->do_start = true;
+ /* default action is to start when no other active options */
+
+ if (0 == op->device_name) {
+ pr2serr("No DEVICE argument given\n");
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->do_fl >= 0) {
+ if (! op->do_start) {
+ pr2serr("Giving '--fl=FL' with '--stop' (or '--eject') is "
+ "invalid\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->do_pc > 0) {
+ pr2serr("Giving '--fl=FL' with '--pc=PC' when PC is non-zero "
+ "is invalid\n");
+ return SG_LIB_CONTRADICT;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(op->device_name, op->do_readonly,
+ op->verbose);
+ if (sg_fd < 0) {
+ if (op->verbose)
+ pr2serr("Error trying to open %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (op->do_fl >= 0)
+ res = sg_ll_start_stop_unit(sg_fd, op->do_immed, op->do_fl, 0 /* pc */,
+ true /* fl */, true /* loej */,
+ true /*start */, true /* noisy */,
+ op->verbose);
+ else if (op->do_pc > 0)
+ res = sg_ll_start_stop_unit(sg_fd, op->do_immed, op->do_mod,
+ op->do_pc, op->do_noflush, false, false,
+ true, op->verbose);
+ else
+ res = sg_ll_start_stop_unit(sg_fd, op->do_immed, 0, false,
+ op->do_noflush, op->do_loej,
+ op->do_start, true, op->verbose);
+ ret = res;
+ if (res) {
+ if (op->verbose < 2) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("%s\n", b);
+ }
+ pr2serr("START STOP UNIT command failed\n");
+ }
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_start failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_stpg.c b/src/sg_stpg.c
new file mode 100644
index 00000000..ce21c9c1
--- /dev/null
+++ b/src/sg_stpg.c
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2004-2022 Hannes Reinecke, Christophe Varoqui, 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI command SET TARGET PORT GROUPS
+ * to the given SCSI device.
+ */
+
+static const char * version_str = "1.21 20220118";
+
+#define TGT_GRP_BUFF_LEN 1024
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+
+#define TPGS_STATE_OPTIMIZED 0x0
+#define TPGS_STATE_NONOPTIMIZED 0x1
+#define TPGS_STATE_STANDBY 0x2
+#define TPGS_STATE_UNAVAILABLE 0x3
+#define TPGS_STATE_OFFLINE 0xe /* SPC-4 rev 9 */
+#define TPGS_STATE_TRANSITIONING 0xf
+
+/* See also table 306 - Target port group descriptor format in SPC-4 rev 36e */
+#ifdef __cplusplus
+
+// C++ does not support designated initializers
+static const uint8_t state_sup_mask[] = {
+ 0x1, 0x2, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x80,
+};
+
+#else
+
+static const uint8_t state_sup_mask[] = {
+ [TPGS_STATE_OPTIMIZED] = 0x01,
+ [TPGS_STATE_NONOPTIMIZED] = 0x02,
+ [TPGS_STATE_STANDBY] = 0x04,
+ [TPGS_STATE_UNAVAILABLE] = 0x08,
+ [TPGS_STATE_OFFLINE] = 0x40,
+ [TPGS_STATE_TRANSITIONING] = 0x80,
+};
+
+#endif /* C or C++ ? */
+
+#define VPD_DEVICE_ID 0x83
+#define DEF_VPD_DEVICE_ID_LEN 252
+
+#define MAX_PORT_LIST_ARR_LEN 16
+
+struct tgtgrp {
+ int id;
+ int current;
+ int valid;
+};
+
+static struct option long_options[] = {
+ {"active", no_argument, 0, 'a'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"offline", no_argument, 0, 'l'},
+ {"optimized", no_argument, 0, 'o'},
+ {"raw", no_argument, 0, 'r'},
+ {"standby", no_argument, 0, 's'},
+ {"state", required_argument, 0, 'S'},
+ {"tp", required_argument, 0, 't'},
+ {"unavailable", no_argument, 0, 'u'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_stpg [--active] [--help] [--hex] [--offline] "
+ "[--optimized] [--raw]\n"
+ " [--standby] [--state=S,S...] [--tp=P,P...] "
+ "[--unavailable]\n"
+ " [--verbose] [--version] DEVICE\n"
+ " where:\n"
+ " --active|-a set asymm. access state to "
+ "active/non-optimized\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H print out report response in hex, then "
+ "exit\n"
+ " --offline|-l|-O set asymm. access state to offline, takes "
+ "relative\n"
+ " target port id, rather than target port "
+ "group id\n"
+ " --optimized|-o set asymm. access state to "
+ "active/optimized\n"
+ " --raw|-r output report response in binary to "
+ "stdout, then exit\n"
+ " --standby|-s set asymm. access state to standby\n"
+ " --state=S,S.. |-S S,S... list of states (values or "
+ "acronyms)\n"
+ " --tp=P,P.. |-t P,P... list of target port group "
+ "identifiers,\n"
+ " or relative target port "
+ "identifiers\n"
+ " --unavailable|-u set asymm. access state to unavailable\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI SET TARGET PORT GROUPS command\n");
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static int
+decode_target_port(uint8_t * buff, int len, int *d_id, int *d_tpg)
+{
+ int c_set, assoc, desig_type, i_len, off;
+ const uint8_t * bp;
+ const uint8_t * ip;
+
+ *d_id = -1;
+ *d_tpg = -1;
+ off = -1;
+ while (sg_vpd_dev_id_iter(buff, len, &off, -1, -1, -1) == 0) {
+ bp = buff + off;
+ i_len = bp[3];
+ if ((off + i_len + 4) > len) {
+ pr2serr(" VPD page error: designator length longer than\n "
+ "remaining response length=%d\n", (len - off));
+ return SG_LIB_CAT_MALFORMED;
+ }
+ ip = bp + 4;
+ c_set = (bp[0] & 0xf);
+ /* piv = ((bp[1] & 0x80) ? 1 : 0); */
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ switch (desig_type) {
+ case 4: /* Relative target port */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target port "
+ "association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ *d_id = sg_get_unaligned_be16(ip + 2);
+ break;
+ case 5: /* (primary) Target port group */
+ if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+ pr2serr(" << expected binary code_set, target port "
+ "association, length 4>>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ *d_tpg = sg_get_unaligned_be16(ip + 2);
+ break;
+ default:
+ break;
+ }
+ }
+ if (-1 == *d_id || -1 == *d_tpg) {
+ pr2serr("VPD page error: no target port group information\n");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ return 0;
+}
+
+static void
+decode_tpgs_state(const int st)
+{
+ switch (st) {
+ case TPGS_STATE_OPTIMIZED:
+ printf(" (active/optimized)");
+ break;
+ case TPGS_STATE_NONOPTIMIZED:
+ printf(" (active/non optimized)");
+ break;
+ case TPGS_STATE_STANDBY:
+ printf(" (standby)");
+ break;
+ case TPGS_STATE_UNAVAILABLE:
+ printf(" (unavailable)");
+ break;
+ case TPGS_STATE_OFFLINE:
+ printf(" (offline)");
+ break;
+ case TPGS_STATE_TRANSITIONING:
+ printf(" (transitioning between states)");
+ break;
+ default:
+ printf(" (unknown: 0x%x)", st);
+ break;
+ }
+}
+
+static int
+transition_tpgs_states(struct tgtgrp *tgtState, int numgrp, int portgroup,
+ int newstate)
+{
+ int i,oldstate;
+
+ for ( i = 0; i < numgrp; i++) {
+ if (tgtState[i].id == portgroup)
+ break;
+ }
+ if (i == numgrp) {
+ printf("Portgroup 0x%02x does not exist\n", portgroup);
+ return 1;
+ }
+
+ if (!( state_sup_mask[newstate] & tgtState[i].valid )) {
+ printf("Portgroup 0x%02x: Invalid state 0x%x\n",
+ portgroup, newstate);
+ return 1;
+ }
+ oldstate = tgtState[i].current;
+ tgtState[i].current = newstate;
+ if (newstate == TPGS_STATE_OPTIMIZED) {
+ /* Switch with current optimized path */
+ for ( i = 0; i < numgrp; i++) {
+ if (tgtState[i].id == portgroup)
+ continue;
+ if (tgtState[i].current == TPGS_STATE_OPTIMIZED)
+ tgtState[i].current = oldstate;
+ }
+ } else if (oldstate == TPGS_STATE_OPTIMIZED) {
+ /* Enable next path group */
+ for ( i = 0; i < numgrp; i++) {
+ if (tgtState[i].id == portgroup)
+ continue;
+ if (tgtState[i].current == TPGS_STATE_NONOPTIMIZED) {
+ tgtState[i].current = TPGS_STATE_OPTIMIZED;
+ break;
+ }
+ }
+ }
+ printf("New target port groups:\n");
+ for (i = 0; i < numgrp; i++) {
+ printf(" target port group id : 0x%x\n",
+ tgtState[i].id);
+ printf(" target port group asymmetric access state : ");
+ printf("0x%02x\n", tgtState[i].current);
+ }
+ return 0;
+}
+
+static void
+encode_tpgs_states(uint8_t *buff, struct tgtgrp *tgtState, int numgrp)
+{
+ int i;
+ uint8_t *desc;
+
+ for (i = 0, desc = buff + 4; i < numgrp; desc += 4, i++) {
+ desc[0] = tgtState[i].current & 0x0f;
+ sg_put_unaligned_be16((uint16_t)tgtState[i].id, desc + 2);
+ }
+}
+
+/* Read numbers (up to 32 bits in size) from command line (comma separated
+ * list). Assumed decimal unless prefixed by '0x', '0X' or contains trailing
+ * 'h' or 'H' (which indicate hex). Returns 0 if ok, else error code. */
+static int
+build_port_arr(const char * inp, int * port_arr, int * port_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ const char * lcp;
+ int v;
+ char * cp;
+
+ if ((NULL == inp) || (NULL == port_arr) ||
+ (NULL == port_arr_len))
+ return SG_LIB_LOGIC_ERROR;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *port_arr_len = 0;
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxX,");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ v = sg_get_num_nomult(lcp);
+ if (-1 != v) {
+ port_arr[k] = v;
+ cp = (char *)strchr(lcp, ',');
+ if (NULL == cp)
+ break;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__, (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ *port_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ return 0;
+}
+
+/* Read numbers (up to 32 bits in size) from command line (comma separated
+ * list). Assumed decimal unless prefixed by '0x', '0X' or contains trailing
+ * 'h' or 'H' (which indicate hex). Also accepts 'ao' for active optimized
+ * [0], 'an' for active/non-optimized [1], 's' for standby [2], 'u' for
+ * unavailable [3], 'o' for offline [14]. Returns 0 if ok, else error code. */
+static int
+build_state_arr(const char * inp, int * state_arr, int * state_arr_len,
+ int max_arr_len)
+{
+ bool try_num;
+ int in_len, k, v;
+ const char * lcp;
+ char * cp;
+
+ if ((NULL == inp) || (NULL == state_arr) ||
+ (NULL == state_arr_len))
+ return SG_LIB_LOGIC_ERROR;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *state_arr_len = 0;
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHnNoOsSuUxX,");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ try_num = true;
+ if (isalpha((uint8_t)*lcp)) {
+ try_num = false;
+ switch (toupper((uint8_t)*lcp)) {
+ case 'A':
+ if ('N' == toupper((uint8_t)*(lcp + 1)))
+ state_arr[k] = 1;
+ else if ('O' == toupper((uint8_t)*(lcp + 1)))
+ state_arr[k] = 0;
+ else
+ try_num = true;
+ break;
+ case 'O':
+ state_arr[k] = 14;
+ break;
+ case 'S':
+ state_arr[k] = 2;
+ break;
+ case 'U':
+ state_arr[k] = 3;
+ break;
+ default:
+ pr2serr("%s: expected 'ao', 'an', 'o', 's' or 'u' at pos "
+ "%d\n", __func__, (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (try_num) {
+ v = sg_get_num_nomult(lcp);
+ if (((v >= 0) && (v <= 3)) || (14 ==v))
+ state_arr[k] = v;
+ else if (-1 == v) {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ pr2serr("%s: expect 0,1,2,3 or 14\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ cp = (char *)strchr(lcp, ',');
+ if (NULL == cp)
+ break;
+ lcp = cp + 1;
+ }
+ *state_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool hex = false;
+ bool raw = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int k, off, res, c, report_len, tgt_port_count;
+ int sg_fd = -1;
+ int port_arr_len = 0;
+ int verbose = 0;
+ uint8_t reportTgtGrpBuff[TGT_GRP_BUFF_LEN];
+ uint8_t setTgtGrpBuff[TGT_GRP_BUFF_LEN];
+ uint8_t rsp_buff[MX_ALLOC_LEN + 2];
+ uint8_t * bp;
+ struct tgtgrp tgtGrpState[256], *tgtStatePtr;
+ int state = -1;
+ const char * state_arg = NULL;
+ const char * tp_arg = NULL;
+ int port_arr[MAX_PORT_LIST_ARR_LEN];
+ int state_arr[MAX_PORT_LIST_ARR_LEN];
+ char b[80];
+ int state_arr_len = 0;
+ int portgroup = -1;
+ int relport = -1;
+ int numgrp = 0;
+ const char * device_name = NULL;
+ int ret = 0;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ahHloOrsS:t:uvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ state = TPGS_STATE_NONOPTIMIZED;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ hex = true;
+ break;
+ case 'l':
+ case 'O':
+ state = TPGS_STATE_OFFLINE;
+ break;
+ case 'o':
+ state = TPGS_STATE_OPTIMIZED;
+ break;
+ case 'r':
+ raw = true;
+ break;
+ case 's':
+ state = TPGS_STATE_STANDBY;
+ break;
+ case 'S':
+ state_arg = optarg;
+ break;
+ case 't':
+ tp_arg = optarg;
+ break;
+ case 'u':
+ state = TPGS_STATE_UNAVAILABLE;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("Version: %s\n", version_str);
+ return 0;
+ }
+
+ if (state_arg) {
+ if ((ret = build_state_arr(state_arg, state_arr, &state_arr_len,
+ MAX_PORT_LIST_ARR_LEN))) {
+ usage();
+ return ret;
+ }
+ }
+ if (tp_arg) {
+ if ((ret = build_port_arr(tp_arg, port_arr, &port_arr_len,
+ MAX_PORT_LIST_ARR_LEN))) {
+ usage();
+ return ret;
+ }
+ }
+ if ((state >= 0) && (state_arr_len > 0)) {
+ pr2serr("either use individual state option or '--state=' but not "
+ "both\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((0 == state_arr_len) && (0 == port_arr_len) && (-1 == state))
+ state = 0; /* default to active/optimized */
+ if ((1 == state_arr_len) && (0 == port_arr_len) && (-1 == state)) {
+ state = state_arr[0];
+ state_arr_len = 0;
+ }
+ if (state_arr_len > port_arr_len) {
+ pr2serr("'state=' list longer than expected\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((port_arr_len > 0) && (0 == state_arr_len)) {
+ if (-1 == state) {
+ pr2serr("target port list given but no state indicated\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ state_arr[0] = state;
+ state_arr_len = 1;
+ state = -1;
+ }
+ if ((port_arr_len > 1) && (1 == state_arr_len)) {
+ for (k = 1; k < port_arr_len; ++k)
+ state_arr[k] = state_arr[0];
+ state_arr_len = port_arr_len;
+ }
+ if (port_arr_len != state_arr_len) {
+ pr2serr("'state=' and '--tp=' lists mismatched\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ if (0 == port_arr_len) {
+ res = sg_ll_inquiry(sg_fd, false, true /* EVPD */, VPD_DEVICE_ID,
+ rsp_buff, DEF_VPD_DEVICE_ID_LEN, true, verbose);
+ if (0 == res) {
+ report_len = sg_get_unaligned_be16(rsp_buff + 2) + 4;
+ if (VPD_DEVICE_ID != rsp_buff[1]) {
+ pr2serr("invalid VPD response; probably a STANDARD INQUIRY "
+ "response\n");
+ if (verbose) {
+ pr2serr("First 32 bytes of bad response\n");
+ hex2stderr(rsp_buff, 32, 0);
+ }
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (report_len > MX_ALLOC_LEN) {
+ pr2serr("response length too long: %d > %d\n", report_len,
+ MX_ALLOC_LEN);
+ return SG_LIB_CAT_MALFORMED;
+ } else if (report_len > DEF_VPD_DEVICE_ID_LEN) {
+ if (sg_ll_inquiry(sg_fd, false, true, VPD_DEVICE_ID, rsp_buff,
+ report_len, true, verbose))
+ return SG_LIB_CAT_OTHER;
+ }
+ decode_target_port(rsp_buff + 4, report_len - 4, &relport,
+ &portgroup);
+ printf("Device is at port Group 0x%02x, relative port 0x%02x\n",
+ portgroup, relport);
+ }
+
+ memset(reportTgtGrpBuff, 0x0, sizeof(reportTgtGrpBuff));
+ /* trunc = 0; */
+
+ res = sg_ll_report_tgt_prt_grp2(sg_fd, reportTgtGrpBuff,
+ sizeof(reportTgtGrpBuff),
+ false /* extended */, true, verbose);
+ ret = res;
+ if (0 == res) {
+ report_len = sg_get_unaligned_be32(reportTgtGrpBuff + 0) + 4;
+ if (report_len > (int)sizeof(reportTgtGrpBuff)) {
+ /* trunc = 1; */
+ pr2serr(" <<report too long for internal buffer, output "
+ "truncated\n");
+ report_len = (int)sizeof(reportTgtGrpBuff);
+ }
+ if (raw) {
+ dStrRaw(reportTgtGrpBuff, report_len);
+ goto err_out;
+ }
+ if (verbose)
+ printf("Report list length = %d\n", report_len);
+ if (hex) {
+ if (verbose)
+ printf("\nOutput response in hex:\n");
+ hex2stdout(reportTgtGrpBuff, report_len, 1);
+ goto err_out;
+ }
+ memset(tgtGrpState, 0, sizeof(struct tgtgrp) * 256);
+ tgtStatePtr = tgtGrpState;
+ printf("Current target port groups:\n");
+ for (k = 4, bp = reportTgtGrpBuff + 4, numgrp = 0; k < report_len;
+ k += off, bp += off, numgrp ++) {
+
+ printf(" target port group id : 0x%x , Pref=%d\n",
+ sg_get_unaligned_be16(bp + 2), !!(bp[0] & 0x80));
+ printf(" target port group asymmetric access state : ");
+ printf("0x%02x", bp[0] & 0x0f);
+ printf("\n");
+ tgtStatePtr->id = sg_get_unaligned_be16(bp + 2);
+ tgtStatePtr->current = bp[0] & 0x0f;
+ tgtStatePtr->valid = bp[1];
+
+ tgt_port_count = bp[7];
+
+ tgtStatePtr++;
+ off = 8 + tgt_port_count * 4;
+ }
+ } else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Report Target Port Groups: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' for more information\n");
+ }
+ if (0 != res)
+ goto err_out;
+
+ printf("Port group 0x%02x: Set asymmetric access state to", portgroup);
+ decode_tpgs_state(state);
+ printf("\n");
+
+ transition_tpgs_states(tgtGrpState, numgrp, portgroup, state);
+
+ memset(setTgtGrpBuff, 0x0, sizeof(setTgtGrpBuff));
+ /* trunc = 0; */
+
+ encode_tpgs_states(setTgtGrpBuff, tgtGrpState, numgrp);
+ report_len = numgrp * 4 + 4;
+ } else { /* port_arr_len > 0 */
+ memset(setTgtGrpBuff, 0x0, sizeof(setTgtGrpBuff));
+ for (k = 0, bp = setTgtGrpBuff + 4; k < port_arr_len; ++k, bp +=4) {
+ bp[0] = state_arr[k] & 0xf;
+ sg_put_unaligned_be16((uint16_t)port_arr[k], bp + 2);
+ }
+ report_len = port_arr_len * 4 + 4;
+ }
+
+ res = sg_ll_set_tgt_prt_grp(sg_fd, setTgtGrpBuff, report_len, true,
+ verbose);
+
+ if (0 == res)
+ goto err_out;
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Set Target Port Groups: %s\n", b);
+ if (0 == verbose)
+ pr2serr(" try '-v' for more information\n");
+ }
+
+err_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_stpg failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_stream_ctl.c b/src/sg_stream_ctl.c
new file mode 100644
index 00000000..77f78eb6
--- /dev/null
+++ b/src/sg_stream_ctl.c
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2018-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/*
+ * This program issues the SCSI STREAM CONTROL or GET STREAM STATUS command
+ * to the given SCSI device. Based on sbc4r15.pdf .
+ */
+
+static const char * version_str = "1.13 20221028";
+
+#define STREAM_CONTROL_SA 0x14
+#define GET_STREAM_STATUS_SA 0x16
+
+#define STREAM_CONTROL_OPEN 0x1
+#define STREAM_CONTROL_CLOSE 0x2
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'},
+ {"close", no_argument, 0, 'c'},
+ {"ctl", required_argument, 0, 'C'},
+ {"get", no_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"id", required_argument, 0, 'i'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"open", no_argument, 0, 'o'},
+ {"readonly", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_stream_ctl [-brief] [--close] [--ctl=CTL] [-get] [--help]\n"
+ " [--id=SID] [--maxlen=LEN] [--open] "
+ "[--readonly]\n"
+ " [--verbose] [--version] DEVICE\n");
+ pr2serr(" where:\n"
+ " --brief|-b for open, output assigned stream id to "
+ "stdout, or\n"
+ " -1 if error; for close, output 0, or "
+ "-1; for get\n"
+ " output list of stream id, 1 per line\n"
+ " --close|-c close stream given by --id=SID\n"
+ " --ctl=CTL|-C CTL CTL is stream control value, "
+ "(STR_CTL field)\n"
+ " 1 -> open; 2 -> close\n"
+ " --get|-g do GET STREAM STATUS command (default "
+ "if no other)\n"
+ " --help|-h print out usage message\n"
+ " --id=SID|-i SID for close, SID is stream_id to close; "
+ "for get,\n"
+ " list from and include this stream id\n"
+ " --maxlen=LEN|-m LEN length in bytes of buffer to "
+ "receive data-in\n"
+ " (def: 8 (for open and close); 252 "
+ "(for get,\n"
+ " but increase if needed)\n"
+ " --open|-o open a new stream, return assigned "
+ "stream id\n"
+ " --readonly|-r open DEVICE read-only (if supported)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI STREAM CONTROL or GET STREAM STATUS command. "
+ "If --open,\n--close or --ctl=CTL given (only one) then "
+ "performs STREAM CONTROL\ncommand. If --get or no other "
+ "selecting option given then performs a\nGET STREAM STATUS "
+ "command. A successful --open will output the assigned\nstream "
+ "id to stdout (and ignore --id=SID , if given).\n"
+ );
+}
+
+/* Invokes a SCSI GET STREAM STATUS command (SBC-4). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_get_stream_status(int sg_fd, uint16_t s_str_id, uint8_t * resp,
+ uint32_t alloc_len, int * residp, bool noisy,
+ int verbose)
+{
+ int k, ret, res, sense_cat;
+ uint8_t gssCdb[16] = {SG_SERVICE_ACTION_IN_16,
+ GET_STREAM_STATUS_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ static const char * const cmd_name = "Get stream status";
+
+ if (s_str_id) /* starting stream id, fetch from and including */
+ sg_put_unaligned_be16(s_str_id, gssCdb + 4);
+ sg_put_unaligned_be32(alloc_len, gssCdb + 10);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", cmd_name,
+ sg_get_command_str(gssCdb, (int)sizeof(gssCdb), false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", cmd_name);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, gssCdb, sizeof(gssCdb));
+ set_scsi_pt_data_in(ptvp, resp, alloc_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ k = ret ? (int)alloc_len : get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = k;
+ if ((verbose > 2) && ((alloc_len - k) > 0)) {
+ pr2serr("%s: parameter data returned:\n", cmd_name);
+ hex2stderr((const uint8_t *)resp, alloc_len - k,
+ ((verbose > 3) ? -1 : 1));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI STREAM CONTROL command (SBC-4). Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * N.B. The is a device modifying command that is SERVICE ACTION IN(16)
+ * command since it has data-in buffer that for open returns the
+ * ASSIGNED_STR_ID field . */
+static int
+sg_ll_stream_control(int sg_fd, uint32_t str_ctl, uint16_t str_id,
+ uint8_t * resp, uint32_t alloc_len, int * residp,
+ bool noisy, int verbose)
+{
+ int k, ret, res, sense_cat;
+ uint8_t scCdb[16] = {SG_SERVICE_ACTION_IN_16,
+ STREAM_CONTROL_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+ static const char * const cmd_name = "Stream control";
+
+ if (str_ctl)
+ scCdb[1] |= (str_ctl & 0x3) << 5;
+ if (str_id) /* Only used for close, stream id to close */
+ sg_put_unaligned_be16(str_id, scCdb + 4);
+ sg_put_unaligned_be32(alloc_len, scCdb + 10);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", cmd_name,
+ sg_get_command_str(scCdb, (int)sizeof(scCdb), false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", cmd_name);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, scCdb, sizeof(scCdb));
+ set_scsi_pt_data_in(ptvp, resp, alloc_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ k = ret ? (int)alloc_len : get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = k;
+ if ((verbose > 2) && ((alloc_len - k) > 0)) {
+ pr2serr("%s: parameter data returned:\n", cmd_name);
+ hex2stderr((const uint8_t *)resp, alloc_len - k,
+ ((verbose > 3) ? -1 : 1));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_brief = false;
+ bool do_close = false;
+ bool do_get = false;
+ bool do_open = false;
+ bool ctl_given = false;
+ bool maxlen_given = false;
+ bool read_only = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, k, res, resid;
+ int sg_fd = -1;
+ int maxlen = 0;
+ int ret = 0;
+ int verbose = 0;
+ uint16_t stream_id = 0;
+ uint16_t num_streams = 0;
+ uint32_t ctl = 0;
+ uint32_t pg_sz = sg_get_page_size();
+ uint32_t param_dl;
+ const char * device_name = NULL;
+ const char * cmd_name = NULL;
+ uint8_t * arr = NULL;
+ uint8_t * free_arr = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "bcC:ghi:m:orvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ do_brief = true;
+ break;
+ case 'c':
+ do_close = true;
+ break;
+ case 'C':
+ if ((1 != sscanf(optarg, "%4u", &ctl)) || (ctl > 3)) {
+ pr2serr("--ctl= expects a number from 0 to 3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ctl_given = true;
+ break;
+ case 'g':
+ do_get = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ k = sg_get_num(optarg);
+ if ((k < 0) || (k > (int)UINT16_MAX)) {
+ pr2serr("--id= expects a number from 0 to 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ stream_id = (uint16_t)k;
+ break;
+ case 'm':
+ k = sg_get_num(optarg);
+ if (k < 0) {
+ pr2serr("--maxlen= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ maxlen_given = true;
+ if (k > 0)
+ maxlen = k;
+ break;
+ case 'o':
+ do_open = true;
+ break;
+ case 'r':
+ read_only = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ k = (int)do_close + (int)do_get + (int)do_open + (int)ctl_given;
+ if (k > 1) {
+ pr2serr("Can only have one of: --close, --ctl==, --get, or --open\n");
+ return SG_LIB_CONTRADICT;
+ } else if (0 == k)
+ do_get = true;
+ if (do_close)
+ ctl = STREAM_CONTROL_CLOSE;
+ else if (do_open)
+ ctl = STREAM_CONTROL_OPEN;
+
+ if (maxlen_given) {
+ if (0 == maxlen)
+ maxlen = do_get ? 248 : 8;
+ } else
+ maxlen = do_get ? 248 : 8;
+
+ if (verbose) {
+ if (read_only && (! do_get))
+ pr2serr("Probably need to open %s read-write\n", device_name);
+ if (do_open && (stream_id > 0))
+ pr2serr("With --open the --id-SID option is ignored\n");
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, read_only, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (maxlen > (int)pg_sz)
+ arr = sg_memalign(maxlen, pg_sz, &free_arr, verbose > 3);
+ else
+ arr = sg_memalign(pg_sz, pg_sz, &free_arr, verbose > 3);
+ if (NULL == arr) {
+ pr2serr("Unable to allocate space for response\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto fini;
+ }
+
+ resid = 0;
+ if (do_get) { /* Get stream status */
+ cmd_name = "Get stream status";
+ ret = sg_ll_get_stream_status(sg_fd, stream_id, arr, maxlen,
+ &resid, false, verbose);
+ if (ret) {
+ if (SG_LIB_CAT_INVALID_OP == ret)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(ret, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+ goto fini;
+ }
+ if ((maxlen - resid) < 8) {
+ pr2serr("Response too short (%d bytes) assigned stream id\n",
+ k);
+ printf("-1\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ } else
+ maxlen -= resid;
+ param_dl = sg_get_unaligned_be32(arr + 0) + 8;
+ if (param_dl > (uint32_t)maxlen) {
+ pr2serr("Response truncated, need to set --maxlen=%u\n",
+ param_dl);
+ if (maxlen < (8 /* header */ + 4 /* enough of first */)) {
+ pr2serr("Response too short to continue\n");
+ goto fini;
+ }
+ }
+ num_streams = sg_get_unaligned_be16(arr + 6);
+ if (! do_brief) {
+ if (stream_id > 0)
+ printf("Starting at stream id: %u\n", stream_id);
+ printf("Number of open streams: %u\n", num_streams);
+ }
+ maxlen = ((uint32_t)maxlen < param_dl) ? maxlen : (int)param_dl;
+ for (k = 8; k < maxlen; k += 8) {
+ stream_id = sg_get_unaligned_be16(arr + k + 2);
+ if (do_brief)
+ printf("%u\n", stream_id);
+ else
+ printf("Open stream id: %u\n", stream_id);
+ }
+ } else { /* Stream control */
+ cmd_name = "Stream control";
+ ret = sg_ll_stream_control(sg_fd, ctl, stream_id, arr, maxlen,
+ &resid, false, verbose);
+ if (ret) {
+ if (SG_LIB_CAT_INVALID_OP == ret)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(ret, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+ goto fini;
+ }
+ if (do_open) {
+ k = arr[0] + 1;
+ k = (k < (maxlen - resid)) ? k : (maxlen - resid);
+ if (k < 5) {
+ pr2serr("Response too short (%d bytes) assigned stream id\n",
+ k);
+ printf("-1\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ } else {
+ stream_id = sg_get_unaligned_be16(arr + 4);
+ if (do_brief)
+ printf("%u\n", stream_id);
+ else
+ printf("Assigned stream id: %u\n", stream_id);
+ }
+ }
+ }
+
+fini:
+ if (free_arr)
+ free(free_arr);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_stream_ctl failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_sync.c b/src/sg_sync.c
new file mode 100644
index 00000000..86d817d5
--- /dev/null
+++ b/src/sg_sync.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2004-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.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_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues the SCSI command SYNCHRONIZE CACHE(10 or 16) to the
+ * given device. This command is defined for SCSI "direct access" devices
+ * (e.g. disks).
+ */
+
+static const char * version_str = "1.27 20211114";
+
+#define SYNCHRONIZE_CACHE16_CMD 0x91
+#define SYNCHRONIZE_CACHE16_CMDLEN 16
+#define SENSE_BUFF_LEN 64
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'S'},
+ {"count", required_argument, 0, 'c'},
+ {"group", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"immed", no_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"sync-nv", no_argument, 0, 's'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void usage()
+{
+ pr2serr("Usage: sg_sync [--16] [--count=COUNT] [--group=GN] [--help] "
+ "[--immed]\n"
+ " [--lba=LBA] [--sync-nv] [--timeout=SECS] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --16|-S calls SYNCHRONIZE CACHE(16) (def: is "
+ "10 byte\n"
+ " variant)\n"
+ " --count=COUNT|-c COUNT number of blocks to sync (def: 0 "
+ "which\n"
+ " implies rest of device)\n"
+ " --group=GN|-g GN set group number field to GN (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --immed|-i command returns immediately when set "
+ "else wait\n"
+ " for 'sync' to complete\n"
+ " --lba=LBA|-l LBA logical block address to start sync "
+ "operation\n"
+ " from (def: 0)\n"
+ " --sync-nv|-s synchronize to non-volatile storage "
+ "(if distinct\n"
+ " from medium). Obsolete in sbc3r35d.\n"
+ " --timeout=SECS|-t SECS command timeout in seconds, only "
+ "active\n"
+ " if '--16' given (def: 60 seconds)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs a SCSI SYNCHRONIZE CACHE(10 or 16) command\n");
+}
+
+static int
+sg_ll_sync_cache_16(int sg_fd, bool sync_nv, bool immed, int group,
+ uint64_t lba, unsigned int num_lb, int to_secs,
+ bool noisy, int verbose)
+{
+ int res, ret, sense_cat;
+ uint8_t sc_cdb[SYNCHRONIZE_CACHE16_CMDLEN] =
+ {SYNCHRONIZE_CACHE16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ if (sync_nv)
+ sc_cdb[1] |= 4; /* obsolete in sbc3r35d */
+ if (immed)
+ sc_cdb[1] |= 2;
+ sg_put_unaligned_be64(lba, sc_cdb + 2);
+ sc_cdb[14] = group & GRPNUM_MASK;
+ sg_put_unaligned_be32((uint32_t)num_lb, sc_cdb + 10);
+
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" Synchronize cache(16) cdb: %s\n",
+ sg_get_command_str(sc_cdb, SYNCHRONIZE_CACHE16_CMDLEN, false,
+ sizeof(b), b));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("synchronize cache(16): out of memory\n");
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, to_secs, verbose);
+ ret = sg_cmds_process_resp(ptvp, "synchronize cache(16)", res,
+ noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool do_16 = false;
+ bool immed = false;
+ bool sync_nv = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c;
+ int sg_fd = -1;
+ int group = 0;
+ int ret = 0;
+ int to_secs = DEF_PT_TIMEOUT;
+ int verbose = 0;
+ unsigned int num_lb = 0;
+ int64_t count = 0;
+ int64_t lba = 0;
+ const char * device_name = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:g:hil:sSt:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ count = sg_get_llnum(optarg);
+ if ((count < 0) || (count > UINT_MAX)) {
+ pr2serr("bad argument to '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ num_lb = (unsigned int)count;
+ break;
+ case 'g':
+ group = sg_get_num(optarg);
+ if ((group < 0) || (group > 63)) {
+ pr2serr("bad argument to '--group'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ immed = true;
+ break;
+ case 'l':
+ lba = sg_get_llnum(optarg);
+ if (lba < 0) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ sync_nv = true;
+ break;
+ case 'S':
+ do_16 = true;
+ break;
+ case 't':
+ to_secs = sg_get_num(optarg);
+ if (to_secs < 0) {
+ pr2serr("bad argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (do_16)
+ res = sg_ll_sync_cache_16(sg_fd, sync_nv, immed, group, lba, num_lb,
+ to_secs, true, verbose);
+ else
+ res = sg_ll_sync_cache_10(sg_fd, sync_nv, immed, group,
+ (unsigned int)lba, num_lb, true, verbose);
+ ret = res;
+ if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Synchronize cache failed: %s\n", b);
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_sync failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_test_rwbuf.c b/src/sg_test_rwbuf.c
new file mode 100644
index 00000000..5b87c3bf
--- /dev/null
+++ b/src/sg_test_rwbuf.c
@@ -0,0 +1,584 @@
+/*
+ * (c) 2000 Kurt Garloff
+ * heavily based on Douglas Gilbert's sg_rbuf program.
+ * (c) 1999-2022 Douglas Gilbert
+ *
+ * Program to test the SCSI host adapter by issuing
+ * write and read operations on a device's buffer
+ * and calculating checksums.
+ * NOTE: If you can not reserve the buffer of the device
+ * for this purpose (SG_GET_RESERVED_SIZE), you risk
+ * serious data corruption, if the device is accessed by
+ * somebody else in the meantime.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * $Id: sg_test_rwbuf.c,v 1.1 2000/03/02 13:50:03 garloff Exp $
+ *
+ * 2003/11/11 switch sg3_utils version to use SG_IO ioctl [dpg]
+ * 2004/06/08 remove SG_GET_VERSION_NUM check [dpg]
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "1.21 20220118";
+
+#define BPI (signed)(sizeof(int))
+
+#define RB_MODE_DESC 3
+#define RWB_MODE_DATA 2
+#define RB_DESC_LEN 4
+
+/* The microcode in a SCSI device is _not_ modified by doing a WRITE BUFFER
+ * with mode set to "data" (0x2) as done by this utility. Therefore this
+ * utility is safe in that respect. [Mode values 0x4, 0x5, 0x6 and 0x7 are
+ * the dangerous ones :-)]
+ */
+
+#define ME "sg_test_rwbuf: "
+
+static int base = 0x12345678;
+static int buf_capacity = 0;
+static int buf_granul = 255;
+static uint8_t *cmpbuf = NULL;
+static uint8_t *free_cmpbuf = NULL;
+
+
+/* Options */
+static int size = -1;
+static bool do_quick = false;
+static int addwrite = 0;
+static int addread = 0;
+static int verbose = 0;
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"quick", no_argument, 0, 'q'},
+ {"addrd", required_argument, 0, 'r'},
+ {"size", required_argument, 0, 's'},
+ {"times", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"addwr", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+static int
+find_out_about_buffer(int sg_fd)
+{
+ uint8_t rb_cdb[] = {READ_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t rbBuff[RB_DESC_LEN];
+ uint8_t sense_buffer[32];
+ struct sg_io_hdr io_hdr;
+ int res;
+
+ rb_cdb[1] = RB_MODE_DESC;
+ rb_cdb[8] = RB_DESC_LEN;
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = RB_DESC_LEN;
+ io_hdr.dxferp = rbBuff;
+ io_hdr.cmdp = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
+
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" read buffer [mode desc] cdb: %s\n",
+ sg_get_command_str(rb_cdb, (int)sizeof(rb_cdb), false,
+ sizeof(b), b));
+ }
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror(ME "SG_IO READ BUFFER descriptor error");
+ return -1;
+ }
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("READ BUFFER descriptor, continuing",
+ &io_hdr, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER descriptor error", &io_hdr,
+ true);
+ return res;
+ }
+
+ buf_capacity = sg_get_unaligned_be24(rbBuff + 1);
+ buf_granul = (uint8_t)rbBuff[0];
+#if 0
+ printf("READ BUFFER reports: %02x %02x %02x %02x\n",
+ rbBuff[0], rbBuff[1], rbBuff[2], rbBuff[3]);
+#endif
+ if (verbose)
+ printf("READ BUFFER reports: buffer capacity=%d, offset "
+ "boundary=%d\n", buf_capacity, buf_granul);
+ return 0;
+}
+
+static int
+mymemcmp (uint8_t *bf1, uint8_t *bf2, int len)
+{
+ int df;
+ for (df = 0; df < len; df++)
+ if (bf1[df] != bf2[df]) return df;
+ return 0;
+}
+
+/* return 0 if good, else 2222 */
+static int
+do_checksum(int *buf, int len, bool quiet)
+{
+ int sum = base;
+ int i; int rln = len;
+ for (i = 0; i < len/BPI; i++)
+ sum += buf[i];
+ while (rln%BPI) sum += ((char*)buf)[--rln];
+ if (sum != 0x12345678) {
+ if (!quiet) printf ("sg_test_rwbuf: Checksum error (sz=%i):"
+ " %08x\n", len, sum);
+ if (cmpbuf && !quiet) {
+ int diff = mymemcmp (cmpbuf, (uint8_t*)buf,
+ len);
+ printf ("Differ at pos %i/%i:\n", diff, len);
+ for (i = 0; i < 24 && i+diff < len; i++)
+ printf (" %02x", cmpbuf[i+diff]);
+ printf ("\n");
+ for (i = 0; i < 24 && i+diff < len; i++)
+ printf (" %02x",
+ ((uint8_t*)buf)[i+diff]);
+ printf ("\n");
+ }
+ return 2222;
+ }
+ else {
+ if (verbose > 1)
+ printf("Checksum value: 0x%x\n", sum);
+ return 0;
+ }
+}
+
+void do_fill_buffer (int *buf, int len)
+{
+ int sum;
+ int i; int rln = len;
+
+ srand(time(0));
+ retry:
+ if (len >= BPI)
+ base = 0x12345678 + rand(); /* don't need strong crypto */
+ else
+ base = 0x12345678 + (char)rand();
+ sum = base;
+ for (i = 0; i < len/BPI - 1; i++)
+ {
+ /* we rely on rand() giving full range of int */
+ buf[i] = rand();
+ sum += buf[i];
+ }
+ while (rln%BPI)
+ {
+ ((char*)buf)[--rln] = rand();
+ sum += ((char*)buf)[rln];
+ }
+ if (len >= BPI) buf[len/BPI - 1] = 0x12345678 - sum;
+ else ((char*)buf)[0] = 0x12345678 + ((char*)buf)[0] - sum;
+ if (do_checksum(buf, len, true)) {
+ if (len < BPI) goto retry;
+ printf ("sg_test_rwbuf: Memory corruption?\n");
+ exit (1);
+ }
+ if (cmpbuf) memcpy (cmpbuf, (char*)buf, len);
+}
+
+
+int read_buffer (int sg_fd, unsigned ssize)
+{
+ int res;
+ uint8_t rb_cdb[] = {READ_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ int bufSize = ssize + addread;
+ uint8_t * free_rbBuff = NULL;
+ uint8_t * rbBuff = (uint8_t *)sg_memalign(bufSize, 0, &free_rbBuff,
+ false);
+ uint8_t sense_buffer[32];
+ struct sg_io_hdr io_hdr;
+
+ if (NULL == rbBuff)
+ return -1;
+ rb_cdb[1] = RWB_MODE_DATA;
+ sg_put_unaligned_be24((uint32_t)bufSize, rb_cdb + 6);
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = bufSize;
+ io_hdr.dxferp = rbBuff;
+ io_hdr.cmdp = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.pack_id = 2;
+ io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" read buffer [mode data] cdb: %s\n",
+ sg_get_command_str(rb_cdb, (int)sizeof(rb_cdb), false,
+ sizeof(b), b));
+ }
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror(ME "SG_IO READ BUFFER data error");
+ free(rbBuff);
+ return -1;
+ }
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("READ BUFFER data, continuing", &io_hdr, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER data error", &io_hdr, true);
+ free(rbBuff);
+ return res;
+ }
+
+ res = do_checksum((int*)rbBuff, ssize, false);
+ if (free_rbBuff)
+ free(free_rbBuff);
+ return res;
+}
+
+int write_buffer (int sg_fd, unsigned ssize)
+{
+ uint8_t wb_cdb[] = {WRITE_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ int bufSize = ssize + addwrite;
+ uint8_t * free_wbBuff = NULL;
+ uint8_t * wbBuff = (uint8_t *)sg_memalign(bufSize, 0, &free_wbBuff,
+ false);
+ uint8_t sense_buffer[32];
+ struct sg_io_hdr io_hdr;
+ int res;
+
+ if (NULL == wbBuff)
+ return -1;
+ memset(wbBuff, 0, bufSize);
+ do_fill_buffer ((int*)wbBuff, ssize);
+ wb_cdb[1] = RWB_MODE_DATA;
+ sg_put_unaligned_be24((uint32_t)bufSize, wb_cdb + 6);
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(wb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ io_hdr.dxfer_len = bufSize;
+ io_hdr.dxferp = wbBuff;
+ io_hdr.cmdp = wb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.pack_id = 1;
+ io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" write buffer [mode data] cdb: %s\n",
+ sg_get_command_str(wb_cdb, (int)sizeof(wb_cdb), false,
+ sizeof(b), b));
+ }
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror(ME "SG_IO WRITE BUFFER data error");
+ free(wbBuff);
+ return -1;
+ }
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("WRITE BUFFER data, continuing", &io_hdr, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("WRITE BUFFER data error", &io_hdr, true);
+ free(wbBuff);
+ return res;
+ }
+ if (free_wbBuff)
+ free(free_wbBuff);
+ return res;
+}
+
+void usage ()
+{
+ printf ("Usage: sg_test_rwbuf [--addrd=AR] [--addwr=AW] [--help] "
+ "[--quick]\n");
+ printf (" --size=SZ [--times=NUM] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " or\n"
+ " sg_test_rwbuf DEVICE SZ [AW] [AR]\n");
+ printf (" where:\n"
+ " --addrd=AR|-r extra bytes to fetch during READ "
+ "BUFFER\n"
+ " --addwr=AW|-w extra bytes to send to WRITE BUFFER\n"
+ " --help|-l output this usage message then exit\n"
+ " --quick|-q output read buffer size then exit\n"
+ " --size=SZ|-s size of buffer (in bytes) to write "
+ "then read back\n"
+ " --times=NUM|-t number of times to run test "
+ "(default 1)\n"
+ " --verbose|-v increase verbosity of output\n"
+ " --version|-V output version then exit\n");
+ printf ("\nWARNING: If you access the device at the same time, e.g. "
+ "because it's a\n");
+ printf (" mounted hard disk, the device's buffer may be used by the "
+ "device itself\n");
+ printf (" for other data at the same time, and overwriting it may or "
+ "may not\n");
+ printf (" cause data corruption!\n");
+ printf ("(c) Douglas Gilbert, Kurt Garloff, 2000-2007, GNU GPL\n");
+}
+
+
+int main (int argc, char * argv[])
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, res;
+ const char * device_name = NULL;
+ int times = 1;
+ int ret = 0;
+ int k = 0;
+ int err;
+
+ while (1) {
+ int option_index = 0;
+ int c;
+
+ c = getopt_long(argc, argv, "hqr:s:t:w:vV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage();
+ return 0;
+ case 'q':
+ do_quick = true;
+ break;
+ case 'r':
+ addread = sg_get_num(optarg);
+ if (-1 == addread) {
+ pr2serr("bad argument to '--addrd'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 's':
+ size = sg_get_num(optarg);
+ if (-1 == size) {
+ pr2serr("bad argument to '--size'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 't':
+ times = sg_get_num(optarg);
+ if (-1 == times) {
+ pr2serr("bad argument to '--times'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ verbose++;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'w':
+ addwrite = sg_get_num(optarg);
+ if (-1 == addwrite) {
+ pr2serr("bad argument to '--addwr'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ default:
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ }
+ if (optind < argc) {
+ if (-1 == size) {
+ size = sg_get_num(argv[optind]);
+ if (-1 == size) {
+ pr2serr("bad <sz>\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (++optind < argc) {
+ addwrite = sg_get_num(argv[optind]);
+ if (-1 == addwrite) {
+ pr2serr("bad [addwr]\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (++optind < argc) {
+ addread = sg_get_num(argv[optind]);
+ if (-1 == addread) {
+ pr2serr("bad [addrd]\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument" ": %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and "
+ "continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("no device name given\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((size <= 0) && (! do_quick)) {
+ pr2serr("must give '--size' or '--quick' options or <sz> "
+ "argument\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = open(device_name, O_RDWR | O_NONBLOCK);
+ if (sg_fd < 0) {
+ err = errno;
+ perror("sg_test_rwbuf: open error");
+ return sg_convert_errno(err);
+ }
+ ret = find_out_about_buffer(sg_fd);
+ if (ret)
+ goto err_out;
+ if (do_quick) {
+ printf ("READ BUFFER read descriptor reports a buffer "
+ "of %d bytes [%d KiB]\n", buf_capacity,
+ buf_capacity / 1024);
+ goto err_out;
+ }
+ if (size > buf_capacity) {
+ pr2serr (ME "sz=%i > buf_capacity=%i\n", size, buf_capacity);
+ ret = SG_LIB_CAT_OTHER;
+ goto err_out;
+ }
+
+ cmpbuf = (uint8_t *)sg_memalign(size, 0, &free_cmpbuf, false);
+ for (k = 0; k < times; ++k) {
+ ret = write_buffer (sg_fd, size);
+ if (ret) {
+ goto err_out;
+ }
+ ret = read_buffer (sg_fd, size);
+ if (ret) {
+ if (2222 == ret)
+ ret = SG_LIB_CAT_MALFORMED;
+ goto err_out;
+ }
+ }
+
+err_out:
+ if (free_cmpbuf)
+ free(free_cmpbuf);
+ res = close(sg_fd);
+ if (res < 0) {
+ perror(ME "close error");
+ if (0 == ret)
+ ret = sg_convert_errno(errno);
+ }
+ if ((0 == ret) && (! do_quick))
+ printf ("Success\n");
+ else if (times > 1)
+ printf ("Failed after %d successful cycles\n", k);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_timestamp.c b/src/sg_timestamp.c
new file mode 100644
index 00000000..fdb68fdc
--- /dev/null
+++ b/src/sg_timestamp.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2015-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues a SCSI REPORT TIMESTAMP and SET TIMESTAMP commands
+ * to the given SCSI device. Based on spc5r07.pdf .
+ */
+
+static const char * version_str = "1.14 20210830";
+
+#define REP_TIMESTAMP_CMDLEN 12
+#define SET_TIMESTAMP_CMDLEN 12
+#define REP_TIMESTAMP_SA 0xf
+#define SET_TIMESTAMP_SA 0xf
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+uint8_t d_buff[256];
+
+/* example Report timestamp parameter data */
+/* uint8_t test[12] = {0, 0xa, 2, 0, 0x1, 0x51, 0x5b, 0xe2, 0xc1, 0x30,
+ * 0, 0}; */
+
+
+static struct option long_options[] = {
+ {"elapsed", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"milliseconds", required_argument, 0, 'm'},
+ {"no_timestamp", no_argument, 0, 'N'},
+ {"no-timestamp", no_argument, 0, 'N'},
+ {"origin", no_argument, 0, 'o'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"seconds", required_argument, 0, 's'},
+ {"srep", no_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+/* Indexed by 'timestamp origin' field value */
+static const char * ts_origin_arr[] = {
+ "initialized to zero at power on or by hard reset",
+ "reserved [0x1]",
+ "initialized by SET TIMESTAMP command",
+ "initialized by other method",
+ "reserved [0x4]",
+ "reserved [0x5]",
+ "reserved [0x6]",
+ "reserved [0x7]",
+};
+
+
+static void
+usage(int num)
+{
+ if (num > 1)
+ goto page2;
+
+ pr2serr("Usage: "
+ "sg_timestamp [--elapsed] [--help] [--hex] [--milliseconds=MS]\n"
+ " [--no-timestamp] [--origin] [--raw] "
+ "[--readonly]\n"
+ " [--seconds=SECS] [--srep] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ );
+ pr2serr(" where:\n"
+ " --elapsed|-e show time as '<n> days hh:mm:ss.xxx' "
+ "where\n"
+ " '.xxx' is the remainder milliseconds. "
+ "Don't show\n"
+ " '<n> days' if <n> is 0 (unless '-e' "
+ "given twice)\n"
+ " --help|-h print out usage message, use twice for "
+ "examples\n"
+ " --hex|-H output response in ASCII hexadecimal\n"
+ " --milliseconds=MS|-m MS set timestamp to MS "
+ "milliseconds since\n"
+ " 1970-01-01 00:00:00 UTC\n"
+ " --no-timestamp|-N suppress output of timestamp\n"
+ " --origin|-o show Report timestamp origin "
+ "(def: don't)\n"
+ " used twice outputs value of field\n"
+ " 0: power up or hard reset; 2: SET "
+ "TIMESTAMP\n"
+ " --raw|-r output Report timestamp response to "
+ "stdout in\n"
+ " binary\n"
+ " --readonly|-R open DEVICE read only (def: "
+ "read/write)\n"
+ " --seconds=SECS|-s SECS set timestamp to SECS "
+ "seconds since\n"
+ " 1970-01-01 00:00:00 UTC\n"
+ " --srep|-S output Report timestamp in seconds "
+ "(def:\n"
+ " milliseconds)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ );
+ pr2serr("Performs a SCSI REPORT TIMESTAMP or SET TIMESTAMP command. "
+ "The timestamp\nis SET if either the --milliseconds=MS or "
+ "--seconds=SECS option is given,\notherwise the existing "
+ "timestamp is reported in milliseconds. The\nDEVICE stores "
+ "the timestamp as the number of milliseconds since power up\n"
+ "(or reset) or since 1970-01-01 00:00:00 UTC which also "
+ "happens to\nbe the time 'epoch'of Unix machines.\n\n"
+ "Use '-hh' (the '-h' option twice) for examples.\n"
+#if 0
+ "The 'date +%%s' command in "
+ "Unix returns the number of\nseconds since the epoch. To "
+ "convert a reported timestamp (in seconds since\nthe epoch) "
+ "to a more readable form use "
+ "'date --date=@<secs_since_epoch>' .\n"
+#endif
+ );
+ return;
+page2:
+ pr2serr("sg_timestamp examples:\n"
+ "It is possible that the target device containing a SCSI "
+ "Logical Unit (LU)\nhas a battery (or supercapacitor) to "
+ "keep its RTC (real time clock)\nticking during a power "
+ "outage. More likely it doesn't and its RTC is\ncleared to "
+ "zero after a power cycle or hard reset.\n\n"
+ "Either way REPORT TIMESTAMP returns a 48 bit counter value "
+ "whose unit is\na millisecond. A heuristic to determine if a "
+ "date or elapsed time is\nbeing returned is to choose a date "
+ "like 1 January 2000 which is 30 years\nafter the Unix epoch "
+ "(946,684,800,000 milliseconds) and values less than\nthat are "
+ "elapsed times and greater are timestamps. Observing the "
+ "TIMESTAMP\nORIGIN field of REPORT TIMESTAMP is a better "
+ "method:\n\n"
+ );
+ pr2serr(" $ sg_timestamp -o -N /dev/sg1\n"
+ "Device clock initialized to zero at power on or by hard "
+ "reset\n"
+ " $ sg_timestamp -oo -N /dev/sg1\n"
+ "0\n\n"
+ " $ sg_timestamp /dev/sg1\n"
+ "3984499\n"
+ " $ sg_timestamp --elapsed /dev/sg1\n"
+ "01:06:28.802\n\n"
+ "The last output indicates an elapsed time of 1 hour, 6 minutes "
+ "and 28.802\nseconds. Next set the clock to the current time:\n\n"
+ " $ sg_timestamp --seconds=`date +%%s` /dev/sg1\n\n"
+ " $ sg_timestamp -o -N /dev/sg1\n"
+ "Device clock initialized by SET TIMESTAMP command\n\n"
+ "Now show that as an elapsed time:\n\n"
+ " $ sg_timestamp -e /dev/sg1\n"
+ "17652 days 20:53:22.545\n\n"
+ "That is over 48 years worth of days. Lets try again as a "
+ "data-time\nstamp in UTC:\n\n"
+ " $ date -u -R --date=@`sg_timestamp -S /dev/sg1`\n"
+ "Tue, 01 May 2018 20:56:38 +0000\n"
+ );
+}
+
+/* Invokes a SCSI REPORT TIMESTAMP command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_rep_timestamp(int sg_fd, void * resp, int mx_resp_len, int * residp,
+ bool noisy, int verbose)
+{
+ int k, ret, res, sense_cat;
+ uint8_t rt_cdb[REP_TIMESTAMP_CMDLEN] =
+ {SG_MAINTENANCE_IN, REP_TIMESTAMP_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rt_cdb + 6);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" Report timestamp cdb: %s\n",
+ sg_get_command_str(rt_cdb, REP_TIMESTAMP_CMDLEN, false,
+ sizeof(b), b));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, rt_cdb, sizeof(rt_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, "report timestamp", res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ k = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = k;
+ if ((verbose > 2) && ((mx_resp_len - k) > 0)) {
+ pr2serr("Parameter data returned:\n");
+ hex2stderr((const uint8_t *)resp, mx_resp_len - k,
+ ((verbose > 3) ? -1 : 1));
+ }
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+/* Invokes the SET TIMESTAMP command. Return of 0 -> success, various
+ * SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_set_timestamp(int sg_fd, void * paramp, int param_len, bool noisy,
+ int verbose)
+{
+ int ret, res, sense_cat;
+ uint8_t st_cdb[SET_TIMESTAMP_CMDLEN] =
+ {SG_MAINTENANCE_OUT, SET_TIMESTAMP_SA, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ sg_put_unaligned_be32(param_len, st_cdb + 6);
+ if (verbose) {
+ char b[128];
+
+ pr2serr(" Set timestamp cdb: %s\n",
+ sg_get_command_str(st_cdb, SET_TIMESTAMP_CMDLEN, false,
+ sizeof(b), b));
+ if ((verbose > 1) && paramp && param_len) {
+ pr2serr(" set timestamp parameter list:\n");
+ hex2stderr((const uint8_t *)paramp, param_len, -1);
+ }
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", __func__);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, st_cdb, sizeof(st_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, "set timestamp", res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_srep = false;
+ bool do_raw = false;
+ bool no_timestamp = false;
+ bool readonly = false;
+ bool secs_given = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c;
+ int sg_fd = 1;
+ int elapsed = 0;
+ int do_origin = 0;
+ int do_help = 0;
+ int do_hex = 0;
+ int do_set = 0;
+ int ret = 0;
+ int verbose = 0;
+ uint64_t secs = 0;
+ uint64_t msecs = 0;
+ int64_t ll;
+ const char * device_name = NULL;
+ const char * cmd_name;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ehHm:NorRs:SvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'e':
+ ++elapsed;
+ break;
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'm':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--milliseconds=MS'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ msecs = (uint64_t)ll;
+ ++do_set;
+ break;
+ case 'N':
+ no_timestamp = true;
+ break;
+ case 'o':
+ ++do_origin;
+ break;
+ case 'r':
+ do_raw = true;
+ break;
+ case 'R':
+ readonly = true;
+ break;
+ case 's':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--seconds=SECS'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ secs = (uint64_t)ll;
+ ++do_set;
+ secs_given = true;
+ break;
+ case 'S':
+ do_srep = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (do_help) {
+ usage(do_help);
+ return 0;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (do_set > 1) {
+ pr2serr("either --milliseconds=MS or --seconds=SECS may be given, "
+ "not both\n");
+ usage(1);
+ return SG_LIB_CONTRADICT;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ memset(d_buff, 0, 12);
+ if (do_set) {
+ cmd_name = "Set timestamp";
+ sg_put_unaligned_be48(secs_given ? (secs * 1000) : msecs, d_buff + 4);
+ res = sg_ll_set_timestamp(sg_fd, d_buff, 12, true, verbose);
+ } else {
+ cmd_name = "Report timestamp";
+ res = sg_ll_rep_timestamp(sg_fd, d_buff, 12, NULL, true, verbose);
+ if (0 == res) {
+ if (do_raw)
+ dStrRaw(d_buff, 12);
+ else if (do_hex)
+ hex2stderr(d_buff, 12, 1);
+ else {
+ int len = sg_get_unaligned_be16(d_buff + 0);
+
+ if (len < 8)
+ pr2serr("timestamp parameter data length too short, "
+ "expect >= 10, got %d\n", len + 2);
+ else {
+ if (do_origin) {
+ if (1 == do_origin)
+ printf("Device clock %s\n",
+ ts_origin_arr[0x7 & d_buff[2]]);
+ else if (2 == do_origin)
+ printf("%d\n", 0x7 & d_buff[2]);
+ else
+ printf("TIMESTAMP_ORIGIN=%d\n", 0x7 & d_buff[2]);
+ }
+ if (! no_timestamp) {
+ msecs = sg_get_unaligned_be48(d_buff + 4);
+ if (elapsed) {
+ int days = (int)(msecs / 1000 / 60 / 60 / 24);
+ int hours = (int)(msecs / 1000 / 60 / 60 % 24);
+ int mins = (int)(msecs / 1000 / 60 % 60);
+ int secs_in_min =(int)( msecs / 1000 % 60);
+ int rem_msecs = (int)(msecs % 1000);
+
+ if ((elapsed > 1) || (days > 0))
+ printf("%d day%s ", days,
+ ((1 == days) ? "" : "s"));
+ printf("%02d:%02d:%02d.%03d\n", hours, mins,
+ secs_in_min, rem_msecs);
+ } else
+ printf("%" PRIu64 "\n", do_srep ?
+ (msecs / 1000) : msecs);
+ }
+ }
+ }
+ }
+ }
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", cmd_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", cmd_name, b);
+ }
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_timestamp failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_turs.c b/src/sg_turs.c
new file mode 100644
index 00000000..2d473c48
--- /dev/null
+++ b/src/sg_turs.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2000-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This program sends a user specified number of TEST UNIT READY ("tur")
+ * commands to the given sg device. Since TUR is a simple command involing
+ * no data transfer (and no REQUEST SENSE command iff the unit is ready)
+ * then this can be used for timing per SCSI command overheads.
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+#include <time.h>
+#elif defined(HAVE_GETTIMEOFDAY)
+#include <time.h>
+#include <sys/time.h>
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "3.51 20220425";
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"delay", required_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"low", no_argument, 0, 'l'},
+ {"new", no_argument, 0, 'N'},
+ {"number", required_argument, 0, 'n'},
+ {"num", required_argument, 0, 'n'}, /* added in v3.32 (sg3_utils
+ * v1.43) for sg_requests compatibility */
+ {"old", no_argument, 0, 'O'},
+ {"progress", no_argument, 0, 'p'},
+ {"time", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool delay_given;
+ bool do_low;
+ bool do_progress;
+ bool do_time;
+ bool opts_new;
+ bool verbose_given;
+ bool version_given;
+ int delay;
+ int do_help;
+ int do_number;
+ int verbose;
+ const char * device_name;
+};
+
+struct loop_res_t {
+ bool reported;
+ int num_errs;
+ int ret;
+};
+
+
+static void
+usage()
+{
+ printf("Usage: sg_turs [--delay=MS] [--help] [--low] [--number=NUM] "
+ "[--num=NUM]\n"
+ " [--progress] [--time] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --delay=MS|-d MS delay MS miiliseconds before sending "
+ "each tur\n"
+ " --help|-h print usage message then exit\n"
+ " --low|-l use low level (sg_pt) interface for "
+ "speed\n"
+ " --number=NUM|-n NUM number of test_unit_ready commands "
+ "(def: 1)\n"
+ " --num=NUM|-n NUM same action as '--number=NUM'\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --progress|-p outputs progress indication (percentage) "
+ "if available\n"
+ " waits 30 seconds before TUR unless "
+ "--delay=MS given\n"
+ " --time|-t outputs total duration and commands per "
+ "second\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n\n"
+ "Performs a SCSI TEST UNIT READY command (or many of them).\n"
+ "This SCSI command is often known by its abbreviation: TUR .\n");
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_turs [-d=MS] [-l] [-n=NUM] [-p] [-t] [-v] [-V] "
+ "DEVICE\n"
+ " where:\n"
+ " -d=MS same as --delay=MS in new interface\n"
+ " -l use low level interface (sg_pt) for speed\n"
+ " -n=NUM number of test_unit_ready commands "
+ "(def: 1)\n"
+ " -p outputs progress indication (percentage) "
+ "if available\n"
+ " -t outputs total duration and commands per "
+ "second\n"
+ " -v increase verbosity\n"
+ " -N|--new use new interface\n"
+ " -V print version string then exit\n\n"
+ "Performs a SCSI TEST UNIT READY command (or many of them).\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opts_new)
+ usage();
+ else
+ usage_old();
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "d:hln:NOptvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--delay='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->delay = n;
+ op->delay_given = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'l':
+ op->do_low = true;
+ break;
+ case 'n':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--number='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_number = n;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opts_new = false;
+ return 0;
+ case 'p':
+ op->do_progress = true;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'l':
+ op->do_low = true;
+ return 0;
+ case 'N':
+ op->opts_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'p':
+ op->do_progress = true;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case '?':
+ ++op->do_help;
+ return 0;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("d=", cp, 2)) {
+ op->delay = sg_get_num(cp + 2);
+ if (op->delay < 0) {
+ printf("Couldn't decode number after 'd=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->delay_given = true;
+ } else if (0 == strncmp("n=", cp, 2)) {
+ op->do_number = sg_get_num(cp + 2);
+ if (op->do_number <= 0) {
+ printf("Couldn't decode number after 'n=' option\n");
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_old();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opts_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opts_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opts_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (0 == op->opts_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+#ifdef SG_LIB_MINGW
+
+#include <windows.h>
+
+static void
+wait_millisecs(int millisecs)
+{
+ /* MinGW requires pthreads library for nanosleep, use Sleep() instead */
+ Sleep(millisecs);
+}
+
+#else
+
+static void
+wait_millisecs(int millisecs)
+{
+ struct timespec wait_period, rem;
+
+ wait_period.tv_sec = millisecs / 1000;
+ wait_period.tv_nsec = (millisecs % 1000) * 1000000;
+ while ((nanosleep(&wait_period, &rem) < 0) && (EINTR == errno))
+ wait_period = rem;
+}
+
+#endif
+
+/* Returns true if prints estimate of duration to ready */
+bool
+check_for_lu_becoming(struct sg_pt_base * ptvp)
+{
+ int s_len = get_scsi_pt_sense_len(ptvp);
+ uint64_t info;
+ uint8_t * sense_b = get_scsi_pt_sense_buf(ptvp);
+ struct sg_scsi_sense_hdr ssh;
+
+ /* Check for "LU is in process of becoming ready" with a non-zero INFO
+ * field that isn't too big. As per 20-061r2 it means the following: */
+ if (sg_scsi_normalize_sense(sense_b, s_len, &ssh) && (ssh.asc == 0x4) &&
+ (ssh.ascq == 0x1) && sg_get_sense_info_fld(sense_b, s_len, &info) &&
+ (info > 0x0) && (info < 0x1000000)) {
+ printf("device not ready, estimated to be ready in %" PRIu64
+ " milliseconds\n", info);
+ return true;
+ }
+ return false;
+}
+
+/* Returns number of TURs performed */
+static int
+loop_turs(struct sg_pt_base * ptvp, struct loop_res_t * resp,
+ struct opts_t * op)
+{
+ int k, res;
+ int packet_id = 0;
+ int vb = op->verbose;
+ char b[80];
+ uint8_t sense_b[32] SG_C_CPP_ZERO_INIT;
+
+ if (op->do_low) {
+ int rs, n, sense_cat;
+ uint8_t cdb[6];
+
+ for (k = 0; k < op->do_number; ++k) {
+ if (op->delay > 0)
+ wait_millisecs(op->delay);
+ /* Might get Unit Attention on first invocation */
+ memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */
+ set_scsi_pt_cdb(ptvp, cdb, sizeof(cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_packet_id(ptvp, ++packet_id);
+ rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, vb);
+ n = sg_cmds_process_resp(ptvp, "Test unit ready", rs, (0 == k),
+ vb, &sense_cat);
+ if (-1 == n) {
+ if (get_scsi_pt_transport_err(ptvp))
+ resp->ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ resp->ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ return k;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++resp->num_errs;
+ if ((1 == op->do_number) || (op->delay > 0)) {
+ if (! check_for_lu_becoming(ptvp))
+ printf("device not ready\n");
+ resp->ret = sense_cat;
+ resp->reported = true;
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++resp->num_errs;
+ if (vb) {
+ pr2serr("Ignoring Unit attention (sense key)\n");
+ resp->reported = true;
+ }
+ break;
+ case SG_LIB_CAT_STANDBY:
+ ++resp->num_errs;
+ if (vb) {
+ pr2serr("Ignoring standby device (sense key)\n");
+ resp->reported = true;
+ }
+ break;
+ case SG_LIB_CAT_UNAVAILABLE:
+ ++resp->num_errs;
+ if (vb) {
+ pr2serr("Ignoring unavailable device (sense key)\n");
+ resp->reported = true;
+ }
+ break;
+ default:
+ ++resp->num_errs;
+ if (1 == op->do_number) {
+ resp->ret = sense_cat;
+ sg_get_category_sense_str(sense_cat, sizeof(b), b, vb);
+ printf("%s\n", b);
+ resp->reported = true;
+ return k;
+ }
+ break;
+ }
+ }
+ partial_clear_scsi_pt_obj(ptvp);
+ }
+ return k;
+ } else {
+ for (k = 0; k < op->do_number; ++k) {
+ if (op->delay > 0)
+ wait_millisecs(op->delay);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ /* Might get Unit Attention on first invocation */
+ res = sg_ll_test_unit_ready_pt(ptvp, k, (0 == k), vb);
+ if (res) {
+ ++resp->num_errs;
+ resp->ret = res;
+ if ((1 == op->do_number) || (op->delay > 0)) {
+ if (SG_LIB_CAT_NOT_READY == res) {
+ if (! check_for_lu_becoming(ptvp))
+ printf("device not ready\n");
+ continue;
+ } else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ printf("%s\n", b);
+ }
+ resp->reported = true;
+ break;
+ }
+ }
+ }
+ return k;
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool start_tm_valid = false;
+ int k, res, progress, pr, rem, num_done;
+ int err = 0;
+ int ret = 0;
+ int sg_fd = -1;
+ int64_t elapsed_usecs = 0;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ struct timespec start_tm, end_tm;
+#elif defined(HAVE_GETTIMEOFDAY)
+ struct timeval start_tm, end_tm;
+#endif
+ struct loop_res_t loop_res;
+ struct loop_res_t * resp = &loop_res;
+ struct sg_pt_base * ptvp = NULL;
+ struct opts_t opts;
+ struct opts_t * op = &opts;
+
+
+ memset(op, 0, sizeof(opts));
+ memset(resp, 0, sizeof(loop_res));
+ op->do_number = 1;
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return res;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+ if (op->do_progress && (! op->delay_given))
+ op->delay = 30 * 1000; /* progress has 30 second default delay */
+
+ if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ pr2serr("%s: error opening file: %s: %s\n", __func__,
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose);
+ if ((NULL == ptvp) || ((err = get_scsi_pt_os_err(ptvp)))) {
+ pr2serr("%s: unable to construct pt object\n", __func__);
+ ret = sg_convert_errno(err ? err : ENOMEM);
+ goto fini;
+ }
+ if (op->do_progress) {
+ for (k = 0; k < op->do_number; ++k) {
+ if (op->delay > 0) {
+ if (op->delay_given)
+ wait_millisecs(op->delay);
+ else if (k > 0)
+ wait_millisecs(op->delay);
+ }
+ progress = -1;
+ res = sg_ll_test_unit_ready_progress_pt(ptvp, k, &progress,
+ (1 == op->do_number), op->verbose);
+ if (progress < 0) {
+ ret = res;
+ break;
+ } else {
+ pr = (progress * 100) / 65536;
+ rem = ((progress * 100) % 65536) / 656;
+ printf("Progress indication: %d.%02d%% done\n", pr, rem);
+ }
+ }
+ if (op->do_number > 1)
+ printf("Completed %d Test Unit Ready commands\n",
+ ((k < op->do_number) ? k + 1 : k));
+ } else { /* --progress not given */
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (op->do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_nsec = 0;
+ if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
+ start_tm_valid = true;
+ else
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if (op->do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+#else
+ start_tm_valid = false;
+#endif
+
+ num_done = loop_turs(ptvp, resp, op);
+
+ if (op->do_time && start_tm_valid) {
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (start_tm.tv_sec || start_tm.tv_nsec) {
+
+ res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
+ if (res < 0) {
+ err = errno;
+ perror("clock_gettime");
+ if (EINVAL == err)
+ pr2serr("clock_gettime(CLOCK_MONOTONIC) not "
+ "supported\n");
+ }
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ /* Note: (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
+ elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if (start_tm.tv_sec || start_tm.tv_usec) {
+ gettimeofday(&end_tm, NULL);
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
+ }
+#endif
+ if (elapsed_usecs > 0) {
+ int64_t nom = num_done;
+
+ printf("time to perform commands was %u.%06u secs",
+ (unsigned)(elapsed_usecs / 1000000),
+ (unsigned)(elapsed_usecs % 1000000));
+ nom *= 1000000; /* scale for integer division */
+ printf("; %d operations/sec\n", (int)(nom / elapsed_usecs));
+ } else
+ printf("Recorded 0 or less elapsed microseconds ??\n");
+ }
+ if (((op->do_number > 1) || (resp->num_errs > 0)) &&
+ (! resp->reported))
+ printf("Completed %d Test Unit Ready commands with %d errors\n",
+ op->do_number, resp->num_errs);
+ if (1 == op->do_number)
+ ret = resp->ret;
+ }
+fini:
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ if (sg_fd >= 0)
+ sg_cmds_close_device(sg_fd);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_unmap.c b/src/sg_unmap.c
new file mode 100644
index 00000000..2bfb12d6
--- /dev/null
+++ b/src/sg_unmap.c
@@ -0,0 +1,794 @@
+/*
+ * Copyright (c) 2009-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This utility invokes the UNMAP SCSI command to unmap (trim) one or more
+ * logical blocks. Note that DATA MAY BE LOST.
+ */
+
+static const char * version_str = "1.19 20220813";
+
+
+#define DEF_TIMEOUT_SECS 60
+#define MAX_NUM_ADDR 128
+#define RCAP10_RESP_LEN 8
+#define RCAP16_RESP_LEN 32
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+
+static struct option long_options[] = {
+ {"all", required_argument, 0, 'A'},
+ {"anchor", no_argument, 0, 'a'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"dry_run", no_argument, 0, 'd'},
+ {"force", no_argument, 0, 'f'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'I'},
+ {"lba", required_argument, 0, 'l'},
+ {"num", required_argument, 0, 'n'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_unmap [--all=ST,RN[,LA]] [--anchor] [--dry-run] [--force]\n"
+ " [--grpnum=GN] [--help] [--in=FILE] "
+ "[--lba=LBA,LBA...]\n"
+ " [--num=NUM,NUM...] [--timeout=TO] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --all=ST,RN[,LA]|-A ST,RN[,LA] start unmaps at LBA ST, "
+ "RN blocks\n"
+ " per unmap until the end of disk, or "
+ "until\n"
+ " and including LBA LA (last)\n"
+ " --anchor|-a set anchor field in cdb\n"
+ " --dry-run|-d prepare but skip UNMAP call(s)\n"
+ " --force|-f don't ask for confirmation before "
+ "zapping media\n"
+ " --grpnum=GN|-g GN GN is group number field (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --in=FILE|-I FILE read LBA, NUM pairs from FILE (if "
+ "FILE is '-'\n"
+ " then stdin is read)\n"
+ " --lba=LBA,LBA...|-l LBA,LBA... LBA is the logical block "
+ "address\n"
+ " to start NUM unmaps\n"
+ " --num=NUM,NUM...|-n NUM,NUM... NUM is number of logical "
+ "blocks to\n"
+ " unmap starting at "
+ "corresponding LBA\n"
+ " --timeout=TO|-t TO command timeout (unit: seconds) "
+ "(def: 60)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Perform a SCSI UNMAP command. LBA, NUM and the values in FILE "
+ "are assumed\nto be decimal. Use '0x' prefix or 'h' suffix for "
+ "hex values.\n"
+ "Example to unmap LBA 0x12345:\n"
+ " sg_unmap --lba=0x12345 --num=1 /dev/sdb\n"
+ "Example to unmap starting at LBA 0x12345, 256 blocks per command:"
+ "\n sg_unmap --all=0x12345,256 /dev/sg2\n"
+ "until the end if /dev/sg2 (assumed to be a storage device)\n\n"
+ );
+ pr2serr("WARNING: This utility will destroy data on DEVICE in the given "
+ "range(s)\nthat will be unmapped. Unmap is also known as 'trim' "
+ "and is irreversible.\n");
+}
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. */
+static int
+build_lba_arr(const char * inp, uint64_t * lba_arr, int * lba_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ int64_t ll;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == lba_arr) ||
+ (NULL == lba_arr_len))
+ return 1;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *lba_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ pr2serr("'--lba' cannot be read from stdin\n");
+ return 1;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ pr2serr("build_lba_arr: error at pos %d\n", k + 1);
+ return 1;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ lba_arr[k] = (uint64_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("build_lba_arr: error at pos %d\n",
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+ *lba_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("build_lba_arr: array length exceeded\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Read numbers (up to 32 bits in size) from command line (comma (or
+ * (single) space) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. */
+static int
+build_num_arr(const char * inp, uint32_t * num_arr, int * num_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ const char * lcp;
+ int64_t ll;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == num_arr) ||
+ (NULL == num_arr_len))
+ return 1;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *num_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ pr2serr("'--len' cannot be read from stdin\n");
+ return 1;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ pr2serr("build_num_arr: error at pos %d\n", k + 1);
+ return 1;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ if (ll > UINT32_MAX) {
+ pr2serr("build_num_arr: number exceeds 32 bits at pos "
+ "%d\n", (int)(lcp - inp + 1));
+ return 1;
+ }
+ num_arr[k] = (uint32_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("build_num_arr: error at pos %d\n",
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+ *num_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("build_num_arr: array length exceeded\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* Read numbers from filename (or stdin) line by line (comma (or
+ * (single) space) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. */
+static int
+build_joint_arr(const char * file_name, uint64_t * lba_arr, uint32_t * num_arr,
+ int * arr_len, int max_arr_len)
+{
+ bool have_stdin;
+ int off = 0;
+ int in_len, k, j, m, ind, bit0;
+ int64_t ll;
+ char line[1024];
+ char * lcp;
+ FILE * fp = NULL;
+
+ have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0]));
+ if (have_stdin)
+ fp = stdin;
+ else {
+ fp = fopen(file_name, "r");
+ if (NULL == fp) {
+ pr2serr("%s: unable to open %s\n", __func__, file_name);
+ return 1;
+ }
+ }
+
+ for (j = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ // could improve with carry_over logic if sizeof(line) too small
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ }
+ }
+ if (in_len < 1)
+ continue;
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__, j + 1,
+ m + k + 1);
+ goto bad_exit;
+ }
+ for (k = 0; k < 1024; ++k) {
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ ind = ((off + k) >> 1);
+ bit0 = 0x1 & (off + k);
+ if (ind >= max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ goto bad_exit;
+ }
+ if (bit0) {
+ if (ll > UINT32_MAX) {
+ pr2serr("%s: number exceeds 32 bits in line %d, at "
+ "pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ goto bad_exit;
+ }
+ num_arr[ind] = (uint32_t)ll;
+ } else
+ lba_arr[ind] = (uint64_t)ll;
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error on line %d, at pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ goto bad_exit;
+ }
+ }
+ off += (k + 1);
+ }
+ if (0x1 & off) {
+ pr2serr("%s: expect LBA,NUM pairs but decoded odd number\n from "
+ "%s\n", __func__, have_stdin ? "stdin" : file_name);
+ goto bad_exit;
+ }
+ *arr_len = off >> 1;
+ if (fp && (! have_stdin))
+ fclose(fp);
+ return 0;
+
+bad_exit:
+ if (fp && (! have_stdin))
+ fclose(fp);
+ return 1;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool anchor = false;
+ bool do_force = false;
+ bool dry_run = false;
+ bool err_printed = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, num, k, j;
+ int sg_fd = -1;
+ int grpnum = 0;
+ int addr_arr_len = 0;
+ int num_arr_len = 0;
+ int param_len = 4;
+ int ret = 0;
+ int timeout = DEF_TIMEOUT_SECS;
+ int vb = 0;
+ uint32_t all_rn = 0; /* Repetition Number, 0 for inactive */
+ uint64_t all_start = 0;
+ uint64_t all_last = 0;
+ int64_t ll;
+ const char * lba_op = NULL;
+ const char * num_op = NULL;
+ const char * in_op = NULL;
+ const char * device_name = NULL;
+ char * first_comma = NULL;
+ char * second_comma = NULL;
+ struct sg_simple_inquiry_resp inq_resp;
+ uint64_t addr_arr[MAX_NUM_ADDR];
+ uint32_t num_arr[MAX_NUM_ADDR];
+ uint8_t param_arr[8 + (MAX_NUM_ADDR * 16)];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aA:dfg:hI:Hl:n:t:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ anchor = true;
+ break;
+ case 'A':
+ first_comma = strchr(optarg, ',');
+ if (NULL == first_comma) {
+ pr2serr("--all=ST,RN[,LA] expects at least one comma in "
+ "argument, found none\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ll = sg_get_llnum(optarg);
+ if (ll < 0) {
+ pr2serr("unable to decode --all=ST,.... (starting LBA)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ all_start = (uint64_t)ll;
+ ll = sg_get_llnum(first_comma + 1);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("unable to decode --all=ST,RN.... (repeat number)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ all_rn = (uint32_t)ll;
+ if (0 == ll)
+ pr2serr("warning: --all=ST,RN... being ignored because RN "
+ "is 0\n");
+ second_comma = strchr(first_comma + 1, ',');
+ if (second_comma) {
+ ll = sg_get_llnum(second_comma + 1);
+ if (ll < 0) {
+ pr2serr("unable to decode --all=ST,NR,LA (last LBA)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ all_last = (uint64_t)ll;
+ }
+ break;
+ case 'd':
+ dry_run = true;
+ break;
+ case 'f':
+ do_force = true;
+ break;
+ case 'g':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && (res >= 0) && (res <= 63))
+ grpnum = res;
+ else {
+ pr2serr("value for '--grpnum=' must be 0 to 63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'I':
+ in_op = optarg;
+ break;
+ case 'l':
+ lba_op = optarg;
+ break;
+ case 'n':
+ num_op = optarg;
+ break;
+ case 't':
+ timeout = sg_get_num(optarg);
+ if (timeout < 0) {
+ pr2serr("bad argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (0 == timeout)
+ timeout = DEF_TIMEOUT_SECS;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++vb;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ vb = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ vb = 2;
+ } else
+ pr2serr("keep verbose=%d\n", vb);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (all_rn > 0) {
+ if (lba_op || num_op || in_op) {
+ pr2serr("Can't have --all= together with --lba=, --num= or "
+ "--in=\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ /* here if --all= looks okay so far */
+ } else if (in_op && (lba_op || num_op)) {
+ pr2serr("expect '--in=' by itself, or both '--lba=' and "
+ "'--num='\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ } else if (in_op || (lba_op && num_op))
+ ;
+ else {
+ if (lba_op)
+ pr2serr("since '--lba=' is given, also need '--num='\n\n");
+ else
+ pr2serr("expect either both '--lba=' and '--num=', or "
+ "'--in=', or '--all='\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+
+ if (all_rn > 0) {
+ if ((all_last > 0) && (all_start > all_last)) {
+ pr2serr("in --all=ST,RN,LA start address (ST) exceeds last "
+ "address (LA)\n");
+ return SG_LIB_CONTRADICT;
+ }
+ } else {
+ memset(addr_arr, 0, sizeof(addr_arr));
+ memset(num_arr, 0, sizeof(num_arr));
+ addr_arr_len = 0;
+ if (lba_op && num_op) {
+ if (0 != build_lba_arr(lba_op, addr_arr, &addr_arr_len,
+ MAX_NUM_ADDR)) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 != build_num_arr(num_op, num_arr, &num_arr_len,
+ MAX_NUM_ADDR)) {
+ pr2serr("bad argument to '--num'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((addr_arr_len != num_arr_len) || (num_arr_len <= 0)) {
+ pr2serr("need same number of arguments to '--lba=' "
+ "and '--num=' options\n");
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ if (in_op) {
+ if (0 != build_joint_arr(in_op, addr_arr, num_arr, &addr_arr_len,
+ MAX_NUM_ADDR)) {
+ pr2serr("bad argument to '--in'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (addr_arr_len <= 0) {
+ pr2serr("no addresses found in '--in=' argument, file: %s\n",
+ in_op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ param_len = 8 + (16 * addr_arr_len);
+ memset(param_arr, 0, param_len);
+ k = 8;
+ for (j = 0; j < addr_arr_len; ++j) {
+ sg_put_unaligned_be64(addr_arr[j], param_arr + k);
+ k += 8;
+ sg_put_unaligned_be32(num_arr[j], param_arr + k);
+ k += 4 + 4;
+ }
+ k = 0;
+ num = param_len - 2;
+ sg_put_unaligned_be16((uint16_t)num, param_arr + k);
+ k += 2;
+ num = param_len - 8;
+ sg_put_unaligned_be16((uint16_t)num, param_arr + k);
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ ret = sg_convert_errno(-sg_fd);
+ pr2serr("open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ goto err_out;
+ }
+ ret = sg_simple_inquiry(sg_fd, &inq_resp, true, vb);
+
+ if (all_rn > 0) {
+ bool last_retry;
+ bool to_end_of_device = false;
+ uint64_t ull;
+ uint32_t bump;
+
+ if (0 == all_last) { /* READ CAPACITY(10 or 16) to find last */
+ uint8_t resp_buff[RCAP16_RESP_LEN];
+
+ res = sg_ll_readcap_16(sg_fd, false /* pmi */, 0 /* llba */,
+ resp_buff, RCAP16_RESP_LEN, true, vb);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Read capacity(16) unit attention, try again\n");
+ res = sg_ll_readcap_16(sg_fd, false, 0, resp_buff,
+ RCAP16_RESP_LEN, true, vb);
+ }
+ if (0 == res) {
+ if (vb > 3) {
+ pr2serr("Read capacity(16) response:\n");
+ hex2stderr(resp_buff, RCAP16_RESP_LEN, 1);
+ }
+ all_last = sg_get_unaligned_be64(resp_buff + 0);
+ } else if ((SG_LIB_CAT_INVALID_OP == res) ||
+ (SG_LIB_CAT_ILLEGAL_REQ == res)) {
+ if (vb)
+ pr2serr("Read capacity(16) not supported, try Read "
+ "capacity(10)\n");
+ res = sg_ll_readcap_10(sg_fd, false /* pmi */, 0 /* lba */,
+ resp_buff, RCAP10_RESP_LEN, true,
+ vb);
+ if (0 == res) {
+ if (vb > 3) {
+ pr2serr("Read capacity(10) response:\n");
+ hex2stderr(resp_buff, RCAP10_RESP_LEN, 1);
+ }
+ all_last = (uint64_t)sg_get_unaligned_be32(resp_buff + 0);
+ } else {
+ if (res < 0)
+ res = sg_convert_errno(-res);
+ pr2serr("Read capacity(10) failed\n");
+ ret = res;
+ goto err_out;
+ }
+ } else {
+ if (res < 0)
+ res = sg_convert_errno(-res);
+ pr2serr("Read capacity(16) failed\n");
+ ret = res;
+ goto err_out;
+ }
+ if (all_start > all_last) {
+ pr2serr("after READ CAPACITY the last block (0x%" PRIx64
+ ") less than start address (0x%" PRIx64 ")\n",
+ all_start, all_last);
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ to_end_of_device = true;
+ }
+ if (! do_force) {
+ char b[120];
+
+ printf("%s is: %.8s %.16s %.4s\n", device_name,
+ inq_resp.vendor, inq_resp.product, inq_resp.revision);
+ sg_sleep_secs(3);
+ if (to_end_of_device)
+ snprintf(b, sizeof(b), "%s from LBA 0x%" PRIx64 " to end "
+ "(0x%" PRIx64 ")", device_name, all_start, all_last);
+ else
+ snprintf(b, sizeof(b), "%s from LBA 0x%" PRIx64 " to 0x%"
+ PRIx64, device_name, all_start, all_last);
+ sg_warn_and_wait("UNMAP (a.k.a. trim)", b, false);
+ }
+ if (dry_run) {
+ pr2serr("Doing dry-run, would have unmapped from LBA 0x%" PRIx64
+ " to 0x%" PRIx64 "\n %u blocks per UNMAP command\n",
+ all_start, all_last, all_rn);
+ goto err_out;
+ }
+ last_retry = false;
+ param_len = 8 + (16 * 1);
+ for (ull = all_start, j = 0; ull <= all_last; ull += bump, ++j) {
+ if ((all_last - ull) < all_rn)
+ bump = (uint32_t)(all_last + 1 - ull);
+ else
+ bump = all_rn;
+retry:
+ memset(param_arr, 0, param_len);
+ k = 8;
+ sg_put_unaligned_be64(ull, param_arr + k);
+ k += 8;
+ sg_put_unaligned_be32(bump, param_arr + k);
+ k = 0;
+ num = param_len - 2;
+ sg_put_unaligned_be16((uint16_t)num, param_arr + k);
+ k += 2;
+ num = param_len - 8;
+ sg_put_unaligned_be16((uint16_t)num, param_arr + k);
+ ret = sg_ll_unmap_v2(sg_fd, anchor, grpnum, timeout, param_arr,
+ param_len, true, (vb > 2 ? vb - 2 : 0));
+ if (last_retry)
+ break;
+ if (ret) {
+ if ((SG_LIB_LBA_OUT_OF_RANGE == ret) &&
+ ((ull + bump) > all_last)) {
+ pr2serr("Typical end of disk out-of-range, decrement "
+ "count and retry\n");
+ if (bump > 1) {
+ --bump;
+ last_retry = true;
+ goto retry;
+ } /* if bump==1 can't do last, so we are finished */
+ }
+ break;
+ }
+ } /* end of for loop doing unmaps */
+ if (vb)
+ pr2serr("Completed %d UNMAP commands\n", j);
+ } else { /* --all= not given */
+ if (dry_run) {
+ pr2serr("Doing dry-run so here is 'LBA, number_of_blocks' list "
+ "of candidates\n");
+ k = 8;
+ for (j = 0; j < addr_arr_len; ++j) {
+ printf(" 0x%" PRIx64 ", 0x%u\n",
+ sg_get_unaligned_be64(param_arr + k),
+ sg_get_unaligned_be32(param_arr + k + 8));
+ k += (8 + 4 + 4);
+ }
+ goto err_out;
+ }
+ if (! do_force) {
+ printf("%s is: %.8s %.16s %.4s\n", device_name,
+ inq_resp.vendor, inq_resp.product, inq_resp.revision);
+ sg_sleep_secs(3);
+ printf("\nAn UNMAP (a.k.a. trim) will commence in 15 seconds\n");
+ printf(" Some data will be LOST\n");
+ printf(" Press control-C to abort\n");
+ sg_sleep_secs(5);
+ printf("\nAn UNMAP will commence in 10 seconds\n");
+ printf(" Some data will be LOST\n");
+ printf(" Press control-C to abort\n");
+ sg_sleep_secs(5);
+ printf("\nAn UNMAP (a.k.a. trim) will commence in 5 seconds\n");
+ printf(" Some data will be LOST\n");
+ printf(" Press control-C to abort\n");
+ sg_sleep_secs(7);
+ }
+ res = sg_ll_unmap_v2(sg_fd, anchor, grpnum, timeout, param_arr,
+ param_len, true, vb);
+ ret = res;
+ err_printed = true;
+ switch (ret) {
+ case SG_LIB_CAT_NOT_READY:
+ pr2serr("UNMAP failed, device not ready\n");
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ pr2serr("UNMAP, unit attention\n");
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ pr2serr("UNMAP, aborted command\n");
+ break;
+ case SG_LIB_CAT_INVALID_OP:
+ pr2serr("UNMAP not supported\n");
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ pr2serr("bad field in UNMAP cdb\n");
+ break;
+ default:
+ err_printed = false;
+ break;
+ }
+ }
+
+err_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if ((0 == vb) && (! err_printed)) {
+ if (! sg_if_can2stderr("sg_unmap failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_verify.c b/src/sg_verify.c
new file mode 100644
index 00000000..ddd71c8e
--- /dev/null
+++ b/src/sg_verify.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2004-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.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"
+#include "sg_pr2serr.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ * This program issues the SCSI VERIFY(10) or VERIFY(16) command to the given
+ * SCSI block device.
+ *
+ * N.B. This utility should, but doesn't, check the logical block size with
+ * the SCSI READ CAPACITY command. It is up to the user to make sure that
+ * the count of blocks requested and the number of bytes transferred (when
+ * BYTCHK>0) are "in sync". That caclculation is somewhat complicated by
+ * the possibility of protection data (DIF).
+ */
+
+static const char * version_str = "1.27 20201029"; /* sbc4r17 */
+
+#define ME "sg_verify: "
+
+#define EBUFF_SZ 256
+
+
+static struct option long_options[] = {
+ {"0", no_argument, 0, '0'},
+ {"16", no_argument, 0, 'S'},
+ {"bpc", required_argument, 0, 'b'},
+ {"bytchk", required_argument, 0, 'B'}, /* 4 backward compatibility */
+ {"count", required_argument, 0, 'c'},
+ {"dpo", no_argument, 0, 'd'},
+ {"ebytchk", required_argument, 0, 'E'}, /* extended bytchk (2 bits) */
+ {"group", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"nbo", required_argument, 0, 'n'}, /* misspelling, legacy */
+ {"ndo", required_argument, 0, 'n'},
+ {"quiet", no_argument, 0, 'q'},
+ {"readonly", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"vrprotect", required_argument, 0, 'P'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_verify [--0] [--16] [--bpc=BPC] [--count=COUNT] "
+ "[--dpo]\n"
+ " [--ebytchk=BCH] [--ff] [--group=GN] [--help] "
+ "[--in=IF]\n"
+ " [--lba=LBA] [--ndo=NDO] [--quiet] "
+ "[--readonly]\n"
+ " [--verbose] [--version] [--vrprotect=VRP] "
+ "DEVICE\n"
+ " where:\n"
+ " --0|-0 fill buffer with zeros (don't read "
+ "stdin)\n"
+ " --16|-S use VERIFY(16) (def: use "
+ "VERIFY(10) )\n"
+ " --bpc=BPC|-b BPC max blocks per verify command "
+ "(def: 128)\n"
+ " --count=COUNT|-c COUNT count of blocks to verify "
+ "(def: 1).\n"
+ " --dpo|-d disable page out (cache retention "
+ "priority)\n"
+ " --ebytchk=BCH|-E BCH sets BYTCHK value, either 1, 2 "
+ "or 3 (def: 0).\n"
+ " BCH overrides BYTCHK=1 set by "
+ "'--ndo='. If\n"
+ " BCH is 3 then NDO must be the LBA "
+ "size\n"
+ " (plus protection size if DIF "
+ "active)\n"
+ " --ff|-f fill buffer with 0xff bytes (don't read "
+ "stdin)\n"
+ " --group=GN|-g GN set group number field to GN (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --in=IF|-i IF input from file called IF (def: "
+ "stdin)\n"
+ " only active if --ebytchk=BCH given\n"
+ " --lba=LBA|-l LBA logical block address to start "
+ "verify (def: 0)\n"
+ " --ndo=NDO|-n NDO NDO is number of bytes placed in "
+ "data-out buffer.\n"
+ " These are fetched from IF (or "
+ "stdin) and used\n"
+ " to verify the device data against. "
+ "Forces\n"
+ " --bpc=COUNT. Sets BYTCHK (byte check) "
+ "to 1\n"
+ " --quiet|-q suppress miscompare report to stderr, "
+ "still\n"
+ " causes an exit status of 14\n"
+ " --readonly|-r open DEVICE read-only (def: open it "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --vrprotect=VRP|-P VRP set vrprotect field to VRP "
+ "(def: 0)\n\n"
+ "Performs one or more SCSI VERIFY(10) or SCSI VERIFY(16) "
+ "commands. sbc3r34\nmade the BYTCHK field two bits wide "
+ "(it was a single bit).\n");
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool bpc_given = false;
+ bool dpo = false;
+ bool ff_given = false;
+ bool got_stdin = false;
+ bool quiet = false;
+ bool readonly = false;
+ bool verbose_given = false;
+ bool verify16 = false;
+ bool version_given = false;
+ bool zero_given = false;
+ int res, c, num, nread, infd;
+ int sg_fd = -1;
+ int bpc = 128;
+ int group = 0;
+ int bytchk = 0;
+ int ndo = 0; /* number of bytes in data-out buffer */
+ int verbose = 0;
+ int ret = 0;
+ int vrprotect = 0;
+ unsigned int info = 0;
+ int64_t count = 1;
+ int64_t ll;
+ int64_t orig_count;
+ uint64_t info64 = 0;
+ uint64_t lba = 0;
+ uint64_t orig_lba;
+ uint8_t * ref_data = NULL;
+ uint8_t * free_ref_data = NULL;
+ const char * device_name = NULL;
+ const char * file_name = NULL;
+ const char * vc;
+ char ebuff[EBUFF_SZ];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "0b:B:c:dE:fg:hi:l:n:P:qrSvV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '0':
+ zero_given = true;
+ break;
+ case 'b':
+ bpc = sg_get_num(optarg);
+ if (bpc < 1) {
+ pr2serr("bad argument to '--bpc'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpc_given = true;
+ break;
+ case 'c':
+ count = sg_get_llnum(optarg);
+ if (count < 0) {
+ pr2serr("bad argument to '--count'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'd':
+ dpo = true;
+ break;
+ case 'E':
+ bytchk = sg_get_num(optarg);
+ if ((bytchk < 0) || (bytchk > 3)) {
+ pr2serr("bad argument to '--ebytchk'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'f':
+ ff_given = true;
+ break;
+ case 'g':
+ group = sg_get_num(optarg);
+ if ((group < 0) || (group > 63)) {
+ pr2serr("bad argument to '--group'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ file_name = optarg;
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba = (uint64_t)ll;
+ break;
+ case 'n': /* number of bytes in data-out buffer */
+ case 'B': /* undocumented, old --bytchk=NDO option */
+ ndo = sg_get_num(optarg);
+ if (ndo < 1) {
+ pr2serr("bad argument to '--ndo'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'P':
+ vrprotect = sg_get_num(optarg);
+ if (-1 == vrprotect) {
+ pr2serr("bad argument to '--vrprotect'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((vrprotect < 0) || (vrprotect > 7)) {
+ pr2serr("'--vrprotect' requires a value from 0 to 7 "
+ "(inclusive)\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'r':
+ readonly = true;
+ break;
+ case 'S':
+ verify16 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (ndo > 0) {
+ if (0 == bytchk)
+ bytchk = 1;
+ if (bpc_given && (bpc != count))
+ pr2serr("'bpc' argument ignored, using --bpc=%" PRIu64 "\n",
+ count);
+ if (count > 0x7fffffffLL) {
+ pr2serr("count exceed 31 bits, way too large\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+#if 0
+ if ((3 == bytchk) && (1 != count)) {
+ pr2serr("count must be 1 when bytchk=3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ // bpc = (int)count;
+#endif
+ } else if (bytchk > 0) {
+ pr2serr("when the 'ebytchk=BCH' option is given, then '--ndo=NDO' "
+ "must also be given\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ((zero_given || ff_given) && file_name) {
+ pr2serr("giving --0 or --ff is not compatible with --if=%s\n",
+ file_name);
+ return SG_LIB_CONTRADICT;
+ }
+
+ if ((bpc > 0xffff) && (! verify16)) {
+ pr2serr("'%s' exceeds 65535, so use VERIFY(16)\n",
+ (ndo > 0) ? "count" : "bpc");
+ verify16 = true;
+ }
+ if (((lba + count - 1) > 0xffffffffLLU) && (! verify16)) {
+ pr2serr("'lba' exceed 32 bits, so use VERIFY(16)\n");
+ verify16 = true;
+ }
+ if ((group > 0) && (! verify16))
+ pr2serr("group number ignored with VERIFY(10) command, use the --16 "
+ "option\n");
+
+ orig_count = count;
+ orig_lba = lba;
+
+ if (ndo > 0) {
+ ref_data = (uint8_t *)sg_memalign(ndo, 0, &free_ref_data, verbose > 4);
+ if (NULL == ref_data) {
+ pr2serr("failed to allocate %d byte buffer\n", ndo);
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ if (ff_given)
+ memset(ref_data, 0xff, ndo);
+ if (zero_given || ff_given)
+ goto skip;
+ if ((NULL == file_name) || (0 == strcmp(file_name, "-"))) {
+ got_stdin = true;
+ infd = STDIN_FILENO;
+ if (sg_set_binary_mode(STDIN_FILENO) < 0)
+ perror("sg_set_binary_mode");
+ } else {
+ if ((infd = open(file_name, O_RDONLY)) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", file_name);
+ perror(ebuff);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ }
+ if (verbose && got_stdin)
+ pr2serr("about to wait on STDIN\n");
+ for (nread = 0; nread < ndo; nread += res) {
+ res = read(infd, ref_data + nread, ndo - nread);
+ if (res <= 0) {
+ ret = sg_convert_errno(errno);
+ pr2serr("reading from %s failed at file offset=%d\n",
+ (got_stdin ? "stdin" : file_name), nread);
+ goto err_out;
+ }
+ }
+ if (! got_stdin)
+ close(infd);
+ }
+skip:
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ vc = verify16 ? "VERIFY(16)" : "VERIFY(10)";
+ for (; count > 0; count -= bpc, lba += bpc) {
+ num = (count > bpc) ? bpc : count;
+ if (verify16)
+ res = sg_ll_verify16(sg_fd, vrprotect, dpo, bytchk,
+ lba, num, group, ref_data,
+ ndo, &info64, !quiet , verbose);
+ else
+ res = sg_ll_verify10(sg_fd, vrprotect, dpo, bytchk,
+ (unsigned int)lba, num, ref_data,
+ ndo, &info, !quiet, verbose);
+ if (0 != res) {
+ char b[80];
+
+ ret = res;
+ switch (res) {
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ pr2serr("bad field in %s cdb, near lba=0x%" PRIx64 "\n", vc,
+ lba);
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ pr2serr("%s medium or hardware error near lba=0x%" PRIx64 "\n",
+ vc, lba);
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD_WITH_INFO:
+ if (verify16)
+ pr2serr("%s medium or hardware error, reported lba=0x%"
+ PRIx64 "\n", vc, info64);
+ else
+ pr2serr("%s medium or hardware error, reported lba=0x%x\n",
+ vc, info);
+ break;
+ case SG_LIB_CAT_MISCOMPARE:
+ if ((0 == quiet) || verbose)
+ pr2serr("%s MISCOMPARE: started at LBA 0x%" PRIx64 "\n",
+ vc, lba);
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s: %s\n", vc, b);
+ pr2serr(" failed near lba=%" PRIu64 " [0x%" PRIx64 "]\n",
+ lba, lba);
+ break;
+ }
+ break;
+ }
+ }
+
+ if (verbose && (0 == ret) && (orig_count > 1))
+ pr2serr("Verified %" PRId64 " [0x%" PRIx64 "] blocks from lba %" PRIu64
+ " [0x%" PRIx64 "]\n without error\n", orig_count,
+ (uint64_t)orig_count, orig_lba, orig_lba);
+
+ err_out:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (free_ref_data)
+ free(free_ref_data);
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_verify failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_vpd.c b/src/sg_vpd.c
new file mode 100644
index 00000000..c8900fb7
--- /dev/null
+++ b/src/sg_vpd.c
@@ -0,0 +1,2770 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#include "sg_vpd_common.h" /* shared with sg_inq */
+
+/* This utility program was originally written for the Linux OS SCSI subsystem.
+
+ This program fetches Vital Product Data (VPD) pages from the given
+ device and outputs it as directed. VPD pages are obtained via a
+ SCSI INQUIRY command. Most of the data in this program is obtained
+ from the SCSI SPC-4 document at https://www.t10.org .
+
+*/
+
+static const char * version_str = "1.83 20220915"; /* spc6r06 + sbc5r03 */
+
+#define MY_NAME "sg_vpd"
+
+/* Device identification VPD page associations */
+#define VPD_ASSOC_LU 0
+#define VPD_ASSOC_TPORT 1
+#define VPD_ASSOC_TDEVICE 2
+
+/* values for selection one or more associations (2**vpd_assoc),
+ except _AS_IS */
+#define VPD_DI_SEL_LU 1
+#define VPD_DI_SEL_TPORT 2
+#define VPD_DI_SEL_TARGET 4
+#define VPD_DI_SEL_AS_IS 32
+
+#define DEF_ALLOC_LEN 252
+#define MIN_MAXLEN 16
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+#define VPD_ATA_INFO_LEN 572
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+uint8_t * rsp_buff;
+
+static int svpd_decode_t10(int sg_fd, struct opts_t * op, sgj_opaque_p jop,
+ int subvalue, int off, const char * prefix);
+static int svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue,
+ int off);
+
+static int filter_dev_ids(const char * print_if_found, int num_leading,
+ uint8_t * buff, int len, int m_assoc,
+ struct opts_t * op, sgj_opaque_p jop);
+
+static const int rsp_buff_sz = MX_ALLOC_LEN + 2;
+
+static uint8_t * free_rsp_buff;
+
+static struct option long_options[] = {
+ {"all", no_argument, 0, 'a'},
+ {"enumerate", no_argument, 0, 'e'},
+ {"examine", no_argument, 0, 'E'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"ident", no_argument, 0, 'i'},
+ {"inhex", required_argument, 0, 'I'},
+ {"json", optional_argument, 0, 'j'},
+ {"long", no_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"page", required_argument, 0, 'p'},
+ {"quiet", no_argument, 0, 'q'},
+ {"raw", no_argument, 0, 'r'},
+ {"sinq_inraw", required_argument, 0, 'Q'},
+ {"sinq-inraw", required_argument, 0, 'Q'},
+ {"vendor", required_argument, 0, 'M'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+/* arranged in alphabetical order by acronym */
+static struct svpd_values_name_t standard_vpd_pg[] = {
+ {VPD_AUTOMATION_DEV_SN, 0, 1, "adsn", "Automation device serial "
+ "number (SSC)"},
+ {VPD_ATA_INFO, 0, -1, "ai", "ATA information (SAT)"},
+ {VPD_ASCII_OP_DEF, 0, -1, "aod",
+ "ASCII implemented operating definition (obsolete)"},
+ {VPD_BLOCK_DEV_CHARS, 0, 0, "bdc", "Block device characteristics "
+ "(SBC)"},
+ {VPD_BLOCK_DEV_C_EXTENS, 0, 0, "bdce", "Block device characteristics "
+ "extension (SBC)"},
+ {VPD_BLOCK_LIMITS, 0, 0, "bl", "Block limits (SBC)"},
+ {VPD_BLOCK_LIMITS_EXT, 0, 0, "ble", "Block limits extension (SBC)"},
+ {VPD_CFA_PROFILE_INFO, 0, 0, "cfa", "CFA profile information"},
+ {VPD_CON_POS_RANGE, 0, 0, "cpr", "Concurrent positioning ranges"},
+ {VPD_DEVICE_CONSTITUENTS, 0, -1, "dc", "Device constituents"},
+ {VPD_DEVICE_ID, 0, -1, "di", "Device identification"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_AS_IS, -1, "di_asis", "Like 'di' "
+ "but designators ordered as found"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_LU, -1, "di_lu", "Device identification, "
+ "lu only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TPORT, -1, "di_port", "Device "
+ "identification, target port only"},
+ {VPD_DEVICE_ID, VPD_DI_SEL_TARGET, -1, "di_target", "Device "
+ "identification, target device only"},
+ {VPD_DTDE_ADDRESS, 0, 1, "dtde",
+ "Data transfer device element address (SSC)"},
+ {VPD_EXT_INQ, 0, -1, "ei", "Extended inquiry data"},
+ {VPD_FORMAT_PRESETS, 0, 0, "fp", "Format presets"},
+ {VPD_IMP_OP_DEF, 0, -1, "iod",
+ "Implemented operating definition (obsolete)"},
+ {VPD_LB_PROTECTION, 0, 0, "lbpro", "Logical block protection (SSC)"},
+ {VPD_LB_PROVISIONING, 0, 0, "lbpv", "Logical block provisioning (SBC)"},
+ {VPD_MAN_ASS_SN, 0, 1, "mas", "Manufacturer assigned serial number (SSC)"},
+ {VPD_MAN_ASS_SN, 0, 0x12, "masa",
+ "Manufacturer assigned serial number (ADC)"},
+ {VPD_MAN_NET_ADDR, 0, -1, "mna", "Management network addresses"},
+ {VPD_MODE_PG_POLICY, 0, -1, "mpp", "Mode page policy"},
+ {VPD_OSD_INFO, 0, 0x11, "oi", "OSD information"},
+ {VPD_POWER_CONDITION, 0, -1, "pc", "Power condition"},/* "po" in sg_inq */
+ {VPD_POWER_CONSUMPTION, 0, -1, "psm", "Power consumption"},
+ {VPD_PROTO_LU, 0, -1, "pslu", "Protocol-specific logical unit "
+ "information"},
+ {VPD_PROTO_PORT, 0, -1, "pspo", "Protocol-specific port information"},
+ {VPD_REFERRALS, 0, 0, "ref", "Referrals (SBC)"},
+ {VPD_SA_DEV_CAP, 0, 1, "sad",
+ "Sequential access device capabilities (SSC)"},
+ {VPD_SUP_BLOCK_LENS, 0, 0, "sbl", "Supported block lengths and "
+ "protection types (SBC)"},
+ {VPD_SCSI_FEATURE_SETS, 0, -1, "sfs", "SCSI feature sets"},
+ {VPD_SOFTW_INF_ID, 0, -1, "sii", "Software interface identification"},
+ {VPD_NOPE_WANT_STD_INQ, 0, -1, "sinq", "Standard inquiry data format"},
+ {VPD_UNIT_SERIAL_NUM, 0, -1, "sn", "Unit serial number"},
+ {VPD_SCSI_PORTS, 0, -1, "sp", "SCSI ports"},
+ {VPD_SECURITY_TOKEN, 0, 0x11, "st", "Security token (OSD)"},
+ {VPD_SUPPORTED_VPDS, 0, -1, "sv", "Supported VPD pages"},
+ {VPD_TA_SUPPORTED, 0, 1, "tas", "TapeAlert supported flags (SSC)"},
+ {VPD_3PARTY_COPY, 0, -1, "tpc", "Third party copy"},
+ {VPD_ZBC_DEV_CHARS, 0, -1, "zbdch", "Zoned block device characteristics"},
+ /* Use pdt of -1 since this page both for pdt=0 and pdt=0x14 */
+ {0, 0, 0, NULL, NULL},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_vpd [--all] [--enumerate] [--examine] [--force] "
+ "[--help] [--hex]\n"
+ " [--ident] [--inhex=FN] [--long] [--maxlen=LEN] "
+ "[--page=PG]\n"
+ " [--quiet] [--raw] [--sinq_inraw=RFN] "
+ "[--vendor=VP] [--verbose]\n"
+ " [--version] DEVICE\n");
+ pr2serr(" where:\n"
+ " --all|-a output all pages listed in the supported "
+ "pages VPD\n"
+ " page\n"
+ " --enumerate|-e enumerate known VPD pages names (ignore "
+ "DEVICE),\n"
+ " can be used with --page=num to search\n"
+ " --examine|-E starting at 0x80 scan pages code to 0xff\n"
+ " --force|-f skip VPD page 0 (supported VPD pages) "
+ "checking\n"
+ " --help|-h output this usage message then exit\n"
+ " --hex|-H output page in ASCII hexadecimal\n"
+ " --ident|-i output device identification VPD page, "
+ "twice for\n"
+ " short logical unit designator (equiv: "
+ "'-qp di_lu')\n"
+ " --inhex=FN|-I FN read ASCII hex from file FN instead of "
+ "DEVICE;\n"
+ " if used with --raw then read binary "
+ "from FN\n"
+ " --json[=JO]|-j[JO] output in JSON instead of human "
+ "readable text.\n"
+ " Use --json=? for JSON help\n"
+ " --long|-l perform extra decoding\n"
+ " --maxlen=LEN|-m LEN max response length (allocation "
+ "length in cdb)\n"
+ " (def: 0 -> 252 bytes)\n"
+ " --page=PG|-p PG fetch VPD page where PG is an "
+ "acronym, or a decimal\n"
+ " number unless hex indicator "
+ "is given (e.g. '0x83');\n"
+ " can also take PG,VP as an "
+ "operand\n"
+ " --quiet|-q suppress some output when decoding\n"
+ " --raw|-r output page in binary; if --inhex=FN is "
+ "also\n"
+ " given, FN is in binary (else FN is in "
+ "hex)\n"
+ " --sinq_inraw=RFN|-Q RFN read raw (binary) standard "
+ "INQUIRY\n"
+ " response from the RFN filename\n"
+ " --vendor=VP|-M VP vendor/product abbreviation [or "
+ "number]\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Fetch Vital Product Data (VPD) page using SCSI INQUIRY or "
+ "decodes VPD\npage response held in file FN. To list available "
+ "pages use '-e'. Also\n'-p -1' or '-p sinq' yields the standard "
+ "INQUIRY response.\n");
+}
+
+static const struct svpd_values_name_t *
+sdp_get_vpd_detail(int page_num, int subvalue, int pdt)
+{
+ const struct svpd_values_name_t * vnp;
+ int sv, ty;
+
+ sv = (subvalue < 0) ? 1 : 0;
+ ty = (pdt < 0) ? 1 : 0;
+ for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
+ if ((page_num == vnp->value) &&
+ (sv || (subvalue == vnp->subvalue)) &&
+ (ty || (pdt == vnp->pdt)))
+ return vnp;
+ }
+ if (! ty)
+ return sdp_get_vpd_detail(page_num, subvalue, -1);
+ if (! sv)
+ return sdp_get_vpd_detail(page_num, -1, -1);
+ return NULL;
+}
+
+static const struct svpd_values_name_t *
+sdp_find_vpd_by_acron(const char * ap)
+{
+ const struct svpd_values_name_t * vnp;
+
+ for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ return NULL;
+}
+
+static void
+enumerate_vpds(int standard, int vendor)
+{
+ const struct svpd_values_name_t * vnp;
+
+ if (standard) {
+ for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
+ if (vnp->name) {
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+ }
+ if (vendor)
+ svpd_enumerate_vendor(-2);
+}
+
+static int
+count_standard_vpds(int vpd_pn)
+{
+ const struct svpd_values_name_t * vnp;
+ int matches = 0;
+
+ for (vnp = standard_vpd_pg; vnp->acron; ++vnp) {
+ if ((vpd_pn == vnp->value) && vnp->name) {
+ if (0 == matches)
+ printf("Matching standard VPD pages:\n");
+ ++matches;
+ if (vnp->value < 0)
+ printf(" %-10s -1 %s\n", vnp->acron, vnp->name);
+ else
+ printf(" %-10s 0x%02x %s\n", vnp->acron, vnp->value,
+ vnp->name);
+ }
+ }
+ return matches;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Assume index is less than 16 */
+static const char * sg_ansi_version_arr[16] =
+{
+ "no conformance claimed",
+ "SCSI-1", /* obsolete, ANSI X3.131-1986 */
+ "SCSI-2", /* obsolete, ANSI X3.131-1994 */
+ "SPC", /* withdrawn, ANSI INCITS 301-1997 */
+ "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */
+ "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */
+ "SPC-4", /* ANSI INCITS 513-2015 */
+ "SPC-5", /* ANSI INCITS 502-2020 */
+ "ecma=1, [8h]",
+ "ecma=1, [9h]",
+ "ecma=1, [Ah]",
+ "ecma=1, [Bh]",
+ "reserved [Ch]",
+ "reserved [Dh]",
+ "reserved [Eh]",
+ "reserved [Fh]",
+};
+
+static void
+std_inq_decode(uint8_t * b, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ uint8_t ver;
+ int pqual, pdt, hp, j, n;
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ char c[256];
+ static const int clen = sizeof(c);
+ static const char * np = "Standard INQUIRY data format:";
+
+ if (len < 4) {
+ pr2serr("%s: len [%d] too short\n", __func__, len);
+ return;
+ }
+ pqual = (b[0] & 0xe0) >> 5;
+ pdt = b[0] & PDT_MASK;
+ hp = (b[1] >> 4) & 0x3;
+ ver = b[2];
+ sgj_pr_hr(jsp, "%s", np);
+ if (0 == pqual)
+ sgj_pr_hr(jsp, "\n");
+ else {
+ cp = pqual_str(pqual);
+
+ if (pqual < 3)
+ sgj_pr_hr(jsp, " [PQ indicates %s]\n", cp);
+ else
+ sgj_pr_hr(jsp, " [PQ indicates %s [0x%x] ]\n", cp, pqual);
+ }
+ sgj_pr_hr(jsp, " PQual=%d PDT=%d RMB=%d LU_CONG=%d hot_pluggable="
+ "%d version=0x%02x [%s]\n", pqual, pdt, !!(b[1] & 0x80),
+ !!(b[1] & 0x40), hp, ver, sg_ansi_version_arr[ver & 0xf]);
+ sgj_pr_hr(jsp, " [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d "
+ " Resp_data_format=%d\n",
+ !!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20),
+ !!(b[3] & 0x10), b[3] & 0x0f);
+ if (len < 5)
+ goto skip1;
+ j = b[4] + 5;
+ if (op->verbose > 2)
+ pr2serr(">> requested %d bytes, %d bytes available\n", len, j);
+ sgj_pr_hr(jsp, " SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d "
+ "[BQue=%d]\n", !!(b[5] & 0x80), !!(b[5] & 0x40),
+ ((b[5] & 0x30) >> 4), !!(b[5] & 0x08), !!(b[5] & 0x01),
+ !!(b[6] & 0x80));
+ n = 0;
+ n += sg_scnpr(c + n, clen - n, "EncServ=%d ", !!(b[6] & 0x40));
+ if (b[6] & 0x10)
+ n += sg_scnpr(c + n, clen - n, "MultiP=1 (VS=%d) ", !!(b[6] & 0x20));
+ else
+ n += sg_scnpr(c + n, clen - n, "MultiP=0 ");
+ n += sg_scnpr(c + n, clen - n, "[MChngr=%d] [ACKREQQ=%d] Addr16=%d",
+ !!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01));
+ sgj_pr_hr(jsp, " %s\n", c);
+ sgj_pr_hr(jsp, " [RelAdr=%d] WBus16=%d Sync=%d [Linked=%d] "
+ "[TranDis=%d] CmdQue=%d\n", !!(b[7] & 0x80), !!(b[7] & 0x20),
+ !!(b[7] & 0x10), !!(b[7] & 0x08), !!(b[7] & 0x04),
+ !!(b[7] & 0x02));
+ if (len < 36)
+ goto skip1;
+ sgj_pr_hr(jsp, " %s: %.8s\n", t10_vendor_id_hr, b + 8);
+ sgj_pr_hr(jsp, " %s: %.16s\n", product_id_hr, b + 16);
+ sgj_pr_hr(jsp, " %s: %.4s\n", product_rev_lev_hr, b + 32);
+skip1:
+ if (! jsp->pr_as_json || (len < 8))
+ return;
+ std_inq_decode_js(b, len, op, jop);
+}
+
+/* VPD_DEVICE_ID 0x83 ["di, di_asis, di_lu, di_port, di_target"] */
+static void
+device_id_vpd_variants(uint8_t * buff, int len, int subvalue,
+ struct opts_t * op, sgj_opaque_p jap)
+{
+ int m_a, blen;
+ uint8_t * b;
+
+ if (len < 4) {
+ pr2serr("Device identification VPD page length too short=%d\n", len);
+ return;
+ }
+ blen = len - 4;
+ b = buff + 4;
+ m_a = -1;
+ if (0 == subvalue) {
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), 0, b, blen,
+ VPD_ASSOC_LU, op, jap);
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), 0, b, blen,
+ VPD_ASSOC_TPORT, op, jap);
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), 0, b, blen,
+ VPD_ASSOC_TDEVICE, op, jap);
+ } else if (VPD_DI_SEL_AS_IS == subvalue)
+ filter_dev_ids(NULL, 0, b, blen, m_a, op, jap);
+ else {
+ if (VPD_DI_SEL_LU & subvalue)
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), 0, b, blen,
+ VPD_ASSOC_LU, op, jap);
+ if (VPD_DI_SEL_TPORT & subvalue)
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), 0, b,
+ blen, VPD_ASSOC_TPORT, op, jap);
+ if (VPD_DI_SEL_TARGET & subvalue)
+ filter_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), 0,
+ b, blen, VPD_ASSOC_TDEVICE, op, jap);
+ }
+}
+
+static void /* VPD_SUPPORTED_VPDS ["sv"] */
+decode_supported_vpd_4vpd(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ uint8_t pn;
+ int k, rlen, pdt;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const struct svpd_values_name_t * vnp;
+ uint8_t * bp;
+ char b[144];
+ static const int blen = sizeof(b);
+ static const char * svps = "Supported VPD pages";
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ rlen = buff[3] + 4;
+ if (rlen > len)
+ pr2serr("%s VPD page truncated, indicates %d, got %d\n", svps, rlen,
+ len);
+ else
+ len = rlen;
+ if (len < 4) {
+ pr2serr("%s VPD page length too short=%d\n", svps, len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+
+ for (k = 0; k < len; ++k) {
+ pn = bp[k];
+ snprintf(b, blen, "0x%02x", pn);
+ vnp = sdp_get_vpd_detail(pn, -1, pdt);
+ if (vnp) {
+ if (op->do_long)
+ sgj_pr_hr(jsp, " %s %s [%s]\n", b, vnp->name, vnp->acron);
+ else
+ sgj_pr_hr(jsp, " %s [%s]\n", vnp->name, vnp->acron);
+ } else if (op->vend_prod_num >= 0) {
+ vnp = svpd_find_vendor_by_num(pn, op->vend_prod_num);
+ if (vnp) {
+ if (op->do_long)
+ sgj_pr_hr(jsp, " %s %s [%s]\n", b, vnp->name,
+ vnp->acron);
+ else
+ sgj_pr_hr(jsp, " %s [%s]\n", vnp->name, vnp->acron);
+ } else
+ sgj_pr_hr(jsp, " %s\n", b);
+ } else
+ sgj_pr_hr(jsp, " %s\n", b);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_i(jsp, jo2p, "i", pn);
+ sgj_js_nv_s(jsp, jo2p, "hex", b + 2);
+ if (vnp) {
+ sgj_js_nv_s(jsp, jo2p, "name", vnp->name);
+ sgj_js_nv_s(jsp, jo2p, "acronym", vnp->acron);
+ } else {
+ sgj_js_nv_s(jsp, jo2p, "name", "unknown");
+ sgj_js_nv_s(jsp, jo2p, "acronym", "unknown");
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ }
+}
+
+/* VPD_SCSI_PORTS 0x88 ["sp"] */
+static void
+decode_scsi_ports_vpd_4vpd(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, rel_port, ip_tid_len, tpd_len;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p ja2p = NULL;
+ uint8_t * bp;
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (len < 4) {
+ pr2serr("SCSI Ports VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ rel_port = sg_get_unaligned_be16(bp + 2);
+ sgj_pr_hr(jsp, " Relative port=%d\n", rel_port);
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_i(jsp, jo2p, "relative_port", rel_port);
+ ip_tid_len = sg_get_unaligned_be16(bp + 6);
+ bump = 8 + ip_tid_len;
+ if ((k + bump) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor "
+ "length=%d, left=%d\n", bump, (len - k));
+ return;
+ }
+ if (ip_tid_len > 0) {
+ if (op->do_hex > 1) {
+ sgj_pr_hr(jsp, " Initiator port transport id:\n");
+ hex2stdout((bp + 8), ip_tid_len, 1);
+ } else {
+ char b[1024];
+
+ sg_decode_transportid_str(" ", bp + 8, ip_tid_len,
+ true, sizeof(b), b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_s(jsp, jo2p, "initiator_port_transport_id", b);
+ sgj_pr_hr(jsp, "%s",
+ sg_decode_transportid_str(" ", bp + 8,
+ ip_tid_len, true, sizeof(b), b));
+ }
+ }
+ tpd_len = sg_get_unaligned_be16(bp + bump + 2);
+ if ((k + bump + tpd_len + 4) > len) {
+ pr2serr("SCSI Ports VPD page, short descriptor(tgt) "
+ "length=%d, left=%d\n", bump, (len - k));
+ return;
+ }
+ if (tpd_len > 0) {
+ if (op->do_hex > 1) {
+ sgj_pr_hr(jsp, " Target port descriptor(s):\n");
+ hex2stdout(bp + bump + 4, tpd_len, 1);
+ } else {
+ if ((0 == op->do_quiet) || (ip_tid_len > 0))
+ sgj_pr_hr(jsp, " Target port descriptor(s):\n");
+ if (jsp->pr_as_json) {
+ sgj_opaque_p jo3p = sgj_named_subobject_r(jsp, jo2p,
+ "target_port");
+
+ ja2p = sgj_named_subarray_r(jsp, jo3p,
+ "designation_descriptor_list");
+ }
+ filter_dev_ids("", 2 /* leading spaces */, bp + bump + 4,
+ tpd_len, VPD_ASSOC_TPORT, op, ja2p);
+ }
+ }
+ bump += tpd_len + 4;
+ sgj_js_nv_o(jsp, jap, NULL, jo2p);
+ }
+}
+
+/* Prints outs an abridged set of device identification designators
+ selected by association, designator type and/or code set. Not used
+ for JSON output. */
+static int
+filter_dev_ids_quiet(uint8_t * buff, int len, int m_assoc)
+{
+ int k, m, p_id, c_set, piv, desig_type, i_len, naa, off, u;
+ int assoc, is_sas, rtp;
+ const uint8_t * bp;
+ const uint8_t * ip;
+ uint8_t sas_tport_addr[8];
+
+ rtp = 0;
+ memset(sas_tport_addr, 0, sizeof(sas_tport_addr));
+ for (k = 0, off = -1; true; ++k) {
+ if ((0 == k) && (0 != buff[2])) {
+ /* first already in buff */
+ if (m_assoc != VPD_ASSOC_LU)
+ return 0;
+ ip = buff;
+ c_set = 1;
+ assoc = VPD_ASSOC_LU;
+ is_sas = 0;
+ desig_type = 3;
+ i_len = 16;
+ } else {
+ u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1);
+ if (0 != u)
+ break;
+ bp = buff + off;
+ i_len = bp[3];
+ if ((off + i_len + 4) > len) {
+ pr2serr(" VPD page error: designator length longer than\n"
+ " remaining response length=%d\n", (len - off));
+ return SG_LIB_CAT_MALFORMED;
+ }
+ ip = bp + 4;
+ p_id = ((bp[0] >> 4) & 0xf);
+ c_set = (bp[0] & 0xf);
+ piv = ((bp[1] & 0x80) ? 1 : 0);
+ is_sas = (piv && (6 == p_id)) ? 1 : 0;
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig_type = (bp[1] & 0xf);
+ }
+ switch (desig_type) {
+ case 0: /* vendor specific */
+ break;
+ case 1: /* T10 vendor identification */
+ break;
+ case 2: /* EUI-64 based */
+ if ((8 != i_len) && (12 != i_len) && (16 != i_len))
+ pr2serr(" << expect 8, 12 and 16 byte "
+ "EUI, got %d>>\n", i_len);
+ printf(" 0x");
+ for (m = 0; m < i_len; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 3: /* NAA */
+ naa = (ip[0] >> 4) & 0xff;
+ if (1 != c_set) {
+ pr2serr(" << expected binary code_set (1), got %d for "
+ "NAA=%d>>\n", c_set, naa);
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ switch (naa) {
+ case 2: /* NAA IEEE extended */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 2 identifier "
+ "length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ case 3: /* Locally assigned */
+ case 5: /* IEEE Registered */
+ if (8 != i_len) {
+ pr2serr(" << unexpected NAA 3 or 5 "
+ "identifier length: 0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ if ((0 == is_sas) || (1 != assoc)) {
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ } else if (rtp) {
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf(",0x%x\n", rtp);
+ rtp = 0;
+ } else {
+ if (sas_tport_addr[0]) {
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)sas_tport_addr[m]);
+ printf("\n");
+ }
+ memcpy(sas_tport_addr, ip, sizeof(sas_tport_addr));
+ }
+ break;
+ case 6: /* NAA IEEE registered extended */
+ if (16 != i_len) {
+ pr2serr(" << unexpected NAA 6 identifier length: "
+ "0x%x>>\n", i_len);
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ printf(" 0x");
+ for (m = 0; m < 16; ++m)
+ printf("%02x", (unsigned int)ip[m]);
+ printf("\n");
+ break;
+ default:
+ pr2serr(" << bad NAA nibble, expected 2, 3, 5 or 6, got "
+ "%d>>\n", naa);
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ break;
+ case 4: /* Relative target port */
+ if ((0 == is_sas) || (1 != c_set) || (1 != assoc) || (4 != i_len))
+ break;
+ rtp = sg_get_unaligned_be16(ip + 2);
+ if (sas_tport_addr[0]) {
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)sas_tport_addr[m]);
+ printf(",0x%x\n", rtp);
+ memset(sas_tport_addr, 0, sizeof(sas_tport_addr));
+ rtp = 0;
+ }
+ break;
+ case 5: /* (primary) Target port group */
+ break;
+ case 6: /* Logical unit group */
+ break;
+ case 7: /* MD5 logical unit identifier */
+ break;
+ case 8: /* SCSI name string */
+ if (c_set < 2) { /* quietly accept ASCII for UTF-8 */
+ pr2serr(" << expected UTF-8 code_set>>\n");
+ hex2stderr(ip, i_len, 0);
+ break;
+ }
+ if (! (strncmp((const char *)ip, "eui.", 4) ||
+ strncmp((const char *)ip, "EUI.", 4) ||
+ strncmp((const char *)ip, "naa.", 4) ||
+ strncmp((const char *)ip, "NAA.", 4) ||
+ strncmp((const char *)ip, "iqn.", 4))) {
+ pr2serr(" << expected name string prefix>>\n");
+ hex2stderr(ip, i_len, -1);
+ break;
+ }
+ /* 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", i_len, (const char *)ip);
+ break;
+ case 9: /* Protocol specific port identifier */
+ break;
+ case 0xa: /* UUID identifier [spc5r08] RFC 4122 */
+ if ((1 != c_set) || (18 != i_len) || (1 != ((ip[0] >> 4) & 0xf)))
+ break;
+ for (m = 0; m < 16; ++m) {
+ if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
+ printf("-");
+ printf("%02x", (unsigned int)ip[2 + m]);
+ }
+ printf("\n");
+ break;
+ default: /* reserved */
+ break;
+ }
+ }
+ if (sas_tport_addr[0]) {
+ printf(" 0x");
+ for (m = 0; m < 8; ++m)
+ printf("%02x", (unsigned int)sas_tport_addr[m]);
+ printf("\n");
+ }
+ if (-2 == u) {
+ pr2serr("VPD page error: short designator around offset %d\n", off);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ return 0;
+}
+
+/* Prints outs designation descriptors (dd_s) selected by association,
+ designator type and/or code set. VPD_DEVICE_ID and VPD_SCSI_PORTS */
+static int
+filter_dev_ids(const char * print_if_found, int num_leading, uint8_t * buff,
+ int len, int m_assoc, struct opts_t * op, sgj_opaque_p jap)
+{
+ bool printed, sgj_out_hr;
+ int assoc, off, u, i_len;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ char b[1024];
+ char sp[82];
+ static const int blen = sizeof(b);
+
+ if (op->do_quiet && (! jsp->pr_as_json))
+ return filter_dev_ids_quiet(buff, len, m_assoc);
+ sgj_out_hr = false;
+ if (jsp->pr_as_json) {
+ int ret = filter_json_dev_ids(buff, len, m_assoc, op, jap);
+
+ if (ret || (! jsp->pr_out_hr))
+ return ret;
+ sgj_out_hr = true;
+ }
+ if (num_leading > (int)(sizeof(sp) - 2))
+ num_leading = sizeof(sp) - 2;
+ if (num_leading > 0)
+ snprintf(sp, sizeof(sp), "%*c", num_leading, ' ');
+ else
+ sp[0] = '\0';
+ if (buff[2] != 0) { /* all valid dd_s should have 0 in this byte */
+ if (op->verbose)
+ pr2serr("%s: designation descriptors byte 2 should be 0\n"
+ "perhaps this is a standard inquiry response, ignore\n",
+ __func__);
+ return 0;
+ }
+ off = -1;
+ printed = false;
+ while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1)) == 0) {
+ bp = buff + off;
+ i_len = bp[3];
+ if ((off + i_len + 4) > len) {
+ pr2serr(" VPD page error: designator length longer than\n"
+ " remaining response length=%d\n", (len - off));
+ return SG_LIB_CAT_MALFORMED;
+ }
+ assoc = ((bp[1] >> 4) & 0x3);
+ if (print_if_found && (! printed)) {
+ printed = true;
+ if (strlen(print_if_found) > 0) {
+ snprintf(b, blen, " %s:", print_if_found);
+ if (sgj_out_hr)
+ sgj_js_str_out(jsp, b, strlen(b));
+ else
+ printf("%s\n", b);
+ }
+ }
+ if (NULL == print_if_found) {
+ snprintf(b, blen, " %s%s:", sp, sg_get_desig_assoc_str(assoc));
+ if (sgj_out_hr)
+ sgj_js_str_out(jsp, b, strlen(b));
+ else
+ printf("%s\n", b);
+ }
+ sg_get_designation_descriptor_str(sp, bp, i_len + 4, false,
+ op->do_long, blen, b);
+ if (sgj_out_hr)
+ sgj_js_str_out(jsp, b, strlen(b));
+ else
+ printf("%s", b);
+ }
+ if (-2 == u) {
+ pr2serr("VPD page error: short designator around offset %d\n", off);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ return 0;
+}
+
+/* VPD_BLOCK_LIMITS sbc */
+/* VPD_SA_DEV_CAP ssc */
+/* VPD_OSD_INFO osd */
+static void
+decode_b0_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt = PDT_MASK & buff[0];
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_block_limits_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_haj_vi_nex(jsp, jop, 2, "TSMC", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x2), false, "Tape Stream Mirror "
+ "Capable");
+ sgj_haj_vi_nex(jsp, jop, 2, "WORM", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[4] & 0x1), false, "Write Once Read Multiple "
+ "supported");
+ break;
+ case PDT_OSD:
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_BLOCK_DEV_CHARS sbc 0xb1 ["bdc"] */
+/* VPD_MAN_ASS_SN ssc */
+/* VPD_SECURITY_TOKEN osd */
+/* VPD_ES_DEV_CHARS ses-4 */
+static void
+decode_b1_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ pdt = buff[0] & PDT_MASK;
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_block_dev_ch_vpd() in sg_vpd_common.c */
+ case PDT_TAPE: case PDT_MCHANGER: case PDT_ADC:
+ sgj_pr_hr(jsp, " Manufacturer-assigned serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "manufacturer_assigned_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_LB_PROVISIONING sbc */
+/* VPD_TA_SUPPORTED ssc */
+static void
+decode_b2_vpd(uint8_t * buff, int len, int pdt, struct opts_t * op)
+{
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* decode_block_lb_prov_vpd() is now in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ /* decode_tapealert_supported_vpd() is now in sg_vpd_common.c */
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_REFERRALS sbc 0xb3 ["ref"] */
+/* VPD_AUTOMATION_DEV_SN ssc 0xb3 ["adsn"] */
+static void
+decode_b3_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ pdt = buff[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done in decode_referrals_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_pr_hr(jsp, " Automation device serial number: %.*s\n",
+ len - 4, buff + 4);
+ sgj_js_nv_s_len(jsp, jop, "automation_device_serial_number",
+ (const char *)buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_SUP_BLOCK_LENS sbc ["sbl"] */
+/* VPD_DTDE_ADDRESS ssc */
+static void
+decode_b4_vpd(uint8_t * buff, int len, struct opts_t * op, sgj_opaque_p jop)
+{
+ int pdt = buff[0] & PDT_MASK;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_sup_block_lens_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ sgj_pr_hr(jsp, " Device transfer data element:\n");
+ if (! jsp->pr_as_json)
+ hex2stdout(buff + 4, len - 4, 1);
+ sgj_js_nv_hex_bytes(jsp, jop, "device_transfer_data_element",
+ buff + 4, len - 4);
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(buff, len, 0);
+ break;
+ }
+}
+
+/* VPD_BLOCK_DEV_C_EXTENS sbc */
+/* VPD_LB_PROTECTION 0xb5 ["lbpro"] ssc */
+static void
+decode_b5_vpd(uint8_t * b, int len, int do_hex, int pdt)
+{
+ if (do_hex) {
+ hex2stdout(b, len, (1 == do_hex) ? 0 : -1);
+ return;
+ }
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ /* now done by decode_block_dev_char_ext_vpd() in sg_vpd_common.c */
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ /* now done by decode_lb_protection_vpd() in sg_vpd_common.c */
+ break;
+ default:
+ pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt);
+ hex2stderr(b, len, 0);
+ break;
+ }
+}
+
+/* Returns 0 if successful */
+static int
+svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue, int off)
+{
+ bool as_json, json_o_hr, hex0;
+ int res, len, n;
+ sgj_state * jsp = &op->json_st;
+ uint8_t * rp;
+
+ as_json = jsp->pr_as_json;
+ json_o_hr = as_json && jsp->pr_out_hr;
+ hex0 = (0 == op->do_hex);
+ rp = rsp_buff + off;
+ if (hex0 && (! op->do_raw) && (! op->examine_given))
+ sgj_pr_hr(jsp, "Only hex output supported\n");
+ if ((!op->do_raw) && (op->do_hex < 2) && (! op->examine_given)) {
+ if (subvalue) {
+ if (hex0)
+ sgj_pr_hr(jsp, "VPD page code=0x%.2x, subvalue=0x%.2x:\n",
+ op->vpd_pn, subvalue);
+ else
+ printf("VPD page code=0x%.2x, subvalue=0x%.2x:\n", op->vpd_pn,
+ subvalue);
+ } else if (op->vpd_pn >= 0) {
+ if (hex0)
+ sgj_pr_hr(jsp, "VPD page code=0x%.2x:\n", op->vpd_pn);
+ else
+ printf("VPD page code=0x%.2x:\n", op->vpd_pn);
+ } else {
+ if (hex0)
+ sgj_pr_hr(jsp, "VPD page code=%d:\n", op->vpd_pn);
+ else
+ printf("VPD page code=%d:\n", op->vpd_pn);
+ }
+ }
+
+ res = vpd_fetch_page(sg_fd, rp, op->vpd_pn, op->maxlen, op->do_quiet,
+ op->verbose, &len);
+ if (0 == res) {
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (json_o_hr && hex0 && (len > 0) && (len < UINT16_MAX)) {
+ char * p;
+
+ n = len * 4;
+ p = malloc(n);
+ if (p) {
+ n = hex2str(rp, len, NULL, 1, n - 1, p);
+ sgj_js_str_out(jsp, p, n);
+ }
+ } else
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ }
+ } else if ((! op->do_quiet) && (! op->examine_given)) {
+ if (op->vpd_pn >= 0)
+ pr2serr("fetching VPD page code=0x%.2x: failed\n", op->vpd_pn);
+ else
+ pr2serr("fetching VPD page code=%d: failed\n", op->vpd_pn);
+ }
+ return res;
+}
+
+static int
+recurse_vpd_decode(struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ int res = svpd_decode_t10(-1, op, jop, 0, off, NULL);
+
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(-1, op, jop, off);
+ if (SG_LIB_CAT_OTHER == res)
+ svpd_unable_to_decode(-1, op, 0, off);
+ }
+ return res;
+}
+
+/* Returns 0 if successful. If don't know how to decode, returns
+ * SG_LIB_CAT_OTHER else see sg_ll_inquiry(). */
+static int
+svpd_decode_t10(int sg_fd, struct opts_t * op, sgj_opaque_p jop,
+ int subvalue, int off, const char * prefix)
+{
+ bool allow_name, allow_if_found, long_notquiet, qt;
+ bool vpd_supported = false;
+ bool inhex_active = (-1 == sg_fd);
+ bool exam_not_given = ! op->examine_given;
+ int len, pdt, pqual, num, k, resid, alloc_len, pn, vb;
+ int res = 0;
+ sgj_state * jsp = &op->json_st;
+ uint8_t * rp;
+ sgj_opaque_p jap = NULL;
+ sgj_opaque_p jo2p = NULL;
+ const char * np;
+ const char * ep;
+ const char * pre = (prefix ? prefix : "");
+ const char * pdt_str;
+ bool as_json = jsp->pr_as_json;
+ bool not_json = ! as_json;
+ char obuff[DEF_ALLOC_LEN];
+ char d[48];
+
+ vb = op->verbose;
+ qt = op->do_quiet;
+ long_notquiet = op->do_long && (! op->do_quiet);
+ if (op->do_raw || (op->do_quiet && (! op->do_long) && (! op->do_all)) ||
+ (op->do_hex >= 3) || op->examine_given)
+ allow_name = false;
+ else
+ allow_name = true;
+ allow_if_found = op->examine_given && (! op->do_quiet);
+ rp = rsp_buff + off;
+ pn = op->vpd_pn;
+ if ((off > 0) && (VPD_NOPE_WANT_STD_INQ != op->vpd_pn))
+ pn = rp[1];
+ else
+ pn = op->vpd_pn;
+ if (!inhex_active && !op->do_force && exam_not_given &&
+ pn != VPD_NOPE_WANT_STD_INQ &&
+ pn != VPD_SUPPORTED_VPDS) {
+ res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen, qt,
+ vb, &len);
+ if (res)
+ return res;
+
+ num = rp[3];
+ if (num > (len - 4))
+ num = (len - 4);
+ if (vb > 1) {
+ pr2serr("Supported VPD pages, hex list: ");
+ hex2stderr(rp + 4, num, -1);
+ }
+ for (k = 0; k < num; ++k) {
+ if (pn == rp[4 + k]) {
+ vpd_supported = true;
+ break;
+ }
+ }
+ if (! vpd_supported) { /* get creative, was SG_LIB_CAT_ILLEGAL_REQ */
+ if (vb)
+ pr2serr("Given VPD page not in supported list, use --force "
+ "to override this check\n");
+ return sg_convert_errno(EDOM);
+ }
+ }
+ pdt = rp[0] & PDT_MASK;
+ pdt_str = sg_get_pdt_str(pdt, sizeof(d), d);
+ pqual = (rp[0] & 0xe0) >> 5;
+
+ switch(pn) {
+ case VPD_NOPE_WANT_STD_INQ: /* -2 (want standard inquiry response) */
+ if (!inhex_active) {
+ if (op->maxlen > 0)
+ alloc_len = op->maxlen;
+ else if (op->do_long)
+ alloc_len = DEF_ALLOC_LEN;
+ else
+ alloc_len = 36;
+ res = sg_ll_inquiry_v2(sg_fd, false, 0, rp, alloc_len,
+ DEF_PT_TIMEOUT, &resid, ! op->do_quiet, vb);
+ } else {
+ alloc_len = op->maxlen;
+ resid = 0;
+ res = 0;
+ }
+ if (0 == res) {
+ alloc_len -= resid;
+ if (op->do_raw)
+ dStrRaw(rp, alloc_len);
+ else if (op->do_hex) {
+ if (! op->do_quiet && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "Standard Inquiry data format:\n");
+ hex2stdout(rp, alloc_len, (1 == op->do_hex) ? 0 : -1);
+ } else
+ std_inq_decode(rp, alloc_len, op, jop);
+ return 0;
+ }
+ break;
+ case VPD_SUPPORTED_VPDS: /* 0x0 ["sv"] */
+ np = "Supported VPD pages VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ num = rp[3];
+ if (num > (len - 4))
+ num = (len - 4);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "supported_vpd_page_list");
+ }
+ decode_supported_vpd_4vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_UNIT_SERIAL_NUM: /* 0x80 ["sn"] */
+ np = "Unit serial number VPD page";
+ if (allow_name && not_json)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ memset(obuff, 0, sizeof(obuff));
+ len -= 4;
+ if (len >= (int)sizeof(obuff))
+ len = sizeof(obuff) - 1;
+ memcpy(obuff, rp + 4, len);
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ sgj_haj_vs(jsp, jo2p, 2, np, SGJ_SEP_COLON_1_SPACE, obuff);
+ }
+ return 0;
+ }
+ break;
+ case VPD_DEVICE_ID: /* 0x83 ["di, di_asis, di_lu, di_port, di_target"] */
+ np = "Device Identification VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "designation_descriptor_list");
+ }
+ device_id_vpd_variants(rp, len, subvalue, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_SOFTW_INF_ID: /* 0x84 ["sii"] */
+ np = "Software interface identification VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "software_interface_identifier_list");
+ }
+ decode_softw_inf_id(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_MAN_NET_ADDR: /* 0x85 ["mna"] */
+ np= "Management network addresses VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "network_services_descriptor_list");
+ }
+ decode_net_man_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_EXT_INQ: /* 0x86 ["ei"] */
+ np = "extended INQUIRY data VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ bool protect = false;
+
+ op->protect_not_sure = false;
+ if (op->std_inq_a_valid)
+ protect = !! (0x1 & op->std_inq_a[5]);
+ else if ((sg_fd >= 0) && (! op->do_force)) {
+ struct sg_simple_inquiry_resp sir;
+
+ res = sg_simple_inquiry(sg_fd, &sir, false, vb);
+ if (res) {
+ if (op->verbose)
+ pr2serr("%s: sg_simple_inquiry() failed, "
+ "res=%d\n", __func__, res);
+ op->protect_not_sure = true;
+ } else
+ protect = !!(sir.byte_5 & 0x1); /* SPC-3 and later */
+ } else
+ op->protect_not_sure = true;
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp," [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_x_inq_vpd(rp, len, protect, op, jo2p);
+ }
+ return 0;
+ }
+ break;
+ case VPD_MODE_PG_POLICY: /* 0x87 */
+ np = "Mode page policy VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", (prefix ? prefix : ""), np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "mode_page_policy_descriptor_list");
+ }
+ decode_mode_policy_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_SCSI_PORTS: /* 0x88 ["sp"] */
+ np = "SCSI Ports VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "scsi_ports_descriptor_list");
+ }
+ decode_scsi_ports_vpd_4vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_ATA_INFO: /* 0x89 ['ai"] */
+ np = "ATA information VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ alloc_len = op->maxlen ? op->maxlen : VPD_ATA_INFO_LEN;
+ res = vpd_fetch_page(sg_fd, rp, pn, alloc_len, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", (prefix ? prefix : ""), np);
+ if ((2 == op->do_raw) || (3 == op->do_hex)) { /* for hdparm */
+ if (len < (60 + 512))
+ pr2serr("ATA_INFO VPD page len (%d) less than expected "
+ "572\n", len);
+ else
+ dWordHex((const unsigned short *)(rp + 60), 256, -2,
+ sg_is_big_endian());
+ }
+ else if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_ata_info_vpd(rp, len, op, jo2p);
+ }
+ return 0;
+ }
+ break;
+ case VPD_POWER_CONDITION: /* 0x8a ["pc"] */
+ np = "Power condition VPD page:";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ decode_power_condition(rp, len, op, jo2p);
+ }
+ return 0;
+ }
+ break;
+ case VPD_DEVICE_CONSTITUENTS: /* 0x8b ["dc"] */
+ np = "Device constituents VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "constituent_descriptor_list");
+ }
+ decode_dev_constit_vpd(rp, len, op, jap, recurse_vpd_decode);
+ }
+ return 0;
+ }
+ break;
+ case VPD_CFA_PROFILE_INFO: /* 0x8c ["cfa"] */
+ np = "CFA profile information VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "cfa_profile_descriptor_list");
+ }
+ decode_cga_profile_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_POWER_CONSUMPTION: /* 0x8d ["psm"] */
+ np = "Power consumption VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "power_consumption_descriptor_list");
+ }
+ decode_power_consumption(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_3PARTY_COPY: /* 0x8f */
+ np = "Third party copy VPD page"; /* ["tpc"] */
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "third_party_copy_descriptors");
+ }
+ decode_3party_copy_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_PROTO_LU: /* 0x90 ["pslu"] */
+ np = "Protocol-specific logical unit information VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_unit_information_descriptor_list");
+ }
+ decode_proto_lu_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_PROTO_PORT: /* 0x91 ["pspo"] */
+ np = "Protocol-specific port VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "port_information_descriptor_list");
+ }
+ decode_proto_port_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case VPD_SCSI_FEATURE_SETS: /* 0x92 ["sfs"] */
+ np = "SCSI Feature sets VPD page";
+ if (allow_name)
+ sgj_pr_hr(jsp, "%s%s:\n", pre, np);
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ if (! allow_name && allow_if_found)
+ sgj_pr_hr(jsp, "%s%s\n", pre, np);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "feature_set_code_list");
+ }
+ decode_feature_sets_vpd(rp, len, op, jap);
+ }
+ return 0;
+ }
+ break;
+ case 0xb0: /* depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bl = false;
+ bool sad = false;
+ bool oi = false;
+
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits VPD page";
+ ep = "(SBC)";
+ bl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Sequential-access device capabilities VPD page";
+ ep = "(SSC)";
+ sad = true;
+ break;
+ case PDT_OSD:
+ np = "OSD information VPD page";
+ ep = "(OSD)";
+ oi = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bl)
+ decode_block_limits_vpd(rp, len, op, jo2p);
+ else if (sad) {
+ decode_b0_vpd(rp, len, op, jop);
+ } else if (oi) {
+ decode_b0_vpd(rp, len, op, jop);
+ } else {
+
+ }
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb0\n", pre);
+ break;
+ case 0xb1: /* depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdc = false;
+ static const char * masn =
+ "Manufactured-assigned serial number VPD page";
+
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics VPD page";
+ ep = "(SBC)";
+ bdc = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = masn;
+ ep = "(SSC)";
+ break;
+ case PDT_OSD:
+ np = "Security token VPD page";
+ ep = "(OSD)";
+ break;
+ case PDT_ADC:
+ np = masn;
+ ep = "(ADC)";
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdc)
+ decode_block_dev_ch_vpd(rp, len, op, jo2p);
+ else
+ decode_b1_vpd(rp, len, op, jo2p);
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb1\n", pre);
+ break;
+ case 0xb2: /* VPD page depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool lbpv = false;
+ bool tas = false;
+
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Logical block provisioning VPD page";
+ ep = "(SBC)";
+ lbpv = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "TapeAlert supported flags VPD page";
+ ep = "(SSC)";
+ tas = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (lbpv)
+ decode_block_lb_prov_vpd(rp, len, op, jo2p);
+ else if (tas)
+ decode_tapealert_supported_vpd(rp, len, op, jo2p);
+ else
+ decode_b2_vpd(rp, len, pdt, op);
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb2\n", pre);
+ break;
+ case 0xb3: /* VPD page depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ref = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Referrals VPD page";
+ ep = "(SBC)";
+ ref = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Automation device serial number VPD page";
+ ep = "(SSC)";
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ref)
+ decode_referrals_vpd(rp, len, op, jo2p);
+ else
+ decode_b3_vpd(rp, len, op, jo2p);
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb3\n", pre);
+ break;
+ case 0xb4: /* VPD page depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool sbl = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Supported block lengths and protection types VPD page";
+ ep = "(SBC)";
+ sbl = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Data transfer device element address";
+ ep = "(SSC)";
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (sbl) {
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p, "logical_block_"
+ "length_and_protection_types_descriptor_list");
+ decode_sup_block_lens_vpd(rp, len, op, jap);
+ } else
+ decode_b4_vpd(rp, len, op, jo2p);
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb4\n", pre);
+ break;
+ case 0xb5: /* VPD page depends on pdt */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool bdce = false;
+ bool lbp = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block device characteristics extension VPD page";
+ ep = "(SBC)";
+ bdce = true;
+ break;
+ case PDT_TAPE: case PDT_MCHANGER:
+ np = "Logical block protection VPD page";
+ ep = "(SSC)";
+ lbp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (bdce)
+ decode_block_dev_char_ext_vpd(rp, len, op, jo2p);
+ else if (lbp) {
+ if (as_json)
+ jap = sgj_named_subarray_r(jsp, jo2p,
+ "logical_block_protection_method_descriptor_list");
+ decode_lb_protection_vpd(rp, len, op, jap);
+ } else
+ decode_b5_vpd(rp, len, op->do_hex, pdt);
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb5\n", pre);
+ break;
+ case VPD_ZBC_DEV_CHARS: /* 0xb6 for both pdt=0 and pdt=0x14 */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool zbdch = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Zoned block device characteristics VPD page";
+ ep = "(SBC, ZBC)";
+ zbdch = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (zbdch)
+ decode_zbdch_vpd(rp, len, op, jo2p);
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb6\n", pre);
+ break;
+ case 0xb7:
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool ble = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Block limits extension VPD page";
+ ep = "(SBC)";
+ ble = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ if (ble)
+ decode_block_limits_ext_vpd(rp, len, op, jo2p);
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb7\n", pre);
+ break;
+ case 0xb8: /* VPD_FORMAT_PRESETS */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool fp = false;
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Format presets VPD page";
+ ep = "(SBC)";
+ fp = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "format_preset_"
+ "descriptor_list");
+ }
+ if (fp)
+ decode_format_presets_vpd(rp, len, op, jap);
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb8\n", pre);
+ break;
+ case 0xb9: /* VPD_CON_POS_RANGE */
+ res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len);
+ if (0 == res) {
+ bool cpr = false; /* ["cpr"] */
+
+ pdt = rp[0] & PDT_MASK;
+ switch (pdt) {
+ case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC:
+ np = "Concurrent positioning ranges VPD page";
+ ep = "(SBC)";
+ cpr = true;
+ break;
+ default:
+ np = NULL;
+ break;
+ }
+ if (NULL == np)
+ sgj_pr_hr(jsp, "VPD page=0x%x, pdt=0x%x:\n", pn, pdt);
+ else if (allow_name || allow_if_found)
+ sgj_pr_hr(jsp, "%s%s %s:\n", pre, np, ep ? ep : "");
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ if (vb || long_notquiet)
+ sgj_pr_hr(jsp, " [PQual=%d Peripheral device type: "
+ "%s]\n", pqual, pdt_str);
+ if (as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ jap = sgj_named_subarray_r(jsp, jo2p, "lba_range_"
+ "descriptor_list");
+ }
+ if (cpr)
+ decode_con_pos_range_vpd(rp, len, op, jap);
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+ return 0;
+ } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3) &&
+ exam_not_given)
+ sgj_pr_hr(jsp, "%sVPD page=0xb8\n", pre);
+ break;
+ default:
+ return SG_LIB_CAT_OTHER;
+ }
+ return res;
+}
+
+static int
+svpd_decode_all(int sg_fd, struct opts_t * op, sgj_opaque_p jop)
+{
+ int k, res, rlen, n, pn;
+ int max_pn = 255;
+ int any_err = 0;
+ sgj_state * jsp = &op->json_st;
+ uint8_t vpd0_buff[512];
+ uint8_t * rp = vpd0_buff;
+
+ if (op->vpd_pn > 0)
+ max_pn = op->vpd_pn;
+ if (sg_fd >= 0) { /* have valid open file descriptor (handle) */
+ res = vpd_fetch_page(sg_fd, rp, VPD_SUPPORTED_VPDS, op->maxlen,
+ op->do_quiet, op->verbose, &rlen);
+ if (res) {
+ if (! op->do_quiet) {
+ if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("%s: VPD page 0, aborted command\n", __func__);
+ else if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("%s: fetching VPD page 0 failed: %s\n", __func__,
+ b);
+ }
+ }
+ return res;
+ }
+ n = sg_get_unaligned_be16(rp + 2);
+ if (n > (rlen - 4)) {
+ if (op->verbose)
+ pr2serr("%s: rlen=%d > page0 size=%d\n", __func__, rlen,
+ n + 4);
+ n = (rlen - 4);
+ }
+ for (k = 0; k < n; ++k) {
+ pn = rp[4 + k];
+ if (pn > max_pn)
+ continue;
+ op->vpd_pn = pn;
+ if (k > 0)
+ sgj_pr_hr(jsp, "\n");
+ if (op->do_long) {
+ if (jsp->pr_as_json)
+ sgj_pr_hr(jsp, "[0x%x]:\n", pn);
+ else
+ printf("[0x%x] ", pn);
+ }
+
+ res = svpd_decode_t10(sg_fd, op, jop, 0, 0, NULL);
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(sg_fd, op, jop, 0);
+ if (SG_LIB_CAT_OTHER == res)
+ res = svpd_unable_to_decode(sg_fd, op, 0, 0);
+ }
+ if (! op->do_quiet) {
+ if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("fetching VPD page failed, aborted command\n");
+ else if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("fetching VPD page failed: %s\n", b);
+ }
+ }
+ if (res)
+ any_err = res;
+ }
+ res = any_err;
+ } else { /* input is coming from --inhex=FN */
+ int bump, off;
+ int in_len = op->maxlen;
+ int prev_pn = -1;
+
+ res = 0;
+ if (op->page_given && (VPD_NOPE_WANT_STD_INQ == op->vpd_pn))
+ return svpd_decode_t10(-1, op, jop, 0, 0, NULL);
+
+ for (k = 0, off = 0; off < in_len; ++k, off += bump) {
+ rp = rsp_buff + off;
+ pn = rp[1];
+ bump = sg_get_unaligned_be16(rp + 2) + 4;
+ if ((off + bump) > in_len) {
+ pr2serr("%s: page 0x%x size (%d) exceeds buffer\n", __func__,
+ pn, bump);
+ bump = in_len - off;
+ }
+ if (op->page_given && (pn != op->vpd_pn))
+ continue;
+ if (pn <= prev_pn) {
+ pr2serr("%s: prev_pn=0x%x, this pn=0x%x, not ascending so "
+ "exit\n", __func__, prev_pn, pn);
+ break;
+ }
+ prev_pn = pn;
+ op->vpd_pn = pn;
+ if (pn > max_pn) {
+ if (op->verbose > 2)
+ pr2serr("%s: skipping as this pn=0x%x exceeds "
+ "max_pn=0x%x\n", __func__, pn, max_pn);
+ continue;
+ }
+ if (op->do_long) {
+ if (jsp->pr_as_json)
+ sgj_pr_hr(jsp, "[0x%x]:\n", pn);
+ else
+ printf("[0x%x] ", pn);
+ }
+
+ res = svpd_decode_t10(-1, op, jop, 0, off, NULL);
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(-1, op, jop, off);
+ if (SG_LIB_CAT_OTHER == res)
+ res = svpd_unable_to_decode(-1, op, 0, off);
+ }
+ }
+ }
+ return res;
+}
+
+static int
+svpd_examine_all(int sg_fd, struct opts_t * op, sgj_opaque_p jop)
+{
+ bool first = true;
+ bool got_one = false;
+ int k, res, start;
+ int max_pn;
+ int any_err = 0;
+ sgj_state * jsp = &op->json_st;
+ char b[80];
+
+ max_pn = (op->page_given ? op->vpd_pn : 0xff);
+ switch (op->examine) {
+ case 1:
+ start = 0x80;
+ break;
+ case 2:
+ start = 0x0;
+ break;
+ default:
+ start = 0xc0;
+ break;
+ }
+ if (start > max_pn) { /* swap them around */
+ k = start;
+ start = max_pn;
+ max_pn = k;
+ }
+ for (k = start; k <= max_pn; ++k) {
+ op->vpd_pn = k;
+ if (first)
+ first = false;
+ else if (got_one) {
+ sgj_pr_hr(jsp, "\n");
+ got_one = false;
+ }
+ if (op->do_long)
+ snprintf(b, sizeof(b), "[0x%x] ", k);
+ else
+ b[0] = '\0';
+ res = svpd_decode_t10(sg_fd, op, jop, 0, 0, b);
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(sg_fd, op, jop, 0);
+ if (SG_LIB_CAT_OTHER == res)
+ res = svpd_unable_to_decode(sg_fd, op, 0, 0);
+ }
+ if (! op->do_quiet) {
+ if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("fetching VPD page failed, aborted command\n");
+ else if (res && (SG_LIB_CAT_ILLEGAL_REQ != res)) {
+ /* SG_LIB_CAT_ILLEGAL_REQ expected as well examine all */
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("fetching VPD page failed: %s\n", b);
+ }
+ }
+ if (res && (SG_LIB_CAT_ILLEGAL_REQ != res))
+ any_err = res;
+ if (0 == res)
+ got_one = true;
+ }
+ return any_err;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json;
+ int c, res, matches;
+ int sg_fd = -1;
+ int inhex_len = 0;
+ int inraw_len = 0;
+ int ret = 0;
+ int subvalue = 0;
+ const char * cp;
+ sgj_state * jsp;
+ sgj_opaque_p jop = NULL;
+ const struct svpd_values_name_t * vnp;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op = &opts;
+
+ op->invoker = SG_VPD_INV_SG_VPD;
+ dup_sanity_chk((int)sizeof(opts), (int)sizeof(*vnp));
+ op->vend_prod_num = -1;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aeEfhHiI:j::lm:M:p:qQ:rvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ op->do_all = true;
+ break;
+ case 'e':
+ op->do_enum = true;
+ break;
+ case 'E':
+ ++op->examine;
+ op->examine_given = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'i':
+ ++op->do_ident;
+ break;
+ case 'I':
+ if (op->inhex_fn) {
+ pr2serr("only one '--inhex=' option permitted\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->inhex_fn = optarg;
+ break;
+ case 'j':
+ if (! sgj_init_state(&op->json_st, optarg)) {
+ int bad_char = op->json_st.first_bad_char;
+ char e[1500];
+
+ if (bad_char) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n\n", bad_char);
+ }
+ sg_json_usage(0, e, sizeof(e));
+ pr2serr("%s", e);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'l':
+ op->do_long = true;
+ break;
+ case 'm':
+ op->maxlen = sg_get_num(optarg);
+ if ((op->maxlen < 0) || (op->maxlen > MX_ALLOC_LEN)) {
+ pr2serr("argument to '--maxlen' should be %d or less\n",
+ MX_ALLOC_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((op->maxlen > 0) && (op->maxlen < MIN_MAXLEN)) {
+ pr2serr("Warning: overriding '--maxlen' < %d, using "
+ "default\n", MIN_MAXLEN);
+ op->maxlen = 0;
+ }
+ break;
+ case 'M':
+ if (op->vend_prod) {
+ pr2serr("only one '--vendor=' option permitted\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->vend_prod = optarg;
+ break;
+ case 'p':
+ if (op->page_str) {
+ pr2serr("only one '--page=' option permitted\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else
+ op->page_str = optarg;
+ op->page_given = true;
+ break;
+ case 'q':
+ op->do_quiet = true;
+ break;
+ case 'Q':
+ op->sinq_inraw_fn = optarg;
+ break;
+ case 'r':
+ ++op->do_raw;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ jsp = &op->json_st;
+ if (op->do_enum) {
+ if (op->device_name)
+ pr2serr("Device name %s ignored when --enumerate given\n",
+ op->device_name);
+ if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0])) {
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 10)) {
+ pr2serr("Bad vendor/product number after '--vendor=' "
+ "option\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ op->vend_prod_num = svpd_find_vp_num_by_acron(op->vend_prod);
+ if (op->vend_prod_num < 0) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ svpd_enumerate_vendor(op->vend_prod_num);
+ return 0;
+ }
+ if (op->page_str) {
+ if ((0 == strcmp("-1", op->page_str)) ||
+ (0 == strcmp("-2", op->page_str)))
+ op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
+ else if (isdigit((uint8_t)op->page_str[0])) {
+ op->vpd_pn = sg_get_num_nomult(op->page_str);
+ if ((op->vpd_pn < 0) || (op->vpd_pn > 255)) {
+ pr2serr("Bad page code value after '-p' option\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ pr2serr("with --enumerate only search using VPD page "
+ "numbers\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ matches = count_standard_vpds(op->vpd_pn);
+ if (0 == matches)
+ matches = svpd_count_vendor_vpds(op->vpd_pn,
+ op->vend_prod_num);
+ if (0 == matches)
+ sgj_pr_hr(jsp, "No matches found for VPD page number 0x%x\n",
+ op->vpd_pn);
+ } else { /* enumerate standard then vendor VPD pages */
+ sgj_pr_hr(jsp, "Standard VPD pages:\n");
+ enumerate_vpds(1, 1);
+ }
+ return 0;
+ }
+
+ as_json = jsp->pr_as_json;
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (op->page_str) {
+ if ('-' == op->page_str[0])
+ op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
+ else if (isalpha((uint8_t)op->page_str[0])) {
+ vnp = sdp_find_vpd_by_acron(op->page_str);
+ if (NULL == vnp) {
+ vnp = svpd_find_vendor_by_acron(op->page_str);
+ if (NULL == vnp) {
+ if (0 == strcmp("stdinq", op->page_str)) {
+ vnp = sdp_find_vpd_by_acron("sinq");
+ } else {
+ pr2serr("abbreviation doesn't match a VPD page\n");
+ sgj_pr_hr(jsp, "Available standard VPD pages:\n");
+ enumerate_vpds(1, 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ }
+ }
+ op->vpd_pn = vnp->value;
+ subvalue = vnp->subvalue;
+ op->vend_prod_num = subvalue;
+ } else {
+ cp = strchr(op->page_str, ',');
+ if (cp && op->vend_prod) {
+ pr2serr("the --page=pg,vp and the --vendor=vp forms overlap, "
+ "choose one or the other\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ op->vpd_pn = sg_get_num_nomult(op->page_str);
+ if ((op->vpd_pn < 0) || (op->vpd_pn > 255)) {
+ pr2serr("Bad page code value after '-p' option\n");
+ sgj_pr_hr(jsp, "Available standard VPD pages:\n");
+ enumerate_vpds(1, 1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ if (cp) {
+ if (isdigit((uint8_t)*(cp + 1)))
+ op->vend_prod_num = sg_get_num_nomult(cp + 1);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(cp + 1);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after comma in '-p' "
+ "option\n");
+ if (op->vend_prod_num < 0)
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ subvalue = op->vend_prod_num;
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num =
+ svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ subvalue = op->vend_prod_num;
+ }
+ }
+ if (op->verbose > 3)
+ pr2serr("'--page=' matched pn=%d [0x%x], subvalue=%d\n",
+ op->vpd_pn, op->vpd_pn, subvalue);
+ } else if (op->vend_prod) {
+ if (isdigit((uint8_t)op->vend_prod[0]))
+ op->vend_prod_num = sg_get_num_nomult(op->vend_prod);
+ else
+ op->vend_prod_num = svpd_find_vp_num_by_acron(op->vend_prod);
+ if ((op->vend_prod_num < 0) || (op->vend_prod_num > 255)) {
+ pr2serr("Bad vendor/product acronym after '--vendor=' "
+ "option\n");
+ svpd_enumerate_vendor(-1);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto fini;
+ }
+ subvalue = op->vend_prod_num;
+ }
+
+ rsp_buff = sg_memalign(rsp_buff_sz, 0 /* page align */, &free_rsp_buff,
+ false);
+ if (NULL == rsp_buff) {
+ pr2serr("Unable to allocate %d bytes on heap\n", rsp_buff_sz);
+ ret = sg_convert_errno(ENOMEM);
+ goto fini;
+ }
+ if (op->sinq_inraw_fn) {
+ if ((ret = sg_f2hex_arr(op->sinq_inraw_fn, true, false, rsp_buff,
+ &inraw_len, rsp_buff_sz))) {
+ goto err_out;
+ }
+ if (inraw_len < 36) {
+ pr2serr("Unable to read 36 or more bytes from %s\n",
+ op->sinq_inraw_fn);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ memcpy(op->std_inq_a, rsp_buff, 36);
+ op->std_inq_a_valid = true;
+ }
+ if (op->inhex_fn) {
+ if (op->device_name) {
+ pr2serr("Cannot have both a DEVICE and --inhex= option\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if ((ret = sg_f2hex_arr(op->inhex_fn, !!op->do_raw, false, rsp_buff,
+ &inhex_len, rsp_buff_sz))) {
+ goto err_out;
+ }
+ if (op->verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n", inhex_len,
+ inhex_len);
+ if (op->verbose > 3)
+ hex2stderr(rsp_buff, inhex_len, 0);
+ op->do_raw = 0; /* don't want raw on output with --inhex= */
+ if ((NULL == op->page_str) && (! op->do_all)) {
+ /* may be able to deduce VPD page */
+ if ((0x2 == (0xf & rsp_buff[3])) && (rsp_buff[2] > 2)) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex= this is a standard "
+ "INQUIRY\n");
+ } else if (rsp_buff[2] <= 2) {
+ if (op->verbose)
+ pr2serr("Guessing from --inhex this is VPD page 0x%x\n",
+ rsp_buff[1]);
+ op->vpd_pn = rsp_buff[1];
+ } else {
+ if (op->vpd_pn > 0x80) {
+ op->vpd_pn = rsp_buff[1];
+ if (op->verbose)
+ pr2serr("Guessing from --inhex this is VPD page "
+ "0x%x\n", rsp_buff[1]);
+ } else {
+ op->vpd_pn = VPD_NOPE_WANT_STD_INQ;
+ if (op->verbose)
+ pr2serr("page number unclear from --inhex, hope "
+ "it's a standard INQUIRY response\n");
+ }
+ }
+ }
+ } else if ((NULL == op->device_name) && (! op->std_inq_a_valid)) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage();
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+
+ if (op->do_raw && op->do_hex) {
+ pr2serr("Can't do hex and raw at the same time\n");
+ usage();
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->do_ident) {
+ op->vpd_pn = VPD_DEVICE_ID;
+ if (op->do_ident > 1) {
+ if (! op->do_long)
+ op->do_quiet = true;
+ subvalue = VPD_DI_SEL_LU;
+ }
+ }
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ }
+
+ if (op->inhex_fn) {
+ if ((0 == op->maxlen) || (inhex_len < op->maxlen))
+ op->maxlen = inhex_len;
+ if (op->do_all || op->page_given)
+ res = svpd_decode_all(-1, op, jop);
+ else {
+ res = svpd_decode_t10(-1, op, jop, subvalue, 0, NULL);
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(-1, op, jop, 0);
+ if (SG_LIB_CAT_OTHER == res)
+ res = svpd_unable_to_decode(-1, op, subvalue, 0);
+ }
+ }
+ ret = res;
+ goto err_out;
+ } else if (op->std_inq_a_valid && (NULL == op->device_name)) {
+ /* nothing else to do ... */
+ /* --sinq_inraw=RFN contents still in rsp_buff */
+ if (op->do_raw)
+ dStrRaw(rsp_buff, inraw_len);
+ else if (op->do_hex) {
+ if (! op->do_quiet && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "Standard Inquiry data format:\n");
+ hex2stdout(rsp_buff, inraw_len, (1 == op->do_hex) ? 0 : -1);
+ } else
+ std_inq_decode(rsp_buff, inraw_len, op, jop);
+ ret = 0;
+ goto fini;
+ }
+
+ if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
+ op->verbose)) < 0) {
+ if (op->verbose > 0)
+ pr2serr("error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ if (ret < 0)
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+
+ if (op->examine_given) {
+ ret = svpd_examine_all(sg_fd, op, jop);
+ } else if (op->do_all)
+ ret = svpd_decode_all(sg_fd, op, jop);
+ else {
+ memset(rsp_buff, 0, rsp_buff_sz);
+
+ res = svpd_decode_t10(sg_fd, op, jop, subvalue, 0, NULL);
+ if (SG_LIB_CAT_OTHER == res) {
+ res = svpd_decode_vendor(sg_fd, op, jop, 0);
+ if (SG_LIB_CAT_OTHER == res)
+ res = svpd_unable_to_decode(sg_fd, op, subvalue, 0);
+ }
+ if (! op->do_quiet) {
+ if (SG_LIB_CAT_ABORTED_COMMAND == res)
+ pr2serr("fetching VPD page failed, aborted command\n");
+ else if (res) {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("fetching VPD page failed: %s\n", b);
+ }
+ }
+ ret = res;
+ }
+err_out:
+ if (free_rsp_buff)
+ free(free_rsp_buff);
+ if ((0 == op->verbose) && (! op->do_quiet)) {
+ if (! sg_if_can2stderr("sg_vpd failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+fini:
+ res = (sg_fd >= 0) ? sg_cmds_close_device(sg_fd) : 0;
+
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == op->do_hex)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
diff --git a/src/sg_vpd_common.c b/src/sg_vpd_common.c
new file mode 100644
index 00000000..4ec58020
--- /dev/null
+++ b/src/sg_vpd_common.c
@@ -0,0 +1,3501 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#include "sg_vpd_common.h"
+
+/* This file holds common code for sg_inq and sg_vpd as both those utilities
+ * decode SCSI VPD pages. */
+
+const char * t10_vendor_id_hr = "T10_vendor_identification";
+const char * t10_vendor_id_js = "t10_vendor_identification";
+const char * product_id_hr = "Product_identification";
+const char * product_id_js = "product_identification";
+const char * product_rev_lev_hr = "Product_revision_level";
+const char * product_rev_lev_js = "product_revision_level";
+static const char * const y_s = "yes";
+static const char * const n_s = "no";
+static const char * const nl_s = "no limit";
+static const char * const nlr_s = "no limit reported";
+/* Earlier gcc compilers (e.g. 6.4) don't accept this first form when it is
+ * used in another array of strings initialization (e.g. bdc_zoned_strs) */
+// static const char * const nr_s = "not reported";
+static char nr_s[] = "not reported";
+static const char * const ns_s = "not supported";
+// static const char * const rsv_s = "Reserved";
+static char rsv_s[] = "Reserved";
+static const char * const vs_s = "Vendor specific";
+static const char * const null_s = "";
+static const char * const mn_s = "meaning";
+
+/* Supported vendor specific VPD pages */
+/* Arrange in alphabetical order by acronym */
+struct svpd_vp_name_t vp_arr[] = {
+ {VPD_VP_DDS, "dds", "DDS tape family from IBM"},
+ {VPD_VP_EMC, "emc", "EMC (company)"},
+ {VPD_VP_WDC_HITACHI, "hit", "WDC/Hitachi disk"},
+ {VPD_VP_HP3PAR, "hp3par", "3PAR array (HP was Left Hand)"},
+ {VPD_VP_HP_LTO, "hp_lto", "HP LTO tape/systems"},
+ {VPD_VP_IBM_LTO, "ibm_lto", "IBM LTO tape/systems"},
+ {VPD_VP_NVME, "nvme", "NVMe related"},
+ {VPD_VP_RDAC, "rdac", "RDAC array (NetApp E-Series)"},
+ {VPD_VP_SEAGATE, "sea", "Seagate disk"},
+ {VPD_VP_SG, "sg", "sg3_utils extensions"},
+ {VPD_VP_WDC_HITACHI, "wdc", "WDC/Hitachi disk"},
+ {0, NULL, NULL},
+};
+
+/* Supported vendor specific VPD pages */
+/* 'subvalue' holds vendor/product number to disambiguate */
+/* Arrange in alphabetical order by acronym */
+struct svpd_values_name_t vendor_vpd_pg[] = {
+ {VPD_V_ACI_LTO, VPD_VP_HP_LTO, 1, "aci", "ACI revision level (HP LTO)"},
+ {VPD_V_DATC_SEA, VPD_VP_SEAGATE, 0, "datc", "Date code (Seagate)"},
+ {VPD_V_DCRL_LTO, VPD_VP_IBM_LTO, 1, "dcrl", "Drive component revision "
+ "levels (IBM LTO)"},
+ {VPD_V_FVER_DDS, VPD_VP_DDS, 1, "ddsver", "Firmware revision (DDS)"},
+ {VPD_V_DEV_BEH_SEA, VPD_VP_SEAGATE, 0, "devb", "Device behavior "
+ "(Seagate)"},
+ {VPD_V_DSN_LTO, VPD_VP_IBM_LTO, 1, "dsn", "Drive serial numbers (IBM "
+ "LTO)"},
+ {VPD_V_DUCD_LTO, VPD_VP_IBM_LTO, 1, "ducd", "Device unique "
+ "configuration data (IBM LTO)"},
+ {VPD_V_EDID_RDAC, VPD_VP_RDAC, 0, "edid", "Extended device "
+ "identification (RDAC)"},
+ {VPD_V_FIRM_SEA, VPD_VP_SEAGATE, 0, "firm", "Firmware numbers "
+ "(Seagate)"},
+ {VPD_V_FVER_LTO, VPD_VP_HP_LTO, 0, "frl", "Firmware revision level "
+ "(HP LTO)"},
+ {VPD_V_FVER_RDAC, VPD_VP_RDAC, 0, "fwr4", "Firmware version (RDAC)"},
+ {VPD_V_HEAD_LTO, VPD_VP_HP_LTO, 1, "head", "Head Assy revision level "
+ "(HP LTO)"},
+ {VPD_V_HP3PAR, VPD_VP_HP3PAR, 0, "hp3par", "Volume information "
+ "(HP/3PAR)"},
+ {VPD_V_HVER_LTO, VPD_VP_HP_LTO, 1, "hrl", "Hardware revision level "
+ "(HP LTO)"},
+ {VPD_V_HVER_RDAC, VPD_VP_RDAC, 0, "hwr4", "Hardware version (RDAC)"},
+ {VPD_V_JUMP_SEA, VPD_VP_SEAGATE, 0, "jump", "Jump setting (Seagate)"},
+ {VPD_V_MECH_LTO, VPD_VP_HP_LTO, 1, "mech", "Mechanism revision level "
+ "(HP LTO)"},
+ {VPD_V_MPDS_LTO, VPD_VP_IBM_LTO, 1, "mpds", "Mode parameter default "
+ "settings (IBM LTO)"},
+ {SG_NVME_VPD_NICR, VPD_VP_SG, 0, "nicr",
+ "NVMe Identify Controller Response (sg3_utils)"},
+ {VPD_V_PCA_LTO, VPD_VP_HP_LTO, 1, "pca", "PCA revision level (HP LTO)"},
+ {VPD_V_FEAT_RDAC, VPD_VP_RDAC, 0, "prm4", "Feature Parameters (RDAC)"},
+ {VPD_V_RVSI_RDAC, VPD_VP_RDAC, 0, "rvsi", "Replicated volume source "
+ "identifier (RDAC)"},
+ {VPD_V_SAID_RDAC, VPD_VP_RDAC, 0, "said", "Storage array world wide "
+ "name (RDAC)"},
+ {VPD_V_SUBS_RDAC, VPD_VP_RDAC, 0, "subs", "Subsystem identifier (RDAC)"},
+ {VPD_V_SVER_RDAC, VPD_VP_RDAC, 0, "swr4", "Software version (RDAC)"},
+ {VPD_V_UPR_EMC, VPD_VP_EMC, 0, "upr", "Unit path report (EMC)"},
+ {VPD_V_VAC_RDAC, VPD_VP_RDAC, 0, "vac1", "Volume access control (RDAC)"},
+ {VPD_V_HIT_PG3, VPD_VP_WDC_HITACHI, 0, "wp3", "Page 0x3 (WDC/Hitachi)"},
+ {VPD_V_HIT_PG_D1, VPD_VP_WDC_HITACHI, 0, "wpd1",
+ "Page 0xd1 (WDC/Hitachi)"},
+ {VPD_V_HIT_PG_D2, VPD_VP_WDC_HITACHI, 0, "wpd2",
+ "Page 0xd2 (WDC/Hitachi)"},
+ {0, 0, 0, NULL, NULL},
+};
+
+
+int
+no_ascii_4hex(const struct opts_t * op)
+{
+ if (op->do_hex < 2)
+ return 1;
+ else if (2 == op->do_hex)
+ return 0;
+ else
+ return -1;
+}
+
+int
+svpd_find_vp_num_by_acron(const char * vp_ap)
+{
+ size_t len;
+ const struct svpd_vp_name_t * vpp;
+
+ for (vpp = vp_arr; vpp->acron; ++vpp) {
+ len = strlen(vpp->acron);
+ if (0 == strncmp(vpp->acron, vp_ap, len))
+ return vpp->vend_prod_num;
+ }
+ return -1;
+}
+
+/* if vend_prod_num < -1 then list vendor_product ids + vendor pages, =-1
+ * list only vendor_product ids, else list pages for that vend_prod_num */
+void
+svpd_enumerate_vendor(int vend_prod_num)
+{
+ bool seen;
+ const struct svpd_vp_name_t * vpp;
+ const struct svpd_values_name_t * vnp;
+
+ if (vend_prod_num < 0) {
+ for (seen = false, vpp = vp_arr; vpp->acron; ++vpp) {
+ if (vpp->name) {
+ if (! seen) {
+ printf("\nVendor/product identifiers:\n");
+ seen = true;
+ }
+ printf(" %-10s %d %s\n", vpp->acron,
+ vpp->vend_prod_num, vpp->name);
+ }
+ }
+ }
+ if (-1 == vend_prod_num)
+ return;
+ for (seen = false, vnp = vendor_vpd_pg; vnp->acron; ++vnp) {
+ if ((vend_prod_num >= 0) && (vend_prod_num != vnp->subvalue))
+ continue;
+ if (vnp->name) {
+ if (! seen) {
+ printf("\nVendor specific VPD pages:\n");
+ seen = true;
+ }
+ printf(" %-10s 0x%02x,%d %s\n", vnp->acron,
+ vnp->value, vnp->subvalue, vnp->name);
+ }
+ }
+}
+
+/* mxlen is command line --maxlen=LEN option (def: 0) or -1 for a VPD page
+ * with a short length (1 byte). Returns 0 for success. */
+int /* global: use by sg_vpd_vendor.c */
+vpd_fetch_page(int sg_fd, uint8_t * rp, int page, int mxlen, bool qt,
+ int vb, int * rlenp)
+{
+ int res, resid, rlen, len, n;
+
+ if (sg_fd < 0) {
+ len = sg_get_unaligned_be16(rp + 2) + 4;
+ if (vb && (len > mxlen))
+ pr2serr("warning: VPD page's length (%d) > bytes in --inhex=FN "
+ "file (%d)\n", len , mxlen);
+ if (rlenp)
+ *rlenp = (len < mxlen) ? len : mxlen;
+ return 0;
+ }
+ if (mxlen > MX_ALLOC_LEN) {
+ pr2serr("--maxlen=LEN too long: %d > %d\n", mxlen, MX_ALLOC_LEN);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ n = (mxlen > 0) ? mxlen : DEF_ALLOC_LEN;
+ res = sg_ll_inquiry_v2(sg_fd, true, page, rp, n, DEF_PT_TIMEOUT, &resid,
+ ! qt, vb);
+ if (res)
+ return res;
+ rlen = n - resid;
+ if (rlen < 4) {
+ pr2serr("VPD response too short (len=%d)\n", rlen);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (page != rp[1]) {
+ pr2serr("invalid VPD response; probably a STANDARD INQUIRY "
+ "response\n");
+ n = (rlen < 32) ? rlen : 32;
+ if (vb) {
+ pr2serr("First %d bytes of bad response\n", n);
+ hex2stderr(rp, n, 0);
+ }
+ return SG_LIB_CAT_MALFORMED;
+ } else if ((0x80 == page) && (0x2 == rp[2]) && (0x2 == rp[3])) {
+ /* could be a Unit Serial number VPD page with a very long
+ * length of 4+514 bytes; more likely standard response for
+ * SCSI-2, RMB=1 and a response_data_format of 0x2. */
+ pr2serr("invalid Unit Serial Number VPD response; probably a "
+ "STANDARD INQUIRY response\n");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (mxlen < 0)
+ len = rp[3] + 4;
+ else
+ len = sg_get_unaligned_be16(rp + 2) + 4;
+ if (len <= rlen) {
+ if (rlenp)
+ *rlenp = len;
+ return 0;
+ } else if (mxlen) {
+ if (rlenp)
+ *rlenp = rlen;
+ return 0;
+ }
+ if (len > MX_ALLOC_LEN) {
+ pr2serr("response length too long: %d > %d\n", len, MX_ALLOC_LEN);
+ return SG_LIB_CAT_MALFORMED;
+ } else {
+ res = sg_ll_inquiry_v2(sg_fd, true, page, rp, len, DEF_PT_TIMEOUT,
+ &resid, ! qt, vb);
+ if (res)
+ return res;
+ rlen = len - resid;
+ /* assume it is well behaved: hence page and len still same */
+ if (rlenp)
+ *rlenp = rlen;
+ return 0;
+ }
+}
+
+sgj_opaque_p
+sg_vpd_js_hdr(sgj_state * jsp, sgj_opaque_p jop, const char * name,
+ const uint8_t * vpd_hdrp)
+{
+ int pdt = vpd_hdrp[0] & PDT_MASK;
+ int pqual = (vpd_hdrp[0] & 0xe0) >> 5;
+ int pn = vpd_hdrp[1];
+ const char * pdt_str;
+ sgj_opaque_p jo2p = sgj_snake_named_subobject_r(jsp, jop, name);
+ char d[64];
+
+ pdt_str = sg_get_pdt_str(pdt, sizeof(d), d);
+ sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_qualifier",
+ pqual, NULL, pqual_str(pqual));
+ sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type",
+ pdt, NULL, pdt_str);
+ sgj_js_nv_ihex(jsp, jo2p, "page_code", pn);
+ return jo2p;
+}
+
+const char *
+pqual_str(int pqual)
+{
+ switch (pqual) {
+ case 0:
+ return "LU accessible";
+ case 1:
+ return "LU temporarily unavailable";
+ case 3:
+ return "LU not accessible via this port";
+ default:
+ return "value reserved by T10";
+ }
+}
+
+static const char * network_service_type_arr[] =
+{
+ "unspecified",
+ "storage configuration service",
+ "diagnostics",
+ "status",
+ "logging",
+ "code download",
+ "copy service",
+ "administrative configuration service",
+ "reserved[0x8]", "reserved[0x9]",
+ "reserved[0xa]", "reserved[0xb]", "reserved[0xc]", "reserved[0xd]",
+ "reserved[0xe]", "reserved[0xf]", "reserved[0x10]", "reserved[0x11]",
+ "reserved[0x12]", "reserved[0x13]", "reserved[0x14]", "reserved[0x15]",
+ "reserved[0x16]", "reserved[0x17]", "reserved[0x18]", "reserved[0x19]",
+ "reserved[0x1a]", "reserved[0x1b]", "reserved[0x1c]", "reserved[0x1d]",
+ "reserved[0x1e]", "reserved[0x1f]",
+};
+
+/* VPD_MAN_NET_ADDR 0x85 ["mna"] */
+void
+decode_net_man_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, na_len, assoc, nst;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const uint8_t * bp;
+ const char * assoc_str;
+ const char * nst_str;
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("Management network addresses VPD page length too short=%d\n",
+ len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ assoc = (bp[0] >> 5) & 0x3;
+ assoc_str = sg_get_desig_assoc_str(assoc);
+ nst = bp[0] & 0x1f;
+ nst_str = network_service_type_arr[nst];
+ sgj_pr_hr(jsp, " %s, Service type: %s\n", assoc_str, nst_str);
+ na_len = sg_get_unaligned_be16(bp + 2);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihexstr(jsp, jo2p, "association", assoc, NULL,
+ assoc_str);
+ sgj_js_nv_ihexstr(jsp, jo2p, "service_type", nst, NULL,
+ nst_str);
+ sgj_js_nv_s_len(jsp, jo2p, "network_address",
+ (const char *)(bp + 4), na_len);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if (na_len > 0) {
+ if (op->do_hex > 1) {
+ sgj_pr_hr(jsp, " Network address:\n");
+ hex2stdout((bp + 4), na_len, 0);
+ } else
+ sgj_pr_hr(jsp, " %s\n", bp + 4);
+ }
+ bump = 4 + na_len;
+ if ((k + bump) > len) {
+ pr2serr("Management network addresses VPD page, short "
+ "descriptor length=%d, left=%d\n", bump, (len - k));
+ return;
+ }
+ }
+}
+
+/* VPD_EXT_INQ Extended Inquiry VPD ["ei"] */
+void
+decode_x_inq_vpd(const uint8_t * b, int len, bool protect, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool do_long_nq = op->do_long && (! op->do_quiet);
+ int n;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const char * cp;
+ const char * np;
+ const char * nex_p;
+ char d[128];
+ static const int dlen = sizeof(d);
+
+ if (len < 7) {
+ pr2serr("Extended INQUIRY data VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(b, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (do_long_nq || jsp->pr_as_json) {
+ n = (b[4] >> 6) & 0x3;
+ if (1 == n)
+ cp = "before final WRITE BUFFER";
+ else if (2 == n)
+ cp = "after power on or hard reset";
+ else {
+ cp = "none";
+ d[0] = '\0';
+ }
+ if (cp[0])
+ snprintf(d, dlen, " [%s]", cp);
+ sgj_pr_hr(jsp, " ACTIVATE_MICROCODE=%d%s\n", n, d);
+ sgj_js_nv_ihexstr(jsp, jop, "activate_microcode", n, NULL, cp);
+ n = (b[4] >> 3) & 0x7;
+ if (protect) {
+ switch (n)
+ {
+ case 0:
+ cp = "protection type 1 supported";
+ break;
+ case 1:
+ cp = "protection types 1 and 2 supported";
+ break;
+ case 2:
+ cp = "protection type 2 supported";
+ break;
+ case 3:
+ cp = "protection types 1 and 3 supported";
+ break;
+ case 4:
+ cp = "protection type 3 supported";
+ break;
+ case 5:
+ cp = "protection types 2 and 3 supported";
+ break;
+ case 6:
+ cp = "see Supported block lengths and protection types "
+ "VPD page";
+ break;
+ case 7:
+ cp = "protection types 1, 2 and 3 supported";
+ break;
+ }
+ } else if (op->protect_not_sure) {
+ cp = "Unsure because unable to read PROTECT bit in standard "
+ "INQUIRY response";
+ d[0] = '\0';
+ } else {
+ cp = "none";
+ d[0] = '\0';
+ }
+ if (cp[0])
+ snprintf(d, dlen, " [%s]", cp);
+ sgj_pr_hr(jsp, " SPT=%d%s\n", n, d);
+ sgj_js_nv_ihexstr_nex(jsp, jop, "spt", n, false, NULL,
+ cp, "Supported Protection Type");
+ sgj_haj_vi_nex(jsp, jop, 2, "GRD_CHK", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[4] & 0x4), false, "guard check");
+ sgj_haj_vi_nex(jsp, jop, 2, "APP_CHK", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[4] & 0x2), false, "application tag check");
+ sgj_haj_vi_nex(jsp, jop, 2, "REF_CHK", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[4] & 0x1), false, "reference tag check");
+ sgj_haj_vi_nex(jsp, jop, 2, "UASK_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x20), false, "Unit Attention "
+ "condition Sense Key specific data Supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "GROUP_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x10), false, "grouping function supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "PRIOR_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x8), false, "priority supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "HEADSUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x4), false, "head of queue supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "ORDSUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x2), false, "ordered (task attribute) "
+ "supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "SIMPSUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[5] & 0x1), false, "simple (task attribute) "
+ "supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "WU_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[6] & 0x8), false, "Write uncorrectable "
+ "supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "CRD_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[6] & 0x4), false, "Correction disable "
+ "supported (obsolete SPC-5)");
+ sgj_haj_vi_nex(jsp, jop, 2, "NV_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[6] & 0x2), false, "Nonvolatile cache "
+ "supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "V_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[6] & 0x1), false, "Volatile cache supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "NO_PI_CHK", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[7] & 0x20), false, "No protection "
+ "information checking"); /* spc5r02 */
+ sgj_haj_vi_nex(jsp, jop, 2, "P_I_I_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[7] & 0x10), false, "Protection information "
+ "interval supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "LUICLR", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[7] & 0x1), false, "Logical unit I_T nexus clear");
+ np = "LU_COLL_TYPE";
+ n = (b[8] >> 5) & 0x7;
+ nex_p = "Logical unit collection type";
+ if (jsp && (jsp->pr_string)) {
+ switch (n) {
+ case 0:
+ cp = "not reported";
+ break;
+ case 1:
+ cp = "Conglomerate";
+ break;
+ case 2:
+ cp = "Logical unit group";
+ break;
+ default:
+ cp = rsv_s;
+ break;
+ }
+ jo2p = sgj_haj_subo_r(jsp, jop, 2, np, SGJ_SEP_EQUAL_NO_SPACE,
+ n, false);
+ sgj_js_nv_s(jsp, jo2p, mn_s, cp);
+ if (jsp->pr_name_ex)
+ sgj_js_nv_s(jsp, jo2p, "abbreviated_name_expansion", nex_p);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, np, SGJ_SEP_EQUAL_NO_SPACE, n,
+ true, nex_p);
+
+ sgj_haj_vi_nex(jsp, jop, 2, "R_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[8] & 0x10), false, "Referrals supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "RTD_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[8] & 0x8), false,
+ "Revert to defaults supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "HSSRELEF", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[8] & 0x2), false,
+ "History snapshots release effects");
+ sgj_haj_vi_nex(jsp, jop, 2, "CBCS", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[8] & 0x1), false, "Capability-based command "
+ "security (obsolete SPC-5)");
+ sgj_haj_vi(jsp, jop, 2, "Multi I_T nexus microcode download",
+ SGJ_SEP_EQUAL_NO_SPACE, b[9] & 0xf, true);
+ sgj_haj_vi(jsp, jop, 2, "Extended self-test completion minutes",
+ SGJ_SEP_EQUAL_NO_SPACE,
+ sg_get_unaligned_be16(b + 10), true);
+ sgj_haj_vi_nex(jsp, jop, 2, "POA_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[12] & 0x80), false,
+ "Power on activation supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "HRA_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[12] & 0x40), false,
+ "Hard reset activation supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "VSA_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[12] & 0x20), false,
+ "Vendor specific activation supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DMS_VALID", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[12] & 0x10), false,
+ "Download microcode support byte valid");
+ sgj_haj_vi(jsp, jop, 2, "Maximum supported sense data length",
+ SGJ_SEP_EQUAL_NO_SPACE, b[13], true);
+ sgj_haj_vi_nex(jsp, jop, 2, "IBS", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[14] & 0x80), false, "Implicit bind supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "IAS", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[14] & 0x40), false,
+ "Implicit affiliation supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "SAC", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[14] & 0x4), false,
+ "Set affiliation command supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "NRD1", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[14] & 0x2), false,
+ "No redirect one supported (BIND)");
+ sgj_haj_vi_nex(jsp, jop, 2, "NRD0", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[14] & 0x1), false,
+ "No redirect zero supported (BIND)");
+ sgj_haj_vi(jsp, jop, 2, "Maximum inquiry change logs",
+ SGJ_SEP_EQUAL_NO_SPACE,
+ sg_get_unaligned_be16(b + 15), true);
+ sgj_haj_vi(jsp, jop, 2, "Maximum mode page change logs",
+ SGJ_SEP_EQUAL_NO_SPACE,
+ sg_get_unaligned_be16(b + 17), true);
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_4", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x80), false,
+ "Download microcode mode 4 supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_5", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x40), false,
+ "Download microcode mode 5 supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_6", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x20), false,
+ "Download microcode mode 6 supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_7", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x10), false,
+ "Download microcode mode 7 supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_D", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x8), false,
+ "Download microcode mode 0xd supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_E", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x4), false,
+ "Download microcode mode 0xe supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "DM_MD_F", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(b[19] & 0x2), false,
+ "Download microcode mode 0xf supported");
+ if (do_long_nq || (! jsp->pr_out_hr))
+ return;
+ }
+ sgj_pr_hr(jsp, " ACTIVATE_MICROCODE=%d SPT=%d GRD_CHK=%d APP_CHK=%d "
+ "REF_CHK=%d\n", ((b[4] >> 6) & 0x3), ((b[4] >> 3) & 0x7),
+ !!(b[4] & 0x4), !!(b[4] & 0x2), !!(b[4] & 0x1));
+ sgj_pr_hr(jsp, " UASK_SUP=%d GROUP_SUP=%d PRIOR_SUP=%d HEADSUP=%d "
+ "ORDSUP=%d SIMPSUP=%d\n", !!(b[5] & 0x20), !!(b[5] & 0x10),
+ !!(b[5] & 0x8), !!(b[5] & 0x4), !!(b[5] & 0x2), !!(b[5] & 0x1));
+ sgj_pr_hr(jsp, " WU_SUP=%d [CRD_SUP=%d] NV_SUP=%d V_SUP=%d\n",
+ !!(b[6] & 0x8), !!(b[6] & 0x4), !!(b[6] & 0x2), !!(b[6] & 0x1));
+ sgj_pr_hr(jsp, " NO_PI_CHK=%d P_I_I_SUP=%d LUICLR=%d\n", !!(b[7] & 0x20),
+ !!(b[7] & 0x10), !!(b[7] & 0x1));
+ /* RTD_SUP added in spc5r11, LU_COLL_TYPE added in spc5r09,
+ * HSSRELEF added in spc5r02; CBCS obsolete in spc5r01 */
+ sgj_pr_hr(jsp, " LU_COLL_TYPE=%d R_SUP=%d RTD_SUP=%d HSSRELEF=%d "
+ "[CBCS=%d]\n", (b[8] >> 5) & 0x7, !!(b[8] & 0x10),
+ !!(b[8] & 0x8), !!(b[8] & 0x2), !!(b[8] & 0x1));
+ sgj_pr_hr(jsp, " Multi I_T nexus microcode download=%d\n", b[9] & 0xf);
+ sgj_pr_hr(jsp, " Extended self-test completion minutes=%d\n",
+ sg_get_unaligned_be16(b + 10)); /* spc4r27 */
+ sgj_pr_hr(jsp, " POA_SUP=%d HRA_SUP=%d VSA_SUP=%d DMS_VALID=%d\n",
+ !!(b[12] & 0x80), !!(b[12] & 0x40), !!(b[12] & 0x20),
+ !!(b[12] & 0x10)); /* spc5r20 */
+ sgj_pr_hr(jsp, " Maximum supported sense data length=%d\n",
+ b[13]); /* spc4r34 */
+ sgj_pr_hr(jsp, " IBS=%d IAS=%d SAC=%d NRD1=%d NRD0=%d\n",
+ !!(b[14] & 0x80), !!(b[14] & 0x40), !!(b[14] & 0x4),
+ !!(b[14] & 0x2), !!(b[14] & 0x1)); /* added in spc5r09 */
+ sgj_pr_hr(jsp, " Maximum inquiry change logs=%u\n",
+ sg_get_unaligned_be16(b + 15)); /* spc5r17 */
+ sgj_pr_hr(jsp, " Maximum mode page change logs=%u\n",
+ sg_get_unaligned_be16(b + 17)); /* spc5r17 */
+ sgj_pr_hr(jsp, " DM_MD_4=%d DM_MD_5=%d DM_MD_6=%d DM_MD_7=%d\n",
+ !!(b[19] & 0x80), !!(b[19] & 0x40), !!(b[19] & 0x20),
+ !!(b[19] & 0x10)); /* spc5r20 */
+ sgj_pr_hr(jsp, " DM_MD_D=%d DM_MD_E=%d DM_MD_F=%d\n",
+ !!(b[19] & 0x8), !!(b[19] & 0x4), !!(b[19] & 0x2));
+}
+
+/* VPD_SOFTW_INF_ID 0x84 */
+void
+decode_softw_inf_id(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jop;
+ uint64_t ieee_id;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ len -= 4;
+ buff += 4;
+ for ( ; len > 5; len -= 6, buff += 6) {
+ ieee_id = sg_get_unaligned_be48(buff + 0);
+ sgj_pr_hr(jsp, " IEEE identifier: 0x%" PRIx64 "\n", ieee_id);
+ if (jsp->pr_as_json) {
+ jop = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jop, "ieee_identifier", ieee_id);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jop);
+ }
+ }
+}
+
+static const char * mode_page_policy_arr[] =
+{
+ "shared",
+ "per target port",
+ "per initiator port",
+ "per I_T nexus",
+};
+
+/* VPD_MODE_PG_POLICY 0x87 ["mpp"] */
+void
+decode_mode_policy_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, n, bump, ppc, pspc;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const uint8_t * bp;
+ char b[128];
+ static const int blen = sizeof(b);
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("Mode page policy VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ bump = 4;
+ if ((k + bump) > len) {
+ pr2serr("Mode page policy VPD page, short "
+ "descriptor length=%d, left=%d\n", bump, (len - k));
+ return;
+ }
+ if (op->do_hex > 1)
+ hex2stdout(bp, 4, 1);
+ else {
+ n = 0;
+ ppc = (bp[0] & 0x3f);
+ pspc = bp[1];
+ n = sg_scnpr(b + n, blen - n, " Policy page code: 0x%x", ppc);
+ if (pspc)
+ n += sg_scnpr(b + n, blen - n, ", subpage code: 0x%x", pspc);
+ sgj_pr_hr(jsp, "%s\n", b);
+ if ((0 == k) && (0x3f == (0x3f & bp[0])) && (0xff == bp[1]))
+ sgj_pr_hr(jsp, " therefore the policy applies to all modes "
+ "pages and subpages\n");
+ sgj_pr_hr(jsp, " MLUS=%d, Policy: %s\n", !!(bp[2] & 0x80),
+ mode_page_policy_arr[bp[2] & 0x3]);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_ihex(jsp, jo2p, "policy_page_code", ppc);
+ sgj_js_nv_ihex(jsp, jo2p, "policy_subpage_code", pspc);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "mlus", !!(bp[2] & 0x80), false,
+ "Multiple logical units share");
+ sgj_js_nv_ihexstr(jsp, jo2p, "mode_page_policy", bp[2] & 0x3,
+ NULL, mode_page_policy_arr[bp[2] & 0x3]);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ }
+ }
+}
+
+/* VPD_POWER_CONDITION 0x8a ["pc"] */
+void
+decode_power_condition(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ sgj_state * jsp = &op->json_st;
+
+ if (len < 18) {
+ pr2serr("Power condition VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ sgj_pr_hr(jsp, " Standby_y=%d Standby_z=%d Idle_c=%d Idle_b=%d "
+ "Idle_a=%d\n", !!(buff[4] & 0x2), !!(buff[4] & 0x1),
+ !!(buff[5] & 0x4), !!(buff[5] & 0x2), !!(buff[5] & 0x1));
+ if (jsp->pr_as_json) {
+ sgj_js_nv_ihex(jsp, jop, "standby_y", !!(buff[4] & 0x2));
+ sgj_js_nv_ihex(jsp, jop, "standby_z", !!(buff[4] & 0x1));
+ sgj_js_nv_ihex(jsp, jop, "idle_c", !!(buff[5] & 0x4));
+ sgj_js_nv_ihex(jsp, jop, "idle_b", !!(buff[5] & 0x2));
+ sgj_js_nv_ihex(jsp, jop, "idle_a", !!(buff[5] & 0x1));
+ }
+ sgj_haj_vi_nex(jsp, jop, 2, "Stopped condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 6),
+ true, "unit: millisecond");
+ sgj_haj_vi_nex(jsp, jop, 2, "Standby_z condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 8),
+ true, "unit: millisecond");
+ sgj_haj_vi_nex(jsp, jop, 2, "Standby_y condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 10),
+ true, "unit: millisecond");
+ sgj_haj_vi_nex(jsp, jop, 2, "Idle_a condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 12),
+ true, "unit: millisecond");
+ sgj_haj_vi_nex(jsp, jop, 2, "Idle_b condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 14),
+ true, "unit: millisecond");
+ sgj_haj_vi_nex(jsp, jop, 2, "Idle_c condition recovery time",
+ SGJ_SEP_SPACE_1, sg_get_unaligned_be16(buff + 16),
+ true, "unit: millisecond");
+}
+
+int
+filter_json_dev_ids(uint8_t * buff, int len, int m_assoc, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int u, off, i_len;
+ sgj_opaque_p jo2p;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+
+ off = -1;
+ while ((u = sg_vpd_dev_id_iter(buff, len, &off, m_assoc, -1, -1)) == 0) {
+ bp = buff + off;
+ i_len = bp[3];
+ if ((off + i_len + 4) > len) {
+ pr2serr(" VPD page error: designator length longer than\n"
+ " remaining response length=%d\n", (len - off));
+ return SG_LIB_CAT_MALFORMED;
+ }
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_designation_descriptor(jsp, jo2p, bp, i_len + 4);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ if (-2 == u) {
+ pr2serr("VPD page error: short designator around offset %d\n", off);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ return 0;
+}
+
+/* VPD_ATA_INFO 0x89 ["ai"] */
+void
+decode_ata_info_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool do_long_nq = op->do_long && (! op->do_quiet);
+ int num, is_be, cc, n;
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ const char * ata_transp;
+ char b[512];
+ char d[80];
+ static const int blen = sizeof(b);
+ static const int dlen = sizeof(d);
+ static const char * sat_vip = "SAT Vendor identification";
+ static const char * sat_pip = "SAT Product identification";
+ static const char * sat_prlp = "SAT Product revision level";
+
+ if (len < 36) {
+ pr2serr("ATA information VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex && (2 != op->do_hex)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ memcpy(b, buff + 8, 8);
+ b[8] = '\0';
+ sgj_pr_hr(jsp, " %s: %s\n", sat_vip, b);
+ memcpy(b, buff + 16, 16);
+ b[16] = '\0';
+ sgj_pr_hr(jsp, " %s: %s\n", sat_pip, b);
+ memcpy(b, buff + 32, 4);
+ b[4] = '\0';
+ sgj_pr_hr(jsp, " %s: %s\n", sat_prlp, b);
+ if (len < 56)
+ return;
+ ata_transp = (0x34 == buff[36]) ? "SATA" : "PATA";
+ if (do_long_nq) {
+ sgj_pr_hr(jsp, " Device signature [%s] (in hex):\n", ata_transp);
+ hex2stdout(buff + 36, 20, 0);
+ } else
+ sgj_pr_hr(jsp, " Device signature indicates %s transport\n",
+ ata_transp);
+ cc = buff[56]; /* 0xec for IDENTIFY DEVICE and 0xa1 for IDENTIFY
+ * PACKET DEVICE (obsolete) */
+ n = sg_scnpr(b, blen, " Command code: 0x%x\n", cc);
+ if (len < 60)
+ return;
+ if (0xec == cc)
+ cp = null_s;
+ else if (0xa1 == cc)
+ cp = "PACKET ";
+ else
+ cp = NULL;
+ is_be = sg_is_big_endian();
+ if (cp) {
+ n += sg_scnpr(b + n, blen - n, " ATA command IDENTIFY %sDEVICE "
+ "response summary:\n", cp);
+ num = sg_ata_get_chars((const unsigned short *)(buff + 60), 27, 20,
+ is_be, d);
+ d[num] = '\0';
+ n += sg_scnpr(b + n, blen - n, " model: %s\n", d);
+ num = sg_ata_get_chars((const unsigned short *)(buff + 60), 10, 10,
+ is_be, d);
+ d[num] = '\0';
+ n += sg_scnpr(b + n, blen - n, " serial number: %s\n", d);
+ num = sg_ata_get_chars((const unsigned short *)(buff + 60), 23, 4,
+ is_be, d);
+ d[num] = '\0';
+ n += sg_scnpr(b + n, blen - n, " firmware revision: %s\n", d);
+ sgj_pr_hr(jsp, "%s", b);
+ if (do_long_nq)
+ sgj_pr_hr(jsp, " ATA command IDENTIFY %sDEVICE response in "
+ "hex:\n", cp);
+ } else if (do_long_nq)
+ sgj_pr_hr(jsp, " ATA command 0x%x got following response:\n",
+ (unsigned int)cc);
+ if (jsp->pr_as_json) {
+ sgj_convert_to_snake_name(sat_vip, d, dlen);
+ sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 8), 8);
+ sgj_convert_to_snake_name(sat_pip, d, dlen);
+ sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 16), 16);
+ sgj_convert_to_snake_name(sat_prlp, d, dlen);
+ sgj_js_nv_s_len(jsp, jop, d, (const char *)(buff + 32), 4);
+ sgj_js_nv_hex_bytes(jsp, jop, "ata_device_signature", buff + 36, 20);
+ sgj_js_nv_ihex(jsp, jop, "command_code", buff[56]);
+ sgj_js_nv_s(jsp, jop, "ata_identify_device_data_example",
+ "sg_vpd -p ai -HHH /dev/sdc | hdparm --Istdin");
+ }
+ if (len < 572)
+ return;
+ if (2 == op->do_hex)
+ hex2stdout((buff + 60), 512, 0);
+ else if (do_long_nq)
+ dWordHex((const unsigned short *)(buff + 60), 256, 0, is_be);
+}
+
+/* VPD_SCSI_FEATURE_SETS 0x92 ["sfs"] */
+void
+decode_feature_sets_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump;
+ uint16_t sf_code;
+ bool found;
+ const uint8_t * bp;
+ sgj_opaque_p jo2p;
+ sgj_state * jsp = &op->json_st;
+ char b[256];
+ char d[80];
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("SCSI Feature sets VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 8;
+ bp = buff + 8;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sf_code = sg_get_unaligned_be16(bp);
+ bump = 2;
+ if ((k + bump) > len) {
+ pr2serr("SCSI Feature sets, short descriptor length=%d, "
+ "left=%d\n", bump, (len - k));
+ return;
+ }
+ if (2 == op->do_hex)
+ hex2stdout(bp + 8, 2, 1);
+ else if (op->do_hex > 2)
+ hex2stdout(bp, 2, 1);
+ else {
+ sg_scnpr(b, sizeof(b), " %s",
+ sg_get_sfs_str(sf_code, -2, sizeof(d), d, &found,
+ op->verbose));
+ if (op->verbose == 1)
+ sgj_pr_hr(jsp, "%s [0x%x]\n", b, (unsigned int)sf_code);
+ else if (op->verbose > 1)
+ sgj_pr_hr(jsp, "%s [0x%x] found=%s\n", b,
+ (unsigned int)sf_code, found ? "true" : "false");
+ else
+ sgj_pr_hr(jsp, "%s\n", b);
+ sgj_js_nv_ihexstr(jsp, jo2p, "feature_set_code", sf_code, NULL,
+ d);
+ if (jsp->verbose)
+ sgj_js_nv_b(jsp, jo2p, "meaning_is_match", found);
+ }
+ sgj_js_nv_o(jsp, jap, NULL, jo2p);
+ }
+}
+
+static const char * constituent_type_arr[] = {
+ "Reserved",
+ "Virtual tape library",
+ "Virtual tape drive",
+ "Direct access block device",
+};
+
+/* VPD_DEVICE_CONSTITUENTS 0x8b ["dc"] */
+void
+decode_dev_constit_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap, recurse_vpd_decodep fp)
+{
+ uint16_t constit_type;
+ int k, j, res, bump, csd_len;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p, jo3p, ja2p;
+ const uint8_t * bp;
+ char b[256];
+ char d[64];
+ static const int blen = sizeof(b);
+ static const int dlen = sizeof(d);
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0, j = 0; k < len; k += bump, bp += bump, ++j) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ if (j > 0)
+ sgj_pr_hr(jsp, "\n");
+ sgj_pr_hr(jsp, " Constituent descriptor %d:\n", j + 1);
+ if ((k + 36) > len) {
+ pr2serr("short descriptor length=36, left=%d\n", (len - k));
+ sgj_js_nv_o(jsp, jap, NULL, jo2p);
+ return;
+ }
+ constit_type = sg_get_unaligned_be16(bp + 0);
+ if (constit_type >= SG_ARRAY_SIZE(constituent_type_arr))
+ sgj_pr_hr(jsp," Constituent type: unknown [0x%x]\n",
+ constit_type);
+ else
+ sgj_pr_hr(jsp, " Constituent type: %s [0x%x]\n",
+ constituent_type_arr[constit_type], constit_type);
+ sg_scnpr(b, blen, " Constituent device type: ");
+ if (0xff == bp[2])
+ sgj_pr_hr(jsp, "%sUnknown [0xff]\n", b);
+ else if (bp[2] >= 0x20)
+ sgj_pr_hr(jsp, "%s%s [0x%x]\n", b, rsv_s, bp[2]);
+ else
+ sgj_pr_hr(jsp, "%s%s [0x%x]\n", b,
+ sg_get_pdt_str(PDT_MASK & bp[2], dlen, d), bp[2]);
+ snprintf(b, blen, "%.8s", bp + 4);
+ sgj_pr_hr(jsp, " %s: %s\n", t10_vendor_id_hr, b);
+ sgj_js_nv_s(jsp, jo2p, t10_vendor_id_js, b);
+ snprintf(b, blen, "%.16s", bp + 12);
+ sgj_pr_hr(jsp, " %s: %s\n", product_id_hr, b);
+ sgj_js_nv_s(jsp, jo2p, product_id_js, b);
+ snprintf(b, blen, "%.4s", bp + 28);
+ sgj_pr_hr(jsp, " %s: %s\n", product_rev_lev_hr, b);
+ sgj_js_nv_s(jsp, jo2p, product_rev_lev_js, b);
+ csd_len = sg_get_unaligned_be16(bp + 34);
+ bump = 36 + csd_len;
+ if ((k + bump) > len) {
+ pr2serr("short descriptor length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL, jo2p);
+ return;
+ }
+ if (csd_len > 0) {
+ int m, q, cs_bump;
+ uint8_t cs_type;
+ uint8_t cs_len;
+ const uint8_t * cs_bp;
+
+ sgj_pr_hr(jsp, " Constituent specific descriptors:\n");
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "constituent_specific_descriptor_list");
+ for (m = 0, q = 0, cs_bp = bp + 36; m < csd_len;
+ m += cs_bump, ++q, cs_bp += cs_bump) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ cs_type = cs_bp[0];
+ cs_len = sg_get_unaligned_be16(cs_bp + 2);
+ cs_bump = cs_len + 4;
+ sgj_js_nv_ihex(jsp, jo3p, "constituent_specific_type",
+ cs_type);
+ if (1 == cs_type) { /* VPD page */
+ int off = cs_bp + 4 - buff;
+
+ sgj_pr_hr(jsp, " Constituent specific VPD page "
+ "%d:\n", q + 1);
+ /* SPC-5 says these shall _not_ themselves be Device
+ * Constituent VPD pages. So no infinite recursion. */
+ res = (*fp)(op, jo3p, off);
+ if (res)
+ pr2serr("%s: recurse_vpd_decode() failed, res=%d\n",
+ __func__, res);
+ } else {
+ if (0xff == cs_type)
+ sgj_pr_hr(jsp, " Vendor specific data (in "
+ "hex):\n");
+ else
+ sgj_pr_hr(jsp, " %s [0x%x] specific data (in "
+ "hex):\n", rsv_s, cs_type);
+ if (jsp->pr_as_json)
+ sgj_js_nv_hex_bytes(jsp, jo3p,
+ "constituent_specific_data_hex",
+ cs_bp + 4, cs_len);
+ else
+ hex2stdout(cs_bp + 4, cs_len, 0 /* plus ASCII */);
+ }
+ sgj_js_nv_o(jsp, ja2p, NULL, jo3p);
+ } /* end of Constituent specific descriptor loop */
+ }
+ sgj_js_nv_o(jsp, jap, NULL, jo2p);
+ } /* end Constituent descriptor loop */
+}
+
+/* VPD_CFA_PROFILE_INFO 0x8c ["cfa"] */
+void
+decode_cga_profile_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k;
+ uint32_t u;
+ sgj_state * jsp = &op->json_st;
+ const uint8_t * bp;
+ sgj_opaque_p jo2p;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += 4, bp += 4) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_haj_vi(jsp, jo2p, 0, "CGA profile supported",
+ SGJ_SEP_COLON_1_SPACE, bp[0], true);
+ u = sg_get_unaligned_be16(bp + 2);
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Sequential write data size",
+ SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* Assume index is less than 16 */
+static const char * sg_ansi_version_arr[16] =
+{
+ "no conformance claimed",
+ "SCSI-1", /* obsolete, ANSI X3.131-1986 */
+ "SCSI-2", /* obsolete, ANSI X3.131-1994 */
+ "SPC", /* withdrawn, ANSI INCITS 301-1997 */
+ "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */
+ "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */
+ "SPC-4", /* ANSI INCITS 513-2015 */
+ "SPC-5", /* ANSI INCITS 502-2020 */
+ "ecma=1, [8h]",
+ "ecma=1, [9h]",
+ "ecma=1, [Ah]",
+ "ecma=1, [Bh]",
+ "reserved [Ch]",
+ "reserved [Dh]",
+ "reserved [Eh]",
+ "reserved [Fh]",
+};
+
+static const char *
+hot_pluggable_str(int hp)
+{
+ switch (hp) {
+ case 0:
+ return "No information";
+ case 1:
+ return "target device designed to be removed from SCSI domain";
+ case 2:
+ return "target device not designed to be removed from SCSI domain";
+ default:
+ return "value reserved by T10";
+ }
+}
+
+static const char *
+tpgs_str(int tpgs)
+{
+ switch (tpgs) {
+ case 1:
+ return "only implicit asymmetric logical unit access";
+ case 2:
+ return "only explicit asymmetric logical unit access";
+ case 3:
+ return "both explicit and implicit asymmetric logical unit access";
+ case 0:
+ default:
+ return ns_s;
+ }
+}
+
+sgj_opaque_p
+std_inq_decode_js(const uint8_t * b, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int tpgs;
+ int pqual = (b[0] & 0xe0) >> 5;
+ int pdt = b[0] & PDT_MASK;
+ int hp = (b[1] >> 4) & 0x3;
+ int ver = b[2];
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ char c[256];
+ static const int clen = sizeof(c);
+
+ jo2p = sgj_named_subobject_r(jsp, jop, "standard_inquiry_data_format");
+ sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_qualifier", pqual, NULL,
+ pqual_str(pqual));
+ sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type", pdt, NULL,
+ sg_get_pdt_str(pdt, clen, c));
+ sgj_js_nv_ihex_nex(jsp, jo2p, "rmb", !!(b[1] & 0x80), false,
+ "Removable Medium Bit");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "lu_cong", !!(b[1] & 0x40), false,
+ "Logical Unit Conglomerate");
+ sgj_js_nv_ihexstr(jsp, jo2p, "hot_pluggable", hp, NULL,
+ hot_pluggable_str(hp));
+ snprintf(c, clen, "%s", (ver > 0xf) ? "old or reserved version code" :
+ sg_ansi_version_arr[ver]);
+ sgj_js_nv_ihexstr(jsp, jo2p, "version", ver, NULL, c);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "aerc", !!(b[3] & 0x80), false,
+ "Asynchronous Event Reporting Capability (obsolete "
+ "SPC-3)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "trmtsk", !!(b[3] & 0x40), false,
+ "Terminate Task (obsolete SPC-2)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "normaca", !!(b[3] & 0x20), false,
+ "Normal ACA (Auto Contingent Allegiance)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "hisup", !!(b[3] & 0x10), false,
+ "Hierarchial Support");
+ sgj_js_nv_ihex(jsp, jo2p, "response_data_format", b[3] & 0xf);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "sccs", !!(b[5] & 0x80), false,
+ "SCC (SCSI Storage Commands) Supported");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "acc", !!(b[5] & 0x40), false,
+ "Access Commands Coordinator (obsolete SPC-5)");
+ tpgs = (b[5] >> 4) & 0x3;
+ sgj_js_nv_ihexstr_nex(jsp, jo2p, "tpgs", tpgs, false, NULL,
+ tpgs_str(tpgs), "Target Port Group Support");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "3pc", !!(b[5] & 0x8), false,
+ "Third Party Copy");
+ sgj_js_nv_ihex(jsp, jo2p, "protect", !!(b[5] & 0x1));
+ /* Skip SPI specific flags which have been obsolete for a while) */
+ sgj_js_nv_ihex_nex(jsp, jo2p, "bque", !!(b[6] & 0x80), false,
+ "Basic task management model (obsolete SPC-4)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "encserv", !!(b[6] & 0x40), false,
+ "Enclousure Services supported");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "multip", !!(b[6] & 0x10), false,
+ "Multiple SCSI port");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "mchngr", !!(b[6] & 0x8), false,
+ "Medium changer (obsolete SPC-4)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "reladr", !!(b[7] & 0x80), false,
+ "Relative Addressing (obsolete in SPC-4)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "linked", !!(b[7] & 0x8), false,
+ "Linked Commands (obsolete in SPC-4)");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "cmdque", !!(b[7] & 0x2), false,
+ "Command Management Model (command queuing)");
+ if (len < 16)
+ return jo2p;
+ snprintf(c, clen, "%.8s", b + 8);
+ sgj_js_nv_s(jsp, jo2p, t10_vendor_id_js, c);
+ if (len < 32)
+ return jo2p;
+ snprintf(c, clen, "%.16s", b + 16);
+ sgj_js_nv_s(jsp, jo2p, product_id_js, c);
+ if (len < 36)
+ return jo2p;
+ snprintf(c, clen, "%.4s", b + 32);
+ sgj_js_nv_s(jsp, jo2p, product_rev_lev_js, c);
+ return jo2p;
+}
+
+static const char * power_unit_arr[] =
+{
+ "Gigawatts",
+ "Megawatts",
+ "Kilowatts",
+ "Watts",
+ "Milliwatts",
+ "Microwatts",
+ "Unit reserved",
+ "Unit reserved",
+};
+
+/* VPD_POWER_CONSUMPTION 0x8d ["psm"] */
+void
+decode_power_consumption(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, pcmp_id, pcmp_unit;
+ unsigned int pcmp_val;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ const uint8_t * bp;
+ char b[128];
+ static const int blen = sizeof(b);
+ static const char * pcmp = "power_consumption";
+ static const char * pci = "Power consumption identifier";
+ static const char * mpc = "Maximum power consumption";
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ bump = 4;
+ if ((k + bump) > len) {
+ pr2serr("short descriptor length=%d, left=%d\n", bump,
+ (len - k));
+ return;
+ }
+ if (op->do_hex > 1)
+ hex2stdout(bp, 4, 1);
+ else {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ pcmp_id = bp[0];
+ pcmp_unit = 0x7 & bp[1];
+ pcmp_val = sg_get_unaligned_be16(bp + 2);
+ if (jsp->pr_as_json) {
+ sgj_convert_to_snake_name(pci, b, blen);
+ sgj_js_nv_ihex(jsp, jo2p, b, pcmp_id);
+ snprintf(b, blen, "%s_units", pcmp);
+ sgj_js_nv_ihexstr(jsp, jo2p, b, pcmp_unit, NULL,
+ power_unit_arr[pcmp_unit]);
+ snprintf(b, blen, "%s_value", pcmp);
+ sgj_js_nv_ihex(jsp, jo2p, b, pcmp_val);
+ }
+ snprintf(b, blen, " %s: 0x%x", pci, pcmp_id);
+ if (pcmp_val >= 1000 && pcmp_unit > 0)
+ sgj_pr_hr(jsp, "%s %s: %d.%03d %s\n", b, mpc,
+ pcmp_val / 1000, pcmp_val % 1000,
+ power_unit_arr[pcmp_unit - 1]); /* up one unit */
+ else
+ sgj_pr_hr(jsp, "%s %s: %u %s\n", b, mpc, pcmp_val,
+ power_unit_arr[pcmp_unit]);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+ }
+}
+
+
+/* VPD_BLOCK_LIMITS 0xb0 ["bl"] */
+void
+decode_block_limits_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int wsnz, ugavalid;
+ uint32_t u;
+ uint64_t ull;
+ sgj_state * jsp = &op->json_st;
+ char b[144];
+ static const int blen = sizeof(b);
+ static const char * mcawl = "Maximum compare and write length";
+ static const char * otlg = "Optimal transfer length granularity";
+ static const char * cni = "command not implemented";
+ static const char * ul = "unlimited";
+ static const char * mtl = "Maximum transfer length";
+ static const char * otl = "Optimal transfer length";
+ static const char * mpl = "Maximum prefetch length";
+ static const char * mulc = "Maximum unmap LBA count";
+ static const char * mubdc = "Maximum unmap block descriptor count";
+ static const char * oug = "Optimal unmap granularity";
+ static const char * ugav = "Unmap granularity alignment valid";
+ static const char * uga = "Unmap granularity alignment";
+ static const char * mwsl = "Maximum write same length";
+ static const char * matl = "Maximum atomic transfer length";
+ static const char * aa = "Atomic alignment";
+ static const char * atlg = "Atomic transfer length granularity";
+ static const char * matlwab = "Maximum atomic transfer length with "
+ "atomic boundary";
+ static const char * mabs = "Maximum atomic boundary size";
+
+ if (len < 16) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ wsnz = !!(buff[4] & 0x1);
+ sgj_pr_hr(jsp, " Write same non-zero (WSNZ): %d\n", wsnz);
+ sgj_js_nv_ihex_nex(jsp, jop, "wsnz", wsnz, false,
+ "Write Same Non-Zero (number of LBs must be > 0)");
+ u = buff[5];
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mcawl, cni);
+ sgj_convert_to_snake_name(mcawl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mcawl, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+
+ u = sg_get_unaligned_be16(buff + 6);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", otlg, nr_s);
+ sgj_convert_to_snake_name(otlg, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, otlg, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 8);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mtl, nr_s);
+ sgj_convert_to_snake_name(mtl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mtl, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 12);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", otl, nr_s);
+ sgj_convert_to_snake_name(otl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, otl, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+ if (len > 19) { /* added in sbc3r09 */
+ u = sg_get_unaligned_be32(buff + 16);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mpl, nr_s);
+ sgj_convert_to_snake_name(mpl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mpl, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+ }
+ if (len > 27) { /* added in sbc3r18 */
+ u = sg_get_unaligned_be32(buff + 20);
+ sgj_convert_to_snake_name(mulc, b, blen);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mulc, cni);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni);
+ } else if (0xffffffff == u) {
+ sgj_pr_hr(jsp, " %s: %s blocks\n", ul, mulc);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ul);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mulc, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 24);
+ sgj_convert_to_snake_name(mulc, b, blen);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 block descriptors [%s]\n", mubdc, cni);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cni);
+ } else if (0xffffffff == u) {
+ sgj_pr_hr(jsp, " %s: %s block descriptors\n", ul, mubdc);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ul);
+ } else
+ sgj_haj_vi(jsp, jop, 2, mubdc, SGJ_SEP_COLON_1_SPACE,
+ u, true);
+ }
+ if (len > 35) { /* added in sbc3r19 */
+ u = sg_get_unaligned_be32(buff + 28);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", oug, nr_s);
+ sgj_convert_to_snake_name(oug, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, oug, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+
+ ugavalid = !!(buff[32] & 0x80);
+ sgj_pr_hr(jsp, " %s: %s\n", ugav, ugavalid ? "true" : "false");
+ sgj_js_nv_i(jsp, jop, ugav, ugavalid);
+ if (ugavalid) {
+ u = 0x7fffffff & sg_get_unaligned_be32(buff + 32);
+ sgj_haj_vi_nex(jsp, jop, 2, uga, SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: LB");
+ }
+ }
+ if (len > 43) { /* added in sbc3r26 */
+ ull = sg_get_unaligned_be64(buff + 36);
+ if (0 == ull) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mwsl, nr_s);
+ sgj_convert_to_snake_name(mwsl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, ull, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mwsl, SGJ_SEP_COLON_1_SPACE,
+ ull, true, "unit: LB");
+ }
+ if (len > 47) { /* added in sbc4r02 */
+ u = sg_get_unaligned_be32(buff + 44);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", matl, nr_s);
+ sgj_convert_to_snake_name(matl, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, matl, SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 48);
+ if (0 == u) {
+ static const char * uawp = "unaligned atomic writes permitted";
+
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", aa, uawp);
+ sgj_convert_to_snake_name(aa, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, uawp);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, aa, SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 52);
+ if (0 == u) {
+ static const char * ngr = "no granularity requirement";
+
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", atlg, ngr);
+ sgj_convert_to_snake_name(atlg, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, ngr);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, aa, SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: LB");
+ }
+ if (len > 56) {
+ u = sg_get_unaligned_be32(buff + 56);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", matlwab, nr_s);
+ sgj_convert_to_snake_name(matlwab, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, nr_s);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, matlwab, SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: LB");
+
+ u = sg_get_unaligned_be32(buff + 60);
+ if (0 == u) {
+ static const char * cowa1b = "can only write atomic 1 block";
+
+ sgj_pr_hr(jsp, " %s: 0 blocks [%s]\n", mabs, cowa1b);
+ sgj_convert_to_snake_name(mabs, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, cowa1b);
+ } else
+ sgj_haj_vi_nex(jsp, jop, 2, mabs, SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: LB");
+ }
+}
+
+static const char * product_type_arr[] =
+{
+ "Not specified",
+ "CFast",
+ "CompactFlash",
+ "MemoryStick",
+ "MultiMediaCard",
+ "Secure Digital Card (SD)",
+ "XQD",
+ "Universal Flash Storage Card (UFS)",
+};
+
+/* ZONED field here replaced by ZONED BLOCK DEVICE EXTENSION field in the
+ * Zoned Block Device Characteristics VPD page. The new field includes
+ * Zone Domains and Realms (see ZBC-2) */
+static const char * bdc_zoned_strs[] = {
+ nr_s,
+ "host-aware",
+ "host-managed",
+ rsv_s,
+};
+
+/* VPD_BLOCK_DEV_CHARS 0xb1 ["bdc"] */
+void
+decode_block_dev_ch_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int zoned;
+ unsigned int u, k;
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ char b[144];
+ static const char * mrr_j = "medium_rotation_rate";
+ static const char * mrr_h = "Medium rotation rate";
+ static const char * nrm = "Non-rotating medium (e.g. solid state)";
+ static const char * pt_j = "product_type";
+
+ if (len < 64) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ u = sg_get_unaligned_be16(buff + 4);
+ if (0 == u) {
+ sgj_pr_hr(jsp, " %s is %s\n", mrr_h, nr_s);
+ sgj_js_nv_ihexstr(jsp, jop, mrr_j, 0, NULL, nr_s);
+ } else if (1 == u) {
+ sgj_pr_hr(jsp, " %s\n", nrm);
+ sgj_js_nv_ihexstr(jsp, jop, mrr_j, 1, NULL, nrm);
+ } else if ((u < 0x401) || (0xffff == u)) {
+ sgj_pr_hr(jsp, " %s [0x%x]\n", rsv_s, u);
+ sgj_js_nv_ihexstr(jsp, jop, mrr_j, u, NULL, rsv_s);
+ } else {
+ sgj_js_nv_ihex_nex(jsp, jop, mrr_j, u, true,
+ "unit: rpm; nominal rotation rate");
+ }
+ u = buff[6];
+ k = SG_ARRAY_SIZE(product_type_arr);
+ if (u < k) {
+ sgj_pr_hr(jsp, " %s: %s\n", "Product type", product_type_arr[u]);
+ sgj_js_nv_ihexstr(jsp, jop, pt_j, u, NULL, product_type_arr[u]);
+ } else {
+ sgj_pr_hr(jsp, " %s: %s [0x%x]\n", "Product type",
+ (u < 0xf0) ? rsv_s : vs_s, u);
+ sgj_js_nv_ihexstr(jsp, jop, pt_j, u, NULL, (u < 0xf0) ? rsv_s : vs_s);
+ }
+ sgj_haj_vi_nex(jsp, jop, 2, "WABEREQ", SGJ_SEP_EQUAL_NO_SPACE,
+ (buff[7] >> 6) & 0x3, false,
+ "Write After Block Erase REQuired");
+ sgj_haj_vi_nex(jsp, jop, 2, "WACEREQ", SGJ_SEP_EQUAL_NO_SPACE,
+ (buff[7] >> 4) & 0x3, false,
+ "Write After Cryptographic Erase REQuired");
+ u = buff[7] & 0xf;
+ switch (u) {
+ case 0:
+ strcpy(b, nr_s);
+ break;
+ case 1:
+ strcpy(b, "5.25 inch");
+ break;
+ case 2:
+ strcpy(b, "3.5 inch");
+ break;
+ case 3:
+ strcpy(b, "2.5 inch");
+ break;
+ case 4:
+ strcpy(b, "1.8 inch");
+ break;
+ case 5:
+ strcpy(b, "less then 1.8 inch");
+ break;
+ default:
+ strcpy(b, rsv_s);
+ break;
+ }
+ sgj_pr_hr(jsp, " Nominal form factor: %s\n", b);
+ sgj_js_nv_ihexstr(jsp, jop, "nominal_forn_factor", u, NULL, b);
+ sgj_haj_vi_nex(jsp, jop, 2, "MACT", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[8] & 0x40), false, "Multiple ACTuator");
+ zoned = (buff[8] >> 4) & 0x3; /* added sbc4r04, obsolete sbc5r01 */
+ cp = bdc_zoned_strs[zoned];
+ sgj_pr_hr(jsp, " ZONED=%d [%s]\n", zoned, cp);
+ sgj_js_nv_ihexstr_nex(jsp, jop, "zoned", zoned, false, NULL,
+ cp, "Added in SBC-4, obsolete in SBC-5");
+ sgj_haj_vi_nex(jsp, jop, 2, "RBWZ", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[8] & 0x4), false,
+ "Background Operation Control Supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "FUAB", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[8] & 0x2), false,
+ "Force Unit Access Behaviour");
+ sgj_haj_vi_nex(jsp, jop, 2, "VBULS", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[8] & 0x1), false,
+ "Verify Byte check Unmapped Lba Supported");
+ u = sg_get_unaligned_be32(buff + 12);
+ sgj_haj_vi_nex(jsp, jop, 2, "DEPOPULATION TIME", SGJ_SEP_COLON_1_SPACE,
+ u, true, "unit: second");
+}
+
+static const char * prov_type_arr[8] = {
+ "not known or fully provisioned",
+ "resource provisioned",
+ "thin provisioned",
+ rsv_s,
+ rsv_s,
+ rsv_s,
+ rsv_s,
+ rsv_s,
+};
+
+/* VPD_LB_PROVISIONING 0xb2 ["lbpv"] */
+int
+decode_block_lb_prov_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ unsigned int u, dp, pt, t_exp;
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ char b[1024];
+ static const int blen = sizeof(b);
+ static const char * mp = "Minimum percentage";
+ static const char * tp = "Threshold percentage";
+ static const char * pgd = "Provisioning group descriptor";
+
+ if (len < 4) {
+ pr2serr("page too short=%d\n", len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ t_exp = buff[4];
+ sgj_js_nv_ihexstr(jsp, jop, "threshold_exponent", t_exp, NULL,
+ (0 == t_exp) ? ns_s : NULL);
+ sgj_haj_vi_nex(jsp, jop, 2, "LBPU", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[5] & 0x80), false,
+ "Logical Block Provisioning Unmap command supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "LBPWS", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[5] & 0x40), false, "Logical Block Provisioning "
+ "Write Same (16) command supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "LBPWS10", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[5] & 0x20), false, "Logical Block Provisioning "
+ "Write Same (10) command supported");
+ sgj_haj_vi_nex(jsp, jop, 2, "LBPRZ", SGJ_SEP_EQUAL_NO_SPACE,
+ (0x7 & (buff[5] >> 2)), true,
+ "Logical Block Provisioning Read Zero");
+ sgj_haj_vi_nex(jsp, jop, 2, "ANC_SUP", SGJ_SEP_EQUAL_NO_SPACE,
+ !!(buff[5] & 0x2), false,
+ "ANChor SUPported");
+ dp = !!(buff[5] & 0x1);
+ sgj_haj_vi_nex(jsp, jop, 2, "DP", SGJ_SEP_EQUAL_NO_SPACE,
+ dp, false, "Descriptor Present");
+ u = 0x1f & (buff[6] >> 3); /* minimum percentage */
+ if (0 == u)
+ sgj_pr_hr(jsp, " %s: 0 [%s]\n", mp, nr_s);
+ else
+ sgj_pr_hr(jsp, " %s: %u\n", mp, u);
+ sgj_convert_to_snake_name(mp, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, (0 == u) ? nr_s : NULL);
+ pt = buff[6] & 0x7;
+ cp = prov_type_arr[pt];
+ if (pt > 2)
+ snprintf(b, blen, " [%u]", u);
+ else
+ b[0] = '\0';
+ sgj_pr_hr(jsp, " Provisioning type: %s%s\n", cp, b);
+ sgj_js_nv_ihexstr(jsp, jop, "provisioning_type", pt, NULL, cp);
+ u = buff[7]; /* threshold percentage */
+ strcpy(b, tp);
+ if (0 == u)
+ sgj_pr_hr(jsp, " %s: 0 [percentages %s]\n", b, ns_s);
+ else
+ sgj_pr_hr(jsp, " %s: %u", b, u);
+ sgj_convert_to_snake_name(tp, b, blen);
+ sgj_js_nv_ihexstr(jsp, jop, b, u, NULL, (0 == u) ? ns_s : NULL);
+ if (dp && (len > 11)) {
+ int i_len;
+ const uint8_t * bp;
+ sgj_opaque_p jo2p;
+
+ bp = buff + 8;
+ i_len = bp[3];
+ if (0 == i_len) {
+ pr2serr("%s too short=%d\n", pgd, i_len);
+ return 0;
+ }
+ if (jsp->pr_as_json) {
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, pgd);
+ sgj_js_designation_descriptor(jsp, jo2p, bp, i_len + 4);
+ }
+ sgj_pr_hr(jsp, " %s:\n", pgd);
+ sg_get_designation_descriptor_str(" ", bp, i_len + 4, true,
+ op->do_long, blen, b);
+ if (jsp->pr_as_json && jsp->pr_out_hr)
+ sgj_js_str_out(jsp, b, strlen(b));
+ else
+ sgj_pr_hr(jsp, "%s", b);
+ }
+ return 0;
+}
+
+/* VPD_REFERRALS 0xb3 ["ref"] */
+void
+decode_referrals_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ uint32_t u;
+ sgj_state * jsp = &op->json_st;
+ char b[64];
+
+ if (len < 16) {
+ pr2serr("Referrals VPD page length too short=%d\n", len);
+ return;
+ }
+ u = sg_get_unaligned_be32(buff + 8);
+ strcpy(b, " User data segment size: ");
+ if (0 == u)
+ sgj_pr_hr(jsp, "%s0 [per sense descriptor]\n", b);
+ else
+ sgj_pr_hr(jsp, "%s%u\n", b, u);
+ sgj_js_nv_ihex(jsp, jop, "user_data_segment_size", u);
+ u = sg_get_unaligned_be32(buff + 12);
+ sgj_haj_vi(jsp, jop, 2, "User data segment multiplier",
+ SGJ_SEP_COLON_1_SPACE, u, true);
+}
+
+/* VPD_SUP_BLOCK_LENS 0xb4 ["sbl"] (added sbc4r01) */
+void
+decode_sup_block_lens_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k;
+ unsigned int u;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+
+ if (len < 4) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += 8, bp += 8) {
+ if (jsp->pr_as_json)
+ jo2p = sgj_new_unattached_object_r(jsp);
+ u = sg_get_unaligned_be32(bp);
+ sgj_haj_vi(jsp, jo2p, 2, "Logical block length",
+ SGJ_SEP_COLON_1_SPACE, u, true);
+ sgj_haj_vi_nex(jsp, jo2p, 4, "P_I_I_SUP",
+ SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x40), false,
+ "Protection Information Interval SUPported");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "NO_PI_CHK",
+ SGJ_SEP_COLON_1_SPACE, !!(bp[4] & 0x8), false,
+ "NO Protection Information CHecKing");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "GRD_CHK", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[4] & 0x4), false, "GuaRD CHecK");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "APP_CHK", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[4] & 0x2), false, "APPlication tag CHecK");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "REF_CHK", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[4] & 0x1), false, "REFerence tag CHecK");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "T3PS", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[5] & 0x8), false, "Type 3 Protection Supported");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "T2PS", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[5] & 0x4), false, "Type 2 Protection Supported");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "T1PS", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[5] & 0x2), false, "Type 1 Protection Supported");
+ sgj_haj_vi_nex(jsp, jo2p, 4, "T0PS", SGJ_SEP_COLON_1_SPACE,
+ !!(bp[5] & 0x1), false, "Type 0 Protection Supported");
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* VPD_BLOCK_DEV_C_EXTENS 0xb5 ["bdce"] (added sbc4r02) */
+void
+decode_block_dev_char_ext_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool b_active = false;
+ bool combined = false;
+ int n;
+ uint32_t u;
+ sgj_state * jsp = &op->json_st;
+ const char * utp = null_s;
+ const char * uup = null_s;
+ const char * uip = null_s;
+ char b[128];
+ static const int blen = sizeof(b);
+
+ if (len < 16) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ switch (buff[5]) {
+ case 1:
+ utp = "Combined writes and reads";
+ combined = true;
+ break;
+ case 2:
+ utp = "Writes only";
+ break;
+ case 3:
+ utp = "Separate writes and reads";
+ b_active = true;
+ break;
+ default:
+ utp = rsv_s;
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Utilization type", SGJ_SEP_COLON_1_SPACE,
+ buff[5], true, utp);
+ switch (buff[6]) {
+ case 2:
+ uup = "megabytes";
+ break;
+ case 3:
+ uup = "gigabytes";
+ break;
+ case 4:
+ uup = "terabytes";
+ break;
+ case 5:
+ uup = "petabytes";
+ break;
+ case 6:
+ uup = "exabytes";
+ break;
+ default:
+ uup = rsv_s;
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Utilization units", SGJ_SEP_COLON_1_SPACE,
+ buff[6], true, uup);
+ switch (buff[7]) {
+ case 0xa:
+ uip = "per day";
+ break;
+ case 0xe:
+ uip = "per year";
+ break;
+ default:
+ uip = rsv_s;
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Utilization interval", SGJ_SEP_COLON_1_SPACE,
+ buff[7], true, uip);
+ u = sg_get_unaligned_be32(buff + 8);
+ sgj_haj_vistr(jsp, jop, 2, "Utilization B", SGJ_SEP_COLON_1_SPACE,
+ u, true, (b_active ? NULL : rsv_s));
+ n = sg_scnpr(b, blen, "%s: ", "Designed utilization");
+ if (b_active)
+ n += sg_scnpr(b + n, blen - n, "%u %s for reads and ", u, uup);
+ u = sg_get_unaligned_be32(buff + 12);
+ sgj_haj_vi(jsp, jop, 2, "Utilization A", SGJ_SEP_COLON_1_SPACE, u, true);
+ n += sg_scnpr(b + n, blen - n, "%u %s for %swrites, %s", u, uup,
+ combined ? "reads and " : null_s, uip);
+ sgj_pr_hr(jsp, " %s\n", b);
+ if (jsp->pr_string)
+ sgj_js_nv_s(jsp, jop, "summary", b);
+}
+
+/* VPD_ZBC_DEV_CHARS 0xb6 ["zdbch"] sbc or zbc [zbc2r04] */
+void
+decode_zbdch_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ uint32_t u, pdt;
+ sgj_state * jsp = &op->json_st;
+ char b[128];
+ static const int blen = sizeof(b);
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 64) {
+ pr2serr("Zoned block device characteristics VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ pdt = PDT_MASK & buff[0];
+ sgj_pr_hr(jsp, " Peripheral device type: %s\n",
+ sg_get_pdt_str(pdt, blen, b));
+
+ sgj_pr_hr(jsp, " Zoned block device extension: ");
+ u = (buff[4] >> 4) & 0xf;
+ switch (u) {
+ case 0:
+ if (PDT_ZBC == (PDT_MASK & buff[0]))
+ strcpy(b, "host managed zoned block device");
+ else
+ strcpy(b, nr_s);
+ break;
+ case 1:
+ strcpy(b, "host aware zoned block device model");
+ break;
+ case 2:
+ strcpy(b, "Domains and realms zoned block device model");
+ break;
+ default:
+ strcpy(b, rsv_s);
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Zoned block device extension",
+ SGJ_SEP_COLON_1_SPACE, u, true, b);
+ sgj_haj_vi_nex(jsp, jop, 2, "AAORB", SGJ_SEP_COLON_1_SPACE,
+ !!(buff[4] & 0x2), false,
+ "Activation Aligned On Realm Boundaries");
+ sgj_haj_vi_nex(jsp, jop, 2, "URSWRZ", SGJ_SEP_COLON_1_SPACE,
+ !!(buff[4] & 0x1), false,
+ "Unrestricted Read in Sequential Write Required Zone");
+ u = sg_get_unaligned_be32(buff + 8);
+ sgj_haj_vistr(jsp, jop, 2, "Optimal number of open sequential write "
+ "preferred zones", SGJ_SEP_COLON_1_SPACE, u, true,
+ (SG_LIB_UNBOUNDED_32BIT == u) ? nr_s : NULL);
+ u = sg_get_unaligned_be32(buff + 12);
+ sgj_haj_vistr(jsp, jop, 2, "Optimal number of non-sequentially "
+ "written sequential write preferred zones",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (SG_LIB_UNBOUNDED_32BIT == u) ? nr_s : NULL);
+ u = sg_get_unaligned_be32(buff + 16);
+ sgj_haj_vistr(jsp, jop, 2, "Maximum number of open sequential write "
+ "required zones", SGJ_SEP_COLON_1_SPACE, u, true,
+ (SG_LIB_UNBOUNDED_32BIT == u) ? nl_s : NULL);
+ u = buff[23] & 0xf;
+ switch (u) {
+ case 0:
+ strcpy(b, nr_s);
+ break;
+ case 1:
+ strcpy(b, "Zoned starting LBAs aligned using constant zone lengths");
+ break;
+ case 0x8:
+ strcpy(b, "Zoned starting LBAs potentially non-constant (as "
+ "reported by REPORT ZONES)");
+ break;
+ default:
+ strcpy(b, rsv_s);
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Zoned alignment method",
+ SGJ_SEP_COLON_1_SPACE, u, true, b);
+ sgj_haj_vi(jsp, jop, 2, "Zone starting LBA granularity",
+ SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be64(buff + 24), true);
+}
+
+/* VPD_BLOCK_LIMITS_EXT 0xb7 ["ble"] SBC */
+void
+decode_block_limits_ext_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ uint32_t u;
+ sgj_state * jsp = &op->json_st;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 12) {
+ pr2serr("page length too short=%d\n", len);
+ return;
+ }
+ u = sg_get_unaligned_be16(buff + 6);
+ sgj_haj_vistr(jsp, jop, 2, "Maximum number of streams",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u) ? "Stream control not supported" : NULL);
+ u = sg_get_unaligned_be16(buff + 8);
+ sgj_haj_vi_nex(jsp, jop, 2, "Optimal stream write size",
+ SGJ_SEP_COLON_1_SPACE, u, true, "unit: LB");
+ u = sg_get_unaligned_be32(buff + 10);
+ sgj_haj_vi_nex(jsp, jop, 2, "Stream granularity size",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ "unit: number of optimal stream write size blocks");
+ if (len < 28)
+ return;
+ u = sg_get_unaligned_be32(buff + 16);
+ sgj_haj_vistr_nex(jsp, jop, 2, "Maximum scattered LBA range transfer "
+ "length", SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? nlr_s : NULL),
+ "unit: LB (in a single LBA range descriptor)");
+ u = sg_get_unaligned_be16(buff + 22);
+ sgj_haj_vistr(jsp, jop, 2, "Maximum scattered LBA range descriptor "
+ "count", SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? nlr_s : NULL));
+ u = sg_get_unaligned_be32(buff + 24);
+ sgj_haj_vistr_nex(jsp, jop, 2, "Maximum scattered transfer length",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? nlr_s : NULL),
+ "unit: LB (per single Write Scattered command)");
+}
+
+static const char * sch_type_arr[8] = {
+ rsv_s,
+ "non-zoned",
+ "host aware zoned",
+ "host managed zoned",
+ "zone domain and realms zoned",
+ rsv_s,
+ rsv_s,
+ rsv_s,
+};
+
+static char *
+get_zone_align_method(uint8_t val, char * b, int blen)
+{
+ assert(blen > 32);
+ switch (val) {
+ case 0:
+ strcpy(b, nr_s);
+ break;
+ case 1:
+ strcpy(b, "using constant zone lengths");
+ break;
+ case 8:
+ strcpy(b, "taking gap zones into account");
+ break;
+ default:
+ strcpy(b, rsv_s);
+ break;
+ }
+ return b;
+}
+
+/* VPD_FORMAT_PRESETS 0xb8 ["fp"] (added sbc4r18) */
+void
+decode_format_presets_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ uint8_t sch_type;
+ int k;
+ uint32_t u;
+ uint64_t ul;
+ sgj_state * jsp = &op->json_st;
+ const uint8_t * bp;
+ sgj_opaque_p jo2p, jo3p;
+ const char * cp;
+ char b[128];
+ char d[64];
+ static const int blen = sizeof(b);
+ static const int dlen = sizeof(d);
+ static const char * llczp = "Low LBA conventional zones percentage";
+ static const char * hlczp = "High LBA conventional zones percentage";
+ static const char * ztzd = "Zone type for zone domain";
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += 64, bp += 64) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_haj_vi(jsp, jo2p, 2, "Preset identifier", SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be64(bp + 0), true);
+ sch_type = bp[4];
+ if (sch_type < 8) {
+ cp = sch_type_arr[sch_type];
+ if (rsv_s != cp)
+ snprintf(b, blen, "%s block device", cp);
+ else
+ snprintf(b, blen, "%s", cp);
+ } else
+ strcpy(b, rsv_s);
+ sgj_haj_vistr(jsp, jo2p, 4, "Schema type", SGJ_SEP_COLON_1_SPACE,
+ sch_type, true, b);
+ sgj_haj_vi(jsp, jo2p, 4, "Logical blocks per physical block "
+ "exponent", SGJ_SEP_COLON_1_SPACE,
+ 0xf & bp[7], true);
+ sgj_haj_vi_nex(jsp, jo2p, 4, "Logical block length",
+ SGJ_SEP_COLON_1_SPACE, sg_get_unaligned_be32(bp + 8),
+ true, "unit: byte");
+ sgj_haj_vi(jsp, jo2p, 4, "Designed last Logical Block Address",
+ SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be64(bp + 16), true);
+ sgj_haj_vi_nex(jsp, jo2p, 4, "FMTPINFO", SGJ_SEP_COLON_1_SPACE,
+ (bp[38] >> 6) & 0x3, false,
+ "ForMaT Protection INFOrmation (see Format Unit)");
+ sgj_haj_vi(jsp, jo2p, 4, "Protection field usage",
+ SGJ_SEP_COLON_1_SPACE, bp[38] & 0x7, false);
+ sgj_haj_vi(jsp, jo2p, 4, "Protection interval exponent",
+ SGJ_SEP_COLON_1_SPACE, bp[39] & 0xf, true);
+ jo3p = sgj_named_subobject_r(jsp, jo2p,
+ "schema_type_specific_information");
+ switch (sch_type) {
+ case 2:
+ sgj_pr_hr(jsp, " Defines zones for host aware device:\n");
+ u = bp[40 + 0];
+ sgj_pr_hr(jsp, " %s: %u.%u %%\n", llczp, u / 10, u % 10);
+ sgj_convert_to_snake_name(llczp, b, blen);
+ sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a "
+ "percent");
+ u = bp[40 + 1];
+ sgj_pr_hr(jsp, " %s: %u.%u %%\n", hlczp, u / 10, u % 10);
+ sgj_convert_to_snake_name(hlczp, b, blen);
+ sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a "
+ "percent");
+ u = sg_get_unaligned_be32(bp + 40 + 12);
+ sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? rsv_s : NULL));
+ break;
+ case 3:
+ sgj_pr_hr(jsp, " Defines zones for host managed device:\n");
+ u = bp[40 + 0];
+ sgj_pr_hr(jsp, " %s: %u.%u %%\n", llczp, u / 10, u % 10);
+ sgj_convert_to_snake_name(llczp, b, blen);
+ sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a "
+ "percent");
+ u = bp[40 + 1];
+ sgj_pr_hr(jsp, " %s: %u.%u %%\n", hlczp, u / 10, u % 10);
+ sgj_convert_to_snake_name(hlczp, b, blen);
+ sgj_js_nv_ihex_nex(jsp, jo3p, b, u, true, "unit: 1/10 of a "
+ "percent");
+ u = bp[40 + 3] & 0x7;
+ sgj_haj_vistr(jsp, jo3p, 6, "Designed zone alignment method",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ get_zone_align_method(u, d, dlen));
+ ul = sg_get_unaligned_be64(bp + 40 + 4);
+ sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone starting LBA "
+ "granularity", SGJ_SEP_COLON_1_SPACE, ul, true,
+ "unit: LB");
+ u = sg_get_unaligned_be32(bp + 40 + 12);
+ sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? rsv_s : NULL));
+ break;
+ case 4:
+ sgj_pr_hr(jsp, " Defines zones for zone domains and realms "
+ "device:\n");
+ snprintf(b, blen, "%s 0", ztzd);
+ u = bp[40 + 0];
+ sg_get_zone_type_str((u >> 4) & 0xf, dlen, d);
+ sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d);
+ snprintf(b, blen, "%s 1", ztzd);
+ sg_get_zone_type_str(u & 0xf, dlen, d);
+ sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d);
+
+ snprintf(b, blen, "%s 2", ztzd);
+ u = bp[40 + 1];
+ sg_get_zone_type_str((u >> 4) & 0xf, dlen, d);
+ sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d);
+ snprintf(b, blen, "%s 3", ztzd);
+ sg_get_zone_type_str(u & 0xf, dlen, d);
+ sgj_haj_vistr(jsp, jo3p, 6, b, SGJ_SEP_COLON_1_SPACE, u, true, d);
+ u = bp[40 + 3] & 0x7;
+ sgj_haj_vistr(jsp, jo3p, 6, "Designed zone alignment method",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ get_zone_align_method(u, d, dlen));
+ ul = sg_get_unaligned_be64(bp + 40 + 4);
+ sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone starting LBA "
+ "granularity", SGJ_SEP_COLON_1_SPACE, ul, true,
+ "unit: LB");
+ u = sg_get_unaligned_be32(bp + 40 + 12);
+ sgj_haj_vistr(jsp, jo3p, 6, "Logical blocks per zone",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u ? rsv_s : NULL));
+ ul = sg_get_unaligned_be64(bp + 40 + 16);
+ sgj_haj_vi_nex(jsp, jo3p, 6, "Designed zone maximum address",
+ SGJ_SEP_COLON_1_SPACE, ul, true, "unit: LBA");
+ break;
+ default:
+ sgj_pr_hr(jsp, " No schema type specific information\n");
+ break;
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* VPD_CON_POS_RANGE 0xb9 (added sbc5r01) */
+void
+decode_con_pos_range_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k;
+ uint32_t u;
+ sgj_state * jsp = &op->json_st;
+ const uint8_t * bp;
+ sgj_opaque_p jo2p;
+
+ if (op->do_hex) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 64) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 64;
+ bp = buff + 64;
+ for (k = 0; k < len; k += 32, bp += 32) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_haj_vi(jsp, jo2p, 2, "LBA range number",
+ SGJ_SEP_COLON_1_SPACE, bp[0], true);
+ u = bp[1];
+ sgj_haj_vistr(jsp, jo2p, 4, "Number of storage elements",
+ SGJ_SEP_COLON_1_SPACE, u, true, (0 == u ? nr_s : NULL));
+ sgj_haj_vi(jsp, jo2p, 4, "Starting LBA", SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be64(bp + 8), true);
+ sgj_haj_vi(jsp, jo2p, 4, "Number of LBAs", SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be64(bp + 16), true);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* This is xcopy(LID4) related: "ROD" == Representation Of Data
+ * Used by VPD_3PARTY_COPY 0x8f ["tpc"] */
+static void
+decode_rod_descriptor(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ uint8_t pdt;
+ uint32_t u;
+ int k, bump;
+ uint64_t ull;
+ const uint8_t * bp = buff;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p;
+ char b[80];
+ static const int blen = sizeof(b);
+ static const char * ab_pdt = "abnormal use of 'pdt'";
+
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ bump = sg_get_unaligned_be16(bp + 2) + 4;
+ pdt = 0x1f & bp[0];
+ u = (bp[0] >> 5) & 0x7;
+ sgj_js_nv_i(jsp, jo2p, "descriptor_format", u);
+ if (0 != u) {
+ sgj_pr_hr(jsp, " Unhandled descriptor (format %u, device type "
+ "%u)\n", u, pdt);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ break;
+ }
+ switch (pdt) {
+ case 0:
+ /* Block ROD device type specific descriptor */
+ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type",
+ pdt, false, NULL, "Block ROD device "
+ "type specific descriptor", ab_pdt);
+ sgj_haj_vi_nex(jsp, jo2p, 4, "Optimal block ROD length "
+ "granularity", SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be16(bp + 6), true, "unit: LB");
+ ull = sg_get_unaligned_be64(bp + 8);
+ sgj_haj_vi(jsp, jo2p, 4, "Maximum bytes in block ROD",
+ SGJ_SEP_COLON_1_SPACE, ull, true);
+ ull = sg_get_unaligned_be64(bp + 16);
+ sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes in block ROD "
+ "transfer", SGJ_SEP_COLON_1_SPACE, ull, true,
+ (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL);
+ ull = sg_get_unaligned_be64(bp + 24);
+ sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes to token per "
+ "segment", SGJ_SEP_COLON_1_SPACE, ull, true,
+ (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL);
+ ull = sg_get_unaligned_be64(bp + 32);
+ sgj_haj_vistr(jsp, jo2p, 4, "Optimal Bytes from token per "
+ "segment", SGJ_SEP_COLON_1_SPACE, ull, true,
+ (SG_LIB_UNBOUNDED_64BIT == ull) ? nl_s : NULL);
+ break;
+ case 1:
+ /* Stream ROD device type specific descriptor */
+ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type",
+ pdt, false, NULL, "Stream ROD device "
+ "type specific descriptor", ab_pdt);
+ ull = sg_get_unaligned_be64(bp + 8);
+ sgj_haj_vi(jsp, jo2p, 4, "Maximum bytes in stream ROD",
+ SGJ_SEP_COLON_1_SPACE, ull, true);
+ ull = sg_get_unaligned_be64(bp + 16);
+ snprintf(b, blen, " Optimal Bytes in stream ROD transfer: ");
+ if (SG_LIB_UNBOUNDED_64BIT == ull)
+ sgj_pr_hr(jsp, "%s-1 [no limit]\n", b);
+ else
+ sgj_pr_hr(jsp, "%s%" PRIu64 "\n", b, ull);
+ break;
+ case 3:
+ /* Copy manager ROD device type specific descriptor */
+ sgj_js_nv_ihexstr_nex(jsp, jo2p, "peripheral_device_type",
+ pdt, false, NULL, "Copy manager ROD "
+ "device type specific descriptor",
+ ab_pdt);
+ sgj_pr_hr(jsp, " Maximum Bytes in processor ROD: %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 8));
+ ull = sg_get_unaligned_be64(bp + 16);
+ snprintf(b, blen, " Optimal Bytes in processor ROD transfer: ");
+ if (SG_LIB_UNBOUNDED_64BIT == ull)
+ sgj_pr_hr(jsp, "%s-1 [no limit]\n", b);
+ else
+ sgj_pr_hr(jsp, "%s%" PRIu64 "\n", b, ull);
+ break;
+ default:
+ sgj_js_nv_ihexstr(jsp, jo2p, "peripheral_device_type",
+ pdt, NULL, "unknown");
+ break;
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+struct tpc_desc_type {
+ uint8_t code;
+ const char * name;
+};
+
+static struct tpc_desc_type tpc_desc_arr[] = {
+ {0x0, "block -> stream"},
+ {0x1, "stream -> block"},
+ {0x2, "block -> block"},
+ {0x3, "stream -> stream"},
+ {0x4, "inline -> stream"},
+ {0x5, "embedded -> stream"},
+ {0x6, "stream -> discard"},
+ {0x7, "verify CSCD"},
+ {0x8, "block<o> -> stream"},
+ {0x9, "stream -> block<o>"},
+ {0xa, "block<o> -> block<o>"},
+ {0xb, "block -> stream & application_client"},
+ {0xc, "stream -> block & application_client"},
+ {0xd, "block -> block & application_client"},
+ {0xe, "stream -> stream&application_client"},
+ {0xf, "stream -> discard&application_client"},
+ {0x10, "filemark -> tape"},
+ {0x11, "space -> tape"}, /* obsolete: spc5r02 */
+ {0x12, "locate -> tape"}, /* obsolete: spc5r02 */
+ {0x13, "<i>tape -> <i>tape"},
+ {0x14, "register persistent reservation key"},
+ {0x15, "third party persistent reservation source I_T nexus"},
+ {0x16, "<i>block -> <i>block"},
+ {0x17, "positioning -> tape"}, /* this and next added spc5r02 */
+ {0x18, "<loi>tape -> <loi>tape"}, /* loi: logical object identifier */
+ {0xbe, "ROD <- block range(n)"},
+ {0xbf, "ROD <- block range(1)"},
+ {0xe0, "CSCD: FC N_Port_Name"},
+ {0xe1, "CSCD: FC N_Port_ID"},
+ {0xe2, "CSCD: FC N_Port_ID with N_Port_Name, checking"},
+ {0xe3, "CSCD: Parallel interface: I_T"},
+ {0xe4, "CSCD: Identification Descriptor"},
+ {0xe5, "CSCD: IPv4"},
+ {0xe6, "CSCD: Alias"},
+ {0xe7, "CSCD: RDMA"},
+ {0xe8, "CSCD: IEEE 1394 EUI-64"},
+ {0xe9, "CSCD: SAS SSP"},
+ {0xea, "CSCD: IPv6"},
+ {0xeb, "CSCD: IP copy service"},
+ {0xfe, "CSCD: ROD"},
+ {0xff, "CSCD: extension"},
+ {0x0, NULL},
+};
+
+static const char *
+get_tpc_desc_name(uint8_t code)
+{
+ const struct tpc_desc_type * dtp;
+
+ for (dtp = tpc_desc_arr; dtp->name; ++dtp) {
+ if (code == dtp->code)
+ return dtp->name;
+ }
+ return "";
+}
+
+struct tpc_rod_type {
+ uint32_t type;
+ const char * name;
+};
+
+static struct tpc_rod_type tpc_rod_arr[] = {
+ {0x0, "copy manager internal"},
+ {0x10000, "access upon reference"},
+ {0x800000, "point in time copy - default"},
+ {0x800001, "point in time copy - change vulnerable"},
+ {0x800002, "point in time copy - persistent"},
+ {0x80ffff, "point in time copy - any"},
+ {0xffff0001, "block device zero"},
+ {0x0, NULL},
+};
+
+static const char *
+get_tpc_rod_name(uint32_t rod_type)
+{
+ const struct tpc_rod_type * rtp;
+
+ for (rtp = tpc_rod_arr; rtp->name; ++rtp) {
+ if (rod_type == rtp->type)
+ return rtp->name;
+ }
+ return "";
+}
+
+struct cscd_desc_id_t {
+ uint16_t id;
+ const char * name;
+};
+
+static struct cscd_desc_id_t cscd_desc_id_arr[] = {
+ /* only values higher than 0x7ff are listed */
+ {0xc000, "copy src or dst null LU, pdt=0"},
+ {0xc001, "copy src or dst null LU, pdt=1"},
+ {0xf800, "copy src or dst in ROD token"},
+ {0xffff, "copy src or dst is copy manager LU"},
+ {0x0, NULL},
+};
+
+static const char *
+get_cscd_desc_id_name(uint16_t cscd_desc_id)
+{
+ const struct cscd_desc_id_t * cdip;
+
+ for (cdip = cscd_desc_id_arr; cdip->name; ++cdip) {
+ if (cscd_desc_id == cdip->id)
+ return cdip->name;
+ }
+ return "";
+}
+
+static const char *
+get_tpc_desc_type_s(uint32_t desc_type)
+{
+ switch(desc_type) {
+ case 0:
+ return "Block Device ROD Limits";
+ case 1:
+ return "Supported Commands";
+ case 4:
+ return "Parameter Data";
+ case 8:
+ return "Supported Descriptors";
+ case 0xc:
+ return "Supported CSCD Descriptor IDs";
+ case 0xd:
+ return "Copy Group Identifier";
+ case 0x106:
+ return "ROD Token Features";
+ case 0x108:
+ return "Supported ROD Token and ROD Types";
+ case 0x8001:
+ return "General Copy Operations";
+ case 0x9101:
+ return "Stream Copy Operations";
+ case 0xC001:
+ return "Held Data";
+ default:
+ if ((desc_type >= 0xE000) && (desc_type <= 0xEFFF))
+ return "Restricted";
+ else
+ return "Reserved";
+ }
+}
+
+/* VPD_3PARTY_COPY 3PC, third party copy 0x8f ["tpc"] */
+void
+decode_3party_copy_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap)
+{
+ int j, k, m, bump, desc_type, desc_len, sa_len, pdt;
+ uint32_t u, v;
+ uint64_t ull;
+ const uint8_t * bp;
+ const char * cp;
+ const char * dtp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p ja2p = NULL;
+ sgj_opaque_p jo3p = NULL;
+ char b[144];
+ static const int blen = sizeof(b);
+
+ if (len < 4) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ if (3 == op->do_hex) {
+ hex2stdout(buff, len, -1);
+ return;
+ }
+ pdt = buff[0] & PDT_MASK;
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ desc_type = sg_get_unaligned_be16(bp);
+ desc_len = sg_get_unaligned_be16(bp + 2);
+ if (op->verbose)
+ sgj_pr_hr(jsp, "Descriptor type=%d [0x%x] , len %d\n", desc_type,
+ desc_type, desc_len);
+ bump = 4 + desc_len;
+ if ((k + bump) > len) {
+ pr2serr("VPD page, short descriptor length=%d, left=%d\n", bump,
+ (len - k));
+ break;
+ }
+ if (0 == desc_len)
+ goto skip; /* continue plus attach jo2p */
+ if (2 == op->do_hex)
+ hex2stdout(bp + 4, desc_len, 1);
+ else if (op->do_hex > 2)
+ hex2stdout(bp, bump, 1);
+ else {
+ int csll;
+
+ dtp = get_tpc_desc_type_s(desc_type);
+ sgj_js_nv_ihexstr(jsp, jo2p, "third_party_copy_descriptor_type",
+ desc_type, NULL, dtp);
+ sgj_js_nv_ihex(jsp, jo2p, "third_party_copy_descriptor_length",
+ desc_len);
+
+ switch (desc_type) {
+ case 0x0000: /* Required if POPULATE TOKEN (or friend) used */
+ sgj_pr_hr(jsp, " %s:\n", dtp);
+ u = sg_get_unaligned_be16(bp + 10);
+ sgj_haj_vistr(jsp, jo2p, 2, "Maximum range descriptors",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u) ? nr_s : NULL);
+ u = sg_get_unaligned_be32(bp + 12);
+ if (0 == u)
+ cp = nr_s;
+ else if (SG_LIB_UNBOUNDED_32BIT == u)
+ cp = "No maximum given";
+ else
+ cp = NULL;
+ sgj_haj_vistr_nex(jsp, jo2p, 2, "Maximum inactivity timeout",
+ SGJ_SEP_COLON_1_SPACE, u, true, cp,
+ "unit: second");
+ u = sg_get_unaligned_be32(bp + 16);
+ sgj_haj_vistr_nex(jsp, jo2p, 2, "Default inactivity timeout",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ (0 == u) ? nr_s : NULL, "unit: second");
+ ull = sg_get_unaligned_be64(bp + 20);
+ sgj_haj_vistr_nex(jsp, jo2p, 2, "Maximum token transfer size",
+ SGJ_SEP_COLON_1_SPACE, ull, true,
+ (0 == ull) ? nr_s : NULL, "unit: LB");
+ ull = sg_get_unaligned_be64(bp + 28);
+ sgj_haj_vistr_nex(jsp, jo2p, 2, "Optimal transfer count",
+ SGJ_SEP_COLON_1_SPACE, ull, true,
+ (0 == ull) ? nr_s : NULL, "unit: LB");
+ break;
+ case 0x0001: /* Mandatory (SPC-4) */
+ sgj_pr_hr(jsp, " %s:\n", "Commands supported list");
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "commands_supported_list");
+ j = 0;
+ csll = bp[4];
+ if (csll >= desc_len) {
+ pr2serr("Command supported list length (%d) >= "
+ "descriptor length (%d), wrong so trim\n",
+ csll, desc_len);
+ csll = desc_len - 1;
+ }
+ while (j < csll) {
+ uint8_t opc, sa;
+ static const char * soc = "supported_operation_code";
+ static const char * ssa = "supported_service_action";
+
+ jo3p = NULL;
+ opc = bp[5 + j];
+ sa_len = bp[6 + j];
+ for (m = 0; (m < sa_len) && ((j + m) < csll); ++m) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ sa = bp[7 + j + m];
+ sg_get_opcode_sa_name(opc, sa, pdt, blen, b);
+ sgj_pr_hr(jsp, " %s\n", b);
+ sgj_js_nv_s(jsp, jo3p, "name", b);
+ sgj_js_nv_ihex(jsp, jo3p, soc, opc);
+ sgj_js_nv_ihex(jsp, jo3p, ssa, sa);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ if (0 == sa_len) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ sg_get_opcode_name(opc, pdt, blen, b);
+ sgj_pr_hr(jsp, " %s\n", b);
+ sgj_js_nv_s(jsp, jo3p, "name", b);
+ sgj_js_nv_ihex(jsp, jo3p, soc, opc);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ } else if (m < sa_len)
+ pr2serr("Supported service actions list length (%d) "
+ "is too large\n", sa_len);
+ j += m + 2;
+ }
+ break;
+ case 0x0004:
+ sgj_pr_hr(jsp, " %s:\n", dtp);
+ sgj_haj_vi(jsp, jo2p, 2, "Maximum CSCD descriptor count",
+ SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be16(bp + 8), true);
+ sgj_haj_vi(jsp, jo2p, 2, "Maximum segment descriptor count",
+ SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be16(bp + 10), true);
+ sgj_haj_vi(jsp, jo2p, 2, "Maximum descriptor list length",
+ SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be32(bp + 12), true);
+ sgj_haj_vi(jsp, jo2p, 2, "Maximum inline data length",
+ SGJ_SEP_COLON_1_SPACE,
+ sg_get_unaligned_be32(bp + 17), true);
+ break;
+ case 0x0008:
+ sgj_pr_hr(jsp, " Supported descriptors:\n");
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "supported_descriptor_list");
+ for (j = 0; j < bp[4]; j++) {
+ bool found_name;
+
+ jo3p = sgj_new_unattached_object_r(jsp);
+ u = bp[5 + j];
+ cp = get_tpc_desc_name(u);
+ found_name = (strlen(cp) > 0);
+ if (found_name)
+ sgj_pr_hr(jsp, " %s [0x%x]\n", cp, u);
+ else
+ sgj_pr_hr(jsp, " 0x%x\n", u);
+ sgj_js_nv_s(jsp, jo3p, "name", found_name ? cp : nr_s);
+ sgj_js_nv_ihex(jsp, jo3p, "code", u);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ break;
+ case 0x000C:
+ sgj_pr_hr(jsp, " Supported CSCD IDs (above 0x7ff):\n");
+ ja2p = sgj_named_subarray_r(jsp, jo2p, "supported_cscd_"
+ "descriptor_id_list");
+ v = sg_get_unaligned_be16(bp + 4);
+ for (j = 0; j < (int)v; j += 2) {
+ bool found_name;
+
+ jo3p = sgj_new_unattached_object_r(jsp);
+ u = sg_get_unaligned_be16(bp + 6 + j);
+ cp = get_cscd_desc_id_name(u);
+ found_name = (strlen(cp) > 0);
+ if (found_name)
+ sgj_pr_hr(jsp, " %s [0x%04x]\n", cp, u);
+ else
+ sgj_pr_hr(jsp, " 0x%04x\n", u);
+ sgj_js_nv_s(jsp, jo3p, "name", found_name ? cp : nr_s);
+ sgj_js_nv_ihex(jsp, jo3p, "id", u);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ break;
+ case 0x000D:
+ sgj_pr_hr(jsp, " Copy group identifier:\n");
+ u = bp[4];
+ sg_t10_uuid_desig2str(bp + 5, u, 1 /* c_set */, false,
+ true, NULL, blen, b);
+ sgj_pr_hr(jsp, " Locally assigned UUID: %s", b);
+ sgj_js_nv_s(jsp, jo2p, "locally_assigned_uuid", b);
+ break;
+ case 0x0106:
+ sgj_pr_hr(jsp, " ROD token features:\n");
+ sgj_haj_vi(jsp, jo2p, 2, "Remote tokens",
+ SGJ_SEP_COLON_1_SPACE, bp[4] & 0x0f, true);
+ u = sg_get_unaligned_be32(bp + 16);
+ sgj_pr_hr(jsp, " Minimum token lifetime: %u seconds\n", u);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "minimum_token_lifetime", u,
+ true, "unit: second");
+ u = sg_get_unaligned_be32(bp + 20);
+ sgj_pr_hr(jsp, " Maximum token lifetime: %u seconds\n", u);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "maximum_token_lifetime", u,
+ true, "unit: second");
+ u = sg_get_unaligned_be32(bp + 24);
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum token inactivity "
+ "timeout", SGJ_SEP_COLON_1_SPACE, u,
+ true, "unit: second");
+ u = sg_get_unaligned_be16(bp + 46);
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "rod_device_type_specific_features_descriptor_list");
+ decode_rod_descriptor(bp + 48, u, op, ja2p);
+ break;
+ case 0x0108:
+ sgj_pr_hr(jsp, " Supported ROD token and ROD types:\n");
+ ja2p = sgj_named_subarray_r(jsp, jo2p, "rod_type_"
+ "descriptor_list");
+ for (j = 0; j < sg_get_unaligned_be16(bp + 6); j+= 64) {
+ bool found_name;
+
+ jo3p = sgj_new_unattached_object_r(jsp);
+ u = sg_get_unaligned_be32(bp + 8 + j);
+ cp = get_tpc_rod_name(u);
+ found_name = (strlen(cp) > 0);
+ if (found_name > 0)
+ sgj_pr_hr(jsp, " ROD type: %s [0x%x]\n", cp, u);
+ else
+ sgj_pr_hr(jsp, " ROD type: 0x%x\n", u);
+ sgj_js_nv_ihexstr(jsp, jo3p, "rod_type", u, NULL,
+ found_name ? cp : NULL);
+ u = bp[8 + j + 4];
+ sgj_pr_hr(jsp, " ECPY_INT: %s\n",
+ (u & 0x80) ? y_s : n_s);
+ sgj_js_nv_ihex_nex(jsp, jo3p, "ecpy_int", !!(0x80 & u),
+ false, "Extended CoPY INTernal rods");
+ sgj_pr_hr(jsp, " Token in: %s\n",
+ (u & 0x2) ? y_s : n_s);
+ sgj_js_nv_i(jsp, jo3p, "token_in", !!(0x2 & u));
+ sgj_pr_hr(jsp, " Token out: %s\n",
+ (u & 0x1) ? y_s : n_s);
+ sgj_js_nv_i(jsp, jo3p, "token_out", !!(0x2 & u));
+ u = sg_get_unaligned_be16(bp + 8 + j + 6);
+ sgj_haj_vi(jsp, jo3p, 4, "Preference indicator",
+ SGJ_SEP_COLON_1_SPACE, u, true);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ break;
+ case 0x8001: /* Mandatory (SPC-4) */
+ sgj_pr_hr(jsp, " General copy operations:\n");
+ u = sg_get_unaligned_be32(bp + 4);
+ sgj_haj_vi(jsp, jo2p, 2, "Total concurrent copies",
+ SGJ_SEP_COLON_1_SPACE, u, true);
+ u = sg_get_unaligned_be32(bp + 8);
+ sgj_haj_vi(jsp, jo2p, 2, "Maximum identified concurrent "
+ "copies", SGJ_SEP_COLON_1_SPACE, u, true);
+ u = sg_get_unaligned_be32(bp + 12);
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum segment length",
+ SGJ_SEP_COLON_1_SPACE, u, true, "unit: byte");
+ u = bp[16]; /* field is power of 2 */
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Data segment granularity",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ "unit: 2^val LB");
+ u = bp[17]; /* field is power of 2 */
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Inline data granularity",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ "unit: 2^val LB");
+ break;
+ case 0x9101:
+ sgj_pr_hr(jsp, " Stream copy operations:\n");
+ u = sg_get_unaligned_be32(bp + 4);
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Maximum stream device transfer "
+ "size", SGJ_SEP_COLON_1_SPACE, u, true,
+ "unit: byte");
+ break;
+ case 0xC001:
+ sgj_pr_hr(jsp, " Held data:\n");
+ u = sg_get_unaligned_be32(bp + 4);
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Held data limit",
+ SGJ_SEP_COLON_1_SPACE, u, true,
+ "unit: byte; (lower limit: minimum)");
+ sgj_haj_vi_nex(jsp, jo2p, 2, "Held data granularity",
+ SGJ_SEP_COLON_1_SPACE, bp[8], true,
+ "unit: 2^val byte");
+ break;
+ default:
+ pr2serr("Unexpected type=%d\n", desc_type);
+ hex2stderr(bp, bump, 1);
+ break;
+ }
+ }
+skip:
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ jo2p = NULL;
+ }
+ if (jo2p)
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+}
+
+/* VPD_PROTO_LU 0x90 ["pslu"] */
+void
+decode_proto_lu_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump, rel_port, desc_len, proto;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ char b[128];
+ static const int blen = sizeof(b);
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ rel_port = sg_get_unaligned_be16(bp);
+ sgj_haj_vi(jsp, jo2p, 2, "Relative port",
+ SGJ_SEP_COLON_1_SPACE, rel_port, true);
+ proto = bp[2] & 0xf;
+ sg_get_trans_proto_str(proto, blen, b);
+ sgj_haj_vistr(jsp, jo2p, 4, "Protocol identifier",
+ SGJ_SEP_COLON_1_SPACE, proto, false, b);
+ desc_len = sg_get_unaligned_be16(bp + 6);
+ bump = 8 + desc_len;
+ if ((k + bump) > len) {
+ pr2serr("Protocol-specific logical unit information VPD page, "
+ "short descriptor length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (0 == desc_len)
+ goto again;
+ if (2 == op->do_hex) {
+ hex2stdout(bp + 8, desc_len, 1);
+ goto again;
+ }
+ switch (proto) {
+ case TPROTO_SAS:
+ sgj_haj_vi(jsp, jo2p, 2, "TLR control supported",
+ SGJ_SEP_COLON_1_SPACE, !!(bp[8] & 0x1), false);
+ break;
+ default:
+ pr2serr("Unexpected proto=%d\n", proto);
+ hex2stderr(bp, bump, 1);
+ break;
+ }
+again:
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* VPD_PROTO_PORT 0x91 ["pspo"] */
+void
+decode_proto_port_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ bool pds, ssp_pers;
+ int k, j, bump, rel_port, desc_len, proto, phy;
+ const uint8_t * bp;
+ const uint8_t * pidp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p ja2p = NULL;
+ sgj_opaque_p jo3p = NULL;
+ char b[128];
+ static const int blen = sizeof(b);
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1);
+ return;
+ }
+ if (len < 4) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 4;
+ bp = buff + 4;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ rel_port = sg_get_unaligned_be16(bp);
+ sgj_haj_vi(jsp, jo2p, 2, "Relative port",
+ SGJ_SEP_COLON_1_SPACE, rel_port, true);
+ proto = bp[2] & 0xf;
+ sg_get_trans_proto_str(proto, blen, b);
+ sgj_haj_vistr(jsp, jo2p, 4, "Protocol identifier",
+ SGJ_SEP_COLON_1_SPACE, proto, false, b);
+ desc_len = sg_get_unaligned_be16(bp + 6);
+ bump = 8 + desc_len;
+ if ((k + bump) > len) {
+ pr2serr("VPD page, short descriptor length=%d, left=%d\n",
+ bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ if (0 == desc_len)
+ goto again;
+ if (2 == op->do_hex) {
+ hex2stdout(bp + 8, desc_len, 1);
+ goto again;
+ }
+ switch (proto) {
+ case TPROTO_SAS: /* page added in spl3r02 */
+ pds = !!(bp[3] & 0x1);
+ sgj_pr_hr(jsp, " power disable supported (pwr_d_s)=%d\n", pds);
+ sgj_js_nv_ihex_nex(jsp, jo2p, "pwr_d_s", pds, false,
+ "PoWeR Disable Supported");
+ ja2p = sgj_named_subarray_r(jsp, jo2p,
+ "sas_phy_information_descriptor_list");
+ pidp = bp + 8;
+ for (j = 0; j < desc_len; j += 4, pidp += 4) {
+ jo3p = sgj_new_unattached_object_r(jsp);
+ phy = pidp[1];
+ ssp_pers = !!(0x1 & pidp[2]);
+ sgj_pr_hr(jsp, " phy id=%d, SSP persistent capable=%d\n",
+ phy, ssp_pers);
+ sgj_js_nv_ihex(jsp, jo3p, "phy_identifier", phy);
+ sgj_js_nv_i(jsp, jo3p, "ssp_persistent_capable", ssp_pers);
+ sgj_js_nv_o(jsp, ja2p, NULL /* name */, jo3p);
+ }
+ break;
+ default:
+ pr2serr("Unexpected proto=%d\n", proto);
+ hex2stderr(bp, bump, 1);
+ break;
+ }
+again:
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* VPD_LB_PROTECTION 0xb5 (SSC) [added in ssc5r02a] */
+void
+decode_lb_protection_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap)
+{
+ int k, bump;
+ const uint8_t * bp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+
+ if ((1 == op->do_hex) || (op->do_hex > 2)) {
+ hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1);
+ return;
+ }
+ if (len < 8) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ len -= 8;
+ bp = buff + 8;
+ for (k = 0; k < len; k += bump, bp += bump) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ bump = 1 + bp[0];
+ sgj_pr_hr(jsp, " method: %d, info_len: %d, LBP_W_C=%d, LBP_R_C=%d, "
+ "RBDP_C=%d\n", bp[1], 0x3f & bp[2], !!(0x80 & bp[3]),
+ !!(0x40 & bp[3]), !!(0x20 & bp[3]));
+ sgj_js_nv_ihex(jsp, jo2p, "logical_block_protection_method", bp[1]);
+ sgj_js_nv_ihex_nex(jsp, jo2p,
+ "logical_block_protection_information_length",
+ 0x3f & bp[2], true, "unit: byte");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "lbp_w_c", !!(0x80 & bp[3]), false,
+ "Logical Blocks Protected during Write supported");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "lbp_r_c", !!(0x40 & bp[3]), false,
+ "Logical Blocks Protected during Read supported");
+ sgj_js_nv_ihex_nex(jsp, jo2p, "rbdp_c", !!(0x20 & bp[3]), false,
+ "Recover Buffered Data Protected supported");
+ if ((k + bump) > len) {
+ pr2serr("Logical block protection VPD page, short "
+ "descriptor length=%d, left=%d\n", bump, (len - k));
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ return;
+ }
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+}
+
+/* VPD_TA_SUPPORTED 0xb2 ["tas"] */
+void
+decode_tapealert_supported_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop)
+{
+ bool have_ta_strs = !! sg_lib_tapealert_strs[0];
+ int k, mod, div, n;
+ unsigned int supp;
+ sgj_state * jsp = &op->json_st;
+ char b[144];
+ char d[64];
+ static const int blen = sizeof(b);
+
+ if (len < 12) {
+ pr2serr("VPD page length too short=%d\n", len);
+ return;
+ }
+ b[0] ='\0';
+ for (k = 1, n = 0; k < 0x41; ++k) {
+ mod = ((k - 1) % 8);
+ div = (k - 1) / 8;
+ supp = !! (buff[4 + div] & (1 << (7 - mod)));
+ if (jsp->pr_as_json) {
+ snprintf(d, sizeof(d), "flag%02xh", k);
+ if (have_ta_strs)
+ sgj_js_nv_ihex_nex(jsp, jop, d, supp, false,
+ sg_lib_tapealert_strs[k]);
+ else
+ sgj_js_nv_i(jsp, jop, d, supp);
+ }
+ if (0 == mod) {
+ if (div > 0) {
+ sgj_pr_hr(jsp, "%s\n", b);
+ n = 0;
+ }
+ n += sg_scnpr(b + n, blen - n, " Flag%02Xh: %d", k, supp);
+ } else
+ n += sg_scnpr(b + n, blen - n, " %02Xh: %d", k, supp);
+ }
+ sgj_pr_hr(jsp, "%s\n", b);
+}
+
+/*
+ * Some of the vendor specific VPD pages are common as well. So place them here
+ * to save on code duplication.
+ */
+
+static const char * lun_state_arr[] =
+{
+ "LUN not bound or LUN_Z report",
+ "LUN bound, but not owned by this SP",
+ "LUN bound and owned by this SP",
+};
+
+static const char * ip_mgmt_arr[] =
+{
+ "No IP access",
+ "Reserved (undefined)",
+ "via IPv4",
+ "via IPv6",
+};
+
+static const char * sp_arr[] =
+{
+ "SP A",
+ "SP B",
+};
+
+static const char * lun_op_arr[] =
+{
+ "Normal operations",
+ "I/O Operations being rejected, SP reboot or NDU in progress",
+};
+
+static const char * failover_mode_arr[] =
+{
+ "Legacy mode 0",
+ "Unknown mode (1)",
+ "Unknown mode (2)",
+ "Unknown mode (3)",
+ "Active/Passive (PNR) mode 1",
+ "Unknown mode (5)",
+ "Active/Active (ALUA) mode 4",
+ "Unknown mode (7)",
+ "Legacy mode 2",
+ "Unknown mode (9)",
+ "Unknown mode (10)",
+ "Unknown mode (11)",
+ "Unknown mode (12)",
+ "Unknown mode (13)",
+ "AIX Active/Passive (PAR) mode 3",
+ "Unknown mode (15)",
+};
+
+/* VPD_UPR_EMC,VPD_V_UPR_EMC 0xc0 ["upr","upr"] */
+void
+decode_upr_vpd_c0_emc(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ uint8_t uc;
+ int k, n, ip_mgmt, vpp80, lun_z;
+ sgj_state * jsp = &op->json_st;
+ const char * cp;
+ const char * c2p;
+ char b[256];
+ static const int blen = sizeof(b);
+
+ if (len < 3) {
+ pr2serr("EMC upr VPD page [0xc0]: length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (buff[9] != 0x00) {
+ pr2serr("Unsupported page revision %d, decoding not possible.\n",
+ buff[9]);
+ return;
+ }
+ for (k = 0, n = 0; k < 16; ++k)
+ n += sg_scnpr(b + n, blen - n, "%02x", buff[10 + k]);
+ sgj_haj_vs(jsp, jop, 2, "LUN WWN", SGJ_SEP_COLON_1_SPACE, b);
+ snprintf(b, blen, "%.*s", buff[49], buff + 50);
+ sgj_haj_vs(jsp, jop, 2, "Array Serial Number", SGJ_SEP_COLON_1_SPACE, b);
+
+ if (buff[4] > 0x02)
+ snprintf(b, blen, "Unknown (%x)", buff[4]);
+ else
+ snprintf(b, blen, "%s", lun_state_arr[buff[4]]);
+ sgj_haj_vistr(jsp, jop, 2, "LUN State", SGJ_SEP_COLON_1_SPACE,
+ buff[4], true, b);
+
+ uc = buff[8];
+ n = 0;
+ if (uc > 0x01)
+ n += sg_scnpr(b + n, blen - n, "Unknown SP (%x)", uc);
+ else
+ n += sg_scnpr(b + n, blen - n, "%s", sp_arr[uc]);
+ sgj_js_nv_ihexstr(jsp, jop, "path_connects_to", uc, NULL, b);
+ n += sg_scnpr(b + n, blen - n, ", Port Number: %u", buff[7]);
+ sgj_pr_hr(jsp, " This path connects to: %s\n", b);
+ sgj_js_nv_ihex(jsp, jop, "port_number", buff[7]);
+
+ if (buff[5] > 0x01)
+ snprintf(b, blen, "Unknown (%x)\n", buff[5]);
+ else
+ snprintf(b, blen, "%s\n", sp_arr[buff[5]]);
+ sgj_haj_vistr(jsp, jop, 2, "Default owner", SGJ_SEP_COLON_1_SPACE,
+ buff[5], true, b);
+
+ cp = (buff[6] & 0x40) ? "supported" : "not supported";
+ sgj_pr_hr(jsp, " NO_ATF: %s, Access Logix: %s\n",
+ buff[6] & 0x80 ? "set" : "not set", cp);
+ sgj_js_nv_i(jsp, jop, "no_atf", !! (buff[6] & 0x80));
+ sgj_js_nv_istr(jsp, jop, "access_logix", !! (buff[6] & 0x40),
+ NULL, cp);
+
+ ip_mgmt = (buff[6] >> 4) & 0x3;
+ cp = ip_mgmt_arr[ip_mgmt];
+ sgj_pr_hr(jsp, " SP IP Management Mode: %s\n", cp);
+ sgj_js_nv_istr(jsp, jop, "sp_ip_management_mode", !! ip_mgmt,
+ NULL, cp);
+ if (ip_mgmt == 2) {
+ snprintf(b, blen, "%u.%u.%u.%u", buff[44], buff[45], buff[46],
+ buff[47]);
+ sgj_pr_hr(jsp, " SP IPv4 address: %s\n", b);
+ sgj_js_nv_s(jsp, jop, "sp_ipv4_address", b);
+ } else if (ip_mgmt == 3) {
+ printf(" SP IPv6 address: ");
+ n = 0;
+ for (k = 0; k < 16; ++k)
+ n += sg_scnpr(b + n, blen - n, "%02x", buff[32 + k]);
+ sgj_pr_hr(jsp, " SP IPv6 address: %s\n", b);
+ sgj_js_nv_hex_bytes(jsp, jop, "sp_ipv6_address", buff + 32, 16);
+ }
+
+ k = buff[28] & 0x0f;
+ sgj_pr_hr(jsp, " System Type: %x, Failover mode: %s\n",
+ buff[27], failover_mode_arr[k]);
+ sgj_js_nv_ihex(jsp, jop, "system_type", buff[27]);
+ sgj_js_nv_ihexstr(jsp, jop, "failover_mode", k, NULL,
+ failover_mode_arr[k]);
+
+ vpp80 = buff[30] & 0x08;
+ lun_z = buff[30] & 0x04;
+ cp = vpp80 ? "array serial#" : "LUN serial#";
+ c2p = lun_z ? "Set to 1" : "Unknown";
+ sgj_pr_hr(jsp, " Inquiry VPP 0x80 returns: %s, Arraycommpath: %s\n",
+ cp, c2p);
+ sgj_js_nv_istr(jsp, jop, "inquiry_vpp_0x80_returns", !! vpp80, NULL, cp);
+ sgj_js_nv_istr(jsp, jop, "arraycommpath", !! lun_z, NULL, c2p);
+
+ cp = buff[48] > 1 ? "undefined" : lun_op_arr[buff[48]];
+ sgj_pr_hr(jsp, " Lun operations: %s\n", cp);
+ sgj_js_nv_istr(jsp, jop, "lun_operations", 0x1 & buff[48], NULL, cp);
+
+ return;
+}
+
+/* VPD_RDAC_VERS,VPD_V_SVER_RDAC 0xc2 ["rdac_vers", "swr4"] */
+void
+decode_rdac_vpd_c2(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ int i, n, v, r, m, p, d, y, num_part;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jap = NULL;
+ // const char * cp;
+ // const char * c2p;
+ char b[256];
+ static const int blen = sizeof(b);
+ char part[5];
+
+ if (len < 3) {
+ pr2serr("Software Version VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (buff[4] != 's' && buff[5] != 'w' && buff[6] != 'r') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ snprintf(b, blen, "%02x.%02x.%02x", buff[8], buff[9], buff[10]);
+ sgj_haj_vs(jsp, jop, 2, "Software Version", SGJ_SEP_COLON_1_SPACE, b);
+ snprintf(b, blen, "%02d/%02d/%02d\n", buff[11], buff[12], buff[13]);
+ sgj_haj_vs(jsp, jop, 2, "Software Date", SGJ_SEP_COLON_1_SPACE, b);
+ n = 0;
+ n += sg_scnpr(b + n, blen - n, " Features:");
+ if (buff[14] & 0x01)
+ n += sg_scnpr(b + n, blen - n, " Dual Active,");
+ if (buff[14] & 0x02)
+ n += sg_scnpr(b + n, blen - n, " Series 3,");
+ if (buff[14] & 0x04)
+ n += sg_scnpr(b + n, blen - n, " Multiple Sub-enclosures,");
+ if (buff[14] & 0x08)
+ n += sg_scnpr(b + n, blen - n, " DCE/DRM/DSS/DVE,");
+ if (buff[14] & 0x10)
+ n += sg_scnpr(b + n, blen - n, " Asymmetric Logical Unit Access,");
+ sgj_pr_hr(jsp, "%s\n", b);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, "features");
+ sgj_js_nv_i(jsp, jo2p, "dual_active", !! (buff[14] & 0x01));
+ sgj_js_nv_i(jsp, jo2p, "series_3", !! (buff[14] & 0x02));
+ sgj_js_nv_i(jsp, jo2p, "multiple_sub_enclosures",
+ !! (buff[14] & 0x04));
+ sgj_js_nv_i(jsp, jo2p, "dcm_drm_dss_dve", !! (buff[14] & 0x08));
+ sgj_js_nv_i(jsp, jo2p, "asymmetric_logical_unit_access",
+ !! (buff[14] & 0x10));
+ }
+ sgj_haj_vi(jsp, jop, 2, "Maximum number of LUNS",
+ SGJ_SEP_COLON_1_SPACE, buff[15], true);
+
+ num_part = (len - 12) / 16;
+ n = 16;
+ printf(" Partitions: %d\n", num_part);
+ sgj_haj_vi(jsp, jop, 2, "Partitions", SGJ_SEP_COLON_1_SPACE, num_part,
+ true);
+ if (num_part > 0)
+ jap = sgj_named_subarray_r(jsp, jop, "partition_list");
+ for (i = 0; i < num_part; i++) {
+ memset(part,0, 5);
+ memcpy(part, &buff[n], 4);
+ sgj_pr_hr(jsp, " Name: %s\n", part);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_new_unattached_object_r(jsp);
+ sgj_js_nv_s(jsp, jo2p, "name", part);
+ }
+ n += 4;
+ v = buff[n++];
+ r = buff[n++];
+ m = buff[n++];
+ p = buff[n++];
+ snprintf(b, blen, "%d.%d.%d.%d", v, r, m, p);
+ sgj_pr_hr(jsp, " Version: %s\n", b);
+ if (jsp->pr_as_json)
+ sgj_js_nv_s(jsp, jo2p, "version", b);
+ m = buff[n++];
+ d = buff[n++];
+ y = buff[n++];
+ snprintf(b, blen, "%d/%d/%d\n", m, d, y);
+ sgj_pr_hr(jsp, " Date: %s\n", b);
+ if (jsp->pr_as_json) {
+ sgj_js_nv_s(jsp, jo2p, "date", b);
+ sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
+ }
+
+ n += 5;
+ }
+ return;
+}
+
+static char *
+decode_rdac_vpd_c9_aas_s(uint8_t aas, char * b, int blen)
+{
+ // snprintf(" Asymmetric Access State:");
+ switch(aas & 0x0F) {
+ case 0x0:
+ snprintf(b, blen, "Active/Optimized");
+ break;
+ case 0x1:
+ snprintf(b, blen, "Active/Non-Optimized");
+ break;
+ case 0x2:
+ snprintf(b, blen, "Standby");
+ break;
+ case 0x3:
+ snprintf(b, blen, "Unavailable");
+ break;
+ case 0xE:
+ snprintf(b, blen, "Offline");
+ break;
+ case 0xF:
+ snprintf(b, blen, "Transitioning");
+ break;
+ default:
+ snprintf(b, blen, "(unknown)");
+ break;
+ }
+ return b;
+}
+
+static char *
+decode_rdac_vpd_c9_vs_s(uint8_t vendor, char * b, int blen)
+{
+ // printf(" Vendor Specific Field:");
+ switch(vendor) {
+ case 0x01:
+ snprintf(b, blen, "Operating normally");
+ break;
+ case 0x02:
+ snprintf(b, blen, "Non-responsive to queries");
+ break;
+ case 0x03:
+ snprintf(b, blen, "Controller being held in reset");
+ break;
+ case 0x04:
+ snprintf(b, blen, "Performing controller firmware download (1st "
+ "controller)");
+ break;
+ case 0x05:
+ snprintf(b, blen, "Performing controller firmware download (2nd "
+ "controller)");
+ break;
+ case 0x06:
+ snprintf(b, blen,
+ "Quiesced as a result of an administrative request");
+ break;
+ case 0x07:
+ snprintf(b, blen,
+ "Service mode as a result of an administrative request");
+ break;
+ case 0xFF:
+ snprintf(b, blen, "Details are not available");
+ break;
+ default:
+ snprintf(b, blen, "(unknown)");
+ break;
+ }
+ return b;
+}
+
+/* VPD_RDAC_VAC,VPD_V_VAC_RDAC 0xc9 ["rdac_vac", "vac1"] */
+void
+decode_rdac_vpd_c9(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop)
+{
+ bool vav;
+ int n, n_hold;
+ sgj_state * jsp = &op->json_st;
+ char b[196];
+ static const int blen = sizeof(b);
+
+ if (len < 3) {
+ pr2serr("Volume Access Control VPD page length too short=%d\n", len);
+ return;
+ }
+ if (op->do_hex) {
+ hex2stdout(buff, len, no_ascii_4hex(op));
+ return;
+ }
+ if (buff[4] != 'v' && buff[5] != 'a' && buff[6] != 'c') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding "
+ "not possible.\n" , buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ if (buff[7] != '1') {
+ pr2serr("Invalid page version '%c' (should be 1)\n", buff[7]);
+ }
+ n = ((buff[8] & 0xE0) == 0xE0 );
+ if (n) {
+ sgj_pr_hr(jsp, " IOShipping (ALUA): Enabled\n");
+ sgj_js_nv_ihexstr_nex(jsp, jop, "ioshipping", n, true, NULL,
+ "Enabled",
+ "a.k.a. ALUA (Asymmetric Logical Unit Access)");
+ } else {
+ n = 0;
+ n = snprintf(b, blen, " AVT:");
+ n_hold = n;
+ if (buff[8] & 0x80) {
+ n += sg_scnpr(b + n, blen - n, " Enabled");
+ if (buff[8] & 0x40)
+ n += sg_scnpr(b + n, blen - n, " (Allow reads on sector 0)");
+ sgj_pr_hr(jsp, "%s\n", b);
+ sgj_js_nv_ihexstr(jsp, jop, "avt", buff[8], NULL, b + n_hold);
+
+ } else {
+ sgj_pr_hr(jsp, "%s: Disabled\n", b);
+ sgj_js_nv_ihexstr(jsp, jop, "avt", buff[8], NULL, "Disabled");
+ }
+ }
+ vav = !! (0x1 & buff[8]);
+ sgj_haj_vistr(jsp, jop, 2, "Volume access via", SGJ_SEP_COLON_1_SPACE,
+ (int)vav, false,
+ (vav ? "primary controller" : "alternate controller"));
+
+ if (buff[8] & 0x08) {
+ n = buff[15] & 0xf;
+ // printf(" Path priority: %d ", n);
+ switch (n) {
+ case 0x1:
+ snprintf(b, blen, "(preferred path)");
+ break;
+ case 0x2:
+ snprintf(b, blen, "(secondary path)");
+ break;
+ default:
+ snprintf(b, blen, "(unknown)");
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Path priority", SGJ_SEP_COLON_1_SPACE, n,
+ true, b);
+
+ // printf(" Preferred Path Auto Changeable:");
+ n = buff[14] & 0x3C;
+ switch (n) {
+ case 0x14:
+ snprintf(b, blen, "No (User Disabled and Host Type Restricted)");
+ break;
+ case 0x18:
+ snprintf(b, blen, "No (User Disabled)");
+ break;
+ case 0x24:
+ snprintf(b, blen, "No (Host Type Restricted)");
+ break;
+ case 0x28:
+ snprintf(b, blen, "Yes");
+ break;
+ default:
+ snprintf(b, blen, "(Unknown)");
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Preferred path auto changeable",
+ SGJ_SEP_COLON_1_SPACE, n, true, b);
+
+ n = buff[14] & 0x03;
+ // printf(" Implicit Failback:");
+ switch (n) {
+ case 0x1:
+ snprintf(b, blen, "Disabled");
+ break;
+ case 0x2:
+ snprintf(b, blen, "Enabled");
+ break;
+ default:
+ snprintf(b, blen, "(Unknown)");
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Implicit failback",
+ SGJ_SEP_COLON_1_SPACE, n, false, b);
+ } else {
+ n = buff[9] & 0xf;
+ // printf(" Path priority: %d ", buff[9] & 0xf);
+ switch (n) {
+ case 0x1:
+ snprintf(b, blen, "(preferred path)");
+ break;
+ case 0x2:
+ snprintf(b, blen, "(secondary path)");
+ break;
+ default:
+ snprintf(b, blen, "(unknown)");
+ break;
+ }
+ sgj_haj_vistr(jsp, jop, 2, "Path priority",
+ SGJ_SEP_COLON_1_SPACE, n, false, b);
+ }
+
+ n = !! (buff[8] & 0x80);
+ sgj_haj_vi(jsp, jop, 2, "Target port group present",
+ SGJ_SEP_COLON_1_SPACE, n, false);
+ if (n) {
+ sgj_opaque_p jo2p = NULL;
+ sgj_opaque_p jo3p = NULL;
+ static const char * tpg_s = "Target port group data";
+ static const char * aas_s = "Asymmetric access state";
+ static const char * vsf_s = "Vendor specific field";
+ char d1[80];
+ char d2[80];
+
+ sgj_pr_hr(jsp, " Target Port Group Data (This controller):\n");
+ decode_rdac_vpd_c9_aas_s(buff[10], d1, sizeof(d1));
+ decode_rdac_vpd_c9_vs_s(buff[11], d2, sizeof(d2));
+ sgj_pr_hr(jsp, " %s: %s\n", aas_s, d1);
+ sgj_pr_hr(jsp, " %s: %s\n", vsf_s, d2);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, tpg_s);
+ jo3p = sgj_snake_named_subobject_r(jsp, jo2p, "this_controller");
+ sgj_convert_to_snake_name(aas_s, b, blen);
+ sgj_js_nv_ihexstr(jsp, jo3p, b, buff[10], NULL, d1);
+ sgj_convert_to_snake_name(vsf_s, b, blen);
+ sgj_js_nv_ihexstr(jsp, jo3p, b, buff[11], NULL, d2);
+ }
+ sgj_pr_hr(jsp, " Target Port Group Data (Alternate controller):\n");
+ // decode_rdac_vpd_c9_rtpg_data(buff[12], buff[13]);
+
+ decode_rdac_vpd_c9_aas_s(buff[12], d1, sizeof(d1));
+ decode_rdac_vpd_c9_vs_s(buff[13], d2, sizeof(d2));
+ sgj_pr_hr(jsp, " %s: %s\n", aas_s, d1);
+ sgj_pr_hr(jsp, " %s: %s\n", vsf_s, d2);
+ if (jsp->pr_as_json) {
+ jo2p = sgj_snake_named_subobject_r(jsp, jop, tpg_s);
+ jo3p = sgj_snake_named_subobject_r(jsp, jo2p,
+ "alternate_controller");
+ sgj_convert_to_snake_name(aas_s, b, blen);
+ sgj_js_nv_ihexstr(jsp, jo3p, b, buff[12], NULL, d1);
+ sgj_convert_to_snake_name(vsf_s, b, blen);
+ sgj_js_nv_ihexstr(jsp, jo3p, b, buff[13], NULL, d2);
+ }
+ }
+}
diff --git a/src/sg_vpd_common.h b/src/sg_vpd_common.h
new file mode 100644
index 00000000..13134336
--- /dev/null
+++ b/src/sg_vpd_common.h
@@ -0,0 +1,294 @@
+#ifndef SG_VPD_H
+#define SG_VPD_H
+
+/*
+ * Copyright (c) 2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* This is a common header file for the sg_inq and sg_vpd utilities */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* standard VPD pages, in ascending page number order */
+#define VPD_SUPPORTED_VPDS 0x0
+#define VPD_UNIT_SERIAL_NUM 0x80
+#define VPD_IMP_OP_DEF 0x81 /* obsolete in SPC-2 */
+#define VPD_ASCII_OP_DEF 0x82 /* obsolete in SPC-2 */
+#define VPD_DEVICE_ID 0x83
+#define VPD_SOFTW_INF_ID 0x84
+#define VPD_MAN_NET_ADDR 0x85
+#define VPD_EXT_INQ 0x86 /* Extended Inquiry */
+#define VPD_MODE_PG_POLICY 0x87
+#define VPD_SCSI_PORTS 0x88
+#define VPD_ATA_INFO 0x89
+#define VPD_POWER_CONDITION 0x8a
+#define VPD_DEVICE_CONSTITUENTS 0x8b
+#define VPD_CFA_PROFILE_INFO 0x8c
+#define VPD_POWER_CONSUMPTION 0x8d
+#define VPD_3PARTY_COPY 0x8f /* 3PC, XCOPY, SPC-5, SBC-4 */
+#define VPD_PROTO_LU 0x90
+#define VPD_PROTO_PORT 0x91
+#define VPD_SCSI_FEATURE_SETS 0x92 /* spc5r11 */
+#define VPD_BLOCK_LIMITS 0xb0 /* SBC-3 */
+#define VPD_SA_DEV_CAP 0xb0 /* SSC-3 */
+#define VPD_OSD_INFO 0xb0 /* OSD */
+#define VPD_BLOCK_DEV_CHARS 0xb1 /* SBC-3 */
+#define VPD_MAN_ASS_SN 0xb1 /* SSC-3, ADC-2 */
+#define VPD_SECURITY_TOKEN 0xb1 /* OSD */
+#define VPD_TA_SUPPORTED 0xb2 /* SSC-3 */
+#define VPD_LB_PROVISIONING 0xb2 /* SBC-3 */
+#define VPD_REFERRALS 0xb3 /* SBC-3 */
+#define VPD_AUTOMATION_DEV_SN 0xb3 /* SSC-3 */
+#define VPD_SUP_BLOCK_LENS 0xb4 /* sbc4r01 */
+#define VPD_DTDE_ADDRESS 0xb4 /* SSC-4 */
+#define VPD_BLOCK_DEV_C_EXTENS 0xb5 /* sbc4r02 */
+#define VPD_LB_PROTECTION 0xb5 /* SSC-5 */
+#define VPD_ZBC_DEV_CHARS 0xb6 /* zbc-r01b */
+#define VPD_BLOCK_LIMITS_EXT 0xb7 /* sbc4r08 */
+#define VPD_FORMAT_PRESETS 0xb8 /* sbc4r18 */
+#define VPD_CON_POS_RANGE 0xb9 /* sbc5r01 */
+#define VPD_NOPE_WANT_STD_INQ -2 /* request for standard inquiry */
+
+/* vendor/product identifiers */
+#define VPD_VP_SEAGATE 0
+#define VPD_VP_RDAC 1
+#define VPD_VP_EMC 2
+#define VPD_VP_DDS 3
+#define VPD_VP_HP3PAR 4
+#define VPD_VP_IBM_LTO 5
+#define VPD_VP_HP_LTO 6
+#define VPD_VP_WDC_HITACHI 7
+#define VPD_VP_NVME 8
+#define VPD_VP_SG 9 /* this package/library as a vendor */
+
+
+/* vendor VPD pages */
+#define VPD_V_HIT_PG3 0x3
+#define VPD_V_HP3PAR 0xc0
+#define VPD_V_FIRM_SEA 0xc0
+#define VPD_V_UPR_EMC 0xc0
+#define VPD_V_HVER_RDAC 0xc0
+#define VPD_V_FVER_DDS 0xc0
+#define VPD_V_FVER_LTO 0xc0
+#define VPD_V_DCRL_LTO 0xc0
+#define VPD_V_DATC_SEA 0xc1
+#define VPD_V_FVER_RDAC 0xc1
+#define VPD_V_HVER_LTO 0xc1
+#define VPD_V_DSN_LTO 0xc1
+#define VPD_V_JUMP_SEA 0xc2
+#define VPD_V_SVER_RDAC 0xc2
+#define VPD_V_PCA_LTO 0xc2
+#define VPD_V_DEV_BEH_SEA 0xc3
+#define VPD_V_FEAT_RDAC 0xc3
+#define VPD_V_MECH_LTO 0xc3
+#define VPD_V_SUBS_RDAC 0xc4
+#define VPD_V_HEAD_LTO 0xc4
+#define VPD_V_ACI_LTO 0xc5
+#define VPD_V_DUCD_LTO 0xc7
+#define VPD_V_EDID_RDAC 0xc8
+#define VPD_V_MPDS_LTO 0xc8
+#define VPD_V_VAC_RDAC 0xc9
+#define VPD_V_RVSI_RDAC 0xca
+#define VPD_V_SAID_RDAC 0xd0
+#define VPD_V_HIT_PG_D1 0xd1
+#define VPD_V_HIT_PG_D2 0xd2
+
+#ifndef SG_NVME_VPD_NICR
+#define SG_NVME_VPD_NICR 0xde /* NVME Identify Controller Response */
+#endif
+
+#define DEF_ALLOC_LEN 252
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+enum sg_vpd_invoker_e {
+ SG_VPD_INV_NONE = 0,
+ SG_VPD_INV_SG_INQ,
+ SG_VPD_INV_SG_VPD,
+};
+
+/* This structure holds the union of options available in sg_inq and sg_vpd */
+struct opts_t {
+ enum sg_vpd_invoker_e invoker; /* indicates if for sg_inq or sg_vpd */
+ bool do_all; /* sg_vpd */
+ bool do_ata; /* sg_inq */
+ bool do_decode; /* sg_inq */
+ bool do_descriptors; /* sg_inq */
+ bool do_enum; /* sg_enum */
+ bool do_export; /* sg_inq */
+ bool do_force; /* sg_inq + sg_vpd */
+ bool do_only; /* sg_inq: --only after stdinq: don't fetch VPD page 0x80 */
+ bool do_quiet; /* sg_vpd */
+ bool examine_given; /* sg_vpd */
+ bool page_given; /* sg_inq + sg_vpd */
+ bool possible_nvme; /* sg_inq */
+ bool protect_not_sure; /* sg_vpd */
+ bool verbose_given; /* sg_inq + sg_vpd */
+ bool version_given; /* sg_inq + sg_vpd */
+ bool do_vpd; /* sg_inq */
+ bool std_inq_a_valid; /* sg_inq + sg_vpd */
+#ifdef SG_SCSI_STRINGS
+ bool opt_new; /* sg_inq */
+#endif
+ int do_block; /* do_block */
+ int do_cmddt; /* sg_inq */
+ int do_help; /* sg_inq */
+ int do_hex; /* sg_inq + sg_vpd */
+ int do_ident; /* sg_vpd */
+ int do_long; /* sg_inq[int] + sg_vpd[bool] */
+ int do_raw; /* sg_inq + sg_vpd */
+ int do_vendor; /* sg_inq */
+ int examine; /* sg_vpd */
+ int maxlen; /* sg_inq[was: resp_len] + sg_vpd */
+ int num_pages; /* sg_inq */
+ int page_pdt; /* sg_inq */
+ int vend_prod_num; /* sg_vpd */
+ int verbose; /* sg_inq + sg_vpd */
+ int vpd_pn; /* sg_vpd */
+ const char * device_name; /* sg_inq + sg_vpd */
+ const char * page_str; /* sg_inq + sg_vpd */
+ const char * inhex_fn; /* sg_inq + sg_vpd */
+ const char * sinq_inraw_fn; /* sg_inq + sg_vpd */
+ const char * vend_prod; /* sg_vpd */
+ sgj_state json_st;
+ uint8_t std_inq_a[36];
+};
+
+struct svpd_values_name_t {
+ int value; /* VPD page number */
+ int subvalue; /* to differentiate if value+pdt are not unique */
+ int pdt; /* peripheral device type id, -1 is the default */
+ /* (all or not applicable) value */
+ const char * acron;
+ const char * name;
+};
+
+struct svpd_vp_name_t {
+ int vend_prod_num; /* vendor/product identifier */
+ const char * acron;
+ const char * name;
+};
+
+typedef int (*recurse_vpd_decodep)(struct opts_t *, sgj_opaque_p jop, int off);
+
+
+sgj_opaque_p sg_vpd_js_hdr(sgj_state * jsp, sgj_opaque_p jop,
+ const char * name, const uint8_t * vpd_hdrp);
+void decode_net_man_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void decode_x_inq_vpd(const uint8_t * b, int len, bool protect,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_softw_inf_id(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void decode_mode_policy_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void decode_cga_profile_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void decode_power_condition(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+int filter_json_dev_ids(uint8_t * buff, int len, int m_assoc,
+ struct opts_t * op, sgj_opaque_p jap);
+void decode_ata_info_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+void decode_feature_sets_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void decode_dev_constit_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap,
+ recurse_vpd_decodep fp);
+sgj_opaque_p std_inq_decode_js(const uint8_t * b, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_power_consumption(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap);
+void decode_block_limits_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_block_dev_ch_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+int decode_block_lb_prov_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_referrals_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+void decode_sup_block_lens_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap);
+void decode_block_dev_char_ext_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_zbdch_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+void decode_block_limits_ext_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+void decode_format_presets_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap);
+void decode_con_pos_range_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jap);
+void decode_3party_copy_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void
+decode_proto_lu_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void
+decode_proto_port_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void
+decode_lb_protection_vpd(const uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jap);
+void
+decode_tapealert_supported_vpd(const uint8_t * buff, int len,
+ struct opts_t * op, sgj_opaque_p jop);
+/* Share some vendor specific VPD pages as well */
+void
+decode_upr_vpd_c0_emc(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+void
+decode_rdac_vpd_c2(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+void
+decode_rdac_vpd_c9(uint8_t * buff, int len, struct opts_t * op,
+ sgj_opaque_p jop);
+
+const char * pqual_str(int pqual);
+int no_ascii_4hex(const struct opts_t * op);
+
+void svpd_enumerate_vendor(int vend_prod_num);
+int svpd_count_vendor_vpds(int vpd_pn, int vend_prod_num);
+int svpd_decode_vendor(int sg_fd, struct opts_t * op, sgj_opaque_p jop,
+ int off);
+const struct svpd_values_name_t * svpd_find_vendor_by_acron(const char * ap);
+int svpd_find_vp_num_by_acron(const char * vp_ap);
+const struct svpd_values_name_t * svpd_find_vendor_by_num(int page_num,
+ int vend_prod_num);
+int vpd_fetch_page(int sg_fd, uint8_t * rp, int page, int mxlen,
+ bool qt, int vb, int * rlenp);
+void dup_sanity_chk(int sz_opts_t, int sz_values_name_t);
+
+extern uint8_t * rsp_buff;
+extern const char * t10_vendor_id_hr;
+extern const char * t10_vendor_id_js;
+extern const char * product_id_hr;
+extern const char * product_id_js;
+extern const char * product_rev_lev_hr;
+extern const char * product_rev_lev_js;
+extern struct svpd_vp_name_t vp_arr[];
+extern struct svpd_values_name_t vendor_vpd_pg[];
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* end of SG_VPD_H */
diff --git a/src/sg_vpd_vendor.c b/src/sg_vpd_vendor.c
new file mode 100644
index 00000000..156dd83b
--- /dev/null
+++ b/src/sg_vpd_vendor.c
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (c) 2006-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifndef SG_LIB_MINGW
+#include <time.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#include "sg_vpd_common.h"
+
+/* This is a companion file to sg_vpd.c . It contains logic to output and
+ decode vendor specific VPD pages
+
+ This program fetches Vital Product Data (VPD) pages from the given
+ device and outputs it as directed. VPD pages are obtained via a
+ SCSI INQUIRY command. Most of the data in this program is obtained
+ from the SCSI SPC-4 document at https://www.t10.org .
+
+ Acknowledgments:
+ - Lars Marowsky-Bree <lmb at suse dot de> contributed Unit Path Report
+ VPD page decoding for EMC CLARiiON devices [20041016]
+ - Hannes Reinecke <hare at suse dot de> contributed RDAC vendor
+ specific VPD pages [20060421]
+ - Jonathan McDowell <noodles at hp dot com> contributed HP/3PAR InServ
+ VPD page [0xc0] containing volume information [20110922]
+
+*/
+
+/* vendor/product identifiers */
+#define VPD_VP_SEAGATE 0
+#define VPD_VP_RDAC 1
+#define VPD_VP_EMC 2
+#define VPD_VP_DDS 3
+#define VPD_VP_HP3PAR 4
+#define VPD_VP_IBM_LTO 5
+#define VPD_VP_HP_LTO 6
+#define VPD_VP_WDC_HITACHI 7
+#define VPD_VP_NVME 8
+#define VPD_VP_SG 9 /* this package/library as a vendor */
+
+
+/* vendor VPD pages */
+#define VPD_V_HIT_PG3 0x3
+#define VPD_V_HP3PAR 0xc0
+#define VPD_V_FIRM_SEA 0xc0
+#define VPD_V_UPR_EMC 0xc0
+#define VPD_V_HVER_RDAC 0xc0
+#define VPD_V_FVER_DDS 0xc0
+#define VPD_V_FVER_LTO 0xc0
+#define VPD_V_DCRL_LTO 0xc0
+#define VPD_V_DATC_SEA 0xc1
+#define VPD_V_FVER_RDAC 0xc1
+#define VPD_V_HVER_LTO 0xc1
+#define VPD_V_DSN_LTO 0xc1
+#define VPD_V_JUMP_SEA 0xc2
+#define VPD_V_SVER_RDAC 0xc2
+#define VPD_V_PCA_LTO 0xc2
+#define VPD_V_DEV_BEH_SEA 0xc3
+#define VPD_V_FEAT_RDAC 0xc3
+#define VPD_V_MECH_LTO 0xc3
+#define VPD_V_SUBS_RDAC 0xc4
+#define VPD_V_HEAD_LTO 0xc4
+#define VPD_V_ACI_LTO 0xc5
+#define VPD_V_DUCD_LTO 0xc7
+#define VPD_V_EDID_RDAC 0xc8
+#define VPD_V_MPDS_LTO 0xc8
+#define VPD_V_VAC_RDAC 0xc9
+#define VPD_V_RVSI_RDAC 0xca
+#define VPD_V_SAID_RDAC 0xd0
+#define VPD_V_HIT_PG_D1 0xd1
+#define VPD_V_HIT_PG_D2 0xd2
+
+
+#define DEF_ALLOC_LEN 252
+#define MX_ALLOC_LEN (0xc000 + 0x80)
+
+void
+dup_sanity_chk(int sz_opts_t, int sz_values_name_t)
+{
+ const size_t my_sz_opts_t = sizeof(struct opts_t);
+ const size_t my_sz_values_name_t = sizeof(struct svpd_values_name_t);
+
+ if (sz_opts_t != (int)my_sz_opts_t)
+ pr2serr(">>> struct opts_t differs in size from sg_vpd.c [%d != "
+ "%d]\n", (int)my_sz_opts_t, sz_opts_t);
+ if (sz_values_name_t != (int)my_sz_values_name_t)
+ pr2serr(">>> struct svpd_values_name_t differs in size from "
+ "sg_vpd.c [%d != %d]\n", (int)my_sz_values_name_t,
+ sz_values_name_t);
+}
+
+static bool
+is_like_pdt(int actual_pdt, const struct svpd_values_name_t * vnp)
+{
+ if (actual_pdt == vnp->pdt)
+ return true;
+ if (PDT_DISK == vnp->pdt) {
+ switch (actual_pdt) {
+ case PDT_DISK:
+ case PDT_RBC:
+ case PDT_PROCESSOR:
+ case PDT_SAC:
+ case PDT_ZBC:
+ return true;
+ default:
+ return false;
+ }
+ } else if (PDT_TAPE == vnp->pdt) {
+ switch (actual_pdt) {
+ case PDT_TAPE:
+ case PDT_MCHANGER:
+ case PDT_ADC:
+ return true;
+ default:
+ return false;
+ }
+ } else
+ return false;
+}
+
+static const struct svpd_values_name_t *
+svpd_get_v_detail(int page_num, int vend_prod_num, int pdt)
+{
+ const struct svpd_values_name_t * vnp;
+ int vp, ty;
+
+ vp = (vend_prod_num < 0) ? 1 : 0;
+ ty = (pdt < 0) ? 1 : 0;
+ for (vnp = vendor_vpd_pg; vnp->acron; ++vnp) {
+ if ((page_num == vnp->value) &&
+ (vp || (vend_prod_num == vnp->subvalue)) &&
+ (ty || is_like_pdt(pdt, vnp)))
+ return vnp;
+ }
+#if 0
+ if (! ty)
+ return svpd_get_v_detail(page_num, vend_prod_num, -1);
+ if (! vp)
+ return svpd_get_v_detail(page_num, -1, pdt);
+#endif
+ return NULL;
+}
+
+const struct svpd_values_name_t *
+svpd_find_vendor_by_num(int page_num, int vend_prod_num)
+{
+ const struct svpd_values_name_t * vnp;
+
+ for (vnp = vendor_vpd_pg; vnp->acron; ++vnp) {
+ if ((page_num == vnp->value) &&
+ ((vend_prod_num < 0) || (vend_prod_num == vnp->subvalue)))
+ return vnp;
+ }
+ return NULL;
+}
+
+const struct svpd_values_name_t *
+svpd_find_vendor_by_acron(const char * ap)
+{
+ const struct svpd_values_name_t * vnp;
+
+ for (vnp = vendor_vpd_pg; vnp->acron; ++vnp) {
+ if (0 == strcmp(vnp->acron, ap))
+ return vnp;
+ }
+ return NULL;
+}
+
+int
+svpd_count_vendor_vpds(int vpd_pn, int vend_prod_num)
+{
+ const struct svpd_values_name_t * vnp;
+ int matches;
+
+ for (vnp = vendor_vpd_pg, matches = 0; vnp->acron; ++vnp) {
+ if ((vpd_pn == vnp->value) && vnp->name) {
+ if ((vend_prod_num < 0) || (vend_prod_num == vnp->subvalue)) {
+ if (0 == matches)
+ printf("Matching vendor specific VPD pages:\n");
+ ++matches;
+ printf(" %-10s 0x%02x,%d %s\n", vnp->acron,
+ vnp->value, vnp->subvalue, vnp->name);
+ }
+ }
+ }
+ return matches;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static void
+decode_vpd_c0_hp3par(uint8_t * buff, int len)
+{
+ int rev;
+ long offset;
+
+ if (len < 24) {
+ pr2serr("HP/3PAR vendor specific VPD page length too short=%d\n",
+ len);
+ return;
+ }
+
+ rev = buff[4];
+ printf(" Page revision: %d\n", rev);
+
+ printf(" Volume type: %s\n", (buff[5] & 0x01) ? "tpvv" :
+ (buff[5] & 0x02) ? "snap" : "base");
+ printf(" Reclaim supported: %s\n", (buff[5] & 0x04) ? "yes" : "no");
+ printf(" ATS supported: %s\n", (buff[5] & 0x10) ? "yes" : "no");
+ printf(" XCopy supported: %s\n", (buff[5] & 0x20) ? "yes" : "no");
+
+ if (rev > 3) {
+ printf(" VV ID: %" PRIu64 "\n", sg_get_unaligned_be64(buff + 28));
+ offset = 44;
+ printf(" Volume name: %s\n", &buff[offset]);
+
+ printf(" Domain ID: %d\n", sg_get_unaligned_be32(buff + 36));
+
+ offset += sg_get_unaligned_be32(buff + offset - 4) + 4;
+ printf(" Domain Name: %s\n", &buff[offset]);
+
+ offset += sg_get_unaligned_be32(buff + offset - 4) + 4;
+ printf(" User CPG: %s\n", &buff[offset]);
+
+ offset += sg_get_unaligned_be32(buff + offset - 4) + 4;
+ printf(" Snap CPG: %s\n", &buff[offset]);
+
+ offset += sg_get_unaligned_be32(buff + offset - 4);
+
+ printf(" VV policies: %s,%s,%s,%s\n",
+ (buff[offset + 3] & 0x01) ? "stale_ss" : "no_stale_ss",
+ (buff[offset + 3] & 0x02) ? "one_host" : "no_one_host",
+ (buff[offset + 3] & 0x04) ? "tp_bzero" : "no_tp_bzero",
+ (buff[offset + 3] & 0x08) ? "zero_detect" : "no_zero_detect");
+
+ }
+
+ if (buff[5] & 0x04) {
+ printf(" Allocation unit: %d\n", sg_get_unaligned_be32(buff + 8));
+
+ printf(" Data pool size: %" PRIu64 "\n",
+ sg_get_unaligned_be64(buff + 12));
+
+ printf(" Space allocated: %" PRIu64 "\n",
+ sg_get_unaligned_be64(buff + 20));
+ }
+ return;
+}
+
+
+static void
+decode_firm_vpd_c0_sea(uint8_t * buff, int len)
+{
+ if (len < 28) {
+ pr2serr("Seagate firmware numbers VPD page length too short=%d\n",
+ len);
+ return;
+ }
+ if (28 == len) {
+ printf(" SCSI firmware release number: %.8s\n", buff + 4);
+ printf(" Servo ROM release number: %.8s\n", buff + 20);
+ } else {
+ printf(" SCSI firmware release number: %.8s\n", buff + 4);
+ printf(" Servo ROM release number: %.8s\n", buff + 12);
+ printf(" SAP block point numbers (major/minor): %.8s\n", buff + 20);
+ if (len < 36)
+ return;
+ printf(" Servo firmware release date: %.4s\n", buff + 28);
+ printf(" Servo ROM release date: %.4s\n", buff + 32);
+ if (len < 44)
+ return;
+ printf(" SAP firmware release number: %.8s\n", buff + 36);
+ if (len < 52)
+ return;
+ printf(" SAP firmware release date: %.4s\n", buff + 44);
+ printf(" SAP firmware release year: %.4s\n", buff + 48);
+ if (len < 60)
+ return;
+ printf(" SAP manufacturing key: %.4s\n", buff + 52);
+ printf(" Servo firmware product family and product family "
+ "member: %.4s\n", buff + 56);
+ }
+}
+
+static void
+decode_date_code_vpd_c1_sea(uint8_t * buff, int len)
+{
+ if (len < 20) {
+ pr2serr("Seagate Data code VPD page length too short=%d\n",
+ len);
+ return;
+ }
+ printf(" ETF log (mmddyyyy): %.8s\n", buff + 4);
+ printf(" Compile date code (mmddyyyy): %.8s\n", buff + 12);
+}
+
+static void
+decode_dev_beh_vpd_c3_sea(uint8_t * buff, int len)
+{
+ if (len < 25) {
+ pr2serr("Seagate Device behaviour VPD page length too short=%d\n",
+ len);
+ return;
+ }
+ printf(" Version number: %d\n", buff[4]);
+ printf(" Behaviour code: %d\n", buff[5]);
+ printf(" Behaviour code version number: %d\n", buff[6]);
+ printf(" ASCII family number: %.16s\n", buff + 7);
+ printf(" Number of interleaves: %d\n", buff[23]);
+ printf(" Default number of cache segments: %d\n", buff[24]);
+}
+
+static void
+decode_rdac_vpd_c0(uint8_t * buff, int len)
+{
+ int memsize;
+ char name[65];
+
+ if (len < 3) {
+ pr2serr("Hardware Version VPD page length too short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'h' && buff[5] != 'w' && buff[6] != 'r') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ printf(" Number of channels: %x\n", buff[8]);
+ memsize = sg_get_unaligned_be16(buff + 10);
+ printf(" Processor Memory Size: %d\n", memsize);
+ memset(name, 0, 65);
+ memcpy(name, buff + 16, 64);
+ printf(" Board Name: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 80, 16);
+ printf(" Board Part Number: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 96, 12);
+ printf(" Schematic Number: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 108, 4);
+ printf(" Schematic Revision Number: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 112, 16);
+ printf(" Board Serial Number: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 144, 8);
+ printf(" Date of Manufacture: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 152, 2);
+ printf(" Board Revision: %s\n", name);
+ memset(name, 0, 65);
+ memcpy(name, buff + 154, 4);
+ printf(" Board Identifier: %s\n", name);
+
+ return;
+}
+
+static void
+decode_rdac_vpd_c1(uint8_t * buff, int len)
+{
+ int i, n, v, r, m, p, d, y, num_part;
+ char part[5];
+
+ if (len < 3) {
+ pr2serr("Firmware Version VPD page length too short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'f' && buff[5] != 'w' && buff[6] != 'r') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ printf(" Firmware Version: %02x.%02x.%02x\n", buff[8], buff[9], buff[10]);
+ printf(" Firmware Date: %02d/%02d/%02d\n", buff[11], buff[12], buff[13]);
+
+ num_part = (len - 12) / 16;
+ n = 16;
+ printf(" Partitions: %d\n", num_part);
+ for (i = 0; i < num_part; i++) {
+ memset(part,0, 5);
+ memcpy(part, &buff[n], 4);
+ printf(" Name: %s\n", part);
+ n += 4;
+ v = buff[n++];
+ r = buff[n++];
+ m = buff[n++];
+ p = buff[n++];
+ printf(" Version: %d.%d.%d.%d\n", v, r, m, p);
+ m = buff[n++];
+ d = buff[n++];
+ y = buff[n++];
+ printf(" Date: %d/%d/%d\n", m, d, y);
+
+ n += 5;
+ }
+
+ return;
+}
+
+static void
+decode_rdac_vpd_c3(uint8_t * buff, int len)
+{
+ if (len < 0x2c) {
+ pr2serr("Feature parameters VPD page length too short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'p' && buff[5] != 'r' && buff[6] != 'm') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ printf(" Maximum number of drives per LUN: %d\n", buff[8]);
+ printf(" Maximum number of hot spare drives: %d\n", buff[9]);
+ printf(" UTM: %s\n", buff[11] & 0x80?"enabled":"disabled");
+ if ((buff[11] & 0x80))
+ printf(" UTM LUN: %02x\n", buff[11] & 0x7f);
+ printf(" Persistent Reservations Bus Reset Support: %s\n",
+ (buff[12] & 0x01) ? "enabled" : "disabled");
+ return;
+}
+
+static void
+decode_rdac_vpd_c4(uint8_t * buff, int len)
+{
+ char subsystem_id[17];
+ char subsystem_rev[5];
+ char slot_id[3];
+
+ if (len < 0x1c) {
+ pr2serr("Subsystem identifier VPD page length too short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 's' && buff[5] != 'u' && buff[6] != 'b') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ memset(subsystem_id, 0, 17);
+ memcpy(subsystem_id, &buff[8], 16);
+ memset(subsystem_rev, 0, 5);
+ memcpy(subsystem_rev, &buff[24], 4);
+ slot_id[0] = buff[28];
+ slot_id[1] = buff[29];
+ slot_id[2] = 0;
+
+ printf(" Subsystem ID: %s\n Subsystem Revision: %s",
+ subsystem_id, subsystem_rev);
+ if (!strcmp(subsystem_rev, "10.0"))
+ printf(" (Board ID 4884)\n");
+ else if (!strcmp(subsystem_rev, "12.0"))
+ printf(" (Board ID 5884)\n");
+ else if (!strcmp(subsystem_rev, "13.0"))
+ printf(" (Board ID 2882)\n");
+ else if (!strcmp(subsystem_rev, "13.1"))
+ printf(" (Board ID 2880)\n");
+ else if (!strcmp(subsystem_rev, "14.0"))
+ printf(" (Board ID 2822)\n");
+ else if (!strcmp(subsystem_rev, "15.0"))
+ printf(" (Board ID 6091)\n");
+ else if (!strcmp(subsystem_rev, "16.0"))
+ printf(" (Board ID 3992)\n");
+ else if (!strcmp(subsystem_rev, "16.1"))
+ printf(" (Board ID 3991)\n");
+ else if (!strcmp(subsystem_rev, "17.0"))
+ printf(" (Board ID 1331)\n");
+ else if (!strcmp(subsystem_rev, "17.1"))
+ printf(" (Board ID 1332)\n");
+ else if (!strcmp(subsystem_rev, "17.3"))
+ printf(" (Board ID 1532)\n");
+ else if (!strcmp(subsystem_rev, "17.4"))
+ printf(" (Board ID 1932)\n");
+ else if (!strcmp(subsystem_rev, "42.0"))
+ printf(" (Board ID 26x0)\n");
+ else if (!strcmp(subsystem_rev, "43.0"))
+ printf(" (Board ID 498x)\n");
+ else if (!strcmp(subsystem_rev, "44.0"))
+ printf(" (Board ID 548x)\n");
+ else if (!strcmp(subsystem_rev, "45.0"))
+ printf(" (Board ID 5501)\n");
+ else if (!strcmp(subsystem_rev, "46.0"))
+ printf(" (Board ID 2701)\n");
+ else if (!strcmp(subsystem_rev, "47.0"))
+ printf(" (Board ID 5601)\n");
+ else
+ printf(" (Board ID unknown)\n");
+
+ printf(" Slot ID: %s\n", slot_id);
+
+ return;
+}
+
+static void
+convert_binary_to_ascii(uint8_t * src, uint8_t * dst, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ sprintf((char *)(dst+2*i), "%02x", *(src+i));
+ }
+}
+
+static void
+decode_rdac_vpd_c8(uint8_t * buff, int len)
+{
+ int i;
+#ifndef SG_LIB_MINGW
+ time_t tstamp;
+#endif
+ char *c;
+ char label[61];
+ int label_len;
+ char uuid[33];
+ int uuid_len;
+ uint8_t port_id[128];
+ int n;
+
+ if (len < 0xab) {
+ pr2serr("Extended Device Identification VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'e' && buff[5] != 'd' && buff[6] != 'i') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+
+ uuid_len = buff[11];
+
+ for (i = 0, c = uuid; i < uuid_len; i++) {
+ sprintf(c,"%02x",buff[12 + i]);
+ c += 2;
+ }
+
+ printf(" Volume Unique Identifier: %s\n", uuid);
+#ifndef SG_LIB_MINGW
+ tstamp = sg_get_unaligned_be32(buff + 24);
+ printf(" Creation Number: %d, Timestamp: %s",
+ sg_get_unaligned_be16(buff + 22), ctime(&tstamp));
+#else
+ printf(" Creation Number: %d, Timestamp value: %u",
+ sg_get_unaligned_be16(buff + 22),
+ sg_get_unaligned_be32(buff + 24));
+#endif
+ memset(label, 0, 61);
+ label_len = buff[28];
+ for(i = 0; i < (label_len - 1); ++i)
+ *(label + i) = buff[29 + (2 * i) + 1];
+ printf(" Volume User Label: %s\n", label);
+
+ uuid_len = buff[89];
+
+ for (i = 0, c = uuid; i < uuid_len; i++) {
+ sprintf(c,"%02x",buff[90 + i]);
+ c += 2;
+ }
+
+ printf(" Storage Array Unique Identifier: %s\n", uuid);
+ memset(label, 0, 61);
+ label_len = buff[106];
+ for(i = 0; i < (label_len - 1); ++i)
+ *(label + i) = buff[107 + (2 * i) + 1];
+ printf(" Storage Array User Label: %s\n", label);
+
+ for (i = 0, c = uuid; i < 8; i++) {
+ sprintf(c,"%02x",buff[167 + i]);
+ c += 2;
+ }
+
+ printf(" Logical Unit Number: %s\n", uuid);
+
+ /* Initiator transport ID */
+ if ( buff[10] & 0x01 ) {
+ memset(port_id, 0, 128);
+ printf(" Transport Protocol: ");
+ switch (buff[175] & 0x0F) {
+ case TPROTO_FCP: /* FC */
+ printf("FC\n");
+ convert_binary_to_ascii(&buff[183], port_id, 8);
+ n = 199;
+ break;
+ case TPROTO_SRP: /* SRP */
+ printf("SRP\n");
+ convert_binary_to_ascii(&buff[183], port_id, 8);
+ n = 199;
+ break;
+ case TPROTO_ISCSI: /* iSCSI */
+ printf("iSCSI\n");
+ n = sg_get_unaligned_be32(buff + 177);
+ memcpy(port_id, &buff[179], n);
+ n = 179 + n;
+ break;
+ case TPROTO_SAS: /* SAS */
+ printf("SAS\n");
+ convert_binary_to_ascii(&buff[179], port_id, 8);
+ n = 199;
+ break;
+ default:
+ return; /* Can't continue decoding, so return */
+ }
+
+ printf(" Initiator Port Identifier: %s\n", port_id);
+ if ( buff[10] & 0x02 ) {
+ memset(port_id, 0, 128);
+ memcpy(port_id, &buff[n], 8);
+ printf(" Supplemental Vendor ID: %s\n", port_id);
+ }
+ }
+
+ return;
+}
+
+#if 0
+static void
+decode_rdac_vpd_c9_rtpg_data(uint8_t aas, uint8_t vendor)
+{
+ printf(" Asymmetric Access State:");
+ switch(aas & 0x0F) {
+ case 0x0:
+ printf(" Active/Optimized");
+ break;
+ case 0x1:
+ printf(" Active/Non-Optimized");
+ break;
+ case 0x2:
+ printf(" Standby");
+ break;
+ case 0x3:
+ printf(" Unavailable");
+ break;
+ case 0xE:
+ printf(" Offline");
+ break;
+ case 0xF:
+ printf(" Transitioning");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+
+ printf(" Vendor Specific Field:");
+ switch(vendor) {
+ case 0x01:
+ printf(" Operating normally");
+ break;
+ case 0x02:
+ printf(" Non-responsive to queries");
+ break;
+ case 0x03:
+ printf(" Controller being held in reset");
+ break;
+ case 0x04:
+ printf(" Performing controller firmware download (1st controller)");
+ break;
+ case 0x05:
+ printf(" Performing controller firmware download (2nd controller)");
+ break;
+ case 0x06:
+ printf(" Quiesced as a result of an administrative request");
+ break;
+ case 0x07:
+ printf(" Service mode as a result of an administrative request");
+ break;
+ case 0xFF:
+ printf(" Details are not available");
+ break;
+ default:
+ printf(" (unknown)");
+ break;
+ }
+ printf("\n");
+}
+
+static void
+decode_rdac_vpd_c9(uint8_t * buff, int len)
+{
+ if (len < 3) {
+ pr2serr("Volume Access Control VPD page length too short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'v' && buff[5] != 'a' && buff[6] != 'c') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ if (buff[7] != '1') {
+ pr2serr("Invalid page version '%c' (should be 1)\n", buff[7]);
+ }
+ if ( (buff[8] & 0xE0) == 0xE0 ) {
+ printf(" IOShipping (ALUA): Enabled\n");
+ } else {
+ printf(" AVT:");
+ if (buff[8] & 0x80) {
+ printf(" Enabled");
+ if (buff[8] & 0x40)
+ printf(" (Allow reads on sector 0)");
+ printf("\n");
+ } else {
+ printf(" Disabled\n");
+ }
+ }
+ printf(" Volume Access via: ");
+ if (buff[8] & 0x01)
+ printf("primary controller\n");
+ else
+ printf("alternate controller\n");
+
+ if (buff[8] & 0x08) {
+ printf(" Path priority: %d ", buff[15] & 0xf);
+ switch(buff[15] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+
+ printf(" Preferred Path Auto Changeable:");
+ switch(buff[14] & 0x3C) {
+ case 0x14:
+ printf(" No (User Disabled and Host Type Restricted)\n");
+ break;
+ case 0x18:
+ printf(" No (User Disabled)\n");
+ break;
+ case 0x24:
+ printf(" No (Host Type Restricted)\n");
+ break;
+ case 0x28:
+ printf(" Yes\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+
+ printf(" Implicit Failback:");
+ switch(buff[14] & 0x03) {
+ case 0x1:
+ printf(" Disabled\n");
+ break;
+ case 0x2:
+ printf(" Enabled\n");
+ break;
+ default:
+ printf(" (Unknown)\n");
+ break;
+ }
+ } else {
+ printf(" Path priority: %d ", buff[9] & 0xf);
+ switch(buff[9] & 0xf) {
+ case 0x1:
+ printf("(preferred path)\n");
+ break;
+ case 0x2:
+ printf("(secondary path)\n");
+ break;
+ default:
+ printf("(unknown)\n");
+ break;
+ }
+ }
+
+
+ if (buff[8] & 0x80) {
+ printf(" Target Port Group Data (This controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[10], buff[11]);
+
+ printf(" Target Port Group Data (Alternate controller):\n");
+ decode_rdac_vpd_c9_rtpg_data(buff[12], buff[13]);
+ }
+}
+#endif
+
+static void
+decode_rdac_vpd_ca(uint8_t * buff, int len)
+{
+ int i;
+
+ if (len < 16) {
+ pr2serr("Replicated Volume Source Identifier VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ if (buff[4] != 'r' && buff[5] != 'v' && buff[6] != 's') {
+ pr2serr("Invalid page identifier %c%c%c%c, decoding not possible.\n",
+ buff[4], buff[5], buff[6], buff[7]);
+ return;
+ }
+ if (buff[8] & 0x01) {
+ printf(" Snapshot Volume\n");
+ printf(" Base Volume WWID: ");
+ for (i = 0; i < 16; i++)
+ printf("%02x", buff[10 + i]);
+ printf("\n");
+ } else if (buff[8] & 0x02) {
+ printf(" Copy Target Volume\n");
+ printf(" Source Volume WWID: ");
+ for (i = 0; i < 16; i++)
+ printf("%02x", buff[10 + i]);
+ printf("\n");
+ } else
+ printf(" Neither a snapshot nor a copy target volume\n");
+
+ return;
+}
+
+static void
+decode_rdac_vpd_d0(uint8_t * buff, int len)
+{
+ int i;
+
+ if (len < 20) {
+ pr2serr("Storage Array World Wide Name VPD page length too "
+ "short=%d\n", len);
+ return;
+ }
+ printf(" Storage Array WWN: ");
+ for (i = 0; i < 16; i++)
+ printf("%02x", buff[8 + i]);
+ printf("\n");
+
+ return;
+}
+
+
+static void
+decode_dds_vpd_c0(uint8_t * buff, int len)
+{
+ char firmware_rev[25];
+ char build_date[43];
+ char hw_conf[21];
+ char fw_conf[21];
+
+ if (len < 0xb3) {
+ pr2serr("Vendor-Unique Firmware revision page invalid length=%d\n",
+ len);
+ return;
+ }
+ memset(firmware_rev, 0x0, 25);
+ memcpy(firmware_rev, &buff[5], 24);
+
+ printf(" %s\n", firmware_rev);
+
+ memset(build_date, 0x0, 43);
+ memcpy(build_date, &buff[30], 42);
+
+ printf(" %s\n", build_date);
+
+ memset(hw_conf, 0x0, 21);
+ memcpy(hw_conf, &buff[73], 20);
+ printf(" %s\n", hw_conf);
+
+ memset(fw_conf, 0x0, 21);
+ memcpy(fw_conf, &buff[94], 20);
+ printf(" %s\n", fw_conf);
+ return;
+}
+
+static void
+decode_hp_lto_vpd_cx(uint8_t * buff, int len, int page)
+{
+ char str[32];
+ const char *comp = NULL;
+
+ if (len < 0x5c) {
+ pr2serr("Driver Component Revision Levels page invalid length=%d\n",
+ len);
+ return;
+ }
+ switch (page) {
+ case 0xc0:
+ comp = "Firmware";
+ break;
+ case 0xc1:
+ comp = "Hardware";
+ break;
+ case 0xc2:
+ comp = "PCA";
+ break;
+ case 0xc3:
+ comp = "Mechanism";
+ break;
+ case 0xc4:
+ comp = "Head Assy";
+ break;
+ case 0xc5:
+ comp = "ACI";
+ break;
+ }
+ if (!comp) {
+ pr2serr("Driver Component Revision Level invalid page=0x%02x\n",
+ page);
+ return;
+ }
+
+ memset(str, 0x0, 32);
+ memcpy(str, &buff[4], 26);
+ printf(" %s\n", str);
+
+ memset(str, 0x0, 32);
+ memcpy(str, &buff[30], 19);
+ printf(" %s\n", str);
+
+ memset(str, 0x0, 32);
+ memcpy(str, &buff[49], 24);
+ printf(" %s\n", str);
+
+ memset(str, 0x0, 32);
+ memcpy(str, &buff[73], 23);
+ printf(" %s\n", str);
+ return;
+}
+
+static void
+decode_ibm_lto_dcrl(uint8_t * buff, int len)
+{
+ if (len < 0x2b) {
+ pr2serr("Driver Component Revision Levels page (IBM LTO) invalid "
+ "length=%d\n", len);
+ return;
+ }
+ printf(" Code name: %.12s\n", buff + 4);
+ printf(" Time (hhmmss): %.7s\n", buff + 16);
+ printf(" Date (yyyymmdd): %.8s\n", buff + 23);
+ printf(" Platform: %.12s\n", buff + 31);
+}
+
+static void
+decode_ibm_lto_dsn(uint8_t * buff, int len)
+{
+ if (len < 0x1c) {
+ pr2serr("Driver Serial Numbers page (IBM LTO) invalid "
+ "length=%d\n", len);
+ return;
+ }
+ printf(" Manufacturing serial number: %.12s\n", buff + 4);
+ printf(" Reported serial number: %.12s\n", buff + 16);
+}
+
+static void
+decode_vpd_3_hit(uint8_t * b, int blen)
+{
+ uint16_t plen = sg_get_unaligned_be16(b + 2);
+
+ if ((plen < 184) || (blen < 184)) {
+ pr2serr("Hitachi VPD page 0x3 length (%u) shorter than %u\n",
+ plen + 4, 184 + 4);
+ return;
+ }
+ printf(" ASCII uCode Identifier: %.12s\n", b + 24);
+ printf(" ASCII servo P/N: %.4s\n", b + 36);
+ printf(" Major Version: %.2s\n", b + 40);
+ printf(" Minor Version: %.2s\n", b + 42);
+ printf(" User Count: %.4s\n", b + 44);
+ printf(" Build Number: %.4s\n", b + 48);
+ printf(" Build Date String: %.32s\n", b + 52);
+ printf(" Product ID: %.8s\n", b + 84);
+ printf(" Interface ID: %.8s\n", b + 92);
+ printf(" Code Type: %.8s\n", b + 100);
+ printf(" User Name: %.12s\n", b + 108);
+ printf(" Machine Name: %.16s\n", b + 120);
+ printf(" Directory Name: %.32s\n", b + 136);
+ printf(" Operating state: %u\n", sg_get_unaligned_be32(b + 168));
+ printf(" Functional Mode: %u\n", sg_get_unaligned_be32(b + 172));
+ printf(" Degraded Reason: %u\n", sg_get_unaligned_be32(b + 176));
+ printf(" Broken Reason: %u\n", sg_get_unaligned_be32(b + 180));
+ printf(" Code Mode: %u\n", sg_get_unaligned_be32(b + 184));
+ printf(" Revision: %.4s\n", b + 188);
+}
+
+static void
+decode_vpd_d1_hit(uint8_t * b, int blen)
+{
+ uint16_t plen = sg_get_unaligned_be16(b + 2);
+
+ if ((plen < 80) || (blen < 80)) {
+ pr2serr("Hitachi VPD page 0xd1 length (%u) shorter than %u\n",
+ plen + 4, 80 + 4);
+ return;
+ }
+ printf(" ASCII Media Disk Definition: %.16s\n", b + 4);
+ printf(" ASCII Motor Serial Number: %.16s\n", b + 20);
+ printf(" ASCII Flex Assembly Serial Number: %.16s\n", b + 36);
+ printf(" ASCII Actuator Serial Number: %.16s\n", b + 52);
+ printf(" ASCII Device Enclosure Serial Number: %.16s\n", b + 68);
+}
+
+static void
+decode_vpd_d2_hit(uint8_t * b, int blen)
+{
+ uint16_t plen = sg_get_unaligned_be16(b + 2);
+
+ if ((plen < 52) || (blen < 52)) {
+ pr2serr("Hitachi VPD page 0xd2 length (%u) shorter than %u\n",
+ plen + 4, 52 + 4);
+ return;
+ }
+ if ((blen - 4) == 120) {
+ printf(" HDC Version: %.*s\n", b[4], b + 5);
+ printf(" Card Serial Number: %.*s\n", b[24], b + 25);
+ printf(" NAND Flash Version: %.*s\n", b[44], b + 45);
+ printf(" Card Assembly Part Number: %.*s\n", b[64], b + 65);
+ printf(" Second Card Serial Number: %.*s\n", b[84], b + 85);
+ printf(" Second Card Assembly Part Number: %.*s\n", b[104], b + 105);
+ } else {
+ printf(" ASCII HDC Version: %.16s\n", b + 5);
+ printf(" ASCII Card Serial Number: %.16s\n", b + 22);
+ printf(" ASCII Card Assembly Part Number: %.16s\n", b + 39);
+ }
+}
+
+/* Returns 0 if successful, see sg_ll_inquiry() plus SG_LIB_CAT_OTHER for
+ unsupported page */
+int
+svpd_decode_vendor(int sg_fd, struct opts_t * op, sgj_opaque_p jop, int off)
+{
+ bool hex0 = (0 == op->do_hex);
+ bool as_json;
+ int len, pdt, plen, pn;
+ int alloc_len = op->maxlen;
+ int res = 0;
+ const struct svpd_values_name_t * vnp;
+ sgj_state * jsp = &op->json_st;
+ sgj_opaque_p jo2p = NULL;
+ uint8_t * rp;
+ char name[80];
+
+ as_json = jsp->pr_as_json;
+ pn = op->vpd_pn;
+ switch (pn) { /* VPD codes that we support vendor pages for */
+ case 0x3:
+ case 0xc0:
+ case 0xc1:
+ case 0xc2:
+ case 0xc3:
+ case 0xc4:
+ case 0xc5:
+ case 0xc8:
+ case 0xc9:
+ case 0xca:
+ case 0xd0:
+ case 0xd1:
+ case 0xd2:
+ case 0xde:
+ break;
+ default: /* not known so return prior to fetching page */
+ return SG_LIB_CAT_OTHER;
+ }
+ rp = rsp_buff + off;
+ if (sg_fd >= 0) {
+ if (0 == alloc_len)
+ alloc_len = DEF_ALLOC_LEN;
+ }
+ res = vpd_fetch_page(sg_fd, rp, pn, alloc_len, op->do_quiet, op->verbose,
+ &len);
+ if (res) {
+ pr2serr("Vendor VPD page=0x%x failed to fetch\n", pn);
+ return res;
+ }
+ pdt = rp[0] & PDT_MASK;
+ vnp = svpd_get_v_detail(pn, op->vend_prod_num, pdt);
+ if (vnp && vnp->name)
+ snprintf(name, sizeof(name), "%s", vnp->name);
+ else
+ snprintf(name, sizeof(name) - 1, "Vendor VPD page=0x%x", pn);
+ if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3))
+ sgj_pr_hr(jsp, "%s VPD Page:\n", name);
+ if (op->do_raw)
+ dStrRaw(rp, len);
+ else {
+ switch(pn) {
+ case 0x3:
+ if (hex0 && (VPD_VP_WDC_HITACHI == op->vend_prod_num))
+ decode_vpd_3_hit(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc0:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_SEAGATE == op->vend_prod_num)
+ decode_firm_vpd_c0_sea(rp, len);
+ else if (VPD_VP_EMC == op->vend_prod_num) {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop,
+ "Unit serial number VPD page", rp);
+ decode_upr_vpd_c0_emc(rp, len, op, jo2p);
+ } else if (VPD_VP_HP3PAR == op->vend_prod_num)
+ decode_vpd_c0_hp3par(rp, len);
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_c0(rp, len);
+ else if (VPD_VP_DDS == op->vend_prod_num)
+ decode_dds_vpd_c0(rp, len);
+ else if (VPD_VP_IBM_LTO == op->vend_prod_num)
+ decode_ibm_lto_dcrl(rp, len);
+ else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc1:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_SEAGATE == op->vend_prod_num)
+ decode_date_code_vpd_c1_sea(rp, len);
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_c1(rp, len);
+ else if (VPD_VP_IBM_LTO == op->vend_prod_num)
+ decode_ibm_lto_dsn(rp, len);
+ else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc2:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num) {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop,
+ "Software version VPD page", rp);
+ decode_rdac_vpd_c2(rp, len, op, jo2p);
+ } else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc3:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_SEAGATE == op->vend_prod_num)
+ decode_dev_beh_vpd_c3_sea(rp, len);
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_c3(rp, len);
+ else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc4:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_c4(rp, len);
+ else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc5:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_HP_LTO == op->vend_prod_num)
+ decode_hp_lto_vpd_cx(rp, len, pn);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc8:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_c8(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xc9:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num) {
+ if (as_json)
+ jo2p = sg_vpd_js_hdr(jsp, jop,
+ "Volume access control VPD page", rp);
+ decode_rdac_vpd_c9(rp, len, op, jo2p);
+ } else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xca:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_ca(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xd0:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_RDAC == op->vend_prod_num)
+ decode_rdac_vpd_d0(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xd1:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_WDC_HITACHI == op->vend_prod_num)
+ decode_vpd_d1_hit(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case 0xd2:
+ if (! hex0)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (VPD_VP_WDC_HITACHI == op->vend_prod_num)
+ decode_vpd_d2_hit(rp, len);
+ else
+ res = SG_LIB_CAT_OTHER;
+ break;
+ case SG_NVME_VPD_NICR: /* 0xde */
+ if (VPD_VP_SG != op->vend_prod_num) {
+ res = SG_LIB_CAT_OTHER;
+ break;
+ }
+ /* NVMe: Identify Controller data structure (CNS 01h) */
+ plen = sg_get_unaligned_be16(rp + 2) + 4;
+ if (plen > len) { /* fetch the whole page */
+ res = vpd_fetch_page(sg_fd, rp, pn, plen,
+ op->do_quiet, op->verbose, &len);
+ if (res) {
+ pr2serr("Vendor VPD page=0x%x failed to fetch\n", pn);
+ return res;
+ }
+ }
+ if (len < 16) {
+ pr2serr("%s expected to be > 15 bytes long (got: %d)\n",
+ name, len);
+ break;
+ } else {
+ int n = len - 16;
+ const char * np = "NVMe Identify Controller Response VPD page";
+ /* NVMe: Identify Controller data structure (CNS 01h) */
+ const char * ep = "(sg3_utils)";
+
+ if (n > 4096) {
+ pr2serr("NVMe Identify response expected to be "
+ "<= 4096 bytes (got: %d)\n", n);
+ break;
+ }
+ if (op->do_hex < 3)
+ sgj_pr_hr(jsp, "VPD INQUIRY: %s %s\n", np, ep);
+ if (op->do_hex)
+ hex2stdout(rp, len, no_ascii_4hex(op));
+ else if (jsp->pr_as_json) {
+ jo2p = sg_vpd_js_hdr(jsp, jop, np, rp);
+ sgj_js_nv_hex_bytes(jsp, jo2p, "response_bytes",
+ rp + 16, n);
+ } else
+ hex2stdout(rp + 16, n, 1);
+ }
+ break;
+ default:
+ res = SG_LIB_CAT_OTHER;
+ }
+ }
+ if (res && op->verbose)
+ pr2serr("%s: can't decode pn=0x%x, vend_prod_num=%d\n", __func__,
+ pn, op->vend_prod_num);
+ return res;
+}
diff --git a/src/sg_wr_mode.c b/src/sg_wr_mode.c
new file mode 100644
index 00000000..b2dff407
--- /dev/null
+++ b/src/sg_wr_mode.c
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2004-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This program writes the given mode page contents to the corresponding
+ * mode page on the given device.
+ */
+
+static const char * version_str = "1.27 20210610";
+
+#define ME "sg_wr_mode: "
+
+#define MX_ALLOC_LEN 2048
+#define SHORT_ALLOC_LEN 252
+
+#define EBUFF_SZ 256
+
+
+static struct option long_options[] = {
+ {"contents", required_argument, 0, 'c'},
+ {"dbd", no_argument, 0, 'd'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"len", required_argument, 0, 'l'},
+ {"mask", required_argument, 0, 'm'},
+ {"page", required_argument, 0, 'p'},
+ {"rtd", no_argument, 0, 'R'},
+ {"save", no_argument, 0, 's'},
+ {"six", no_argument, 0, '6'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_wr_mode [--contents=H,H...] [--dbd] [--force] "
+ "[--help]\n"
+ " [--len=10|6] [--mask=M,M...] "
+ "[--page=PG_H[,SPG_H]]\n"
+ " [--rtd] [--save] [--six] [--verbose] "
+ "[--version]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --contents=H,H... | -c H,H... comma separated string "
+ "of hex numbers\n"
+ " that is mode page contents "
+ "to write\n"
+ " --contents=- | -c - read stdin for mode page contents"
+ " to write\n"
+ " --dbd | -d disable block descriptors (DBD bit"
+ " in cdb)\n"
+ " --force | -f force the contents to be written\n"
+ " --help | -h print out usage message\n"
+ " --len=10|6 | -l 10|6 use 10 byte (def) or 6 byte "
+ "variants of\n"
+ " SCSI MODE SENSE/SELECT commands\n"
+ " --mask=M,M... | -m M,M... comma separated "
+ "string of hex\n"
+ " numbers that mask contents"
+ " to write\n"
+ " --page=PG_H | -p PG_H page_code to be written (in hex)\n"
+ " --page=PG_H,SPG_H | -p PG_H,SPG_H page and subpage code "
+ "to be\n"
+ " written (in hex)\n"
+ " --rtd | -R set RTD bit (revert to defaults) in "
+ "cdb\n"
+ " --save | -s set 'save page' (SP) bit; default "
+ "don't so\n"
+ " only 'current' values changed\n"
+ " --six | -6 do SCSI MODE SENSE/SELECT(6) "
+ "commands\n"
+ " --verbose | -v increase verbosity\n"
+ " --version | -V print version string and exit\n\n"
+ "writes given mode page with SCSI MODE SELECT (10 or 6) "
+ "command\n");
+}
+
+
+/* Read hex numbers from command line or stdin. On the command line can
+ * either be comma or space separated list. Space separated list need to be
+ * quoted. For stdin (indicated by *inp=='-') there should be either
+ * one entry per line, a comma separated list or space separated list.
+ * Returns 0 if ok, or sg3_utils error code if error. */
+static int
+build_mode_page(const char * inp, uint8_t * mp_arr, int * mp_arr_len,
+ int max_arr_len)
+{
+ int in_len, k, j, m;
+ unsigned int h;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == mp_arr) ||
+ (NULL == mp_arr_len))
+ return SG_LIB_LOGIC_ERROR;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *mp_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ bool split_line;
+ int off = 0;
+ char carry_over[4];
+ char line[512];
+
+ carry_over[0] = 0;
+ for (j = 0; j < 512; ++j) {
+ if (NULL == fgets(line, sizeof(line), stdin))
+ break;
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ split_line = false;
+ } else
+ split_line = true;
+ }
+ if (in_len < 1) {
+ carry_over[0] = 0;
+ continue;
+ }
+ if (carry_over[0]) {
+ if (isxdigit((uint8_t)line[0])) {
+ carry_over[1] = line[0];
+ carry_over[2] = '\0';
+ if (1 == sscanf(carry_over, "%x", &h))
+ mp_arr[off - 1] = h; /* back up and overwrite */
+ else {
+ pr2serr("%s: carry_over error ['%s'] around line "
+ "%d\n", __func__, carry_over, j + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lcp = line + 1;
+ --in_len;
+ } else
+ lcp = line;
+ carry_over[0] = 0;
+ } else
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error at line %d, pos %d\n", __func__,
+ j + 1, m + k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < 1024; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff in line %d, "
+ "pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (split_line && (1 == strlen(lcp))) {
+ /* single trailing hex digit might be a split pair */
+ carry_over[0] = *lcp;
+ }
+ if ((off + k) >= max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ mp_arr[off + k] = h;
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else {
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error in line %d, at pos %d\n", __func__,
+ j + 1, (int)(lcp - line + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ off += (k + 1);
+ }
+ *mp_arr_len = off;
+ } else { /* hex string on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ mp_arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ *mp_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Read hex numbers from command line (comma separated list).
+ * Can also be (single) space separated list but needs to be quoted on the
+ * command line. Returns 0 if ok, or 1 if error. */
+static int
+build_mask(const char * inp, uint8_t * mask_arr, int * mask_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ unsigned int h;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == mask_arr) ||
+ (NULL == mask_arr_len))
+ return 1;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *mask_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ pr2serr("'--mask' does not accept input from stdin\n");
+ return 1;
+ } else { /* hex string on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return 1;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ if (1 == sscanf(lcp, "%x", &h)) {
+ if (h > 0xff) {
+ pr2serr("%s: hex number larger than 0xff at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ return 1;
+ }
+ mask_arr[k] = h;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+ *mask_arr_len = k + 1;
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool dbd = false;
+ bool force = false;
+ bool got_contents = false;
+ bool got_mask = false;
+ bool mode_6 = false; /* so default is mode_10 */
+ bool rtd = false; /* added in spc5r11 */
+ bool save = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, num, alloc_len, off, pdt, k, md_len, hdr_len, bd_len;
+ int mask_in_len;
+ int sg_fd = -1;
+ int pg_code = -1;
+ int sub_pg_code = 0;
+ int verbose = 0;
+ int read_in_len = 0;
+ int ret = 0;
+ unsigned u, uu;
+ const char * device_name = NULL;
+ uint8_t read_in[MX_ALLOC_LEN];
+ uint8_t mask_in[MX_ALLOC_LEN];
+ uint8_t ref_md[MX_ALLOC_LEN];
+ char ebuff[EBUFF_SZ];
+ char errStr[128];
+ char b[80];
+ struct sg_simple_inquiry_resp inq_data;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "6c:dfhl:m:p:RsvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '6':
+ mode_6 = true;
+ break;
+ case 'c':
+ memset(read_in, 0, sizeof(read_in));
+ if ((ret = build_mode_page(optarg, read_in, &read_in_len,
+ sizeof(read_in)))) {
+ pr2serr("bad argument to '--contents='\n");
+ return ret;
+ }
+ got_contents = true;
+ break;
+ case 'd':
+ dbd = true;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ num = sscanf(optarg, "%d", &res);
+ if ((1 == num) && ((6 == res) || (10 == res)))
+ mode_6 = (6 == res);
+ else {
+ pr2serr("length (of cdb) must be 6 or 10\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'm':
+ memset(mask_in, 0xff, sizeof(mask_in));
+ if (0 != build_mask(optarg, mask_in, &mask_in_len,
+ sizeof(mask_in))) {
+ pr2serr("bad argument to '--mask'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ got_mask = true;
+ break;
+ case 'p':
+ if (NULL == strchr(optarg, ',')) {
+ num = sscanf(optarg, "%x", &u);
+ if ((1 != num) || (u > 62)) {
+ pr2serr("Bad hex page code value after '--page' "
+ "switch\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ pg_code = u;
+ } else if (2 == sscanf(optarg, "%x,%x", &u, &uu)) {
+ if (uu > 254) {
+ pr2serr("Bad hex sub page code value after '--page' "
+ "switch\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ pg_code = u;
+ sub_pg_code = uu;
+ } else {
+ pr2serr("Bad hex page code, subpage code sequence after "
+ "'--page' switch\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'R':
+ rtd = true;
+ break;
+ case 's':
+ save = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((pg_code < 0) && (! rtd)) {
+ pr2serr("need page code (see '--page=')\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (got_mask && force) {
+ pr2serr("cannot use both '--force' and '--mask'\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ if (rtd)
+ goto revert_to_defaults;
+
+ if (0 == sg_simple_inquiry(sg_fd, &inq_data, false, verbose))
+ pdt = inq_data.peripheral_type;
+ else
+ pdt = PDT_UNKNOWN;
+
+ /* do MODE SENSE to fetch current values */
+ memset(ref_md, 0, MX_ALLOC_LEN);
+ snprintf(errStr, sizeof(errStr), "MODE SENSE (%d): ", mode_6 ? 6 : 10);
+ alloc_len = mode_6 ? SHORT_ALLOC_LEN : MX_ALLOC_LEN;
+ if (mode_6)
+ res = sg_ll_mode_sense6(sg_fd, dbd, 0 /*current */, pg_code,
+ sub_pg_code, ref_md, alloc_len, true,
+ verbose);
+ else
+ res = sg_ll_mode_sense10(sg_fd, false /* llbaa */, dbd,
+ 0 /* current */, pg_code, sub_pg_code,
+ ref_md, alloc_len, true, verbose);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%snot supported, try '--len=%d'\n", errStr,
+ (mode_6 ? 10 : 6));
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s%s\n", errStr, b);
+ }
+ goto fini;
+ }
+ off = sg_mode_page_offset(ref_md, alloc_len, mode_6, ebuff, EBUFF_SZ);
+ if (off < 0) {
+ pr2serr("%s%s\n", errStr, ebuff);
+ goto fini;
+ }
+ md_len = sg_msense_calc_length(ref_md, alloc_len, mode_6, &bd_len);
+ if (md_len < 0) {
+ pr2serr("%ssg_msense_calc_length() failed\n", errStr);
+ goto fini;
+ }
+ hdr_len = mode_6 ? 4 : 8;
+ if (got_contents) {
+ if (read_in_len < 2) {
+ pr2serr("contents length=%d too short\n", read_in_len);
+ goto fini;
+ }
+ ref_md[0] = 0; /* mode data length reserved for mode select */
+ if (! mode_6)
+ ref_md[1] = 0; /* mode data length reserved for mode select */
+ if (0 == pdt) /* for disks mask out DPOFUA bit */
+ ref_md[mode_6 ? 2 : 3] &= 0xef;
+ if (md_len > alloc_len) {
+ pr2serr("mode data length=%d exceeds allocation length=%d\n",
+ md_len, alloc_len);
+ goto fini;
+ }
+ if (got_mask) {
+ for (k = 0; k < (md_len - off); ++k) {
+ if ((0x0 == mask_in[k]) || (k > read_in_len))
+ read_in[k] = ref_md[off + k];
+ else if (mask_in[k] < 0xff) {
+ c = (ref_md[off + k] & (0xff & ~mask_in[k]));
+ read_in[k] = (c | (read_in[k] & mask_in[k]));
+ }
+ }
+ read_in_len = md_len - off;
+ }
+ if (! force) {
+ if ((! (ref_md[off] & 0x80)) && save) {
+ pr2serr("PS bit in existing mode page indicates that it is "
+ "not saveable\n but '--save' option given\n");
+ goto fini;
+ }
+ read_in[0] &= 0x7f; /* mask out PS bit, reserved in mode select */
+ if ((md_len - off) != read_in_len) {
+ pr2serr("contents length=%d but reference mode page "
+ "length=%d\n", read_in_len, md_len - off);
+ goto fini;
+ }
+ if (pg_code != (read_in[0] & 0x3f)) {
+ pr2serr("contents page_code=0x%x but reference "
+ "page_code=0x%x\n", (read_in[0] & 0x3f), pg_code);
+ goto fini;
+ }
+ if ((read_in[0] & 0x40) != (ref_md[off] & 0x40)) {
+ pr2serr("contents flags subpage but reference page does not "
+ "(or vice versa)\n");
+ goto fini;
+ }
+ if ((read_in[0] & 0x40) && (read_in[1] != sub_pg_code)) {
+ pr2serr("contents subpage_code=0x%x but reference "
+ "sub_page_code=0x%x\n", read_in[1], sub_pg_code);
+ goto fini;
+ }
+ } else
+ md_len = off + read_in_len; /* force length */
+
+ memcpy(ref_md + off, read_in, read_in_len);
+ if (mode_6)
+ res = sg_ll_mode_select6_v2(sg_fd, true /* PF */, rtd, save,
+ ref_md, md_len, true, verbose);
+ else
+ res = sg_ll_mode_select10_v2(sg_fd, true /* PF */, rtd, save,
+ ref_md, md_len, true, verbose);
+ ret = res;
+ if (res)
+ goto fini;
+ } else {
+ printf(">>> No contents given, so show current mode page data:\n");
+ printf(" header:\n");
+ hex2stdout(ref_md, hdr_len, -1);
+ if (bd_len) {
+ printf(" block descriptor(s):\n");
+ hex2stdout(ref_md + hdr_len, bd_len, -1);
+ } else
+ printf(" << no block descriptors >>\n");
+ printf(" mode page:\n");
+ hex2stdout(ref_md + off, md_len - off, -1);
+ }
+ ret = 0;
+ goto fini;
+
+revert_to_defaults:
+ if (verbose)
+ pr2serr("Doing MODE SELECT(%d) with revert to defaults (RTD) set "
+ "and SP=%d\n", mode_6 ? 6 : 10, !! save);
+ if (mode_6)
+ res = sg_ll_mode_select6_v2(sg_fd, false /* PF */, true /* rtd */,
+ save, NULL, 0, true, verbose);
+ else
+ res = sg_ll_mode_select10_v2(sg_fd, false /* PF */, true /* rtd */,
+ save, NULL, 0, true, verbose);
+ ret = res;
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_wr_mode failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_write_buffer.c b/src/sg_write_buffer.c
new file mode 100644
index 00000000..2b84323a
--- /dev/null
+++ b/src/sg_write_buffer.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2006-2021 Luben Tuikov 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+#include "sg_pt.h" /* needed for scsi_pt_win32_direct() */
+#endif
+#endif
+
+/*
+ * This utility issues the SCSI WRITE BUFFER command to the given device.
+ */
+
+static const char * version_str = "1.30 20210610"; /* spc6r05 */
+
+#define ME "sg_write_buffer: "
+#define DEF_XFER_LEN (8 * 1024 * 1024)
+#define EBUFF_SZ 256
+
+#define WRITE_BUFFER_CMD 0x3b
+#define WRITE_BUFFER_CMDLEN 10
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 300 /* 300 seconds, 5 minutes */
+
+static struct option long_options[] = {
+ {"bpw", required_argument, 0, 'b'},
+ {"dry-run", no_argument, 0, 'd'},
+ {"dry_run", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"id", required_argument, 0, 'i'},
+ {"in", required_argument, 0, 'I'},
+ {"length", required_argument, 0, 'l'},
+ {"mode", required_argument, 0, 'm'},
+ {"offset", required_argument, 0, 'o'},
+ {"read-stdin", no_argument, 0, 'r'},
+ {"read_stdin", no_argument, 0, 'r'},
+ {"raw", no_argument, 0, 'r'},
+ {"skip", required_argument, 0, 's'},
+ {"specific", required_argument, 0, 'S'},
+ {"timeout", required_argument, 0, 't' },
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] "
+ "[--in=FILE]\n"
+ " [--length=LEN] [--mode=MO] "
+ "[--offset=OFF]\n"
+ " [--read-stdin] [--skip=SKIP] "
+ "[--specific=MS]\n"
+ " [--timeout=TO] [--verbose] [--version] "
+ "DEVICE\n"
+ " where:\n"
+ " --bpw=CS|-b CS CS is chunk size: bytes per write "
+ "buffer\n"
+ " command (def: 0 -> as many as "
+ "possible)\n"
+ " --dry-run|-d skip WRITE BUFFER commands, do "
+ "everything else\n"
+ " --help|-h print out usage message then exit\n"
+ " --id=ID|-i ID buffer identifier (0 (default) to "
+ "255)\n"
+ " --in=FILE|-I FILE read from FILE ('-I -' read "
+ "from stdin)\n"
+ " --length=LEN|-l LEN length in bytes to write; may be "
+ "deduced from\n"
+ " FILE\n"
+ " --mode=MO|-m MO write buffer mode, MO is number or "
+ "acronym\n"
+ " (def: 0 -> 'combined header and "
+ "data' (obs))\n"
+ " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n"
+ " --read-stdin|-r read from stdin (same as '-I -')\n"
+ " --skip=SKIP|-s SKIP bytes in file FILE to skip before "
+ "reading\n"
+ " --specific=MS|-S MS mode specific value; 3 bit field "
+ "(0 to 7)\n"
+ " --timeout=TO|-t TO command timeout in seconds (def: "
+ "300)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' "
+ "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') "
+ "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod "
+ "-m 7 /dev/sg3\n"
+ );
+
+}
+
+#define MODE_HEADER_DATA 0
+#define MODE_VENDOR 1
+#define MODE_DATA 2
+#define MODE_DNLD_MC 4
+#define MODE_DNLD_MC_SAVE 5
+#define MODE_DNLD_MC_OFFS 6
+#define MODE_DNLD_MC_OFFS_SAVE 7
+#define MODE_ECHO_BUFFER 0x0A
+#define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D
+#define MODE_DNLD_MC_OFFS_DEFER 0x0E
+#define MODE_ACTIVATE_MC 0x0F
+#define MODE_EN_EX_ECHO 0x1A
+#define MODE_DIS_EX 0x1B
+#define MODE_DNLD_ERR_HISTORY 0x1C
+
+
+struct mode_s {
+ const char *mode_string;
+ int mode;
+ const char *comment;
+};
+
+static struct mode_s mode_arr[] = {
+ {"hd", MODE_HEADER_DATA, "combined header and data "
+ "(obsolete)"},
+ {"vendor", MODE_VENDOR, "vendor specific"},
+ {"data", MODE_DATA, "data"},
+ {"dmc", MODE_DNLD_MC, "download microcode and activate"},
+ {"dmc_save", MODE_DNLD_MC_SAVE, "download microcode, save and "
+ "activate"},
+ {"dmc_offs", MODE_DNLD_MC_OFFS, "download microcode with offsets "
+ "and activate"},
+ {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
+ "offsets, save and\n\t\t\t\tactivate"},
+ {"echo", MODE_ECHO_BUFFER, "write data to echo buffer"},
+ {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download "
+ "microcode with offsets, select\n\t\t\t\tactivation event, "
+ "save and defer activation"},
+ {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
+ "with offsets, save and\n\t\t\t\tdefer activation"},
+ {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
+ {"en_ex", MODE_EN_EX_ECHO, "enable expander communications "
+ "protocol and\n\t\t\t\techo buffer (obsolete)"},
+ {"dis_ex", MODE_DIS_EX, "disable expander communications "
+ "protocol\n\t\t\t\t(obsolete)"},
+ {"deh", MODE_DNLD_ERR_HISTORY, "download application client "
+ "error history "},
+ {NULL, 0, NULL},
+};
+
+static void
+print_modes(void)
+{
+ const struct mode_s * mp;
+
+ pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
+ "or symbolic:\n");
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ pr2serr(" %2d (0x%02x) %-18s%s\n", mp->mode, mp->mode,
+ mp->mode_string, mp->comment);
+ }
+ pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
+ "microcode after\nsuccessful dmc_offs_defer and "
+ "dmc_offs_ev_defer mode downloads.\n");
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool bpw_then_activate = false;
+ bool dry_run = false;
+ bool got_stdin = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool wb_len_given = false;
+ int infd, res, c, len, k, n;
+ int sg_fd = -1;
+ int bpw = 0;
+ int do_help = 0;
+ int ret = 0;
+ int verbose = 0;
+ int wb_id = 0;
+ int wb_len = 0;
+ int wb_mode = 0;
+ int wb_offset = 0;
+ int wb_skip = 0;
+ int wb_timeout = DEF_PT_TIMEOUT;
+ int wb_mspec = 0;
+ const char * device_name = NULL;
+ const char * file_name = NULL;
+ uint8_t * dop = NULL;
+ uint8_t * read_buf = NULL;
+ uint8_t * free_dop = NULL;
+ char * cp;
+ const struct mode_s * mp;
+ char ebuff[EBUFF_SZ];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ bpw = sg_get_num(optarg);
+ if (bpw < 0) {
+ pr2serr("argument to '--bpw' should be in a positive "
+ "number\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((cp = strchr(optarg, ','))) {
+ if (0 == strncmp("act", cp + 1, 3))
+ bpw_then_activate = true;
+ }
+ break;
+ case 'd':
+ dry_run = true;
+ break;
+ case 'h':
+ case '?':
+ ++do_help;
+ break;
+ case 'i':
+ wb_id = sg_get_num(optarg);
+ if ((wb_id < 0) || (wb_id > 255)) {
+ pr2serr("argument to '--id' should be in the range 0 to "
+ "255\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'I':
+ file_name = optarg;
+ break;
+ case 'l':
+ wb_len = sg_get_num(optarg);
+ if (wb_len < 0) {
+ pr2serr("bad argument to '--length'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ wb_len_given = true;
+ break;
+ case 'm':
+ if (isdigit((uint8_t)*optarg)) {
+ wb_mode = sg_get_num(optarg);
+ if ((wb_mode < 0) || (wb_mode > 31)) {
+ pr2serr("argument to '--mode' should be in the range 0 "
+ "to 31\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ len = strlen(optarg);
+ for (mp = mode_arr; mp->mode_string; ++mp) {
+ if (0 == strncmp(mp->mode_string, optarg, len)) {
+ wb_mode = mp->mode;
+ break;
+ }
+ }
+ if (! mp->mode_string) {
+ print_modes();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ break;
+ case 'o':
+ wb_offset = sg_get_num(optarg);
+ if (wb_offset < 0) {
+ pr2serr("bad argument to '--offset'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'r': /* --read-stdin and --raw (previous name) */
+ file_name = "-";
+ break;
+ case 's':
+ wb_skip = sg_get_num(optarg);
+ if (wb_skip < 0) {
+ pr2serr("bad argument to '--skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'S':
+ wb_mspec = sg_get_num(optarg);
+ if ((wb_mspec < 0) || (wb_mspec > 7)) {
+ pr2serr("expected argument to '--specific' to be 0 to 7\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 't':
+ wb_timeout = sg_get_num(optarg);
+ if (wb_timeout < 0) {
+ pr2serr("Invalid argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (do_help) {
+ if (do_help > 1) {
+ usage();
+ pr2serr("\n");
+ print_modes();
+ } else
+ usage();
+ return 0;
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if ((wb_len > 0) && (bpw > wb_len)) {
+ pr2serr("trim chunk size (CS) to be the same as LEN\n");
+ bpw = wb_len;
+ }
+
+#ifdef SG_LIB_WIN32
+#ifdef SG_LIB_WIN32_DIRECT
+ if (verbose > 4)
+ pr2serr("Initial win32 SPT interface state: %s\n",
+ scsi_pt_win32_spt_state() ? "direct" : "indirect");
+ scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
+#endif
+#endif
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+ if (file_name || (wb_len > 0)) {
+ if (0 == wb_len)
+ wb_len = DEF_XFER_LEN;
+ dop = sg_memalign(wb_len, 0, &free_dop, false);
+ if (NULL == dop) {
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ memset(dop, 0xff, wb_len);
+ if (file_name) {
+ got_stdin = (0 == strcmp(file_name, "-"));
+ if (got_stdin) {
+ if (wb_skip > 0) {
+ pr2serr("Can't skip on stdin\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ infd = STDIN_FILENO;
+ } else {
+ if ((infd = open(file_name, O_RDONLY)) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", file_name);
+ perror(ebuff);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ if (wb_skip > 0) {
+ if (lseek(infd, wb_skip, SEEK_SET) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to "
+ "required position on %s", file_name);
+ perror(ebuff);
+ close(infd);
+ goto err_out;
+ }
+ }
+ }
+ if (infd == STDIN_FILENO) {
+ if (NULL == (read_buf = (uint8_t *)malloc(DEF_XFER_LEN))) {
+ pr2serr(ME "out of memory\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ res = read(infd, read_buf, DEF_XFER_LEN);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from STDIN");
+ perror(ebuff);
+ ret = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+ char * pch;
+ int val = 0;
+ res = 0;
+ pch = strtok((char*)read_buf, ",. \n\t");
+ while (pch != NULL) {
+ val = sg_get_num_nomult(pch);
+ if (val >= 0 && val < 255) {
+ dop[res] = val;
+ res++;
+ } else {
+ pr2serr("Data read from STDIO is wrong.\nPlease "
+ "input the data a byte at a time, the bytes "
+ "should be separated\nby either space, or "
+ "',' ( or by '.'), and the value per byte "
+ "should\nbe between 0~255. Hexadecimal "
+ "numbers should be preceded by either '0x' "
+ "or\n'OX' (or have a trailing 'h' or "
+ "'H').\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ pch = strtok(NULL, ",. \n\t");
+ }
+ } else {
+ res = read(infd, dop, wb_len);
+ if (res < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+ file_name);
+ perror(ebuff);
+ if (! got_stdin)
+ close(infd);
+ goto err_out;
+ }
+ }
+ if (res < wb_len) {
+ if (wb_len_given) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ wb_len, file_name, res);
+ pr2serr("pad with 0xff bytes and continue\n");
+ } else {
+ if (verbose) {
+ pr2serr("tried to read %d bytes from %s, got %d "
+ "bytes\n", wb_len, file_name, res);
+ pr2serr("will write %d bytes", res);
+ if ((bpw > 0) && (bpw < wb_len))
+ pr2serr(", %d bytes per WRITE BUFFER command\n",
+ bpw);
+ else
+ pr2serr("\n");
+ }
+ wb_len = res;
+ }
+ }
+ if (! got_stdin)
+ close(infd);
+ }
+ }
+
+ res = 0;
+ if (bpw > 0) {
+ for (k = 0; k < wb_len; k += n) {
+ n = wb_len - k;
+ if (n > bpw)
+ n = bpw;
+ if (verbose)
+ pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, "
+ " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+ wb_offset + k, n);
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+ wb_offset + k, dop + k, n,
+ wb_timeout, true, verbose);
+ if (res)
+ break;
+ }
+ if (bpw_then_activate) {
+ if (verbose)
+ pr2serr("sending Activate deferred microcode [0xf]\n");
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC,
+ 0 /* buffer_id */,
+ 0 /* buffer_offset */, 0,
+ NULL, 0, wb_timeout, true,
+ verbose);
+ }
+ } else {
+ if (verbose)
+ pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, "
+ "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
+ wb_offset, wb_len);
+ if (dry_run) {
+ if (verbose)
+ pr2serr("skipping WRITE BUFFER(all in one) command due to "
+ "--dry-run\n");
+ res = 0;
+ } else
+ res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
+ wb_offset, dop, wb_len, wb_timeout,
+ true, verbose);
+ }
+ if (0 != res) {
+ char b[80];
+
+ ret = res;
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Write buffer failed: %s\n", b);
+ }
+
+err_out:
+ if (free_dop)
+ free(free_dop);
+ if (read_buf)
+ free(read_buf);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_write_buffer failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_write_long.c b/src/sg_write_long.c
new file mode 100644
index 00000000..af323cc7
--- /dev/null
+++ b/src/sg_write_long.c
@@ -0,0 +1,331 @@
+/* A utility program for the Linux OS SCSI subsystem.
+ * Copyright (C) 2004-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program issues the SCSI command WRITE LONG to a given SCSI device.
+ * It sends the command with the logical block address passed as the lba
+ * argument, and the transfer length set to the xfer_len argument. the
+ * buffer to be written to the device filled with 0xff, this buffer includes
+ * the sector data and the ECC bytes.
+ *
+ * This code was contributed by Saeed Bishara
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.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"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.21 20180723";
+
+
+#define MAX_XFER_LEN (15 * 1024)
+
+#define ME "sg_write_long: "
+
+#define EBUFF_SZ 512
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'S'},
+ {"cor_dis", no_argument, 0, 'c'},
+ {"cor-dis", no_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"pblock", no_argument, 0, 'p'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wr_uncor", no_argument, 0, 'w'},
+ {"wr-uncor", no_argument, 0, 'w'},
+ {"xfer_len", required_argument, 0, 'x'},
+ {"xfer-len", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_write_long [--16] [--cor_dis] [--help] [--in=IF] "
+ "[--lba=LBA]\n"
+ " [--pblock] [--verbose] [--version] "
+ "[--wr_uncor]\n"
+ " [--xfer_len=BTL] DEVICE\n"
+ " where:\n"
+ " --16|-S do WRITE LONG(16) (default: 10)\n"
+ " --cor_dis|-c set correction disabled bit\n"
+ " --help|-h print out usage message\n"
+ " --in=IF|-i IF input from file called IF (default: "
+ "use\n"
+ " 0xff bytes as fill)\n"
+ " --lba=LBA|-l LBA logical block address "
+ "(default: 0)\n"
+ " --pblock|-p physical block (default: logical "
+ "block)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --wr_uncor|-w set an uncorrectable error (no "
+ "data transferred)\n"
+ " --xfer_len=BTL|-x BTL byte transfer length (< 10000) "
+ "(default:\n"
+ " 520 bytes)\n\n"
+ "Performs a SCSI WRITE LONG (10 or 16) command. Writes a single "
+ "block\nincluding associated ECC data. That data may be obtained "
+ "from the\nSCSI READ LONG command. See the sg_read_long utility.\n"
+ );
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool do_16 = false;
+ bool cor_dis = false;
+ bool got_stdin;
+ bool pblock = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool wr_uncor = false;
+ int res, c, infd, offset;
+ int sg_fd = -1;
+ int xfer_len = 520;
+ int ret = 1;
+ int verbose = 0;
+ int64_t ll;
+ uint64_t llba = 0;
+ const char * device_name = NULL;
+ uint8_t * writeLongBuff = NULL;
+ void * rawp = NULL;
+ uint8_t * free_rawp = NULL;
+ const char * ten_or;
+ char file_name[256];
+ char b[80];
+ char ebuff[EBUFF_SZ];
+
+ memset(file_name, 0, sizeof file_name);
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "chi:l:pSvVwx:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ cor_dis = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ strncpy(file_name, optarg, sizeof(file_name) - 1);
+ file_name[sizeof(file_name) - 1] = '\0';
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ llba = (uint64_t)ll;
+ break;
+ case 'p':
+ pblock = true;
+ break;
+ case 'S':
+ do_16 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'w':
+ wr_uncor = true;
+ break;
+ case 'x':
+ xfer_len = sg_get_num(optarg);
+ if (-1 == xfer_len) {
+ pr2serr("bad argument to '--xfer_len'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (wr_uncor)
+ xfer_len = 0;
+ else if (xfer_len >= MAX_XFER_LEN) {
+ pr2serr("xfer_len (%d) is out of range ( < %d)\n", xfer_len,
+ MAX_XFER_LEN);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ if (wr_uncor) {
+ if ('\0' != file_name[0])
+ pr2serr(">>> warning: when '--wr_uncor' given '-in=' is "
+ "ignored\n");
+ } else {
+ if (NULL == (rawp = sg_memalign(MAX_XFER_LEN, 0, &free_rawp, false))) {
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ writeLongBuff = (uint8_t *)rawp;
+ memset(rawp, 0xff, MAX_XFER_LEN);
+ if (file_name[0]) {
+ got_stdin = (0 == strcmp(file_name, "-"));
+ if (got_stdin) {
+ infd = STDIN_FILENO;
+ if (sg_set_binary_mode(STDIN_FILENO) < 0)
+ perror("sg_set_binary_mode");
+ } else {
+ if ((infd = open(file_name, O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", file_name);
+ perror(ebuff);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ }
+ res = read(infd, writeLongBuff, xfer_len);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
+ file_name);
+ perror(ebuff);
+ if (! got_stdin)
+ close(infd);
+ goto err_out;
+ }
+ if (res < xfer_len) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ xfer_len, file_name, res);
+ pr2serr("pad with 0xff bytes and continue\n");
+ }
+ if (! got_stdin)
+ close(infd);
+ }
+ }
+ if (verbose)
+ pr2serr(ME "issue write long to device %s\n\t\txfer_len= %d (0x%x), "
+ "lba=%" PRIu64 " (0x%" PRIx64 ")\n cor_dis=%d, "
+ "wr_uncor=%d, pblock=%d\n", device_name, xfer_len, xfer_len,
+ llba, llba, (int)cor_dis, (int)wr_uncor, (int)pblock);
+
+ ten_or = do_16 ? "16" : "10";
+ if (do_16)
+ res = sg_ll_write_long16(sg_fd, cor_dis, wr_uncor, pblock, llba,
+ writeLongBuff, xfer_len, &offset, true,
+ verbose);
+ else
+ res = sg_ll_write_long10(sg_fd, cor_dis, wr_uncor, pblock,
+ (unsigned int)llba, writeLongBuff, xfer_len,
+ &offset, true, verbose);
+ ret = res;
+ switch (res) {
+ case 0:
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO:
+ pr2serr("<<< device indicates 'xfer_len' should be %d >>>\n",
+ xfer_len - offset);
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr(" SCSI WRITE LONG (%s): %s\n", ten_or, b);
+ break;
+ }
+
+err_out:
+ if (free_rawp)
+ free(free_rawp);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_write_long failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_write_same.c b/src/sg_write_same.c
new file mode 100644
index 00000000..bfdf8159
--- /dev/null
+++ b/src/sg_write_same.c
@@ -0,0 +1,678 @@
+/*
+ * Copyright (c) 2009-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.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_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.34 20220127";
+
+
+#define ME "sg_write_same: "
+
+#define WRITE_SAME10_OP 0x41
+#define WRITE_SAME16_OP 0x93
+#define VARIABLE_LEN_OP 0x7f
+#define WRITE_SAME32_SA 0xd
+#define WRITE_SAME32_ADD 0x18
+#define WRITE_SAME10_LEN 10
+#define WRITE_SAME16_LEN 16
+#define WRITE_SAME32_LEN 32
+#define RCAP10_RESP_LEN 8
+#define RCAP16_RESP_LEN 32
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT_SECS 60
+#define DEF_WS_CDB_SIZE WRITE_SAME10_LEN
+#define DEF_WS_NUMBLOCKS 1
+#define MAX_XFER_LEN (64 * 1024)
+#define EBUFF_SZ 512
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+static struct option long_options[] = {
+ {"10", no_argument, 0, 'R'},
+ {"16", no_argument, 0, 'S'},
+ {"32", no_argument, 0, 'T'},
+ {"anchor", no_argument, 0, 'a'},
+ {"ff", no_argument, 0, 'f'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"lbdata", no_argument, 0, 'L'},
+ {"ndob", no_argument, 0, 'N'},
+ {"num", required_argument, 0, 'n'},
+ {"pbdata", no_argument, 0, 'P'},
+ {"timeout", required_argument, 0, 't'},
+ {"unmap", no_argument, 0, 'U'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrprotect", required_argument, 0, 'w'},
+ {"xferlen", required_argument, 0, 'x'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool anchor;
+ bool ff;
+ bool ndob;
+ bool lbdata;
+ bool pbdata;
+ bool unmap;
+ bool verbose_given;
+ bool version_given;
+ bool want_ws10;
+ int grpnum;
+ int numblocks;
+ int timeout;
+ int verbose;
+ int wrprotect;
+ int xfer_len;
+ int pref_cdb_size;
+ uint64_t lba;
+ char ifilename[256];
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_write_same [--10] [--16] [--32] [--anchor] "
+ "[-ff] [--grpnum=GN]\n"
+ " [--help] [--in=IF] [--lba=LBA] [--lbdata] "
+ "[--ndob]\n"
+ " [--num=NUM] [--pbdata] [--timeout=TO] "
+ "[--unmap]\n"
+ " [--verbose] [--version] [--wrprotect=WRP] "
+ "[xferlen=LEN]\n"
+ " DEVICE\n"
+ " where:\n"
+ " --10|-R send WRITE SAME(10) (even if '--unmap' "
+ "is given)\n"
+ " --16|-S send WRITE SAME(16) (def: 10 unless "
+ "'--unmap' given,\n"
+ " LBA+NUM > 32 bits, or NUM > 65535; "
+ "then def 16)\n"
+ " --32|-T send WRITE SAME(32) (def: 10 or 16)\n"
+ " --anchor|-a set ANCHOR field in cdb\n"
+ " --ff|-f use buffer of 0xff bytes for fill "
+ "(def: 0x0 bytes)\n"
+ " --grpnum=GN|-g GN GN is group number field (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --in=IF|-i IF IF is file to fetch one block of data "
+ "from (use LEN\n"
+ " bytes or whole file). Block written to "
+ "DEVICE\n"
+ " --lba=LBA|-l LBA LBA is the logical block address to "
+ "start (def: 0)\n"
+ " --lbdata|-L set LBDATA bit (obsolete)\n"
+ " --ndob|-N set NDOB (no data-out buffer) bit in "
+ "cdb\n"
+ " --num=NUM|-n NUM NUM is number of logical blocks to "
+ "write (def: 1)\n"
+ " [Beware NUM==0 may mean: 'rest of "
+ "device']\n"
+ " --pbdata|-P set PBDATA bit (obsolete)\n"
+ " --timeout=TO|-t TO command timeout (unit: seconds) (def: "
+ "60)\n"
+ " --unmap|-U set UNMAP bit\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --wrprotect=WPR|-w WPR WPR is the WRPROTECT field value "
+ "(def: 0)\n"
+ " --xferlen=LEN|-x LEN LEN is number of bytes from IF to "
+ "send to\n"
+ " DEVICE (def: IF file length)\n\n"
+ "Performs a SCSI WRITE SAME (10, 16 or 32) command. NDOB bit is "
+ "only\nsupported by the 16 and 32 byte variants. When set the "
+ "specified blocks\nwill be filled with zeros or the "
+ "'provisioning initialization pattern'\nas indicated by the "
+ "LBPRZ field. As a precaution one of the '--in=',\n'--lba=' or "
+ "'--num=' options is required.\nAnother implementation of WRITE "
+ "SAME is found in the sg_write_x utility.\n"
+ );
+}
+
+static int
+do_write_same(int sg_fd, const struct opts_t * op, const void * dataoutp,
+ int * act_cdb_lenp)
+{
+ int ret, res, sense_cat, cdb_len;
+ uint64_t llba;
+ uint8_t ws_cdb[WRITE_SAME32_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ cdb_len = op->pref_cdb_size;
+ if (WRITE_SAME10_LEN == cdb_len) {
+ llba = op->lba + op->numblocks;
+ if ((op->numblocks > 0xffff) || (llba > UINT32_MAX) ||
+ op->ndob || (op->unmap && (! op->want_ws10))) {
+ cdb_len = WRITE_SAME16_LEN;
+ if (op->verbose) {
+ const char * cp = "use WRITE SAME(16) instead of 10 byte "
+ "cdb";
+
+ if (op->numblocks > 0xffff)
+ pr2serr("%s since blocks exceed 65535\n", cp);
+ else if (llba > UINT32_MAX)
+ pr2serr("%s since LBA may exceed 32 bits\n", cp);
+ else
+ pr2serr("%s due to ndob or unmap settings\n", cp);
+ }
+ }
+ }
+ if (act_cdb_lenp)
+ *act_cdb_lenp = cdb_len;
+ switch (cdb_len) {
+ case WRITE_SAME10_LEN:
+ ws_cdb[0] = WRITE_SAME10_OP;
+ ws_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ /* ANCHOR + UNMAP not allowed for WRITE_SAME10 in sbc3r24+r25 but
+ * a proposal has been made to allow it. Anticipate approval. */
+ if (op->anchor)
+ ws_cdb[1] |= 0x10;
+ if (op->unmap)
+ ws_cdb[1] |= 0x8;
+ if (op->pbdata)
+ ws_cdb[1] |= 0x4;
+ if (op->lbdata)
+ ws_cdb[1] |= 0x2;
+ sg_put_unaligned_be32((uint32_t)op->lba, ws_cdb + 2);
+ ws_cdb[6] = (op->grpnum & GRPNUM_MASK);
+ sg_put_unaligned_be16((uint16_t)op->numblocks, ws_cdb + 7);
+ break;
+ case WRITE_SAME16_LEN:
+ ws_cdb[0] = WRITE_SAME16_OP;
+ ws_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ if (op->anchor)
+ ws_cdb[1] |= 0x10;
+ if (op->unmap)
+ ws_cdb[1] |= 0x8;
+ if (op->pbdata)
+ ws_cdb[1] |= 0x4;
+ if (op->lbdata)
+ ws_cdb[1] |= 0x2;
+ if (op->ndob)
+ ws_cdb[1] |= 0x1;
+ sg_put_unaligned_be64(op->lba, ws_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)op->numblocks, ws_cdb + 10);
+ ws_cdb[14] = (op->grpnum & GRPNUM_MASK);
+ break;
+ case WRITE_SAME32_LEN:
+ ws_cdb[0] = VARIABLE_LEN_OP;
+ ws_cdb[6] = (op->grpnum & GRPNUM_MASK);
+ ws_cdb[7] = WRITE_SAME32_ADD;
+ sg_put_unaligned_be16((uint16_t)WRITE_SAME32_SA, ws_cdb + 8);
+ ws_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->anchor)
+ ws_cdb[10] |= 0x10;
+ if (op->unmap)
+ ws_cdb[10] |= 0x8;
+ if (op->pbdata)
+ ws_cdb[10] |= 0x4;
+ if (op->lbdata)
+ ws_cdb[10] |= 0x2;
+ if (op->ndob)
+ ws_cdb[10] |= 0x1;
+ sg_put_unaligned_be64(op->lba, ws_cdb + 12);
+ sg_put_unaligned_be32((uint32_t)op->numblocks, ws_cdb + 28);
+ break;
+ default:
+ pr2serr("do_write_same: bad cdb length %d\n", cdb_len);
+ return -1;
+ }
+
+ if (op->verbose > 1) {
+ char b[128];
+
+ pr2serr(" Write same(%d) cdb: %s\n", cdb_len,
+ sg_get_command_str(ws_cdb, cdb_len, false, sizeof(b), b));
+ pr2serr(" Data-out buffer length=%d\n", op->xfer_len);
+ }
+ if ((op->verbose > 3) && (op->xfer_len > 0)) {
+ pr2serr(" Data-out buffer contents:\n");
+ hex2stderr((const uint8_t *)dataoutp, op->xfer_len, 1);
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("Write same(%d): out of memory\n", cdb_len);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, ws_cdb, cdb_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, (uint8_t *)dataoutp, op->xfer_len);
+ res = do_scsi_pt(ptvp, sg_fd, op->timeout, op->verbose);
+ ret = sg_cmds_process_resp(ptvp, "Write same", res, true /*noisy */,
+ op->verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ {
+ bool valid;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid)
+ pr2serr("Medium or hardware error starting at lba=%"
+ PRIu64 " [0x%" PRIx64 "]\n", ull, ull);
+ }
+ ret = sense_cat;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (op->verbose)
+ sg_print_command_len(ws_cdb, cdb_len);
+ /* FALL THROUGH */
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool got_stdin = false;
+ bool if_given = false;
+ bool lba_given = false;
+ bool num_given = false;
+ bool prot_en;
+ int res, c, infd, act_cdb_len, vb, err;
+ int sg_fd = -1;
+ int ret = -1;
+ uint32_t block_size;
+ int64_t ll;
+ const char * device_name = NULL;
+ struct opts_t * op;
+ uint8_t * wBuff = NULL;
+ uint8_t * free_wBuff = NULL;
+ char ebuff[EBUFF_SZ];
+ char b[80];
+ uint8_t resp_buff[RCAP16_RESP_LEN];
+ struct opts_t opts;
+ struct stat a_stat;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ op->numblocks = DEF_WS_NUMBLOCKS;
+ op->pref_cdb_size = DEF_WS_CDB_SIZE;
+ op->timeout = DEF_TIMEOUT_SECS;
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "afg:hi:l:Ln:NPRSt:TUvVw:x:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ op->anchor = true;
+ break;
+ case 'f':
+ op->ff = true;
+ break;
+ case 'g':
+ op->grpnum = sg_get_num(optarg);
+ if ((op->grpnum < 0) || (op->grpnum > 63)) {
+ pr2serr("bad argument to '--grpnum'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ strncpy(op->ifilename, optarg, sizeof(op->ifilename) - 1);
+ op->ifilename[sizeof(op->ifilename) - 1] = '\0';
+ if_given = true;
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->lba = (uint64_t)ll;
+ lba_given = true;
+ break;
+ case 'L':
+ op->lbdata = true;
+ break;
+ case 'n':
+ op->numblocks = sg_get_num(optarg);
+ if (op->numblocks < 0) {
+ pr2serr("bad argument to '--num'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ num_given = true;
+ break;
+ case 'N':
+ op->ndob = true;
+ break;
+ case 'P':
+ op->pbdata = true;
+ break;
+ case 'R':
+ op->want_ws10 = true;
+ break;
+ case 'S':
+ if (DEF_WS_CDB_SIZE != op->pref_cdb_size) {
+ pr2serr("only one '--10', '--16' or '--32' please\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->pref_cdb_size = 16;
+ break;
+ case 't':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'T':
+ if (DEF_WS_CDB_SIZE != op->pref_cdb_size) {
+ pr2serr("only one '--10', '--16' or '--32' please\n");
+ return SG_LIB_CONTRADICT;
+ }
+ op->pref_cdb_size = 32;
+ break;
+ case 'U':
+ op->unmap = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ op->wrprotect = sg_get_num(optarg);
+ if ((op->wrprotect < 0) || (op->wrprotect > 7)) {
+ pr2serr("bad argument to '--wrprotect'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'x':
+ op->xfer_len = sg_get_num(optarg);
+ if (op->xfer_len < 0) {
+ pr2serr("bad argument to '--xferlen'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->want_ws10 && (DEF_WS_CDB_SIZE != op->pref_cdb_size)) {
+ pr2serr("only one '--10', '--16' or '--32' please\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ vb = op->verbose;
+
+ if ((! if_given) && (! lba_given) && (! num_given)) {
+ pr2serr("As a precaution, one of '--in=', '--lba=' or '--num=' is "
+ "required\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+ if (op->ndob) {
+ if (if_given) {
+ pr2serr("Can't have both --ndob and '--in='\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (0 != op->xfer_len) {
+ pr2serr("With --ndob only '--xferlen=0' (or not given) is "
+ "acceptable\n");
+ return SG_LIB_CONTRADICT;
+ }
+ } else if (op->ifilename[0]) {
+ got_stdin = (0 == strcmp(op->ifilename, "-"));
+ if (! got_stdin) {
+ memset(&a_stat, 0, sizeof(a_stat));
+ if (stat(op->ifilename, &a_stat) < 0) {
+ err = errno;
+ if (vb)
+ pr2serr("unable to stat(%s): %s\n", op->ifilename,
+ safe_strerror(err));
+ return sg_convert_errno(err);
+ }
+ if (op->xfer_len <= 0)
+ op->xfer_len = (int)a_stat.st_size;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ if (op->verbose)
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto err_out;
+ }
+
+ if (! op->ndob) {
+ prot_en = false;
+ if (0 == op->xfer_len) {
+ res = sg_ll_readcap_16(sg_fd, false /* pmi */, 0 /* llba */,
+ resp_buff, RCAP16_RESP_LEN, true,
+ (vb ? (vb - 1): 0));
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Read capacity(16) unit attention, try again\n");
+ res = sg_ll_readcap_16(sg_fd, false, 0, resp_buff,
+ RCAP16_RESP_LEN, true,
+ (vb ? (vb - 1): 0));
+ }
+ if (0 == res) {
+ if (vb > 3)
+ hex2stderr(resp_buff, RCAP16_RESP_LEN, 1);
+ block_size = sg_get_unaligned_be32(resp_buff + 8);
+ prot_en = !!(resp_buff[12] & 0x1);
+ op->xfer_len = block_size;
+ if (prot_en && (op->wrprotect > 0))
+ op->xfer_len += 8;
+ } else if ((SG_LIB_CAT_INVALID_OP == res) ||
+ (SG_LIB_CAT_ILLEGAL_REQ == res)) {
+ if (vb)
+ pr2serr("Read capacity(16) not supported, try Read "
+ "capacity(10)\n");
+ res = sg_ll_readcap_10(sg_fd, false /* pmi */, 0 /* lba */,
+ resp_buff, RCAP10_RESP_LEN, true,
+ (vb ? (vb - 1): 0));
+ if (0 == res) {
+ if (vb > 3)
+ hex2stderr(resp_buff, RCAP10_RESP_LEN, 1);
+ block_size = sg_get_unaligned_be32(resp_buff + 4);
+ op->xfer_len = block_size;
+ } else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("Read capacity(10): %s\n", b);
+ pr2serr("Unable to calculate block size\n");
+ }
+ } else if (vb) {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("Read capacity(16): %s\n", b);
+ pr2serr("Unable to calculate block size\n");
+ }
+ }
+ if (op->xfer_len < 1) {
+ pr2serr("unable to deduce block size, please give '--xferlen=' "
+ "argument\n");
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ if (op->xfer_len > MAX_XFER_LEN) {
+ pr2serr("'--xferlen=%d is out of range ( want <= %d)\n",
+ op->xfer_len, MAX_XFER_LEN);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+ }
+ wBuff = (uint8_t *)sg_memalign(op->xfer_len, 0, &free_wBuff, false);
+ if (NULL == wBuff) {
+ pr2serr("unable to allocate %d bytes of memory with "
+ "sg_memalign()\n", op->xfer_len);
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ if (op->ff)
+ memset(wBuff, 0xff, op->xfer_len);
+ if (op->ifilename[0]) {
+ if (got_stdin) {
+ infd = STDIN_FILENO;
+ if (sg_set_binary_mode(STDIN_FILENO) < 0)
+ perror("sg_set_binary_mode");
+ } else {
+ if ((infd = open(op->ifilename, O_RDONLY)) < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "could not open %.400s for "
+ "reading", op->ifilename);
+ perror(ebuff);
+ goto err_out;
+ } else if (sg_set_binary_mode(infd) < 0)
+ perror("sg_set_binary_mode");
+ }
+ res = read(infd, wBuff, op->xfer_len);
+ if (res < 0) {
+ ret = sg_convert_errno(errno);
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %.400s",
+ op->ifilename);
+ perror(ebuff);
+ if (! got_stdin)
+ close(infd);
+ goto err_out;
+ }
+ if (res < op->xfer_len) {
+ pr2serr("tried to read %d bytes from %s, got %d bytes\n",
+ op->xfer_len, op->ifilename, res);
+ pr2serr(" so pad with 0x0 bytes and continue\n");
+ }
+ if (! got_stdin)
+ close(infd);
+ } else {
+ if (vb)
+ pr2serr("Default data-out buffer set to %d zeros\n",
+ op->xfer_len);
+ if (prot_en && (op->wrprotect > 0)) {
+ /* default for protection is 0xff, rest get 0x0 */
+ memset(wBuff + op->xfer_len - 8, 0xff, 8);
+ if (vb)
+ pr2serr(" ... apart from last 8 bytes which are set to "
+ "0xff\n");
+ }
+ }
+ }
+
+ ret = do_write_same(sg_fd, op, wBuff, &act_cdb_len);
+ if (ret) {
+ sg_get_category_sense_str(ret, sizeof(b), b, vb);
+ pr2serr("Write same(%d): %s\n", act_cdb_len, b);
+ }
+
+err_out:
+ if (free_wBuff)
+ free(free_wBuff);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == op->verbose) {
+ if (! sg_if_can2stderr("sg_write_same failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_write_verify.c b/src/sg_write_verify.c
new file mode 100644
index 00000000..ee6b12bf
--- /dev/null
+++ b/src/sg_write_verify.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2014-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * This program issues the SCSI command WRITE AND VERIFY to a given SCSI
+ * device. It sends the command with the logical block address passed as the
+ * LBA argument, for the given number of blocks. The number of bytes sent is
+ * supplied separately, either by the size of the given file (IF) or
+ * explicitly with ILEN.
+ *
+ * This code was contributed by Bruno Goncalves
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.21 20220127";
+
+
+#define ME "sg_write_verify: "
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+
+#define WRITE_VERIFY10_CMD 0x2e
+#define WRITE_VERIFY10_CMDLEN 10
+#define WRITE_VERIFY16_CMD 0x8e
+#define WRITE_VERIFY16_CMDLEN 16
+
+#define WRPROTECT_MASK (0x7)
+#define WRPROTECT_SHIFT (5)
+
+#define DEF_TIMEOUT_SECS 60
+
+
+static struct option long_options[] = {
+ {"16", no_argument, 0, 'S'},
+ {"bytchk", required_argument, 0, 'b'},
+ {"dpo", no_argument, 0, 'd'},
+ {"group", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"ilen", required_argument, 0, 'I'},
+ {"in", required_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"num", required_argument, 0, 'n'},
+ {"repeat", no_argument, 0, 'R'},
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrprotect", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_write_verify [--16] [--bytchk=BC] [--dpo] [--group=GN] "
+ "[--help]\n"
+ " [--ilen=IL] [--in=IF] --lba=LBA "
+ "[--num=NUM]\n"
+ " [--repeat] [--timeout=TO] [--verbose] "
+ "[--version]\n"
+ " [--wrprotect=WPR] DEVICE\n"
+ " where:\n"
+ " --16|-S do WRITE AND VERIFY(16) (default: 10)\n"
+ " --bytchk=BC|-b BC set BYTCHK field (default: 0)\n"
+ " --dpo|-d set DPO bit (default: 0)\n"
+ " --group=GN|-g GN GN is group number (default: 0)\n"
+ " --help|-h print out usage message\n"
+ " --ilen=IL| -I IL input (file) length in bytes, becomes "
+ "data-out\n"
+ " buffer length (def: deduced from IF "
+ "size)\n"
+ " --in=IF|-i IF IF is a file containing the data to "
+ "be written\n"
+ " --lba=LBA|-l LBA LBA of the first block to write "
+ "and verify;\n"
+ " no default, must be given\n"
+ " --num=NUM|-n NUM logical blocks to write and verify "
+ "(def: 1)\n"
+ " --repeat|-R while IF still has data to read, send "
+ "another\n"
+ " command, bumping LBA with up to NUM "
+ "blocks again\n"
+ " --timeout=TO|-t TO command timeout in seconds (def: 60)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --wrprotect|-w WPR WPR is the WRPROTECT field value "
+ "(def: 0)\n\n"
+ "Performs a SCSI WRITE AND VERIFY (10 or 16) command on DEVICE, "
+ "startings\nat LBA for NUM logical blocks. More commands "
+ "performed only if '--repeat'\noption given. Data to be written "
+ "is fetched from the IF file.\n"
+ );
+}
+
+/* Invokes a SCSI WRITE AND VERIFY according with CDB. Returns 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+run_scsi_transaction(int sg_fd, const uint8_t *cdbp, int cdb_len,
+ uint8_t *dop, int do_len, int timeout,
+ bool noisy, int verbose)
+{
+ int res, sense_cat, ret;
+ struct sg_pt_base * ptvp;
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ char b[32];
+
+ snprintf(b, sizeof(b), "Write and verify(%d)", cdb_len);
+ if (verbose) {
+ char d[128];
+
+ pr2serr(" %s cdb: %s\n", b,
+ sg_get_command_str(cdbp, cdb_len, false, sizeof(d), d));
+ if ((verbose > 2) && dop && do_len) {
+ pr2serr(" Data out buffer [%d bytes]:\n", do_len);
+ hex2stderr(dop, do_len, -1);
+ }
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", b);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, cdbp, cdb_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_out(ptvp, dop, do_len);
+ res = do_scsi_pt(ptvp, sg_fd, timeout, verbose);
+ ret = sg_cmds_process_resp(ptvp, b, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD: /* write or verify failed */
+ {
+ bool valid;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid)
+ pr2serr("Medium or hardware error starting at lba=%"
+ PRIu64 " [0x%" PRIx64 "]\n", ull, ull);
+ }
+ ret = sense_cat;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (verbose)
+ sg_print_command_len(cdbp, cdb_len);
+ /* FALL THROUGH */
+ case SG_LIB_CAT_PROTECTION: /* PI failure */
+ case SG_LIB_CAT_MISCOMPARE: /* only in bytchk=1 case */
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE AND VERIFY (10) command (SBC). Returns 0 -> success,
+* various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_write_verify10(int sg_fd, int wrprotect, bool dpo, int bytchk,
+ unsigned int lba, int num_lb, int group,
+ uint8_t *dop, int do_len, int timeout,
+ bool noisy, int verbose)
+{
+ int ret;
+ uint8_t wv_cdb[WRITE_VERIFY10_CMDLEN];
+
+ memset(wv_cdb, 0, WRITE_VERIFY10_CMDLEN);
+ wv_cdb[0] = WRITE_VERIFY10_CMD;
+ wv_cdb[1] = ((wrprotect & WRPROTECT_MASK) << WRPROTECT_SHIFT);
+ if (dpo)
+ wv_cdb[1] |= 0x10;
+ if (bytchk)
+ wv_cdb[1] |= ((bytchk & 0x3) << 1);
+
+ sg_put_unaligned_be32((uint32_t)lba, wv_cdb + 2);
+ wv_cdb[6] = group & GRPNUM_MASK;
+ sg_put_unaligned_be16((uint16_t)num_lb, wv_cdb + 7);
+ ret = run_scsi_transaction(sg_fd, wv_cdb, sizeof(wv_cdb), dop, do_len,
+ timeout, noisy, verbose);
+ return ret;
+}
+
+/* Invokes a SCSI WRITE AND VERIFY (16) command (SBC). Returns 0 -> success,
+* various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_write_verify16(int sg_fd, int wrprotect, bool dpo, int bytchk,
+ uint64_t llba, int num_lb, int group, uint8_t *dop,
+ int do_len, int timeout, bool noisy, int verbose)
+{
+ int ret;
+ uint8_t wv_cdb[WRITE_VERIFY16_CMDLEN];
+
+
+ memset(wv_cdb, 0, sizeof(wv_cdb));
+ wv_cdb[0] = WRITE_VERIFY16_CMD;
+ wv_cdb[1] = ((wrprotect & WRPROTECT_MASK) << WRPROTECT_SHIFT);
+ if (dpo)
+ wv_cdb[1] |= 0x10;
+ if (bytchk)
+ wv_cdb[1] |= ((bytchk & 0x3) << 1);
+
+ sg_put_unaligned_be64(llba, wv_cdb + 2);
+ sg_put_unaligned_be32((uint32_t)num_lb, wv_cdb + 10);
+ wv_cdb[14] = group & GRPNUM_MASK;
+ ret = run_scsi_transaction(sg_fd, wv_cdb, sizeof(wv_cdb), dop, do_len,
+ timeout, noisy, verbose);
+ return ret;
+}
+
+/* Returns file descriptor ( >= 0) if successful. Else a negated sg3_utils
+ * error code is returned. */
+static int
+open_if(const char * fn, int got_stdin)
+{
+ int fd, err;
+
+ if (got_stdin)
+ fd = STDIN_FILENO;
+ else {
+ fd = open(fn, O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ pr2serr(ME "open error: %s: %s\n", fn, safe_strerror(err));
+ return -sg_convert_errno(err);
+ }
+ }
+ if (sg_set_binary_mode(fd) < 0) {
+ perror("sg_set_binary_mode");
+ return -SG_LIB_FILE_ERROR;
+ }
+ return fd;
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool do_16 = false;
+ bool dpo = false;
+ bool first_time;
+ bool given_do_16 = false;
+ bool has_filename = false;
+ bool lba_given = false;
+ bool repeat = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int sg_fd, res, c, n;
+ int bytchk = 0;
+ int group = 0;
+ int ilen = -1;
+ int ifd = -1;
+ int b_p_lb = 512;
+ int ret = 1;
+ int timeout = DEF_TIMEOUT_SECS;
+ int tnum_lb_wr = 0;
+ int verbose = 0;
+ int wrprotect = 0;
+ uint32_t num_lb = 1;
+ uint32_t snum_lb = 1;
+ uint64_t llba = 0;
+ int64_t ll;
+ uint8_t * wvb = NULL;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * free_wrkBuff = NULL;
+ const char * device_name = NULL;
+ const char * ifnp;
+ char cmd_name[32];
+
+ ifnp = ""; /* keep MinGW quiet */
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dg:hi:I:l:n:RSt:w:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ /* Only bytchk=0 and =1 are meaningful for this command in
+ * sbc4r02 (not =2 nor =3) but that may change in the future. */
+ bytchk = sg_get_num(optarg);
+ if ((bytchk < 0) || (bytchk > 3)) {
+ pr2serr("argument to '--bytchk' expected to be 0 to 3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'd':
+ dpo = true;
+ break;
+ case 'g':
+ group = sg_get_num(optarg);
+ if ((group < 0) || (group > 63)) {
+ pr2serr("argument to '--group' expected to be 0 to 63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ ifnp = optarg;
+ has_filename = true;
+ break;
+ case 'I':
+ ilen = sg_get_num(optarg);
+ if (-1 == ilen) {
+ pr2serr("bad argument to '--ilen'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'l':
+ if (lba_given) {
+ pr2serr("must have one and only one '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ ll = sg_get_llnum(optarg);
+ if (ll < 0) {
+ pr2serr("bad argument to '--lba'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ llba = (uint64_t)ll;
+ lba_given = true;
+ break;
+ case 'n':
+ n = sg_get_num(optarg);
+ if (-1 == n) {
+ pr2serr("bad argument to '--num'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ num_lb = (uint32_t)n;
+ break;
+ case 'R':
+ repeat = true;
+ break;
+ case 'S':
+ do_16 = true;
+ given_do_16 = true;
+ break;
+ case 't':
+ timeout = sg_get_num(optarg);
+ if (timeout < 1) {
+ pr2serr("bad argument to '--timeout'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'w':
+ wrprotect = sg_get_num(optarg);
+ if ((wrprotect < 0) || (wrprotect > 7)) {
+ pr2serr("wrprotect (%d) is out of range ( < %d)\n", wrprotect,
+ 7);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (! lba_given) {
+ pr2serr("need a --lba=LBA option\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (repeat) {
+ if (! has_filename) {
+ pr2serr("with '--repeat' need '--in=IF' option\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (ilen < 1) {
+ pr2serr("with '--repeat' need '--ilen=ILEN' option\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ } else {
+ b_p_lb = ilen / num_lb;
+ if (b_p_lb < 64) {
+ pr2serr("calculated %d bytes per logical block, too small\n",
+ b_p_lb);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ ret = sg_convert_errno(-sg_fd);
+ pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ goto err_out;
+ }
+
+ if ((! do_16) && (llba > UINT_MAX))
+ do_16 = true;
+ if ((! do_16) && (num_lb > 0xffff))
+ do_16 = true;
+ snprintf(cmd_name, sizeof(cmd_name), "Write and verify(%d)",
+ (do_16 ? 16 : 10));
+ if (verbose && (! given_do_16) && do_16)
+ pr2serr("Switching to %s because LBA or NUM too large\n", cmd_name);
+ if (verbose) {
+ pr2serr("Issue %s to device %s\n\tilen=%d", cmd_name, device_name,
+ ilen);
+ if (ilen > 0)
+ pr2serr(" [0x%x]", ilen);
+ pr2serr(", lba=%" PRIu64 " [0x%" PRIx64 "]\n\twrprotect=%d, dpo=%d, "
+ "bytchk=%d, group=%d, repeat=%d\n", llba, llba, wrprotect,
+ (int)dpo, bytchk, group, (int)repeat);
+ }
+
+ first_time = true;
+ do {
+ if (first_time) {
+ //If a file with data to write has been provided
+ if (has_filename) {
+ struct stat a_stat;
+
+ if ((1 == strlen(ifnp)) && ('-' == ifnp[0])) {
+ ifd = STDIN_FILENO;
+ ifnp = "<stdin>";
+ if (verbose > 1)
+ pr2serr("Reading input data from stdin\n");
+ } else {
+ ifd = open_if(ifnp, 0);
+ if (ifd < 0) {
+ ret = -ifd;
+ goto err_out;
+ }
+ }
+ if (ilen < 1) {
+ if (fstat(ifd, &a_stat) < 0) {
+ pr2serr("Could not fstat(%s)\n", ifnp);
+ goto err_out;
+ }
+ if (! S_ISREG(a_stat.st_mode)) {
+ pr2serr("Cannot determine IF size, please give "
+ "'--ilen='\n");
+ goto err_out;
+ }
+ ilen = (int)a_stat.st_size;
+ if (ilen < 1) {
+ pr2serr("%s file size too small\n", ifnp);
+ goto err_out;
+ } else if (verbose)
+ pr2serr("Using file size of %d bytes\n", ilen);
+ }
+ if (NULL == (wrkBuff = (uint8_t *)sg_memalign(ilen, 0,
+ &free_wrkBuff, verbose > 3))) {
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ wvb = (uint8_t *)wrkBuff;
+ res = read(ifd, wvb, ilen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", ifnp);
+ goto err_out;
+ }
+ if (res < ilen) {
+ pr2serr("Read only %d bytes (expected %d) from %s\n", res,
+ ilen, ifnp);
+ if (repeat)
+ pr2serr("Will scale subsequent pieces when "
+ "repeat=true, but this is first\n");
+ goto err_out;
+ }
+ } else {
+ if (ilen < 1) {
+ if (verbose)
+ pr2serr("Default write length to %d*%d=%d bytes\n",
+ num_lb, 512, 512 * num_lb);
+ ilen = 512 * num_lb;
+ }
+ if (NULL == (wrkBuff = (uint8_t *)sg_memalign(ilen, 0,
+ &free_wrkBuff, verbose > 3))) {
+ pr2serr(ME "out of memory\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ wvb = (uint8_t *)wrkBuff;
+ /* Not sure about this: default contents to 0xff bytes */
+ memset(wrkBuff, 0xff, ilen);
+ }
+ first_time = false;
+ snum_lb = num_lb;
+ } else { /* repeat=true, first_time=false, must be reading file */
+ llba += snum_lb;
+ res = read(ifd, wvb, ilen);
+ if (res < 0) {
+ pr2serr("Could not read from %s", ifnp);
+ goto err_out;
+ } else {
+ if (verbose > 1)
+ pr2serr("Subsequent read from %s got %d bytes\n", ifnp, res);
+ if (0 == res)
+ break;
+ if (res < ilen) {
+ snum_lb = (uint32_t)(res / b_p_lb);
+ n = res % b_p_lb;
+ if (0 != n)
+ pr2serr(">>> warning: ignoring last %d bytes of %s\n",
+ n, ifnp);
+ if (snum_lb < 1)
+ break;
+ }
+ }
+ }
+ if (do_16)
+ res = sg_ll_write_verify16(sg_fd, wrprotect, dpo, bytchk, llba,
+ snum_lb, group, wvb, ilen, timeout,
+ verbose > 0, verbose);
+ else
+ res = sg_ll_write_verify10(sg_fd, wrprotect, dpo, bytchk,
+ (unsigned int)llba, snum_lb, group,
+ wvb, ilen, timeout, verbose > 0,
+ verbose);
+ ret = res;
+ if (repeat && (0 == ret))
+ tnum_lb_wr += snum_lb;
+ if (ret || (snum_lb != num_lb))
+ break;
+ } while (repeat);
+
+err_out:
+ if (repeat)
+ pr2serr("%d [0x%x] logical blocks written, in total\n", tnum_lb_wr,
+ tnum_lb_wr);
+ if (free_wrkBuff)
+ free(free_wrkBuff);
+ if ((ifd >= 0) && (STDIN_FILENO != ifd))
+ close(ifd);
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ if (ret && (0 == verbose)) {
+ if (! sg_if_can2stderr("sg_write_verify failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_write_x.c b/src/sg_write_x.c
new file mode 100644
index 00000000..89c91258
--- /dev/null
+++ b/src/sg_write_x.c
@@ -0,0 +1,2678 @@
+/*
+ * Copyright (c) 2017-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * The utility can send six variants of the SCSI WRITE command: (normal)
+ * WRITE(16 or 32), WRITE ATOMIC(16 or 32), ORWRITE(16 or 32),
+ * WRITE SAME(16 or 32), WRITE SCATTERED (16 or 32) or WRITE
+ * STREAM(16 or 32).
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+#include <sys/types.h> /* needed for lseek() */
+#include <sys/stat.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_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.31 20220217";
+
+/* Protection Information refers to 8 bytes of extra information usually
+ * associated with each logical block and is often abbreviated to PI while
+ * its fields: reference-tag (4 bytes), application-tag (2 bytes) and
+ * tag-mask (2 bytes) are often abbreviated to RT, AT and TM respectively.
+ * And the LBA Range Descriptor associated with the WRITE SCATTERED command
+ * is abbreviated to RD. A degenerate RD is one where length components,
+ ( and perhaps the LBA, are zero; it is not illegal according to T10 but are
+ * a little tricky to handle when scanning and little extra information
+ * is provided. */
+
+#define ORWRITE16_OP 0x8b
+#define WRITE_16_OP 0x8a
+#define WRITE_ATOMIC16_OP 0x9c
+#define WRITE_SAME16_OP 0x93
+#define SERVICE_ACTION_OUT_16_OP 0x9f /* WRITE SCATTERED (16) uses this */
+#define WRITE_SCATTERED16_SA 0x12
+#define WRITE_STREAM16_OP 0x9a
+#define VARIABLE_LEN_OP 0x7f
+#define ORWRITE32_SA 0xe
+#define WRITE_32_SA 0xb
+#define WRITE_ATOMIC32_SA 0xf
+#define WRITE_SAME_SA 0xd
+#define WRITE_SCATTERED32_SA 0x11
+#define WRITE_STREAM32_SA 0x10
+#define WRITE_X_16_LEN 16
+#define WRITE_X_32_LEN 32
+#define WRITE_X_32_ADD 0x18
+#define RCAP10_RESP_LEN 8
+#define RCAP16_RESP_LEN 32
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT_SECS 120 /* might need more for large NUM */
+#define DEF_WR_NUMBLOCKS 0 /* do nothing; for safety */
+#define DEF_RT 0xffffffff
+#define DEF_AT 0xffff
+#define DEF_TM 0xffff
+#define EBUFF_SZ 256
+
+#define MAX_NUM_ADDR 128
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX ((uint16_t)-1)
+#endif
+
+static struct option long_options[] = {
+ {"32", no_argument, 0, '3'},
+ {"16", no_argument, 0, '6'},
+ {"app-tag", required_argument, 0, 'a'},
+ {"app_tag", required_argument, 0, 'a'},
+ {"atomic", required_argument, 0, 'A'},
+ {"bmop", required_argument, 0, 'B'},
+ {"bs", required_argument, 0, 'b'},
+ {"combined", required_argument, 0, 'c'},
+ {"dld", required_argument, 0, 'D'},
+ {"dpo", no_argument, 0, 'd'},
+ {"dry-run", no_argument, 0, 'x'},
+ {"dry_run", no_argument, 0, 'x'},
+ {"fua", no_argument, 0, 'f'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"generation", required_argument, 0, 'G'},
+ {"help", no_argument, 0, 'h'},
+ {"in", required_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"normal", no_argument, 0, 'N'},
+ {"num", required_argument, 0, 'n'},
+ {"offset", required_argument, 0, 'o'},
+ {"or", no_argument, 0, 'O'},
+ {"quiet", no_argument, 0, 'Q'},
+ {"ref-tag", required_argument, 0, 'r'},
+ {"ref_tag", required_argument, 0, 'r'},
+ {"same", required_argument, 0, 'M'},
+ {"scat-file", required_argument, 0, 'q'},
+ {"scat_file", required_argument, 0, 'q'},
+ {"scat-raw", no_argument, 0, 'R'},
+ {"scat_raw", no_argument, 0, 'R'},
+ {"scattered", required_argument, 0, 'S'},
+ {"stream", required_argument, 0, 'T'},
+ {"strict", no_argument, 0, 's'},
+ {"tag-mask", required_argument, 0, 't'},
+ {"tag_mask", required_argument, 0, 't'},
+ {"timeout", required_argument, 0, 'I'},
+ {"unmap", required_argument, 0, 'u'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrprotect", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_16; /* default when --32 not given */
+ bool do_32;
+ bool do_anchor; /* from --unmap=U_A , bit 1; WRITE SAME */
+ bool do_atomic; /* selects WRITE ATOMIC(16 or 32) */
+ /* --atomic=AB AB --> .atomic_boundary */
+ bool do_combined; /* -c DOF --> .scat_lbdof */
+ bool do_or; /* -O ORWRITE(16 or 32) */
+ bool do_quiet; /* -Q suppress some messages */
+ bool do_scat_raw;
+ bool do_same; /* -M WRITE SAME(16 or 32) */
+ /* --same=NDOB NDOB --> .ndob */
+ bool do_scattered; /* -S WRITE SCATTERED(16 or 32) */
+ /* --scattered=RD RD --> .scat_num_lbard */
+ bool do_stream; /* -T WRITE STREAM(16 or 32) */
+ /* --stream=ID ID --> .str_id */
+ bool do_unmap; /* from --unmap=U_A , bit 0; WRITE SAME */
+ bool do_write_normal; /* -N WRITE (16 or 32) */
+ bool expect_pi_do; /* expect protection information (PI) which
+ * is 8 bytes long following each logical
+ * block in the data out buffer. */
+ bool dpo; /* "Disable Page Out" bit field */
+ bool fua; /* "Force Unit Access" bit field */
+ bool ndob; /* "No Data-Out Buffer" from --same=NDOB */
+ bool verbose_given;
+ bool version_given;
+ int dld; /* "Duration Limit Descriptor" bit mask; bit 0 -->
+ * DLD0, bit 1 --> DLD1, bit 2 --> DLD2
+ * only WRITE(16) and WRITE SCATTERED(16) */
+ int dry_run; /* temporary write when used more than once */
+ int grpnum; /* "Group Number", 0 to 0x3f (GRPNUM_MASK) */
+ int help;
+ int pi_type; /* -1: unknown: 0: type 0 (none): 1: type 1 */
+ int strict; /* > 0, report then exit on questionable meta data */
+ int timeout; /* timeout (in seconds) to abort SCSI commands */
+ int verbose; /* incremented for each -v */
+ int wrprotect; /* is ORPROTECT field for ORWRITE */
+ uint8_t bmop; /* bit mask operators for ORWRITE(32) */
+ uint8_t pgp; /* previous generation processing for ORWRITE(32) */
+ uint16_t app_tag; /* part of protection information (def: 0xffff) */
+ uint16_t atomic_boundary; /* when 0 atomic write spans given length */
+ uint16_t scat_lbdof; /* by construction this must be >= 1 */
+ uint16_t scat_num_lbard; /* RD from --scattered=RD, number of LBA
+ * Range Descriptors */
+ uint16_t str_id; /* (stream ID) is for WRITE STREAM */
+ uint16_t tag_mask; /* part of protection information (def: 0xffff) */
+ uint32_t bs; /* logical block size (def: 0). 0 implies use READ
+ * CAPACITY(10 or 16) to determine */
+ uint32_t bs_pi_do; /* logical block size plus PI, if any. This value is
+ * used as the actual block size */
+ uint32_t if_dlen; /* bytes to read after .if_offset from .if_name,
+ * if 0 given, read rest of .if_name */
+ uint32_t numblocks; /* defaults to 0, number of blocks (of user data) to
+ * write */
+ uint32_t orw_eog; /* from --generation=EOG,NOG (first argument) */
+ uint32_t orw_nog; /* from --generation=EOG,NOG (for ORWRITE) */
+ uint32_t ref_tag; /* part of protection information (def: 0xffffffff) */
+ uint64_t lba; /* "Logical Block Address", for non-scattered use */
+ uint64_t if_offset; /* byte offset in .if_name to start reading */
+ uint64_t tot_lbs; /* from READ CAPACITY */
+ ssize_t xfer_bytes; /* derived value: bs_pi_do * numblocks */
+ /* for WRITE SCATTERED .xfer_bytes < do_len */
+ const char * device_name;
+ const char * if_name; /* from --in=IF */
+ const char * scat_filename; /* from --scat-file=SF */
+ const char * cmd_name; /* e.g. 'Write atomic' */
+ char cdb_name[24]; /* e.g. 'Write atomic(16)' */
+};
+
+static const char * xx_wr_fname = "sg_write_x.bin";
+static const uint32_t lbard_sz = 32;
+static const char * lbard_str = "LBA range descriptor";
+
+
+static void
+usage(int do_help)
+{
+ if (do_help < 2) {
+ pr2serr("Usage:\n"
+ "sg_write_x [--16] [--32] [--app-tag=AT] [--atomic=AB] "
+ "[--bmop=OP,PGP]\n"
+ " [--bs=BS] [--combined=DOF] [--dld=DLD] [--dpo] "
+ "[--dry-run]\n"
+ " [--fua] [--generation=EOG,NOG] [--grpnum=GN] "
+ "[--help] --in=IF\n"
+ " [--lba=LBA,LBA...] [--normal] [--num=NUM,NUM...]\n"
+ " [--offset=OFF[,DLEN]] [--or] [--quiet] "
+ "[--ref-tag=RT]\n"
+ " [--same=NDOB] [--scat-file=SF] [--scat-raw] "
+ "[--scattered=RD]\n"
+ " [--stream=ID] [--strict] [--tag-mask=TM] "
+ "[--timeout=TO]\n"
+ " [--unmap=U_A] [--verbose] [--version] "
+ "[--wrprotect=WRP]\n"
+ " DEVICE\n");
+ if (1 != do_help) {
+ pr2serr("\nOr the corresponding short option usage:\n"
+ "sg_write_x [-6] [-3] [-a AT] [-A AB] [-B OP,PGP] [-b BS] "
+ "[-c DOF] [-D DLD]\n"
+ " [-d] [-x] [-f] [-G EOG,NOG] [-g GN] [-h] -i IF "
+ "[-l LBA,LBA...]\n"
+ " [-N] [-n NUM,NUM...] [-o OFF[,DLEN]] [-O] [-Q] "
+ "[-r RT] [-M NDOB]\n"
+ " [-q SF] [-R] [-S RD] [-T ID] [-s] [-t TM] [-I TO] "
+ "[-u U_A] [-v]\n"
+ " [-V] [-w WPR] DEVICE\n"
+ );
+ pr2serr("\nUse '-h' or '--help' for more help\n");
+ return;
+ }
+ pr2serr(" where:\n"
+ " --16|-6 send 16 byte cdb variant (this is "
+ "default action)\n"
+ " --32|-3 send 32 byte cdb variant of command "
+ "(def: 16 byte)\n"
+ " --app-tag=AT|-a AT expected application tag field "
+ "(def: 0xffff)\n"
+ " --atomic=AB|-A AB send WRITE ATOMIC command with AB "
+ "being its\n"
+ " Atomic Boundary field (0 to 0xffff)\n"
+ " --bmop=OP,PGP|-p OP,PGP set BMOP field to OP and "
+ " Previous\n"
+ " Generation Processing field "
+ "to PGP\n"
+ " --bs=BS|-b BS block size (def: use READ CAPACITY), "
+ "if power of\n"
+ " 2: logical block size, otherwise: "
+ "actual block size\n"
+ " --combined=DOF|-c DOF scatter list and data combined "
+ "for WRITE\n"
+ " SCATTERED, data starting at "
+ "offset DOF which\n"
+ " has units of sizeof(LB+PI); "
+ "sizeof(PI)=8n or 0\n"
+ " --dld=DLD|-D DLD set duration limit descriptor (dld) "
+ "bits (def: 0)\n"
+ " --dpo|-d set DPO (disable page out) field "
+ "(def: clear)\n"
+ " --dry-run|-x exit just before sending SCSI write "
+ "command\n"
+ " --fua|-f set FUA (force unit access) field "
+ "(def: clear)\n"
+ " --generation=EOG,NOG set Expected ORWgeneration field "
+ "to EOG\n"
+ " |-G EOG,NOG and New ORWgeneration field to "
+ "NOG\n"
+ );
+ pr2serr(
+ " --grpnum=GN|-g GN GN is group number field (def: 0, "
+ "range: 0 to 31)\n"
+ " --help|-h use multiple times for different "
+ "usage messages\n"
+ " --in=IF|-i IF IF is file to fetch NUM blocks of "
+ "data from.\n"
+ " Blocks written to DEVICE. 1 or no "
+ "blocks read\n"
+ " in the case of WRITE SAME\n"
+ " --lba=LBA,LBA... list of LBAs (Logical Block Addresses) "
+ "to start\n"
+ " |-l LBA,LBA... writes (def: --lba=0). Alternative is "
+ "--scat-file=SF\n"
+ " --normal|-N send 'normal' WRITE command (default "
+ "when no other\n"
+ " command option given)\n"
+ " --num=NUM,NUM... NUM is number of logical blocks to "
+ "write (def:\n"
+ " |-n NUM,NUM... --num=0). Number of block sent is "
+ "sum of NUMs\n"
+ " --offset=OFF[,DLEN] OFF is byte offset in IF to start "
+ "reading from\n"
+ " |-o OFF[,DLEN] (def: 0), then read DLEN bytes(def: "
+ "rest of IF)\n"
+ " --or|-O send ORWRITE command\n"
+ " --quiet|-Q suppress some informational messages\n"
+ " --ref-tag=RT|-r RT expected reference tag field (def: "
+ "0xffffffff)\n"
+ " --same=NDOB|-M NDOB send WRITE SAME command. NDOB (no "
+ "data out buffer)\n"
+ " can be either 0 (do send buffer) or "
+ "1 (don't)\n"
+ " --scat-file=SF|-q SF file containing LBA, NUM pairs, "
+ "see manpage\n"
+ " --scat-raw|-R read --scat_file=SF as binary (def: "
+ "ASCII hex)\n"
+ " --scattered=RD|-S RD send WRITE SCATTERED command with "
+ "RD range\n"
+ " descriptors (RD can be 0 when "
+ "--combined= given)\n"
+ " --stream=ID|-T ID send WRITE STREAM command with its "
+ "STR_ID\n"
+ " field set to ID\n"
+ " --strict|-s exit if read less than requested from "
+ "IF ;\n"
+ " require variety of WRITE to be given "
+ "as option\n"
+ " --tag-mask=TM|-t TM tag mask field (def: 0xffff)\n"
+ " --timeout=TO|-I TO command timeout (unit: seconds) "
+ "(def: 120)\n"
+ " --unmap=U_A|-u U_A 0 clears both UNMAP and ANCHOR bits "
+ "(default),\n"
+ " 1 sets UNMAP, 2 sets ANCHOR, 3 sets "
+ "both\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n"
+ " --wrprotect=WPR|-w WPR WPR is the WRPROTECT field "
+ "value (def: 0)\n\n"
+ "Performs a SCSI WRITE (normal), ORWRITE, WRITE ATOMIC, WRITE "
+ "SAME, WRITE\nSCATTERED, or WRITE STREAM command. A 16 or 32 "
+ "byte cdb variant can be\nselected. The --in=IF option (data to "
+ "be written) is required apart from\nwhen --same=1 (i.e. when "
+ "NDOB is set). If no WRITE variant option is given\nthen, in "
+ "the absence of --strict, a (normal) WRITE is performed. Only "
+ "WRITE\nSCATTERED uses multiple LBAs and NUMs, or a SF file "
+ "with multiple pairs.\nThe --num=NUM field defaults to 0 (do "
+ "nothing) for safety. Using '-h'\nmultiple times shows the "
+ "applicable options for each command variant.\n"
+ );
+ } else if (2 == do_help) {
+ printf("WRITE ATOMIC (16 or 32) applicable options:\n"
+ " sg_write_x --atomic=AB --in=IF [--16] [--32] [--app-tag=AT] "
+ "[--bs=BS]\n"
+ " [--dpo] [--fua] [--grpnum=GN] [--lba=LBA] "
+ "[--num=NUM]\n"
+ " [--offset=OFF[,DLEN]] [--ref-tag=RT] [--strict] "
+ "[--tag-mask=TM]\n"
+ " [--timeout=TO] [--wrprotect=WRP] DEVICE\n"
+ "\n"
+ "normal WRITE (32) applicable options:\n"
+ " sg_write_x --normal --in=IF --32 [--app-tag=AT] [--bs=BS] "
+ "[--dpo] [--fua]\n"
+ " [--grpnum=GN] [--lba=LBA] [--num=NUM] "
+ "[--offset=OFF[,DLEN]]\n"
+ " [--ref-tag=RT] [--strict] [--tag-mask=TM] "
+ "[--timeout=TO]\n"
+ " [--wrprotect=WRP] DEVICE\n"
+ "\n"
+ "normal WRITE (16) applicable options:\n"
+ " sg_write_x --normal --in=IF [--16] [--bs=BS] [--dld=DLD] "
+ "[--dpo] [--fua]\n"
+ " [--grpnum=GN] [--lba=LBA] [--num=NUM] "
+ "[--offset=OFF[,DLEN]]\n"
+ " [--strict] [--timeout=TO] [--verbose] "
+ "[--wrprotect=WRP] DEVICE\n"
+ "\n"
+ "ORWRITE (32) applicable options:\n"
+ " sg_write_x --or --in=IF --32 [--bmop=OP,PGP] [--bs=BS] "
+ "[--dpo] [--fua]\n"
+ " [--generation=EOG,NOG] [--grpnum=GN] [--lba=LBA] "
+ "[--num=NUM]\n"
+ " [--offset=OFF{,DLEN]] [--strict] [--timeout=TO]\n"
+ " [--wrprotect=ORP] DEVICE\n"
+ "\n"
+ "ORWRITE (16) applicable options:\n"
+ " sg_write_x --or --in=IF [--16] [--bs=BS] [--dpo] [--fua] "
+ "[--grpnum=GN]\n"
+ " [--lba=LBA] [--num=NUM] [--offset=OFF[,DLEN]] "
+ "[--strict]\n"
+ " [--timeout=TO] [--wrprotect=ORP] DEVICE\n"
+ "\n"
+ );
+ } else if (3 == do_help) {
+ printf("WRITE SAME (32) applicable options:\n"
+ " sg_write_x --same=NDOB --32 [--app-tag=AT] [--bs=BS] "
+ "[--grpnum=GN]\n"
+ " [--in=IF] [--lba=LBA] [--num=NUM] "
+ "[--offset=OFF[,DLEN]]\n"
+ " [--ref-tag=RT] [--strict] [--tag-mask=TM] "
+ "[--timeout=TO]\n"
+ " [--unmap=U_A] [--wrprotect=WRP] DEVICE\n"
+ "\n"
+ "WRITE SCATTERED (32) applicable options:\n"
+ " sg_write_x --scattered --in=IF --32 [--app-tag=AT] "
+ "[--bs=BS]\n"
+ " [--combined=DOF] [--dpo] [--fua] [--grpnum=GN]\n"
+ " [--lba=LBA,LBA...] [--num=NUM,NUM...] "
+ "[--offset=OFF[,DLEN]]\n"
+ " [--ref-tag=RT] [--scat-file=SF] [--scat-raw] "
+ "[--strict]\n"
+ " [--tag-mask=TM] [--timeout=TO] [--wrprotect=WRP] "
+ "DEVICE\n"
+ "\n"
+ "WRITE SCATTERED (16) applicable options:\n"
+ " sg_write_x --scattered --in=IF [--bs=BS] [--combined=DOF] "
+ "[--dld=DLD]\n"
+ " [--dpo] [--fua] [--grpnum=GN] [--lba=LBA,LBA...]\n"
+ " [--num=NUM,NUM...] [--offset=OFF[,DLEN]] "
+ "[--scat-raw]\n"
+ " [--scat-file=SF] [--strict] [--timeout=TO] "
+ "[--wrprotect=WRP]\n"
+ " DEVICE\n"
+ "\n"
+ "WRITE STREAM (32) applicable options:\n"
+ " sg_write_x --stream=ID --in=IF --32 [--app-tag=AT] "
+ "[--bs=BS] [--dpo]\n"
+ " [--fua] [--grpnum=GN] [--lba=LBA] [--num=NUM]\n"
+ " [--offset=OFF[,DLEN]] [--ref-tag=RT] [--strict] "
+ "[--tag-mask=TM]\n"
+ " [--timeout=TO] [--verbose] [--wrprotect=WRP] "
+ "DEVICE\n"
+ "\n"
+ "WRITE STREAM (16) applicable options:\n"
+ " sg_write_x --stream=ID --in=IF [--16] [--bs=BS] [--dpo] "
+ "[--fua]\n"
+ " [--grpnum=GN] [--lba=LBA] [--num=NUM] "
+ "[--offset=OFF[,DLEN]]\n"
+ " [--strict] [--timeout=TO] [--wrprotect=WRP] "
+ "DEVICE\n"
+ "\n"
+ );
+ } else {
+ printf("Notes:\n"
+ " - all 32 byte cdb variants, apart from ORWRITE(32), need type "
+ "1, 2, or 3\n"
+ " protection information active on the DEVICE\n"
+ " - all commands can take one or more --verbose (-v) options "
+ "and/or the\n"
+ " --dry-run option\n"
+ " - all WRITE X commands will accept --scat-file=SF and "
+ "optionally --scat-raw\n"
+ " options but only the first lba,num pair is used (any "
+ "more are ignored)\n"
+ " - when '--rscat-aw --scat-file=SF' are used then the binary "
+ "format expected in\n"
+ " SF is as defined for the WRITE SCATTERED commands. "
+ "That is 32 bytes\n"
+ " of zeros followed by the first LBA range descriptor "
+ "followed by the\n"
+ " second LBA range descriptor, etc. Each LBA range "
+ "descriptor is 32 bytes\n"
+ " long with an 8 byte LBA at offset 0 and a 4 byte "
+ "number_of_logical_\n"
+ " blocks at offset 8 (both big endian). The 'pad' following "
+ "the last LBA\n"
+ " range descriptor does not need to be given\n"
+ " - WRITE SCATTERED(32) additionally has expected initial "
+ "LB reference tag,\n"
+ " application tag and LB application tag mask fields in the "
+ "LBA range\n"
+ " descriptor. If --strict is given then all reserved fields "
+ "are checked\n"
+ " for zeros, an error is generated for non zero bytes.\n"
+ " - when '--lba=LBA,LBA...' is used on commands other than "
+ "WRITE SCATTERED\n"
+ " then only the first LBA value is used.\n"
+ " - when '--num=NUM,NUM...' is used on commands other than "
+ "WRITE SCATTERED\n"
+ " then only the first NUM value is used.\n"
+ " - whenever '--lba=LBA,LBA...' is used then "
+ "'--num=NUM,NUM...' should\n"
+ " also be used. Also they should have the same number of "
+ "elements.\n"
+ );
+ }
+}
+
+/* Returns 0 if successful, else sg3_utils error code. */
+static int
+bin_read(int fd, uint8_t * up, uint32_t len, const char * fname)
+{
+ int res, err;
+
+ res = read(fd, up, len);
+ if (res < 0) {
+ err = errno;
+ pr2serr("Error doing read of %s file: %s\n", fname,
+ safe_strerror(err));
+ return sg_convert_errno(err);
+ }
+ if ((uint32_t)res < len) {
+ pr2serr("Short (%u) read of %s file, wanted %u\n", (unsigned int)res,
+ fname, len);
+ return SG_LIB_FILE_ERROR;
+ }
+ return 0;
+}
+
+/* Returns true if num_of_f_chars of ASCII 'f' or 'F' characters are found
+ * in sequence. Any leading "0x" or "0X" is ignored; otherwise false is
+ * returned (and the comparison stops when the first mismatch is found).
+ * For example a sequence of 'f' characters in a null terminated C string
+ * that is two characters shorter than the requested num_of_f_chars will
+ * compare the null character in the string with 'f', find them unequal,
+ * stop comparing and return false. */
+static bool
+all_ascii_f_s(const char * cp, int num_of_f_chars)
+{
+ if ((NULL == cp) || (num_of_f_chars < 1))
+ return false; /* define degenerate cases */
+ if (('0' == cp[0]) && (('x' == cp[1]) || ('X' == cp[1])))
+ cp += 2;
+ for ( ; num_of_f_chars >= 0 ; --num_of_f_chars, ++cp) {
+ if ('F' != toupper((uint8_t)*cp))
+ return false;
+ }
+ return true;
+}
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. */
+static int
+build_lba_arr(const char * inp, uint64_t * lba_arr, uint32_t * lba_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ int64_t ll;
+ const char * lcp;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == lba_arr) ||
+ (NULL == lba_arr_len))
+ return 1;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *lba_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ pr2serr("'--lba' cannot be read from stdin\n");
+ return 1;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ pr2serr("build_lba_arr: error at pos %d\n", k + 1);
+ return 1;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ lba_arr[k] = (uint64_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("build_lba_arr: error at pos %d\n",
+ (int)(lcp - inp + 1));
+ return 1;
+ }
+ }
+ *lba_arr_len = (uint32_t)(k + 1);
+ if (k == max_arr_len) {
+ pr2serr("build_lba_arr: array length exceeded\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Read numbers (up to 32 bits in size) from command line (comma (or
+ * (single) space) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, else a sg3_utils error code is returned. */
+static int
+build_num_arr(const char * inp, uint32_t * num_arr, uint32_t * num_arr_len,
+ int max_arr_len)
+{
+ int in_len, k;
+ const char * lcp;
+ int64_t ll;
+ char * cp;
+ char * c2p;
+
+ if ((NULL == inp) || (NULL == num_arr) ||
+ (NULL == num_arr_len))
+ return SG_LIB_LOGIC_ERROR;
+ lcp = inp;
+ in_len = strlen(inp);
+ if (0 == in_len)
+ *num_arr_len = 0;
+ if ('-' == inp[0]) { /* read from stdin */
+ pr2serr("'--len' cannot be read from stdin\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < max_arr_len; ++k) {
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ if (ll > UINT32_MAX) {
+ pr2serr("%s: number exceeds 32 bits at pos %d\n",
+ __func__, (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ num_arr[k] = (uint32_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp)
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - inp + 1));
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ *num_arr_len = (uint32_t)(k + 1);
+ if (k == max_arr_len) {
+ pr2serr("%s: array length exceeded\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+/* Tries to parse LBA,NUM[,RT,AP,TM] on one line, comma separated. Returns
+ * 0 if parsed ok, else 999 if nothing parsed, else error (currently always
+ * SG_LIB_SYNTAX_ERROR). If protection information fields not given, then
+ * default values are given (i.e. all 0xff bytes). Ignores all spaces and
+ * tabs and everything after '#' on lcp (assumed to be an ASCII line that
+ * is null terminated). If successful and 'up' is non NULL then writes a
+ * LBA range descriptor starting at 'up'. */
+static int
+parse_scat_pi_line(const char * lcp, uint8_t * up, uint32_t * sum_num)
+{
+ bool ok;
+ int k;
+ int64_t ll;
+ const char * cp;
+ const char * bp;
+ char c[1024];
+
+ bp = c;
+ cp = strchr(lcp, '#');
+ lcp += strspn(lcp, " \t");
+ if (('\0' == *lcp) || (cp && (lcp >= cp)))
+ return 999; /* blank line or blank prior to first '#' */
+ if (cp) { /* copy from first non whitespace ... */
+ memcpy(c, lcp, cp - lcp); /* ... to just prior to first '#' */
+ c[cp - lcp] = '\0';
+ } else {
+ /* ... to end of line, including null */
+ snprintf(c, sizeof(c), "%s", lcp);
+ }
+ ll = sg_get_llnum(bp);
+ ok = ((-1 != ll) || all_ascii_f_s(bp, 16));
+ if (! ok) {
+ pr2serr("%s: error reading LBA (first) item on ", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (up)
+ sg_put_unaligned_be64((uint64_t)ll, up + 0);
+ ok = false;
+ cp = strchr(bp, ',');
+ if (cp) {
+ bp = cp + 1;
+ if (*bp) {
+ ll = sg_get_llnum(bp);
+ if (-1 != ll)
+ ok = true;
+ }
+ }
+ if ((! ok) || (ll > UINT32_MAX)) {
+ pr2serr("%s: error reading NUM (second) item on ", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (up)
+ sg_put_unaligned_be32((uint32_t)ll, up + 8);
+ if (sum_num)
+ *sum_num += (uint32_t)ll;
+ /* now for 3 PI items */
+ for (k = 0; k < 3; ++k) {
+ ok = true;
+ cp = strchr(bp, ',');
+ if (NULL == cp)
+ break;
+ bp = cp + 1;
+ if (*bp) {
+ cp += strspn(bp, " \t");
+ if ('\0' == *cp)
+ break;
+ else if (',' == *cp) {
+ if (0 == k)
+ ll = DEF_RT;
+ else
+ ll = DEF_AT; /* DEF_AT and DEF_TM have same value */
+ } else {
+ ll = sg_get_llnum(bp);
+ if (-1 == ll)
+ ok = false;
+ }
+ }
+ if (! ok) {
+ pr2serr("%s: error reading item %d NUM item on ", __func__,
+ k + 3);
+ break;
+ }
+ switch (k) {
+ case 0:
+ if (ll > UINT32_MAX) {
+ pr2serr("%s: error with item 3, >0xffffffff; on ", __func__);
+ ok = false;
+ } else if (up)
+ sg_put_unaligned_be32((uint32_t)ll, up + 12);
+ break;
+ case 1:
+ if (ll > UINT16_MAX) {
+ pr2serr("%s: error with item 4, >0xffff; on ", __func__);
+ ok = false;
+ } else if (up)
+ sg_put_unaligned_be16((uint16_t)ll, up + 16);
+ break;
+ case 2:
+ if (ll > UINT16_MAX) {
+ pr2serr("%s: error with item 5, >0xffff; on ", __func__);
+ ok = false;
+ } else if (up)
+ sg_put_unaligned_be16((uint16_t)ll, up + 18);
+ break;
+ }
+ if (! ok)
+ break;
+ }
+ if (! ok)
+ return SG_LIB_SYNTAX_ERROR;
+ for ( ; k < 3; ++k) {
+ switch (k) {
+ case 0:
+ if (up)
+ sg_put_unaligned_be32((uint32_t)DEF_RT, up + 12);
+ break;
+ case 1:
+ if (up)
+ sg_put_unaligned_be16((uint16_t)DEF_AT, up + 16);
+ break;
+ case 2:
+ if (up)
+ sg_put_unaligned_be16((uint16_t)DEF_TM, up + 18);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Read pairs or quintets from a scat_file and places them in a T10 scatter
+ * list array is built starting at at t10_scat_list_out (i.e. as per T10 the
+ * first 32 bytes are zeros followed by the first LBA range descriptor (also
+ * 32 bytes long) then the second LBA range descriptor, etc. The pointer
+ * t10_scat_list_out may be NULL in which case the T10 list array is not
+ * built but all other operations take place; this can be useful for sizing
+ * how large the area holding that list needs to be. The max_list_blen may
+ * also be 0. If do_16 is true then only LBA,NUM pairs are expected,
+ * loosely formatted with numbers found alternating between LBA and NUM, with
+ * an even number of elements required overall. If do_16 is false then a
+ * stricter format for quintets is expected: each non comment line should
+ * contain: LBA,NUM[,RT,AT,TM] . If RT,AT,TM are not given then they assume
+ * their defaults (i.e. 0xffffffff, 0xffff, 0xffff). Each number (64 bits for
+ * the LBA, 32 bits for NUM and RT, 16 bit for AT and TM) may be a comma,
+ * space or tab separated list. Assumed decimal unless prefixed by '0x', '0X'
+ * or contains trailing 'h' or 'H' (which indicate hex). Returns 0 if ok,
+ * else error number. If ok also yields the number of LBA range descriptors
+ * written in num_scat_elems and the sum of NUM elements found. Note that
+ * sum_num is not initialized to 0. If parse_one is true then exits
+ * after one LBA range descriptor is decoded. */
+static int
+build_t10_scat(const char * scat_fname, bool do_16, bool parse_one,
+ uint8_t * t10_scat_list_out, uint16_t * num_scat_elems,
+ uint32_t * sum_num, uint32_t max_list_blen)
+{
+ bool have_stdin = false;
+ bool del_fp = false;
+ bool bit0, ok;
+ int off = 0;
+ int in_len, k, j, m, n, res, err;
+ int64_t ll;
+ char * lcp;
+ uint8_t * up = t10_scat_list_out;
+ FILE * fp = NULL;
+ char line[1024];
+
+ if (up) {
+ if (max_list_blen < 64) {
+ pr2serr("%s: t10_scat_list_out is too short\n", __func__);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ memset(up, 0, max_list_blen);
+ }
+ n = lbard_sz;
+
+ have_stdin = ((1 == strlen(scat_fname)) && ('-' == scat_fname[0]));
+ if (have_stdin) {
+ fp = stdin;
+ scat_fname = "<stdin>";
+ } else {
+ fp = fopen(scat_fname, "r");
+ if (NULL == fp) {
+ err = errno;
+ pr2serr("%s: unable to open %s: %s\n", __func__, scat_fname,
+ safe_strerror(err));
+ return sg_convert_errno(err);
+ }
+ del_fp = true;
+ }
+ for (j = 0; j < 1024; ++j) {/* loop over lines in file */
+ if ((max_list_blen > 0) && ((n + lbard_sz) > max_list_blen))
+ goto fini;
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ // could improve with carry_over logic if sizeof(line) too small
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ }
+ }
+ if (in_len < 1)
+ continue;
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp) /* Comment? If so skip rest of line */
+ continue;
+ k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP ,\t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ pr2serr("%s: syntax error in %s at line %d, pos %d\n",
+ __func__, scat_fname, j + 1, m + k + 1);
+ goto bad_exit;
+ }
+ if (! do_16) {
+ res = parse_scat_pi_line(lcp, up ? (up + n) : up, sum_num);
+ if (999 == res)
+ ;
+ else if (0 == res) {
+ n += lbard_sz;
+ if (parse_one)
+ goto fini;
+ } else {
+ if (SG_LIB_CAT_NOT_READY == res)
+ goto bad_mem_exit;
+ pr2serr("line %d in %s\n", j + 1, scat_fname);
+ goto bad_exit;
+ }
+ continue;
+ }
+ for (k = 0; k < 1024; ++k) {
+ ll = sg_get_llnum(lcp);
+ ok = ((-1 != ll) || all_ascii_f_s(lcp, 16));
+ if (ok) {
+ bit0 = !! (0x1 & (off + k));
+ if (bit0) {
+ if (ll > UINT32_MAX) {
+ pr2serr("%s: number exceeds 32 bits in line %d, at "
+ "pos %d of %s\n", __func__, j + 1,
+ (int)(lcp - line + 1), scat_fname);
+ goto bad_exit;
+ }
+ if (up)
+ sg_put_unaligned_be32((uint32_t)ll, up + n + 8);
+ if (sum_num)
+ *sum_num += (uint32_t)ll;
+ n += lbard_sz; /* skip to next LBA range descriptor */
+ if (parse_one)
+ goto fini;
+ } else {
+ if (up)
+ sg_put_unaligned_be64((uint64_t)ll, up + n + 0);
+ }
+ lcp = strpbrk(lcp, " ,\t");
+ if (NULL == lcp)
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } else { /* no valid number found */
+ if ('#' == *lcp) {
+ --k;
+ break;
+ }
+ pr2serr("%s: error on line %d, at pos %d\n", __func__, j + 1,
+ (int)(lcp - line + 1));
+ goto bad_exit;
+ }
+ } /* inner for loop(k) over line elements */
+ off += (k + 1);
+ } /* outer for loop(j) over lines */
+ if (do_16 && (0x1 & off)) {
+ pr2serr("%s: expect LBA,NUM pairs but decoded odd number\n from "
+ "%s\n", __func__, scat_fname);
+ goto bad_exit;
+ }
+fini:
+ *num_scat_elems = (n / lbard_sz) - 1;
+ if (del_fp)
+ fclose(fp);
+ return 0;
+bad_exit:
+ if (del_fp)
+ fclose(fp);
+ return SG_LIB_SYNTAX_ERROR;
+bad_mem_exit:
+ if (del_fp)
+ fclose(fp);
+ return SG_LIB_CAT_NOT_READY; /* flag output buffer too small */
+}
+
+static bool
+is_pi_default(const struct opts_t * op)
+{
+ return ((DEF_AT == op->app_tag) && (DEF_RT == op->ref_tag) &&
+ (DEF_TM == op->tag_mask));
+}
+
+/* Given a t10 parameter list header (32 zero bytes) for WRITE SCATTERED
+ * (16 or 32) followed by n RDs with a total length of at least
+ * max_lbrds_blen bytes, find "n" and increment where num_lbard points
+ * n times. Further get the LBA length component from each RD and add each
+ * length into where sum_num points. Note: the caller probably wants to zero
+ * where num_lbard and sum_num point before invoking this function. If all
+ * goes well return true, else false. If a degenerate RD is detected then
+ * if 'RD' (from --scattered=RD) is 0 then stop looking for further RDs;
+ * otherwise keep going. Currently overlapping LBA range descriptors are no
+ * checked for. If op->strict > 0 then the first 32 bytes are checked for
+ * zeros; any non-zero bytes will report to stderr, stop the check and
+ * return false. If op->strict > 0 then the trailing 20 or 12 bytes (only
+ * 12 if RT, AT and TM fields (for PI) are present) are checked for zeros;
+ * any non-zero bytes cause the same action as the previous check. If
+ * the number of RDs (when 'RD' from --scattered=RD > 0) is greater than
+ * the number of RDs found then a report is sent to stderr and if op->strict
+ * > 0 then returns false, else returns true. */
+static bool
+check_lbrds(const uint8_t * up, uint32_t max_lbrds_blen,
+ const struct opts_t * op, uint16_t * num_lbard,
+ uint32_t * sum_num)
+{
+ bool ok;
+ int k, j, n;
+ const int max_lbrd_start = max_lbrds_blen - lbard_sz;
+ int vb = op->verbose;
+
+ if (op->strict) {
+ if (max_lbrds_blen < lbard_sz) {
+ pr2serr("%s: %ss too short (%d < 32)\n", __func__, lbard_str,
+ max_lbrds_blen);
+ return false;
+ }
+ if (! sg_all_zeros(up, lbard_sz)) {
+ pr2serr("%s: first 32 bytes of WRITE SCATTERED data-out buffer "
+ "should be zero.\nFound non-zero byte.\n", __func__);
+ return false;
+ }
+ }
+ if (max_lbrds_blen < (2 * lbard_sz)) {
+ *num_lbard = 0;
+ return true;
+ }
+ n = op->scat_num_lbard ? (int)op->scat_num_lbard : -1;
+ for (k = lbard_sz, j = 0; k < max_lbrd_start; k += lbard_sz, ++j) {
+ if ((n < 0) && sg_all_zeros(up + k + 0, 12)) { /* degenerate LBA */
+ if (vb) /* ... range descriptor terminator if --scattered=0 */
+ pr2serr("%s: degenerate %s stops scan at k=%d (num_rds=%d)\n",
+ __func__, lbard_str, k, j);
+ break;
+ }
+ *sum_num += sg_get_unaligned_be32(up + k + 8);
+ *num_lbard += 1;
+ if (op->strict) {
+ ok = true;
+ if (op->wrprotect) {
+ if (! sg_all_zeros(up + k + 20, 12))
+ ok = false;
+ } else if (! sg_all_zeros(up + k + 12, 20))
+ ok = false;
+ if (! ok) {
+ pr2serr("%s: %s %d non zero in reserved fields\n", __func__,
+ lbard_str, (k / lbard_sz) - 1);
+ return false;
+ }
+ }
+ if (n >= 0) {
+ if (--n <= 0)
+ break;
+ }
+ }
+ if ((k < max_lbrd_start) && op->strict) { /* check pad all zeros */
+ k += lbard_sz;
+ j = max_lbrds_blen - k;
+ if (! sg_all_zeros(up + k, j)) {
+ pr2serr("%s: pad (%d bytes) following %ss is non zero\n",
+ __func__, j, lbard_str);
+ return false;
+ }
+ }
+ if (vb > 2)
+ pr2serr("%s: about to return true, num_lbard=%u, sum_num=%u "
+ "[k=%d, n=%d]\n", __func__, *num_lbard, *sum_num, k, n);
+ return true;
+}
+
+static int
+sum_num_lbards(const uint8_t * up, int num_lbards)
+{
+ int sum = 0;
+ int k, n;
+
+ for (k = 0, n = lbard_sz; k < num_lbards; ++k, n += lbard_sz)
+ sum += sg_get_unaligned_be32(up + n + 8);
+ return sum;
+}
+
+/* Returns 0 if successful, else sg3_utils error code. */
+static int
+do_write_x(int sg_fd, const void * dataoutp, int dout_len,
+ const struct opts_t * op)
+{
+ int k, ret, res, sense_cat, cdb_len, vb, err;
+ uint8_t x_cdb[WRITE_X_32_LEN]; /* use for both lengths */
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_pt_base * ptvp;
+
+ memset(x_cdb, 0, sizeof(x_cdb));
+ vb = op->verbose;
+ cdb_len = op->do_16 ? WRITE_X_16_LEN : WRITE_X_32_LEN;
+ if (16 == cdb_len) {
+ if (! op->do_scattered)
+ sg_put_unaligned_be64(op->lba, x_cdb + 2);
+ x_cdb[14] = (op->grpnum & GRPNUM_MASK);
+ } else {
+ x_cdb[0] = VARIABLE_LEN_OP;
+ x_cdb[6] = (op->grpnum & GRPNUM_MASK);
+ x_cdb[7] = WRITE_X_32_ADD;
+ if (! op->do_scattered)
+ sg_put_unaligned_be64(op->lba, x_cdb + 12);
+ }
+ if (op->do_write_normal) {
+ if (16 == cdb_len) {
+ x_cdb[0] = WRITE_16_OP;
+ x_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[1] |= 0x10;
+ if (op->fua)
+ x_cdb[1] |= 0x8;
+ if (op->dld) {
+ if (op->dld & 1)
+ x_cdb[14] |= 0x40;
+ if (op->dld & 2)
+ x_cdb[14] |= 0x80;
+ if (op->dld & 4)
+ x_cdb[1] |= 0x1;
+ }
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 10);
+ } else { /* 32 byte WRITE */
+ sg_put_unaligned_be16((uint16_t)WRITE_32_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[10] |= 0x10;
+ if (op->fua)
+ x_cdb[10] |= 0x8;
+ if (op->dld) { /* added in sbc4r19 */
+ if (op->dld & 1)
+ x_cdb[11] |= 0x1;
+ if (op->dld & 2)
+ x_cdb[11] |= 0x2;
+ if (op->dld & 4)
+ x_cdb[11] |= 0x4;
+ }
+ sg_put_unaligned_be32(op->ref_tag, x_cdb + 20);
+ sg_put_unaligned_be16(op->app_tag, x_cdb + 24);
+ sg_put_unaligned_be16(op->tag_mask, x_cdb + 26);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ }
+ } else if (op->do_atomic) {
+ if (16 == cdb_len) {
+ if (op->numblocks > UINT16_MAX) {
+ pr2serr("Need WRITE ATOMIC(32) since blocks exceed 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ x_cdb[0] = WRITE_ATOMIC16_OP;
+ x_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[1] |= 0x10;
+ if (op->fua)
+ x_cdb[1] |= 0x8;
+ sg_put_unaligned_be16(op->atomic_boundary, x_cdb + 10);
+ sg_put_unaligned_be16((uint16_t)op->numblocks, x_cdb + 12);
+ } else { /* 32 byte WRITE ATOMIC */
+ sg_put_unaligned_be16(op->atomic_boundary, x_cdb + 4);
+ sg_put_unaligned_be16((uint16_t)WRITE_ATOMIC32_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[10] |= 0x10;
+ if (op->fua)
+ x_cdb[10] |= 0x8;
+ sg_put_unaligned_be32(op->ref_tag, x_cdb + 20);
+ sg_put_unaligned_be16(op->app_tag, x_cdb + 24);
+ sg_put_unaligned_be16(op->tag_mask, x_cdb + 26);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ }
+ } else if (op->do_or) { /* ORWRITE(16 or 32) */
+ if (16 == cdb_len) {
+ x_cdb[0] = ORWRITE16_OP;
+ x_cdb[1] = ((op->wrprotect & 0x7) << 5); /* actually ORPROTECT */
+ if (op->dpo)
+ x_cdb[1] |= 0x10;
+ if (op->fua)
+ x_cdb[1] |= 0x8;
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 10);
+ } else {
+ x_cdb[2] = op->bmop;
+ x_cdb[3] = op->pgp;
+ sg_put_unaligned_be16((uint16_t)ORWRITE32_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[10] |= 0x10;
+ if (op->fua)
+ x_cdb[10] |= 0x8;
+ sg_put_unaligned_be32(op->orw_eog, x_cdb + 20);
+ sg_put_unaligned_be32(op->orw_nog, x_cdb + 24);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ }
+ } else if (op->do_same) {
+ if (16 == cdb_len) {
+ x_cdb[0] = WRITE_SAME16_OP;
+ x_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ if (op->do_anchor)
+ x_cdb[1] |= 0x10;
+ if (op->do_unmap)
+ x_cdb[1] |= 0x8;
+ if (op->ndob)
+ x_cdb[1] |= 0x1;
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 10);
+ } else {
+ sg_put_unaligned_be16((uint16_t)WRITE_SAME_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->do_anchor)
+ x_cdb[10] |= 0x10;
+ if (op->do_unmap)
+ x_cdb[10] |= 0x8;
+ if (op->ndob)
+ x_cdb[10] |= 0x1;
+ /* Expected initial logical block reference tag */
+ sg_put_unaligned_be32(op->ref_tag, x_cdb + 20);
+ sg_put_unaligned_be16(op->app_tag, x_cdb + 24);
+ sg_put_unaligned_be16(op->tag_mask, x_cdb + 26);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ }
+ } else if (op->do_scattered) {
+ if (16 == cdb_len) {
+ x_cdb[0] = SERVICE_ACTION_OUT_16_OP;
+ x_cdb[1] = WRITE_SCATTERED16_SA;
+ x_cdb[2] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[2] |= 0x10;
+ if (op->fua)
+ x_cdb[2] |= 0x8;
+ if (op->dld) {
+ if (op->dld & 1)
+ x_cdb[14] |= 0x40;
+ if (op->dld & 2)
+ x_cdb[14] |= 0x80;
+ if (op->dld & 4)
+ x_cdb[2] |= 0x1;
+ }
+ sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 4);
+ sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 8);
+ /* Spec says Buffer Transfer Length field (BTL) is the number
+ * of (user) Logical Blocks in the data-out buffer and that BTL
+ * may be 0. So the total data-out buffer length in bytes is:
+ * (scat_lbdof + numblocks) * actual_block_size */
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 10);
+ } else {
+ sg_put_unaligned_be16((uint16_t)WRITE_SCATTERED32_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[10] |= 0x10;
+ if (op->fua)
+ x_cdb[10] |= 0x8;
+ sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 12);
+ sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 16);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ /* ref_tag, app_tag and tag_mask placed in scatter list */
+ }
+ } else if (op->do_stream) {
+ if (16 == cdb_len) {
+ x_cdb[0] = WRITE_STREAM16_OP;
+ x_cdb[1] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[1] |= 0x10;
+ if (op->fua)
+ x_cdb[1] |= 0x8;
+ sg_put_unaligned_be16(op->str_id, x_cdb + 10);
+ sg_put_unaligned_be16((uint16_t)op->numblocks, x_cdb + 12);
+ } else {
+ sg_put_unaligned_be16(op->str_id, x_cdb + 4);
+ sg_put_unaligned_be16((uint16_t)WRITE_STREAM32_SA, x_cdb + 8);
+ x_cdb[10] = ((op->wrprotect & 0x7) << 5);
+ if (op->dpo)
+ x_cdb[10] |= 0x10;
+ if (op->fua)
+ x_cdb[10] |= 0x8;
+ sg_put_unaligned_be32(op->ref_tag, x_cdb + 20);
+ sg_put_unaligned_be16(op->app_tag, x_cdb + 24);
+ sg_put_unaligned_be16(op->tag_mask, x_cdb + 26);
+ sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+ }
+ } else {
+ pr2serr("%s: bad cdb name or length (%d)\n", __func__, cdb_len);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (vb > 1) {
+ char b[128];
+
+ pr2serr(" %s cdb: %s\n", op->cdb_name,
+ sg_get_command_str(x_cdb, cdb_len, false, sizeof(b), b));
+ }
+ if (op->do_scattered && (vb > 2) && (dout_len > 31)) {
+ uint32_t sod_off = op->bs_pi_do * op->scat_lbdof;
+ const uint8_t * up = (const uint8_t *)dataoutp;
+
+ pr2serr(" %s scatter list, number of %ss: %u\n", op->cdb_name,
+ lbard_str, op->scat_num_lbard);
+ pr2serr(" byte offset of data_to_write: %u, dout_len: %d\n",
+ sod_off, dout_len);
+ up += lbard_sz; /* step over parameter list header */
+ for (k = 0; k < (int)op->scat_num_lbard; ++k, up += lbard_sz) {
+ pr2serr(" desc %d: LBA=0x%" PRIx64 " numblocks=%" PRIu32
+ "%s", k, sg_get_unaligned_be64(up + 0),
+ sg_get_unaligned_be32(up + 8), (op->do_16 ? "\n" : " "));
+ if (op->do_32)
+ pr2serr("rt=0x%x at=0x%x tm=0x%x\n",
+ sg_get_unaligned_be32(up + 12),
+ sg_get_unaligned_be16(up + 16),
+ sg_get_unaligned_be16(up + 18));
+ if ((uint32_t)(((k + 2) * lbard_sz) + 20) > sod_off) {
+ pr2serr("Warning: possible clash of descriptor %u with "
+ "data_to_write\n", k);
+ if (op->strict > 1)
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ }
+ if ((vb > 3) && (dout_len > 0)) {
+ if ((dout_len > 1024) && (vb < 7)) {
+ pr2serr(" Data-out buffer contents (first 1024 of %u "
+ "bytes):\n", dout_len);
+ hex2stdout((const uint8_t *)dataoutp, 1024, 1);
+ pr2serr(" Above: dout's first 1024 of %u bytes [%s]\n",
+ dout_len, op->cdb_name);
+ } else {
+ pr2serr(" Data-out buffer contents (length=%u):\n", dout_len);
+ hex2stderr((const uint8_t *)dataoutp, (int)dout_len, 1);
+ }
+ }
+ if (op->dry_run) {
+ if (vb)
+ pr2serr("Exit just before sending %s due to --dry-run\n",
+ op->cdb_name);
+ if (op->dry_run > 1) {
+ int w_fd;
+
+ w_fd = open(xx_wr_fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (w_fd < 0) {
+ err = errno;
+ perror(xx_wr_fname);
+ return sg_convert_errno(err);
+ }
+ res = write(w_fd, dataoutp, dout_len);
+ if (res < 0) {
+ err = errno;
+ perror(xx_wr_fname);
+ close(w_fd);
+ return sg_convert_errno(err);
+ }
+ close(w_fd);
+ printf("Wrote %u bytes to %s", dout_len, xx_wr_fname);
+ if (op->do_scattered)
+ printf(", LB data offset: %u\nNumber of %ss: %u\n",
+ op->scat_lbdof, lbard_str, op->scat_num_lbard);
+ else
+ printf("\n");
+ }
+ return 0;
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", op->cdb_name);
+ return sg_convert_errno(ENOMEM);
+ }
+ set_scsi_pt_cdb(ptvp, x_cdb, cdb_len);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ if (dout_len > 0)
+ set_scsi_pt_data_out(ptvp, (uint8_t *)dataoutp, dout_len);
+ else if (vb && (! op->ndob))
+ pr2serr("%s: dout_len==0, so empty dout buffer\n",
+ op->cdb_name);
+ res = do_scsi_pt(ptvp, sg_fd, op->timeout, vb);
+ ret = sg_cmds_process_resp(ptvp, op->cdb_name, res, true /*noisy */, vb,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ {
+ bool valid;
+ int slen;
+ uint64_t ull = 0;
+
+ slen = get_scsi_pt_sense_len(ptvp);
+ valid = sg_get_sense_info_fld(sense_b, slen, &ull);
+ if (valid) {
+ pr2serr("Medium or hardware error starting at ");
+ if (op->do_scattered) {
+ if (0 == ull)
+ pr2serr("%s=<not reported>\n", lbard_str);
+ else
+ pr2serr("%s=%" PRIu64 " (origin 0)\n", lbard_str,
+ ull - 1);
+ if (sg_get_sense_cmd_spec_fld(sense_b, slen, &ull)) {
+ if (0 == ull)
+ pr2serr(" Number of successfully written "
+ "%ss is 0 or not reported\n",
+ lbard_str);
+ else
+ pr2serr(" Number of successfully written "
+ "%ss is %u\n", lbard_str,
+ (uint32_t)ull);
+ }
+ } else
+ pr2serr("lba=%" PRIu64 " [0x%" PRIx64 "]\n", ull,
+ ull);
+ }
+ }
+ ret = sense_cat;
+ break;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (vb)
+ sg_print_command_len(x_cdb, cdb_len);
+ ret = sense_cat;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+/* Returns 0 if successful, else sg3_utils error code. */
+static int
+do_read_capacity(int sg_fd, struct opts_t *op)
+{
+ bool prot_en = false;
+ int res;
+ int vb = op->verbose;
+ char b[80];
+ uint8_t resp_buff[RCAP16_RESP_LEN];
+
+ res = sg_ll_readcap_16(sg_fd, false /* pmi */, 0 /* llba */, resp_buff,
+ RCAP16_RESP_LEN, true, (vb ? (vb - 1): 0));
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Read capacity(16) unit attention, try again\n");
+ res = sg_ll_readcap_16(sg_fd, false, 0, resp_buff, RCAP16_RESP_LEN,
+ true, (vb ? (vb - 1): 0));
+ }
+ if (0 == res) {
+ uint32_t pi_len = 0;
+
+ if (vb > 3) {
+ pr2serr("Read capacity(16) response:\n");
+ hex2stderr(resp_buff, RCAP16_RESP_LEN, 1);
+ }
+ op->bs = sg_get_unaligned_be32(resp_buff + 8);
+ op->tot_lbs = sg_get_unaligned_be64(resp_buff + 0) + 1;
+ prot_en = !!(resp_buff[12] & 0x1);
+ if (prot_en) {
+ uint32_t pi_exp;
+
+ op->pi_type = ((resp_buff[12] >> 1) & 0x7) + 1;
+ pi_exp = 0xf & (resp_buff[13] >> 4);
+ pi_len = 8 * (1 << pi_exp);
+ if (op->wrprotect > 0) {
+ op->bs_pi_do = op->bs + pi_len;
+ if (vb > 1)
+ pr2serr(" For data out buffer purposes the effective "
+ "block size is %u (lb size\n is %u) because "
+ "PROT_EN=1, PI_EXP=%u and WRPROTECT>0\n", op->bs,
+ pi_exp, op->bs_pi_do);
+ }
+ } else { /* device formatted to PI type 0 (i.e. none) */
+ op->pi_type = 0;
+ if (op->wrprotect > 0) {
+ if (vb)
+ pr2serr("--wrprotect (%d) expects PI but %s says it "
+ "has none\n", op->wrprotect, op->device_name);
+ if (op->strict)
+ return SG_LIB_FILE_ERROR;
+ else if (vb)
+ pr2serr(" ... continue but could be dangerous\n");
+ }
+ }
+ if (vb) {
+ uint8_t d[2];
+
+ pr2serr("Read capacity(16) response fields:\n");
+ pr2serr(" Last_LBA=0x%" PRIx64 " LB size: %u (with PI: "
+ "%u) bytes p_type=%u\n", op->tot_lbs - 1,
+ op->bs, op->bs + (prot_en ? pi_len : 0),
+ ((resp_buff[12] >> 1) & 0x7));
+ pr2serr(" prot_en=%u [PI type=%u] p_i_exp=%u lbppb_exp=%u "
+ "lbpme,rz=%u,", prot_en, op->pi_type,
+ ((resp_buff[13] >> 4) & 0xf), (resp_buff[13] & 0xf),
+ !!(resp_buff[14] & 0x80));
+ memcpy(d, resp_buff + 14, 2);
+ d[0] &= 0x3f;
+ pr2serr("%u low_ali_lba=%u\n", !!(resp_buff[14] & 0x40),
+ sg_get_unaligned_be16(d));
+ }
+ } else if ((SG_LIB_CAT_INVALID_OP == res) ||
+ (SG_LIB_CAT_ILLEGAL_REQ == res)) {
+ if (vb)
+ pr2serr("Read capacity(16) not supported, try Read "
+ "capacity(10)\n");
+ res = sg_ll_readcap_10(sg_fd, false /* pmi */, 0 /* lba */,
+ resp_buff, RCAP10_RESP_LEN, true,
+ (vb ? (vb - 1): 0));
+ if (0 == res) {
+ if (vb > 3) {
+ pr2serr("Read capacity(10) response:\n");
+ hex2stderr(resp_buff, RCAP10_RESP_LEN, 1);
+ }
+ op->tot_lbs = sg_get_unaligned_be32(resp_buff + 0) + 1;
+ op->bs = sg_get_unaligned_be32(resp_buff + 4);
+ } else {
+ strcpy(b,"OS error");
+ if (res > 0)
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ else
+ snprintf(b, sizeof(b), "error: %d", res);
+ pr2serr("Read capacity(10): %s\n", b);
+ pr2serr("Unable to calculate block size\n");
+ return (res > 0) ? res : SG_LIB_FILE_ERROR;
+ }
+ } else {
+ if (vb) {
+ strcpy(b,"OS error");
+ if (res > 0)
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ pr2serr("Read capacity(16): %s\n", b);
+ pr2serr("Unable to calculate block size\n");
+ }
+ return (res > 0) ? res : SG_LIB_FILE_ERROR;
+ }
+ op->bs_pi_do = op->expect_pi_do ? (op->bs + 8) : op->bs;
+ return 0;
+}
+
+#define WANT_ZERO_EXIT 9999
+static const char * const opt_long_ctl_str =
+ "36a:A:b:B:c:dD:Efg:G:hi:I:l:M:n:No:Oq:Qr:RsS:t:T:u:vVw:x";
+
+/* command line processing, options and arguments. Returns 0 if ok,
+ * returns WANT_ZERO_EXIT so upper level yields an exist status of zero.
+ * Other return values (mainly SG_LIB_SYNTAX_ERROR) indicate errors. */
+static int
+parse_cmd_line(struct opts_t *op, int argc, char *argv[],
+ const char ** lba_opp, const char ** num_opp)
+{
+ bool fail_if_strict = false;
+ int c, j;
+ int64_t ll;
+ const char * cp;
+
+ while (1) {
+ int opt_ind = 0;
+
+ c = getopt_long(argc, argv, opt_long_ctl_str, long_options, &opt_ind);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '3': /* same as --32 */
+ op->do_32 = true;
+ break;
+ case '6': /* same as --16 */
+ op->do_16 = true;
+ break;
+ case 'a':
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > (int)UINT16_MAX)) {
+ pr2serr("bad argument to '--app-tag='. Expect 0 to 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->app_tag = (uint16_t)j;
+ break;
+ case 'A':
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > (int)UINT16_MAX)) {
+ pr2serr("bad argument to '--atomic='. Expect 0 to 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->atomic_boundary = (uint16_t)j;
+ op->do_atomic = true;
+ op->cmd_name = "Write atomic";
+ break;
+ case 'b': /* logical block size in bytes */
+ j = sg_get_num(optarg); /* 0 -> look up with READ CAPACITY */
+ if ((j < 0) || (j > (1 << 28))) {
+ pr2serr("bad argument to '--bs='. Expect 0 or greater\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (j > 0) {
+ int k;
+ int m = j;
+ int highest_ind;
+
+ if (j < 512) {
+ pr2serr("warning: --bs=BS value is < 512 which seems too "
+ "small, continue\n");
+ fail_if_strict = true;
+ }
+ if (0 != (j % 8)) {
+ pr2serr("warning: --bs=BS value is not a multiple of 8, "
+ "unexpected, continue\n");
+ fail_if_strict = true;
+ }
+ for (k = 0, highest_ind = 0; k < 28; ++ k, m >>= 1) {
+ if (1 & m)
+ highest_ind = k;
+ } /* loop should get log_base2(j) */
+ k = 1 << highest_ind;
+ if (j == k) { /* j is a power of two; actual and logical
+ * block size is assumed to be the same */
+ op->bs = (uint32_t)j;
+ op->bs_pi_do = op->bs;
+ } else { /* j is not power_of_two, use as actual LB size */
+ op->bs = (uint32_t)k; /* power_of_two less than j */
+ op->bs_pi_do = (uint32_t)j;
+ }
+ } else { /* j==0, let READCAP sort this out */
+ op->bs = 0;
+ op->bs_pi_do = 0;
+ }
+ break;
+ case 'B': /* --bmop=OP,PGP (for ORWRITE(32)) */
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > 7)) {
+ pr2serr("bad first argument to '--bmop='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->bmop = (uint8_t)j;
+ if ((cp = strchr(optarg, ','))) {
+ j = sg_get_num(cp + 1);
+ if ((j < 0) || (j > 15)) {
+ pr2serr("bad second argument to '--bmop='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->pgp = (uint8_t)j;
+ }
+ break;
+ case 'c': /* --combined=DOF for W SCATTERED, DOF: data offset */
+ j = sg_get_num(optarg);
+ if (j < 0) {
+ pr2serr("bad argument to '--combined='. Expect 0 to "
+ "0x7fffffff\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->scat_lbdof = (uint16_t)j;
+ op->do_combined = true;
+ break;
+ case 'd':
+ op->dpo = true;
+ break;
+ case 'D':
+ op->dld = sg_get_num(optarg);
+ if ((op->dld < 0) || (op->dld > 7)) {
+ pr2serr("bad argument to '--dld=', expect 0 to 7 "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'f':
+ op->fua = true;
+ break;
+ case 'g':
+ op->grpnum = sg_get_num(optarg);
+ if ((op->grpnum < 0) || (op->grpnum > 63)) {
+ pr2serr("bad argument to '--grpnum'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'G': /* --generation=EOG,NOG */
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad first argument to '--generation='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->orw_eog = (uint32_t)ll;
+ if ((cp = strchr(optarg, ','))) {
+ ll = sg_get_llnum(cp + 1);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad second argument to '--generation='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->orw_nog = (uint32_t)ll;
+ } else {
+ pr2serr("need two arguments with --generation=EOG,NOG and "
+ "they must be comma separated\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'h':
+ ++op->help;
+ break;
+ case '?':
+ pr2serr("\n");
+ usage((op->help > 0) ? op->help : 0);
+ return SG_LIB_SYNTAX_ERROR;
+ case 'i':
+ op->if_name = optarg;
+ break;
+ case 'I':
+ op->timeout = sg_get_num(optarg);
+ if (op->timeout < 0) {
+ pr2serr("bad argument to '--timeout='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'l':
+ if (*lba_opp) {
+ pr2serr("only expect '--lba=' option once\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ *lba_opp = optarg;
+ break;
+ case 'M': /* WRITE SAME */
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > 1)) {
+ pr2serr("bad argument to '--same', expect 0 or 1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->ndob = (bool)j;
+ op->do_same = true;
+ op->cmd_name = "Write same";
+ break;
+ case 'n':
+ if (*num_opp) {
+ pr2serr("only expect '--num=' option once\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ *num_opp = optarg;
+ break;
+ case 'N':
+ op->do_write_normal = true;
+ op->cmd_name = "Write";
+ break;
+ case 'o':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad first argument to '--offset='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->if_offset = (uint64_t)ll;
+ if ((cp = strchr(optarg, ','))) {
+ ll = sg_get_llnum(cp + 1);
+ if (-1 == ll) {
+ pr2serr("bad second argument to '--offset='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (ll > UINT32_MAX) {
+ pr2serr("bad second argument to '--offset=', cannot "
+ "exceed 32 bits\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->if_dlen = (uint32_t)ll;
+ }
+ break;
+ case 'O':
+ op->do_or = true;
+ op->cmd_name = "Orwrite";
+ break;
+ case 'q':
+ op->scat_filename = optarg;
+ break;
+ case 'Q':
+ op->do_quiet = true;
+ break;
+ case 'R':
+ op->do_scat_raw = true;
+ break;
+ case 'r': /* same as --ref-tag= */
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--ref-tag='. Expect 0 to "
+ "0xffffffff inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->ref_tag = (uint32_t)ll;
+ break;
+ case 's':
+ ++op->strict;
+ break;
+ case 'S':
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > (int)UINT16_MAX)) {
+ pr2serr("bad argument to '--scattered='. Expect 0 to 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->scat_num_lbard = (uint16_t)j;
+ op->do_scattered = true;
+ op->cmd_name = "Write scattered";
+ break;
+ case 't': /* same as --tag-mask= */
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > (int)UINT16_MAX)) {
+ pr2serr("bad argument to '--tag-mask='. Expect 0 to 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->tag_mask = (uint16_t)j;
+ break;
+ case 'T': /* WRITE STREAM */
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > (int)UINT16_MAX)) {
+ pr2serr("bad argument to '--stream=', expect 0 to 65535\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->str_id = (uint16_t)j;
+ op->do_stream = true;
+ op->cmd_name = "Write stream";
+ break;
+ case 'u': /* WRITE SAME, UNMAP and ANCHOR bit */
+ j = sg_get_num(optarg);
+ if ((j < 0) || (j > 3)) {
+ pr2serr("bad argument to '--unmap=', expect 0 to "
+ "3\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_unmap = !!(1 & j);
+ op->do_anchor = !!(2 & j);
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w': /* WRPROTECT field (or ORPROTECT for ORWRITE) */
+ op->wrprotect = sg_get_num(optarg);
+ if ((op->wrprotect < 0) || (op->wrprotect > 7)) {
+ pr2serr("bad argument to '--wrprotect'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->expect_pi_do = (op->wrprotect > 0);
+ break;
+ case 'x':
+ ++op->dry_run;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage((op->help > 0) ? op->help : 0);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage((op->help > 0) ? op->help : 0);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (op->strict && fail_if_strict)
+ return SG_LIB_SYNTAX_ERROR;
+ return 0;
+}
+
+static int
+process_scattered(int sg_fd, int infd, uint32_t if_len, uint32_t if_rlen,
+ int sfr_fd, uint32_t sf_len, uint64_t * addr_arr,
+ uint32_t addr_arr_len, uint32_t * num_arr,
+ uint16_t num_lbard, uint32_t sum_num, struct opts_t * op)
+{
+ int k, n, ret;
+ int vb = op->verbose;
+ uint32_t d, dd, nn, do_len;
+ uint8_t * up = NULL;
+ uint8_t * free_up = NULL;
+ char b[80];
+
+ if (op->do_combined) { /* --combined=DOF (.scat_lbdof) */
+ if (op->scat_lbdof > 0)
+ d = op->scat_lbdof * op->bs_pi_do;
+ else if (op->scat_num_lbard > 0) {
+ d = lbard_sz * (1 + op->scat_num_lbard);
+ if (0 != (d % op->bs_pi_do))
+ d = ((d / op->bs_pi_do) + 1) * op->bs_pi_do;
+ } else if (if_len > 0) {
+ d = if_len;
+ if (0 != (d % op->bs_pi_do))
+ d = ((d / op->bs_pi_do) + 1) * op->bs_pi_do;
+ } else {
+ pr2serr("With --combined= if DOF, RD are 0 and IF has an "
+ "unknown length\nthen give up\n");
+ return SG_LIB_CONTRADICT;
+ }
+ up = sg_memalign(d, 0, &free_up, false);
+ if (NULL == up) {
+ pr2serr("unable to allocate aligned memory for "
+ "scatterlist+data\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = bin_read(infd, up, ((if_len < d) ? if_len : d), "IF c1");
+ if (ret)
+ goto finii;
+ if (! check_lbrds(up, d, op, &num_lbard, &sum_num))
+ goto file_err_outt;
+ if ((op->scat_num_lbard > 0) && (op->scat_num_lbard != num_lbard)) {
+ bool rd_gt = (op->scat_num_lbard > num_lbard);
+
+ if (rd_gt || op->strict || vb) {
+ pr2serr("RD (%u) %s number of %ss (%u) found in IF\n",
+ op->scat_num_lbard, (rd_gt ? ">" : "<"), lbard_str,
+ num_lbard);
+ if (rd_gt)
+ goto file_err_outt;
+ else if (op->strict)
+ goto file_err_outt;
+ }
+ num_lbard = op->scat_num_lbard;
+ sum_num = sum_num_lbards(up, op->scat_num_lbard);
+ } else
+ op->scat_num_lbard = num_lbard;
+ dd = lbard_sz * (num_lbard + 1);
+ if (0 != (dd % op->bs_pi_do))
+ dd = ((dd / op->bs_pi_do) + 1) * op->bs_pi_do; /* round up */
+ nn = op->scat_lbdof * op->bs_pi_do;
+ if (dd != nn) {
+ bool dd_gt = (dd > nn);
+
+ if (dd_gt) {
+ pr2serr("%s: Cannot fit %ss (%u) in given LB data offset "
+ "(%u)\n", __func__, lbard_str, num_lbard,
+ op->scat_lbdof);
+ goto file_err_outt;
+ }
+ if (vb || op->strict)
+ pr2serr("%s: empty blocks before LB data offset (%u), could "
+ "be okay\n", __func__, op->scat_lbdof);
+ if (op->strict) {
+ pr2serr("Exiting due to --strict; perhaps try again with "
+ "--combined=%u\n", dd / op->bs_pi_do);
+ goto file_err_outt;
+ }
+ dd = nn;
+ }
+ dd += (sum_num * op->bs_pi_do);
+ if (dd > d) {
+ uint8_t * u2p;
+ uint8_t * free_u2p;
+
+ if (dd != if_len) {
+ bool dd_gt = (dd > if_len);
+
+ if (dd_gt || op->strict || vb) {
+ pr2serr("Calculated dout length (%u) %s bytes available "
+ "in IF (%u)\n", dd, (dd_gt ? ">" : "<"), if_len);
+ if (dd_gt)
+ goto file_err_outt;
+ else if (op->strict)
+ goto file_err_outt;
+ }
+ }
+ u2p = sg_memalign(dd, 0, &free_u2p, false);
+ if (NULL == u2p) {
+ pr2serr("unable to allocate memory for final "
+ "scatterlist+data\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto finii;
+ }
+ memcpy(u2p, up, d);
+ free(free_up);
+ up = u2p;
+ free_up = free_u2p;
+ ret = bin_read(infd, up + d, dd - d, "IF c2");
+ if (ret)
+ goto finii;
+ }
+ do_len = dd;
+ op->numblocks = sum_num;
+ op->xfer_bytes = sum_num * op->bs_pi_do;
+ goto do_io;
+ }
+
+ /* other than do_combined, so --scat-file= or --lba= */
+ if (addr_arr_len > 0)
+ num_lbard = addr_arr_len;
+
+ if (op->scat_filename && (! op->do_scat_raw)) {
+ d = lbard_sz * (num_lbard + 1);
+ nn = d;
+ op->scat_lbdof = d / op->bs_pi_do;
+ if (0 != (d % op->bs_pi_do)) /* if not multiple, round up */
+ op->scat_lbdof += 1;
+ dd = op->scat_lbdof * op->bs_pi_do;
+ d = sum_num * op->bs_pi_do;
+ do_len = dd + d;
+ /* zeroed data-out buffer for SL+DATA */
+ up = sg_memalign(do_len, 0, &free_up, false);
+ if (NULL == up) {
+ pr2serr("unable to allocate aligned memory for "
+ "scatterlist+data\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ num_lbard = 0;
+ sum_num = 0;
+ nn = (nn > lbard_sz) ? nn : (op->scat_lbdof * op->bs_pi_do);
+ ret = build_t10_scat(op->scat_filename, op->do_16, ! op->do_scattered,
+ up, &num_lbard, &sum_num, nn);
+ if (ret)
+ goto finii;
+ /* Calculate number of bytes to read from IF (place in 'd') */
+ d = sum_num * op->bs_pi_do;
+ if (op->if_dlen > d) {
+ if (op->strict || vb) {
+ pr2serr("DLEN > than bytes implied by sum of scatter "
+ "list NUMs (%u)\n", d);
+ if (vb > 1)
+ pr2serr(" num_lbard=%u, sum_num=%u actual_bs=%u",
+ num_lbard, sum_num, op->bs_pi_do);
+ if (op->strict)
+ goto file_err_outt;
+ }
+ } else if ((op->if_dlen > 0) && (op->if_dlen < d))
+ d = op->if_dlen;
+ if ((if_rlen > 0) && (if_rlen != d)) {
+ bool readable_lt = (if_rlen < d);
+
+ if (vb)
+ pr2serr("readable length (%u) of IF %s bytes implied by "
+ "sum of\nscatter list NUMs (%u) and DLEN\n",
+ (uint32_t)if_rlen,
+ readable_lt ? "<" : ">", d);
+ if (op->strict) {
+ if ((op->strict > 1) || (! readable_lt))
+ goto file_err_outt;
+ }
+ if (readable_lt)
+ d = if_rlen;
+ }
+ if (0 != (d % op->bs_pi_do)) {
+ if (vb || (op->strict > 1)) {
+ pr2serr("Calculated data-out length (0x%x) not a "
+ "multiple of BS (%u", d, op->bs);
+ if (op->bs != op->bs_pi_do)
+ pr2serr(" + %d(PI)", (int)op->bs_pi_do - (int)op->bs);
+ if (op->strict > 1) {
+ pr2serr(")\nexiting ...\n");
+ goto file_err_outt;
+ } else
+ pr2serr(")\nzero pad and continue ...\n");
+ }
+ }
+ ret = bin_read(infd, up + (op->scat_lbdof * op->bs_pi_do), d,
+ "IF 3");
+ if (ret)
+ goto finii;
+ do_len = ((op->scat_lbdof + sum_num) * op->bs_pi_do);
+ op->numblocks = sum_num;
+ op->xfer_bytes = sum_num * op->bs_pi_do;
+ /* dout for scattered write with ASCII scat_file ready */
+ } else if (op->do_scat_raw) {
+ bool if_len_gt = false;
+
+ /* guessing game for length of buffer */
+ if (op->scat_num_lbard > 0) {
+ dd = (op->scat_num_lbard + 1) * lbard_sz;
+ if (sf_len < dd) {
+ pr2serr("SF not long enough (%u bytes) to provide RD "
+ "(%u) %ss\n", sf_len, dd, lbard_str);
+ goto file_err_outt;
+ }
+ nn = dd / op->bs_pi_do;
+ if (0 != (dd % op->bs_pi_do))
+ nn +=1;
+ dd = nn * op->bs_pi_do;
+ } else
+ dd = op->bs_pi_do; /* guess */
+ if (if_len > 0) {
+ nn = if_len / op->bs_pi_do;
+ if (0 != (if_len % op->bs_pi_do))
+ nn += 1;
+ d = nn * op->bs_pi_do;
+ } else
+ d = op->bs_pi_do; /* guess one LB */
+ /* zero data-out buffer for SL+DATA */
+ nn = dd + d;
+ up = sg_memalign(nn, 0, &free_up, false);
+ if (NULL == up) {
+ pr2serr("unable to allocate aligned memory for "
+ "scatterlist+data\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto finii;
+ }
+ ret = bin_read(sfr_fd, up, sf_len, "SF");
+ if (ret)
+ goto finii;
+ if (! check_lbrds(up, dd, op, &num_lbard, &sum_num))
+ goto file_err_outt;
+ if (num_lbard != op->scat_num_lbard) {
+ pr2serr("Try again with --scattered=%u\n", num_lbard);
+ goto file_err_outt;
+ }
+ if ((sum_num * op->bs_pi_do) > d) {
+ uint8_t * u2p;
+ uint8_t * free_u2p;
+
+ d = sum_num * op->bs_pi_do;
+ nn = dd + d;
+ u2p = sg_memalign(nn, 0, &free_u2p, false);
+ if (NULL == u2p) {
+ pr2serr("unable to allocate memory for final "
+ "scatterlist+data\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto finii;
+ }
+ memcpy(u2p, up, dd);
+ free(free_up);
+ up = u2p;
+ free_up = free_u2p;
+ }
+ if ((if_len != (nn - d)) && (op->strict || vb)) {
+ if_len_gt = (if_len > (nn - d));
+ pr2serr("IF length (%u) %s 'sum_num' bytes (%u), ", if_len,
+ (if_len_gt ? ">" : "<"), nn - d);
+ if (op->strict > 1) {
+ pr2serr("exiting (strict=%d)\n", op->strict);
+ goto file_err_outt;
+ } else
+ pr2serr("continuing ...\n");
+ }
+ ret = bin_read(infd, up + d, (if_len_gt ? nn - d : if_len), "IF 4");
+ if (ret)
+ goto finii;
+ do_len = (num_lbard + sum_num) * op->bs_pi_do;
+ op->numblocks = sum_num;
+ op->xfer_bytes = sum_num * op->bs_pi_do;
+ } else if (addr_arr_len > 0) { /* build RDs for --lba= --num= */
+ if ((op->scat_num_lbard > 0) && (op->scat_num_lbard > addr_arr_len)) {
+ pr2serr("%s: number given to --scattered= (%u) exceeds number of "
+ "--lba= elements (%u)\n", __func__, op->scat_num_lbard,
+ addr_arr_len);
+ return SG_LIB_CONTRADICT;
+ }
+ d = lbard_sz * (num_lbard + 1);
+ op->scat_lbdof = d / op->bs_pi_do;
+ if (0 != (d % op->bs_pi_do)) /* if not multiple, round up */
+ op->scat_lbdof += 1;
+ for (sum_num = 0, k = 0; k < (int)addr_arr_len; ++k)
+ sum_num += num_arr[k];
+ do_len = ((op->scat_lbdof + sum_num) * op->bs_pi_do);
+ up = sg_memalign(do_len, 0, &free_up, false);
+ if (NULL == up) {
+ pr2serr("unable to allocate aligned memory for "
+ "scatterlist+data\n");
+ ret = sg_convert_errno(ENOMEM);
+ goto finii;
+ }
+ for (n = lbard_sz, k = 0; k < (int)addr_arr_len; ++k,
+ n += lbard_sz) {
+ sg_put_unaligned_be64(addr_arr[k], up + n + 0);
+ sg_put_unaligned_be32(num_arr[k], up + n + 8);
+ if (op->do_32) {
+ if (0 == k) {
+ sg_put_unaligned_be32(op->ref_tag, up + n + 12);
+ sg_put_unaligned_be16(op->app_tag, up + n + 16);
+ sg_put_unaligned_be16(op->tag_mask, up + n + 18);
+ } else {
+ sg_put_unaligned_be32((uint32_t)DEF_RT, up + n + 12);
+ sg_put_unaligned_be16((uint16_t)DEF_AT, up + n + 16);
+ sg_put_unaligned_be16((uint16_t)DEF_TM, up + n + 18);
+ }
+ }
+ }
+ op->numblocks = sum_num;
+ } else {
+ pr2serr("How did we get here??\n");
+ goto syntax_err_outt;
+ }
+do_io:
+ ret = do_write_x(sg_fd, up, do_len, op);
+ if (ret) {
+ strcpy(b,"OS error");
+ if (ret > 0)
+ sg_get_category_sense_str(ret, sizeof(b), b, vb);
+ pr2serr("%s: %s\n", op->cdb_name, b);
+ }
+ goto finii;
+
+syntax_err_outt:
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto finii;
+file_err_outt:
+ ret = SG_LIB_FILE_ERROR;
+finii:
+ if (free_up)
+ free(free_up);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool got_stdin = false;
+ bool got_stat = false;
+ bool if_reg_file = false;
+ int n, err, vb;
+ int infd = -1;
+ int sg_fd = -1;
+ int sfr_fd = -1;
+ int ret = -1;
+ uint32_t nn, addr_arr_len, num_arr_len; /* --lba= */
+ uint32_t do_len = 0;
+ uint16_t num_lbard = 0;
+ uint32_t if_len = 0; /* after accounting for OFF,DLEN and moving file
+ * file pointer to OFF, is bytes available in IF */
+ uint32_t sf_len = 0;
+ uint32_t sum_num = 0;
+ ssize_t res;
+ off_t if_readable_len = 0; /* similar to if_len but doesn't take DLEN
+ * into account */
+ struct opts_t * op;
+ const char * lba_op = NULL;
+ const char * num_op = NULL;
+ uint8_t * up = NULL;
+ uint8_t * free_up = NULL;
+ char ebuff[EBUFF_SZ];
+ char b[80];
+ uint64_t addr_arr[MAX_NUM_ADDR];
+ uint32_t num_arr[MAX_NUM_ADDR];
+ struct stat if_stat, sf_stat;
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+
+ op = &opts;
+ memset(&if_stat, 0, sizeof(if_stat));
+ memset(&sf_stat, 0, sizeof(sf_stat));
+ op->numblocks = DEF_WR_NUMBLOCKS;
+ op->pi_type = -1; /* Protection information type unknown */
+ op->ref_tag = DEF_RT; /* first 4 bytes of 8 byte protection info */
+ op->app_tag = DEF_AT; /* 2 bytes of protection information */
+ op->tag_mask = DEF_TM; /* final 2 bytes of protection information */
+ op->timeout = DEF_TIMEOUT_SECS;
+
+ /* Process command line */
+ ret = parse_cmd_line(op, argc, argv, &lba_op, &num_op);
+ if (ret) {
+ if (WANT_ZERO_EXIT == ret)
+ return 0;
+ return ret;
+ }
+ if (op->help > 0) {
+ usage(op->help);
+ return 0;
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("sg_write_x version: %s\n", version_str);
+ return WANT_ZERO_EXIT;
+ }
+
+ vb = op->verbose;
+ /* sanity checks */
+ if ((! op->do_16) && (! op->do_32)) {
+ op->do_16 = true;
+ if (vb > 1)
+ pr2serr("Since neither --16 nor --32 given, choose --16\n");
+ } else if (op->do_16 && op->do_32) {
+ op->do_16 = false;
+ if (vb > 1)
+ pr2serr("Since both --16 and --32 given, choose --32\n");
+ }
+ n = (int)op->do_atomic + (int)op->do_write_normal + (int)op->do_or +
+ (int)op->do_same + (int)op->do_scattered + (int)op->do_stream;
+ if (n > 1) {
+ pr2serr("Can only select one command; so only one of --atomic, "
+ "--normal, --or,\n--same=, --scattered= or --stream=\n") ;
+ return SG_LIB_CONTRADICT;
+ } else if (n < 1) {
+ if (op->strict) {
+ pr2serr("With --strict won't default to a normal WRITE, add "
+ "--normal\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ op->do_write_normal = true;
+ op->cmd_name = "Write";
+ if (vb)
+ pr2serr("No command selected so choose 'normal' WRITE\n");
+ }
+ }
+ snprintf(op->cdb_name, sizeof(op->cdb_name), "%s(%d)", op->cmd_name,
+ (op->do_16 ? 16 : 32));
+ if (op->do_combined) {
+ if (! op->do_scattered) {
+ pr2serr("--combined=DOF only allowed with --scattered=RD (i.e. "
+ "only with\nWRITE SCATTERED command)\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->scat_filename) {
+ pr2serr("Ambiguous: got --combined=DOF and --scat-file=SF .\n"
+ "Give one, the other or neither\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (lba_op || num_op) {
+ pr2serr("--scattered=RD --combined=DOF does not use --lba= or "
+ "--num=\nPlease remove.\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->do_scat_raw) {
+ pr2serr("Ambiguous: don't expect --combined=DOF and --scat-raw\n"
+ "Give one or the other\n");
+ return SG_LIB_CONTRADICT;
+ }
+ }
+ if ((NULL == op->scat_filename) && op->do_scat_raw) {
+ pr2serr("--scat-raw only applies to the --scat-file=SF option\n"
+ "--scat-raw without the --scat-file=SF option is an "
+ "error\n");
+ return SG_LIB_CONTRADICT;
+ }
+ n = (!! op->scat_filename) + (!! (lba_op || num_op)) +
+ (!! op->do_combined);
+ if (n > 1) {
+ pr2serr("want one and only one of: (--lba=LBA and/or --num=NUM), or\n"
+ "--scat-file=SF, or --combined=DOF\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (op->scat_filename && (1 == strlen(op->scat_filename)) &&
+ ('-' == op->scat_filename[0])) {
+ pr2serr("don't accept '-' (implying stdin) as a filename in "
+ "--scat-file=SF\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (vb && op->do_16 && (! is_pi_default(op)))
+ pr2serr("--app-tag=, --ref-tag= and --tag-mask= options ignored "
+ "with 16 byte commands\n");
+
+ /* examine .if_name . Open, move to .if_offset, calculate length that we
+ * want to read. */
+ if (! op->ndob) { /* as long as --same=1 is not active */
+ if_len = op->if_dlen; /* from --offset=OFF,DLEN; defaults to 0 */
+ if (NULL == op->if_name) {
+ pr2serr("Need --if=FN option to be given, exiting.\n");
+ if (vb > 1)
+ pr2serr("To write zeros use --in=/dev/zero\n");
+ pr2serr("\n");
+ usage((op->help > 0) ? op->help : 0);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((1 == strlen(op->if_name)) && ('-' == op->if_name[0])) {
+ got_stdin = true;
+ infd = STDIN_FILENO;
+ if (sg_set_binary_mode(STDIN_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ } else {
+ if ((infd = open(op->if_name, O_RDONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "could not open %s for reading",
+ op->if_name);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (sg_set_binary_mode(infd) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ if (fstat(infd, &if_stat) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "could not fstat %s", op->if_name);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ got_stat = true;
+ if (S_ISREG(if_stat.st_mode)) {
+ if_reg_file = true;
+ if_readable_len = if_stat.st_size;
+ if (0 == if_len)
+ if_len = if_readable_len;
+ }
+ }
+ if (got_stat && if_readable_len &&
+ ((int64_t)op->if_offset >= (if_readable_len - 1))) {
+ pr2serr("Offset (%" PRIu64 ") is at or beyond IF byte length (%"
+ PRIu64 ")\n", op->if_offset, (uint64_t)if_readable_len);
+ goto file_err_out;
+ }
+ if (op->if_offset > 0) {
+ off_t off = op->if_offset;
+ off_t h = if_readable_len;
+
+ if (if_reg_file) {
+ /* lseek() won't work with stdin, pipes or sockets, etc */
+ if (lseek(infd, off, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "couldn't offset to required "
+ "position on %s", op->if_name);
+ perror(ebuff);
+ ret = sg_convert_errno(err);
+ goto err_out;
+ }
+ if_readable_len -= op->if_offset;
+ if (if_readable_len <= 0) {
+ pr2serr("--offset [0x%" PRIx64 "] at or beyond file "
+ "length[0x%" PRIx64 "]\n",
+ (uint64_t)op->if_offset, (uint64_t)h);
+ goto file_err_out;
+ }
+ if (op->strict && ((off_t)op->if_dlen > if_readable_len)) {
+ pr2serr("after accounting for OFF, DLEN exceeds %s "
+ "remaining length (%u bytes)\n",
+ op->if_name, (uint32_t)if_readable_len);
+ goto file_err_out;
+ }
+ if_len = (uint32_t)((if_readable_len < (off_t)if_len) ?
+ if_readable_len : (off_t)if_len);
+ if (vb > 2)
+ pr2serr("Moved IF byte pointer to %u, if_len=%u, "
+ "if_readable_len=%u\n", (uint32_t)op->if_offset,
+ if_len, (uint32_t)if_readable_len);
+ } else {
+ if (vb)
+ pr2serr("--offset=OFF ignored when IF is stdin, pipe, "
+ "socket, etc\nDLEN, if given, is used\n");
+ }
+ }
+ }
+ /* Check device name has been given */
+ if (NULL == op->device_name) {
+ pr2serr("missing device name!\n");
+ usage((op->help > 0) ? op->help : 0);
+ goto syntax_err_out;
+ }
+
+ /* Open device file, do READ CAPACITY(16, maybe 10) if no BS */
+ sg_fd = sg_cmds_open_device(op->device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ if (op->verbose)
+ pr2serr("open error: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ if (0 == op->bs) { /* ask DEVICE about logical/actual block size */
+ ret = do_read_capacity(sg_fd, op);
+ if (ret)
+ goto err_out;
+ }
+ if ((0 == op->bs_pi_do) || (0 == op->bs)) {
+ pr2serr("Logic error, need block size by now\n");
+ goto syntax_err_out;
+ }
+ if (! op->ndob) {
+ if (0 != (if_len % op->bs_pi_do)) {
+ if (op->strict > 1) {
+ pr2serr("Error: number of bytes to read from IF [%u] is "
+ "not a multiple\nblock size %u (including "
+ "protection information)\n", (unsigned int)if_len,
+ op->bs_pi_do);
+ goto file_err_out;
+ }
+ if (op->strict || vb)
+ pr2serr("Warning: number of bytes to read from IF [%u] is "
+ "not a multiple\nof actual block size %u; pad with "
+ "zeros\n", (unsigned int)if_len, op->bs_pi_do);
+ }
+ }
+
+ /* decode --lba= and --num= options */
+ memset(addr_arr, 0, sizeof(addr_arr));
+ memset(num_arr, 0, sizeof(num_arr));
+ addr_arr_len = 0;
+ num_arr_len = 0;
+ if (lba_op) {
+ if (0 != build_lba_arr(lba_op, addr_arr, &addr_arr_len,
+ MAX_NUM_ADDR)) {
+ pr2serr("bad argument to '--lba'\n");
+ goto syntax_err_out;
+ }
+ }
+ if (num_op) {
+ if (0 != build_num_arr(num_op, num_arr, &num_arr_len,
+ MAX_NUM_ADDR)) {
+ pr2serr("bad argument to '--num'\n");
+ goto err_out;
+ }
+ }
+ if (((addr_arr_len > 1) && (addr_arr_len != num_arr_len)) ||
+ ((0 == addr_arr_len) && (num_arr_len > 1))) {
+ /* allow all combinations of 0 or 1 element --lba= with 0 or 1
+ * element --num=, otherwise this error ... */
+ pr2serr("need same number of arguments to '--lba=' and '--num=' "
+ "options\n");
+ ret = SG_LIB_CONTRADICT;
+ goto err_out;
+ }
+ if ((0 == addr_arr_len) && (1 == num_arr_len)) {
+ if (num_arr[0] > 0) {
+ pr2serr("won't write %u blocks without an explicit --lba= "
+ "option\n", num_arr[0]);
+ goto syntax_err_out;
+ }
+ addr_arr_len = 1; /* allow --num=0 without --lba= since it is safe */
+ }
+ /* Everything can use a SF, except --same=1 (when op->ndob==true) */
+ if (op->scat_filename) {
+ if (stat(op->scat_filename, &sf_stat) < 0) {
+ err = errno;
+ pr2serr("Unable to stat(%s) as SF: %s\n", op->scat_filename,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto err_out;
+ }
+ if (op->do_scat_raw) {
+ if (! S_ISREG(sf_stat.st_mode)) {
+ pr2serr("Expect scatter file to be a regular file\n");
+ goto file_err_out;
+ }
+ sf_len = sf_stat.st_size;
+ sfr_fd = open(op->scat_filename, O_RDONLY);
+ if (sfr_fd < 0) {
+ err = errno;
+ pr2serr("Failed to open %s for raw read: %s\n",
+ op->scat_filename, safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto err_out;
+ }
+ if (sg_set_binary_mode(sfr_fd) < 0) {
+ perror("sg_set_binary_mode");
+ goto file_err_out;
+ }
+ } else { /* scat_file should contain ASCII hex, preliminary parse */
+ nn = (op->scat_num_lbard > 0) ?
+ lbard_sz * (1 + op->scat_num_lbard) : 0;
+ ret = build_t10_scat(op->scat_filename, op->do_16,
+ ! op->do_scattered, NULL, &num_lbard,
+ &sum_num, nn);
+ if (ret)
+ goto err_out;
+ if ((op->scat_num_lbard > 0) &&
+ (num_lbard != op->scat_num_lbard)) {
+ bool rd_gt = (op->scat_num_lbard > num_lbard);
+
+ if (rd_gt || op->strict || vb) {
+ pr2serr("RD (%u) %s number of %ss (%u) found in SF\n",
+ op->scat_num_lbard, (rd_gt ? ">" : "<"),
+ lbard_str, num_lbard);
+ if (rd_gt)
+ goto file_err_out;
+ else if (op->strict)
+ goto file_err_out;
+ }
+ }
+ }
+ }
+
+ if (op->do_scattered) {
+ ret = process_scattered(sg_fd, infd, if_len, if_readable_len, sfr_fd,
+ sf_len, addr_arr, addr_arr_len, num_arr,
+ num_lbard, sum_num, op);
+ goto fini;
+ }
+
+ /* other than scattered */
+ if (addr_arr_len > 0) {
+ op->lba = addr_arr[0];
+ op->numblocks = num_arr[0];
+ if (vb && (addr_arr_len > 1))
+ pr2serr("warning: %d LBA,number_of_blocks pairs found, only "
+ "taking first\n", addr_arr_len);
+ } else if (op->scat_filename && (! op->do_scat_raw)) {
+ uint8_t upp[96];
+
+ sum_num = 0;
+ ret = build_t10_scat(op->scat_filename, op->do_16,
+ true /* parse one */, upp, &num_lbard,
+ &sum_num, sizeof(upp));
+ if (ret)
+ goto err_out;
+ if (vb && (num_lbard > 1))
+ pr2serr("warning: %d LBA,number_of_blocks pairs found, only "
+ "taking first\n", num_lbard);
+ if (vb > 2)
+ pr2serr("after build_t10_scat, num_lbard=%u, sum_num=%u\n",
+ num_lbard, sum_num);
+ if (1 != num_lbard) {
+ pr2serr("Unable to decode one LBA range descriptor from %s\n",
+ op->scat_filename);
+ goto file_err_out;
+ }
+ op->lba = sg_get_unaligned_be64(upp + 32 + 0);
+ op->numblocks = sg_get_unaligned_be32(upp + 32 + 8);
+ if (op->do_32) {
+ op->ref_tag = sg_get_unaligned_be32(upp + 32 + 12);
+ op->app_tag = sg_get_unaligned_be16(upp + 32 + 16);
+ op->tag_mask = sg_get_unaligned_be16(upp + 32 + 18);
+ }
+ } else if (op->do_scat_raw) {
+ uint8_t upp[64];
+
+ if (sf_len < (2 * lbard_sz)) {
+ pr2serr("raw scatter file must be at least 64 bytes long "
+ "(length: %u)\n", sf_len);
+ goto file_err_out;
+ }
+ ret = bin_read(sfr_fd, upp, sizeof(upp), "SF");
+ if (ret)
+ goto err_out;
+ if (! check_lbrds(upp, sizeof(upp), op, &num_lbard, &sum_num))
+ goto file_err_out;
+ if (1 != num_lbard) {
+ pr2serr("No %ss found in SF (num=%u)\n", lbard_str, num_lbard);
+ goto file_err_out;
+ }
+ op->lba = sg_get_unaligned_be64(upp + 16);
+ op->numblocks = sg_get_unaligned_be32(upp + 16 + 8);
+ do_len = sum_num * op->bs_pi_do;
+ op->xfer_bytes = do_len;
+ } else {
+ pr2serr("No LBA or number_of_blocks given, try using --lba= and "
+ "--num=\n");
+ goto syntax_err_out;
+ }
+ if (op->do_same)
+ op->xfer_bytes = op->ndob ? 0 : op->bs_pi_do;
+ else /* WRITE, ORWRITE, WRITE ATOMIC or WRITE STREAM */
+ op->xfer_bytes = op->numblocks * op->bs_pi_do;
+ do_len = op->xfer_bytes;
+
+ if (do_len > 0) {
+ /* fill allocated buffer with zeros */
+ up = sg_memalign(do_len, 0, &free_up, false);
+ if (NULL == up) {
+ pr2serr("unable to allocate %u bytes of memory\n", do_len);
+ ret = sg_convert_errno(ENOMEM);
+ goto err_out;
+ }
+ ret = bin_read(infd, up, ((if_len < do_len) ? if_len : do_len),
+ "IF 5");
+ if (ret)
+ goto fini;
+ } else
+ up = NULL;
+
+ ret = do_write_x(sg_fd, up, do_len, op);
+ if (ret && (! op->do_quiet)) {
+ strcpy(b,"OS error");
+ if (ret > 0)
+ sg_get_category_sense_str(ret, sizeof(b), b, vb);
+ pr2serr("%s: %s\n", op->cdb_name, b);
+ }
+ goto fini;
+
+syntax_err_out:
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto err_out;
+file_err_out:
+ ret = SG_LIB_FILE_ERROR;
+err_out:
+fini:
+ if (free_up)
+ free(free_up);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ if (! op->do_quiet)
+ pr2serr("sg_fd close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = SG_LIB_FILE_ERROR;
+ }
+ }
+ if (sfr_fd >= 0) {
+ if (close(sfr_fd) < 0) {
+ if (! op->do_quiet)
+ perror("sfr_fd close error");
+ if (0 == ret)
+ ret = SG_LIB_FILE_ERROR;
+ }
+ }
+ if ((! got_stdin) && (infd >= 0)) {
+ if (close(infd) < 0) {
+ if (! op->do_quiet)
+ perror("infd close error");
+ if (0 == ret)
+ ret = SG_LIB_FILE_ERROR;
+ }
+ }
+ if ((0 == op->verbose) && (! op->do_quiet)) {
+ if (! sg_if_can2stderr("sg_write_x failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_xcopy.c b/src/sg_xcopy.c
new file mode 100644
index 00000000..39ad83c6
--- /dev/null
+++ b/src/sg_xcopy.c
@@ -0,0 +1,1934 @@
+/* A utility program for copying files. Similar to 'dd' but using
+ * the 'Extended Copy' command.
+ *
+ * Copyright (c) 2011-2022 Hannes Reinecke, SUSE Labs
+ *
+ * Largely 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.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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, 2.6, 3, 4 and 5 series.
+ */
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.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/stat.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <linux/major.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"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "0.73 20220118";
+
+#define ME "sg_xcopy: "
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+#define EBUFF_SZ 1024
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define MAX_BLOCKS_PER_TRANSFER 65535
+
+#define DEF_MODE_RESP_LEN 252
+#define RW_ERR_RECOVERY_MP 1
+#define CACHING_MP 8
+#define CONTROL_MP 0xa
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#ifndef UINT32_MAX
+#define UINT32_MAX ((uint32_t)-1)
+#endif
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define SG_LIB_FLOCK_ERR 90
+
+/* In SPC-4 the cdb opcodes have more generic names */
+#define THIRD_PARTY_COPY_OUT_CMD 0x83
+#define THIRD_PARTY_COPY_IN_CMD 0x84
+
+/* Third party copy IN (opcode 0x84) and OUT (opcode 0x83) command service
+ * actions */
+#define SA_XCOPY_LID1 0x0 /* OUT, originate */
+#define SA_XCOPY_LID4 0x1 /* OUT, originate */
+#define SA_POP_TOK 0x10 /* OUT, originate */
+#define SA_WR_USING_TOK 0x11 /* OUT, originate */
+#define SA_COPY_ABORT 0x1C /* OUT, abort */
+#define SA_COPY_STATUS_LID1 0x0 /* IN, retrieve */
+#define SA_COPY_DATA_LID1 0x1 /* IN, retrieve */
+#define SA_COPY_OP_PARAMS 0x3 /* IN, retrieve */
+#define SA_COPY_FAIL_DETAILS 0x4 /* IN, retrieve */
+#define SA_COPY_STATUS_LID4 0x5 /* IN, retrieve */
+#define SA_COPY_DATA_LID4 0x6 /* IN, retrieve */
+#define SA_ROD_TOK_INFO 0x7 /* IN, retrieve */
+#define SA_ALL_ROD_TOKS 0x8 /* IN, retrieve */
+
+#define DEF_3PC_OUT_TIMEOUT (10 * 60) /* is 10 minutes enough? */
+#define DEF_GROUP_NUM 0x0
+
+#define VPD_DEVICE_ID 0x83
+#define VPD_3PARTY_COPY 0x8f
+
+#define FT_OTHER 1 /* filetype is probably normal */
+#define FT_SG 2 /* filetype is sg or bsg char device */
+#define FT_RAW 4 /* filetype is raw char device */
+#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */
+#define FT_ST 16 /* filetype is st char device (tape) */
+#define FT_BLOCK 32 /* filetype is 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 TD_IPV6 1024
+#define TD_IP_COPY_SERVICE 2048
+#define TD_ROD 4096
+
+#define XCOPY_TO_SRC "XCOPY_TO_SRC"
+#define XCOPY_TO_DST "XCOPY_TO_DST"
+#define DEF_XCOPY_SRC0_DST1 1
+
+#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 bool do_time = false;
+static bool start_tm_valid = false;
+static bool xcopy_flag_cat = false;
+static bool xcopy_flag_dc = false;
+static bool xcopy_flag_fco = false; /* fast copy only, spc5r20 */
+static int blk_sz = 0;
+static int list_id_usage = -1;
+static int priority = 1;
+static int verbose = 0;
+static struct timeval start_tm;
+
+
+struct xcopy_fp_t {
+ bool append;
+ bool excl;
+ bool flock;
+ bool pad; /* Data descriptor PAD bit (residual data treatment) */
+ bool xcopy_given;
+ int sect_sz;
+ int sg_type, sg_fd;
+ int pdt; /* Peripheral device type */
+ dev_t devno;
+ uint32_t min_bytes;
+ uint32_t max_bytes;
+ int64_t num_sect;
+ char fname[INOUTF_SZ];
+};
+
+static struct xcopy_fp_t ixcf;
+static struct xcopy_fp_t oxcf;
+
+static const char * read_cap_str = "Read capacity";
+static const char * rec_copy_op_params_str = "Receive copy operating "
+ "parameters";
+
+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)
+ pr2serr(" remaining block count=%" PRId64 "\n", dd_count);
+ pr2serr("%s%" PRId64 "+%d records in\n", str, in_full - in_partial,
+ in_partial);
+ pr2serr("%s%" PRId64 "+%d records out\n", str, out_full - out_partial,
+ out_partial);
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ if (do_time)
+ calc_duration_throughput(0);
+ print_stats("");
+ kill(getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+static bool bsg_major_checked = false;
+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)
+ pr2serr("fopen %s failed: %s\n", proc_devices, strerror(errno));
+ return;
+ }
+ while ((cp = fgets(b, sizeof(b), fp))) {
+ if ((1 == sscanf(b, "%126s", a)) &&
+ (0 == memcmp(a, "Character", 9)))
+ break;
+ }
+ while (cp && (cp = fgets(b, sizeof(b), fp))) {
+ if (2 == sscanf(b, "%d %126s", &n, a)) {
+ if (0 == strcmp("bsg", a)) {
+ bsg_major = n;
+ break;
+ }
+ } else
+ break;
+ }
+ if (verbose > 5) {
+ if (cp)
+ pr2serr("found bsg_major=%d\n", bsg_major);
+ else
+ pr2serr("found no bsg char device in %s\n", proc_devices);
+ }
+ fclose(fp);
+}
+
+/* Returns a file descriptor on success (0 or greater), -1 for an open
+ * error, -2 for a standard INQUIRY problem. */
+static int
+open_sg(struct xcopy_fp_t * fp, int vb)
+{
+ int devmajor, devminor, offset;
+ struct sg_simple_inquiry_resp sir;
+ char ebuff[EBUFF_SZ];
+ int len, res;
+
+ devmajor = major(fp->devno);
+ devminor = minor(fp->devno);
+
+ if (fp->sg_type & FT_SG) {
+ snprintf(ebuff, EBUFF_SZ, "%.500s", fp->fname);
+ } else if (fp->sg_type & FT_BLOCK || fp->sg_type & FT_OTHER) {
+ int fd;
+
+ snprintf(ebuff, EBUFF_SZ, "/sys/dev/block/%d:%d/partition",
+ devmajor, devminor);
+ if ((fd = open(ebuff, O_RDONLY)) >= 0) {
+ ebuff[EBUFF_SZ - 1] = '\0';
+ len = read(fd, ebuff, EBUFF_SZ - 1);
+ 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, false /* rw mode */, vb);
+ 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 -sg_convert_errno(-fp->sg_fd);
+ }
+ if (sg_simple_inquiry(fp->sg_fd, &sir, false, vb)) {
+ pr2serr("INQUIRY failed on %s\n", ebuff);
+ res = sg_cmds_close_device(fp->sg_fd);
+ if (res < 0)
+ pr2serr("sg_cmds_close_device() failed as well\n");
+ fp->sg_fd = -1;
+ return -1;
+ }
+
+ fp->pdt = sir.peripheral_type;
+ if (vb)
+ pr2serr(" %s: %.8s %.16s %.4s [pdt=%d, 3pc=%d]\n", fp->fname,
+ sir.vendor, sir.product, sir.revision, fp->pdt,
+ !! (0x8 & sir.byte_5));
+
+ 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 = true;
+ 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 += sg_scnpr(buff + off, 32, "null device ");
+ if (FT_SG & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI generic (sg) device ");
+ if (FT_BLOCK & ft)
+ off += sg_scnpr(buff + off, 32, "block device ");
+ if (FT_FIFO & ft)
+ off += sg_scnpr(buff + off, 32, "fifo (named pipe) ");
+ if (FT_ST & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI tape device ");
+ if (FT_RAW & ft)
+ off += sg_scnpr(buff + off, 32, "raw device ");
+ if (FT_OTHER & ft)
+ off += sg_scnpr(buff + off, 32, "other (perhaps ordinary file) ");
+ if (FT_ERROR & ft)
+ sg_scnpr(buff + off, 32, "unable to 'stat' file ");
+ return buff;
+}
+
+static int
+simplified_ft(const struct xcopy_fp_t * xfp)
+{
+ int ftype = xfp->sg_type;
+
+ switch (ftype) {
+ case FT_BLOCK:
+ case FT_ST:
+ case FT_OTHER: /* typically regular file */
+ case FT_DEV_NULL:
+ case FT_FIFO:
+ case FT_ERROR:
+ return ftype;
+ default:
+ if (FT_SG & ftype) {
+ if ((0 == xfp->pdt) || (0xe == xfp->pdt)) /* D-A or RBC */
+ return FT_BLOCK;
+ else if (0x1 == xfp->pdt)
+ return FT_ST;
+ }
+ return FT_OTHER;
+ }
+}
+
+static int
+seg_desc_from_dd_type(int in_ft, int in_off, int out_ft, int out_off)
+{
+ int desc_type = -1;
+
+ switch (in_ft) {
+ case FT_BLOCK:
+ switch (out_ft) {
+ case FT_ST:
+ if (out_off)
+ break;
+
+ if (in_off)
+ desc_type = 0x8;
+ else
+ desc_type = 0;
+ break;
+ case FT_BLOCK:
+ if (in_off || out_off)
+ desc_type = 0xA;
+ else
+ desc_type = 2;
+ break;
+ default:
+ break;
+ }
+ break;
+ case FT_ST:
+ if (in_off)
+ break;
+
+ switch (out_ft) {
+ case FT_ST:
+ if (!out_off) {
+ desc_type = 3;
+ break;
+ }
+ break;
+ case FT_BLOCK:
+ if (out_off)
+ desc_type = 9;
+ else
+ desc_type = 3;
+ break;
+ case FT_DEV_NULL:
+ desc_type = 6;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return desc_type;
+}
+
+static void
+usage(int n_help)
+{
+ if (n_help < 2)
+ goto primary_help;
+ else
+ goto secondary_help;
+
+primary_help:
+ pr2serr("Usage: "
+ "sg_xcopy [app=0|1] [bpt=BPT] [bs=BS] [cat=0|1] [conv=CONV]\n"
+ " [count=COUNT] [dc=0|1] [ibs=BS]\n"
+ " [id_usage=hold|discard|disable] [if=IFILE] "
+ "[iflag=FLAGS]\n"
+ " [list_id=ID] [obs=BS] [of=OFILE] "
+ "[oflag=FLAGS] [prio=PRIO]\n"
+ " [seek=SEEK] [skip=SKIP] [time=0|1] "
+ "[verbose=VERB]\n"
+ " [--help] [--on_dst|--on_src] [--verbose] "
+ "[--version]\n\n"
+ " where:\n"
+ " app if argument is 1 then open OFILE in append "
+ "mode\n"
+ " bpt is blocks_per_transfer (default: 128)\n"
+ " bs block size (default is 512)\n");
+ pr2serr(" cat xcopy segment descriptor CAT bit (default: "
+ "0)\n"
+ " conv ignored\n"
+ " count number of blocks to copy (def: device size)\n"
+ " dc xcopy segment descriptor DC bit (default: 0)\n"
+ " fco xcopy segment descriptor FCO bit (default: 0)\n"
+ " ibs input block size (if given must be same as "
+ "'bs=')\n"
+ " id_usage sets list_id_usage field to hold (0), "
+ "discard (2) or\n"
+ " disable (3)\n"
+ " if file or device to read from (def: stdin)\n"
+ " iflag comma separated list of flags applying to "
+ "IFILE\n"
+ " list_id sets list_id field to ID (default: 1 or 0)\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");
+ pr2serr(" treated as /dev/null\n"
+ " oflag comma separated list of flags applying to "
+ "OFILE\n"
+ " prio set xcopy priority field to PRIO (def: 1)\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|-h print out this usage message then exit\n"
+ " --on_dst send XCOPY command to OFILE\n"
+ " --on_src send XCOPY command to IFILE\n"
+ " --verbose|-v same action as verbose=1\n"
+ " --version|-V print version information then exit\n\n"
+ "Copy from IFILE to OFILE, similar to dd command; "
+ "but using the SCSI\nEXTENDED COPY (XCOPY(LID1)) command. For "
+ "list of flags, use '-hh'.\n");
+ return;
+
+secondary_help:
+ pr2serr("FLAGS:\n"
+ " append (o) open OFILE in append mode\n"
+ " excl open corresponding device with O_EXCL\n"
+ " flock call flock(LOCK_EX|LOCK_NB)\n"
+ " null does nothing, placeholder\n"
+ " pad set xcopy data descriptor PAD bit on\n"
+ " corresponding device\n"
+ " xcopy send XCOPY command to corresponding device\n"
+ "\n"
+ "ENVIRONMENT VARIABLES:\n"
+ " XCOPY_TO_DST send XCOPY command to OFILE (destination) "
+ "if no other\n"
+ " indication\n"
+ " XCOPY_TO_SRC send XCOPY command to IFILE (source)\n"
+ );
+}
+
+static int
+scsi_encode_seg_desc(uint8_t *seg_desc, int seg_desc_type,
+ int64_t num_blk, uint64_t src_lba, uint64_t dst_lba)
+{
+ int seg_desc_len = 0;
+
+ seg_desc[0] = (uint8_t)seg_desc_type;
+ seg_desc[1] = 0x0;
+ if (xcopy_flag_cat)
+ seg_desc[1] |= 0x1;
+ if (xcopy_flag_dc)
+ seg_desc[1] |= 0x2;
+ if (xcopy_flag_fco)
+ seg_desc[1] |= 0x4;
+ if (seg_desc_type == 0x02) {
+ seg_desc_len = 0x18;
+ seg_desc[4] = 0;
+ seg_desc[5] = 0; /* Source target index */
+ seg_desc[7] = 1; /* Destination target index */
+ sg_put_unaligned_be16(num_blk, seg_desc + 10);
+ sg_put_unaligned_be64(src_lba, seg_desc + 12);
+ sg_put_unaligned_be64(dst_lba, seg_desc + 20);
+ }
+ sg_put_unaligned_be16(seg_desc_len, seg_desc + 2);
+ return seg_desc_len + 4;
+}
+
+static int
+scsi_extended_copy(int sg_fd, uint8_t list_id,
+ uint8_t *src_desc, int src_desc_len,
+ uint8_t *dst_desc, int dst_desc_len,
+ int seg_desc_type, int64_t num_blk,
+ uint64_t src_lba, uint64_t dst_lba)
+{
+ uint8_t xcopyBuff[256];
+ int desc_offset = 16;
+ int seg_desc_len;
+ int verb, res;
+ char b[80];
+
+ verb = (verbose > 1) ? (verbose - 2) : 0;
+ memset(xcopyBuff, 0, 256);
+ xcopyBuff[0] = list_id;
+ xcopyBuff[1] = (list_id_usage << 3) | priority;
+ xcopyBuff[2] = 0;
+ xcopyBuff[3] = src_desc_len + dst_desc_len; /* Two target descriptors */
+ memcpy(xcopyBuff + desc_offset, src_desc, src_desc_len);
+ desc_offset += src_desc_len;
+ memcpy(xcopyBuff + desc_offset, dst_desc, dst_desc_len);
+ desc_offset += dst_desc_len;
+ seg_desc_len = scsi_encode_seg_desc(xcopyBuff + desc_offset,
+ seg_desc_type, num_blk,
+ src_lba, dst_lba);
+ xcopyBuff[11] = seg_desc_len; /* One segment descriptor */
+ desc_offset += seg_desc_len;
+ /* set noisy so if a UA happens it will be printed to stderr */
+ res = sg_ll_3party_copy_out(sg_fd, SA_XCOPY_LID1, list_id,
+ DEF_GROUP_NUM, DEF_3PC_OUT_TIMEOUT,
+ xcopyBuff, desc_offset, true, verb);
+ if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verb);
+ pr2serr("Xcopy(LID1): %s\n", b);
+ }
+ return res;
+}
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(struct xcopy_fp_t *xfp)
+{
+ int res;
+ unsigned int ui;
+ uint8_t rcBuff[RCAP16_REPLY_LEN];
+ int verb;
+ char b[80];
+
+ verb = (verbose ? verbose - 1: 0);
+ res = sg_ll_readcap_10(xfp->sg_fd, false /* pmi */, 0, rcBuff,
+ READ_CAP_REPLY_LEN, true, verb);
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verb);
+ pr2serr("Read capacity(10): %s\n", b);
+ return res;
+ }
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+ uint64_t ls;
+
+ res = sg_ll_readcap_16(xfp->sg_fd, false /* pmi */, 0, rcBuff,
+ RCAP16_REPLY_LEN, true, verb);
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verb);
+ pr2serr("Read capacity(16): %s\n", b);
+ return res;
+ }
+ ls = sg_get_unaligned_be64(rcBuff + 0);
+ xfp->num_sect = (int64_t)(ls + 1);
+ xfp->sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ ui = sg_get_unaligned_be32(rcBuff + 0);
+ /* take care not to sign extend values > 0x7fffffff */
+ xfp->num_sect = (int64_t)ui + 1;
+ xfp->sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ if (verbose)
+ pr2serr(" %s: number of blocks=%" PRId64 " [0x%" PRIx64 "], block "
+ "size=%d\n", xfp->fname, xfp->num_sect, xfp->num_sect,
+ xfp->sect_sz);
+ return 0;
+}
+
+static int
+scsi_operating_parameter(struct xcopy_fp_t *xfp, int is_target)
+{
+ bool valid = false;
+ int res, ftype, snlid, verb;
+ uint32_t rcBuffLen = 256, len, n, td_list = 0;
+ uint32_t num, max_target_num, max_segment_num, max_segment_len;
+ uint32_t max_desc_len, max_inline_data, held_data_limit;
+ uint8_t rcBuff[256];
+ char b[80];
+
+ verb = (verbose ? verbose - 1: 0);
+ ftype = xfp->sg_type;
+ if (FT_SG & ftype) {
+ if ((0 == xfp->pdt) || (0xe == xfp->pdt)) /* direct-access or RBC */
+ ftype |= FT_BLOCK;
+ else if (0x1 == xfp->pdt)
+ ftype |= FT_ST;
+ }
+ res = sg_ll_receive_copy_results(xfp->sg_fd, SA_COPY_OP_PARAMS, 0, rcBuff,
+ rcBuffLen, true, verb);
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verb);
+ pr2serr("Xcopy operating parameters: %s\n", b);
+ return -res;
+ }
+
+ len = sg_get_unaligned_be32(rcBuff + 0);
+ if (len > rcBuffLen) {
+ pr2serr(" <<report len %d > %d too long for internal buffer, output "
+ "truncated\n", len, rcBuffLen);
+ }
+ if (verbose > 2) {
+ pr2serr("\nOutput response in hex:\n");
+ hex2stderr(rcBuff, len, 1);
+ }
+ snlid = rcBuff[4] & 0x1;
+ max_target_num = sg_get_unaligned_be16(rcBuff + 8);
+ max_segment_num = sg_get_unaligned_be16(rcBuff + 10);
+ max_desc_len = sg_get_unaligned_be32(rcBuff + 12);
+ max_segment_len = sg_get_unaligned_be32(rcBuff + 16);
+ xfp->max_bytes = max_segment_len ? max_segment_len : UINT32_MAX;
+ max_inline_data = sg_get_unaligned_be32(rcBuff + 20);
+ if (verbose) {
+ pr2serr(" >> %s response:\n", rec_copy_op_params_str);
+ pr2serr(" Support No List IDentifier (SNLID): %d\n", snlid);
+ pr2serr(" Maximum target descriptor count: %u\n",
+ (unsigned int)max_target_num);
+ pr2serr(" Maximum segment descriptor count: %u\n",
+ (unsigned int)max_segment_num);
+ pr2serr(" Maximum descriptor list length: %u\n",
+ (unsigned int)max_desc_len);
+ pr2serr(" Maximum segment length: %u\n",
+ (unsigned int)max_segment_len);
+ pr2serr(" Maximum inline data length: %u\n",
+ (unsigned int)max_inline_data);
+ }
+ held_data_limit = sg_get_unaligned_be32(rcBuff + 24);
+ if (list_id_usage < 0) {
+ if (!held_data_limit)
+ list_id_usage = 2;
+ else
+ list_id_usage = 0;
+ }
+ if (verbose) {
+ pr2serr(" Held data limit: %u (list_id_usage: %d)\n",
+ (unsigned int)held_data_limit, list_id_usage);
+ num = sg_get_unaligned_be32(rcBuff + 28);
+ pr2serr(" Maximum stream device transfer size: %u\n",
+ (unsigned int)num);
+ pr2serr(" Maximum concurrent copies: %u\n", rcBuff[36]);
+ if (rcBuff[37] > 30)
+ pr2serr(" Data segment granularity: 2**%u bytes\n",
+ rcBuff[37]);
+ else
+ pr2serr(" Data segment granularity: %u bytes\n",
+ 1 << rcBuff[37]);
+ if (rcBuff[38] > 30)
+ pr2serr(" Inline data granularity: 2**%u bytes\n", rcBuff[38]);
+ else
+ pr2serr(" Inline data granularity: %u bytes\n",
+ 1 << rcBuff[38]);
+ if (rcBuff[39] > 30)
+ pr2serr(" Held data granularity: 2**%u bytes\n",
+ 1 << rcBuff[39]);
+ else
+ pr2serr(" Held data granularity: %u bytes\n", 1 << rcBuff[39]);
+
+ pr2serr(" Implemented descriptor list:\n");
+ }
+ xfp->min_bytes = 1 << rcBuff[37];
+
+ for (n = 0; n < rcBuff[43]; n++) {
+ switch(rcBuff[44 + n]) {
+ case 0x00: /* copy block to stream device */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy Block to Stream device\n");
+ break;
+ case 0x01: /* copy stream to block device */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy Stream to Block device\n");
+ break;
+ case 0x02: /* copy block to block device */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy Block to Block device\n");
+ break;
+ case 0x03: /* copy stream to stream device */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy Stream to Stream device\n");
+ break;
+ case 0x04: /* copy inline data to stream device */
+ if (!is_target && (ftype & FT_OTHER))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy inline data to Stream device\n");
+ break;
+ case 0x05: /* copy embedded data to stream device */
+ if (!is_target && (ftype & FT_OTHER))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy embedded data to Stream device\n");
+ break;
+ case 0x06: /* Read from stream device and discard */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_DEV_NULL))
+ valid = true;
+ if (verbose)
+ pr2serr(" Read from stream device and discard\n");
+ break;
+ case 0x07: /* Verify block or stream device operation */
+ if (!is_target && (ftype & (FT_ST | FT_BLOCK)))
+ valid = true;
+ if (is_target && (ftype & (FT_ST | FT_BLOCK)))
+ valid = true;
+ if (verbose)
+ pr2serr(" Verify block or stream device operation\n");
+ break;
+ case 0x08: /* copy block device with offset to stream device */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy block device with offset to stream "
+ "device\n");
+ break;
+ case 0x09: /* copy stream device to block device with offset */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy stream device to block device with "
+ "offset\n");
+ break;
+ case 0x0a: /* copy block device with offset to block device with
+ * offset */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy block device with offset to block "
+ "device with offset\n");
+ break;
+ case 0x0b: /* copy block device to stream device and hold data */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy block device to stream device and hold "
+ "data\n");
+ break;
+ case 0x0c: /* copy stream device to block device and hold data */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy stream device to block device and hold "
+ "data\n");
+ break;
+ case 0x0d: /* copy block device to block device and hold data */
+ if (!is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (is_target && (ftype & FT_BLOCK))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy block device to block device and hold "
+ "data\n");
+ break;
+ case 0x0e: /* copy stream device to stream device and hold data */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_ST))
+ valid = true;
+ if (verbose)
+ pr2serr(" Copy block device to block device and hold "
+ "data\n");
+ break;
+ case 0x0f: /* read from stream device and hold data */
+ if (!is_target && (ftype & FT_ST))
+ valid = true;
+ if (is_target && (ftype & FT_DEV_NULL))
+ valid = true;
+ if (verbose)
+ pr2serr(" Read from stream device and hold data\n");
+ break;
+ case 0xe0: /* FC N_Port_Name */
+ if (verbose)
+ pr2serr(" FC N_Port_Name target descriptor\n");
+ td_list |= TD_FC_WWPN;
+ break;
+ case 0xe1: /* FC Port_ID */
+ if (verbose)
+ pr2serr(" FC Port_ID target descriptor\n");
+ td_list |= TD_FC_PORT;
+ break;
+ case 0xe2: /* FC N_Port_ID with N_Port_Name checking */
+ if (verbose)
+ pr2serr(" 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 */
+ if (verbose)
+ pr2serr(" SPI T_L target descriptor\n");
+ td_list |= TD_SPI;
+ break;
+ case 0xe4: /* identification descriptor */
+ if (verbose)
+ pr2serr(" Identification target descriptor\n");
+ td_list |= TD_VPD;
+ break;
+ case 0xe5: /* IPv4 */
+ if (verbose)
+ pr2serr(" IPv4 target descriptor\n");
+ td_list |= TD_IPV4;
+ break;
+ case 0xe6: /* Alias */
+ if (verbose)
+ pr2serr(" Alias target descriptor\n");
+ td_list |= TD_ALIAS;
+ break;
+ case 0xe7: /* RDMA */
+ if (verbose)
+ pr2serr(" RDMA target descriptor\n");
+ td_list |= TD_RDMA;
+ break;
+ case 0xe8: /* FireWire */
+ if (verbose)
+ pr2serr(" IEEE 1394 target descriptor\n");
+ td_list |= TD_FW;
+ break;
+ case 0xe9: /* SAS */
+ if (verbose)
+ pr2serr(" SAS target descriptor\n");
+ td_list |= TD_SAS;
+ break;
+ case 0xea: /* IPv6 */
+ if (verbose)
+ pr2serr(" IPv6 target descriptor\n");
+ td_list |= TD_IPV6;
+ break;
+ case 0xeb: /* IP Copy Service */
+ if (verbose)
+ pr2serr(" IP Copy Service target descriptor\n");
+ td_list |= TD_IP_COPY_SERVICE;
+ break;
+ case 0xfe: /* ROD */
+ if (verbose)
+ pr2serr(" ROD target descriptor\n");
+ td_list |= TD_ROD;
+ break;
+ default:
+ pr2serr(">> Unhandled target descriptor 0x%02x\n",
+ rcBuff[44 + n]);
+ break;
+ }
+ }
+ if (! valid) {
+ pr2serr(">> no matching target descriptor supported\n");
+ td_list = 0;
+ }
+ return td_list;
+}
+
+static void
+decode_designation_descriptor(const uint8_t * bp, int i_len)
+{
+ char c[2048];
+
+ sg_get_designation_descriptor_str(NULL, bp, i_len, 1, verbose,
+ sizeof(c), c);
+ pr2serr("%s", c);
+}
+
+static int
+desc_from_vpd_id(int sg_fd, uint8_t *desc, int desc_len,
+ unsigned int block_size, bool pad)
+{
+ int res, verb;
+ uint8_t rcBuff[256], *bp, *best = NULL;
+ unsigned int len = 254;
+ int off = -1, i_len, best_len = 0, assoc, desig, f_desig = 0;
+ char b[80];
+
+ verb = (verbose ? verbose - 1: 0);
+ memset(rcBuff, 0xff, len);
+ res = sg_ll_inquiry(sg_fd, false, true /* evpd */, VPD_DEVICE_ID, rcBuff,
+ 4, true, verb);
+ if (0 != res) {
+ if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("Device identification VPD page not found\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("VPD inquiry (Device ID): %s\n", b);
+ pr2serr(" try again with '-vv'\n");
+ }
+ return res;
+ } else if (rcBuff[1] != VPD_DEVICE_ID) {
+ pr2serr("invalid VPD response\n");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ len = sg_get_unaligned_be16(rcBuff + 2) + 4;
+ res = sg_ll_inquiry(sg_fd, false, true, VPD_DEVICE_ID, rcBuff, len, true,
+ verb);
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("VPD inquiry (Device ID): %s\n", b);
+ return res;
+ } else if (rcBuff[1] != VPD_DEVICE_ID) {
+ pr2serr("invalid VPD response\n");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (verbose > 2) {
+ pr2serr("Output response in hex:\n");
+ hex2stderr(rcBuff, len, 1);
+ }
+
+ while (sg_vpd_dev_id_iter(rcBuff + 4, len - 4, &off, 0, -1, -1) == 0) {
+ bp = rcBuff + 4 + off;
+ i_len = bp[3];
+ if (((unsigned int)off + i_len + 4) > len) {
+ pr2serr(" VPD page error: designator length %d longer "
+ "than\n remaining response length=%d\n", i_len,
+ (len - off));
+ return SG_LIB_CAT_MALFORMED;
+ }
+ assoc = ((bp[1] >> 4) & 0x3);
+ desig = (bp[1] & 0xf);
+ if (verbose > 2)
+ pr2serr(" Desc %d: assoc %u desig %u len %d\n", off, assoc,
+ desig, i_len);
+ /* Identification descriptor's Designator length must be <= 20. */
+ if (i_len > 20)
+ continue;
+ if (desig == /*NAA=*/3) {
+ best = bp;
+ best_len = i_len;
+ break;
+ }
+ if (desig == /*EUI64=*/2) {
+ if (!best || f_desig < 2) {
+ best = bp;
+ best_len = i_len;
+ f_desig = 2;
+ }
+ } else if (desig == /*T10*/1) {
+ if (!best || f_desig == 0) {
+ best = bp;
+ best_len = i_len;
+ f_desig = desig;
+ }
+ } else if (desig == /*vend.spec.=*/0) {
+ if (!best) {
+ best = bp;
+ best_len = i_len;
+ f_desig = desig;
+ }
+ }
+ }
+ if (best) {
+ if (verbose)
+ decode_designation_descriptor(best, best_len);
+ if (best_len + 4 < desc_len) {
+ memset(desc, 0, 32);
+ desc[0] = 0xe4; /* Identification Descriptor */
+ memcpy(desc + 4, best, best_len + 4);
+ desc[4] &= 0x0f; /* code set */
+ desc[5] &= 0x3f; /* association and designator type */
+ if (pad)
+ desc[28] = 0x4;
+ sg_put_unaligned_be24((uint32_t)block_size, desc + 29);
+ if (verbose > 3) {
+ pr2serr("Descriptor in hex (bs %d):\n", block_size);
+ hex2stderr(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;
+ pr2serr("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))
+ pr2serr(" at %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ }
+}
+
+/* Process arguments given to 'iflag=" or 'oflag=" options. Returns 0
+ * on success, 1 on error. */
+static int
+process_flags(const char * arg, struct xcopy_fp_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff) - 1);
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "flock"))
+ fp->flock = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "pad"))
+ fp->pad = true;
+ else if (0 == strcmp(cp, "xcopy"))
+ fp->xcopy_given = true; /* for ddpt compatibility */
+ else {
+ pr2serr("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 vb)
+{
+ int infd = -1, flags, fl, res, err;
+ char ebuff[EBUFF_SZ];
+
+ ifp->sg_type = dd_filetype(ifp);
+
+ if (vb)
+ pr2serr(" >> 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) {
+ pr2serr(ME "unable access %s\n", ifp->fname);
+ return -SG_LIB_FILE_ERROR;
+ }
+ 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) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %.500s for sg reading", ifp->fname);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ }
+ if (vb)
+ pr2serr(" 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 %.500s "
+ "failed", ifp->fname);
+ perror(ebuff);
+ return -SG_LIB_FLOCK_ERR;
+ }
+ }
+ return infd;
+}
+
+/* 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 vb)
+{
+ int outfd, flags, res, err;
+ char ebuff[EBUFF_SZ];
+
+ ofp->sg_type = dd_filetype(ofp);
+ if (vb)
+ pr2serr(" >> Output file type: %s, devno %d:%d\n",
+ dd_filetype_str(ofp->sg_type, ebuff),
+ major(ofp->devno), minor(ofp->devno));
+
+ if (!(FT_DEV_NULL & ofp->sg_type)) {
+ flags = O_RDWR | O_NONBLOCK;
+ if (ofp->excl)
+ flags |= O_EXCL;
+ if (ofp->append)
+ flags |= O_APPEND;
+ if ((outfd = open(ofp->fname, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %.500s for sg writing", ofp->fname);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ if (vb)
+ pr2serr(" open output(sg_io), flags=0x%x\n", flags);
+ } else
+ outfd = -1; /* don't bother opening */
+ if ((outfd >= 0) && 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 %.500s "
+ "failed", ofp->fname);
+ perror(ebuff);
+ return -SG_LIB_FLOCK_ERR;
+ }
+ }
+ return outfd;
+}
+
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool bpt_given = false;
+ bool list_id_given = false;
+ bool on_src = false;
+ bool on_src_dst_given = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, k, n, keylen, infd, outfd, xcopy_fd;
+ int blocks = 0;
+ int bpt = DEF_BLOCKS_PER_TRANSFER;
+ int dst_desc_len;
+ int ibs = 0;
+ int num_help = 0;
+ int num_xcopy = 0;
+ int obs = 0;
+ int ret = 0;
+ int seg_desc_type;
+ int src_desc_len;
+ int64_t skip = 0;
+ int64_t seek = 0;
+ uint8_t list_id = 1;
+ char * key;
+ char * buf;
+ char str[STR_SZ];
+ uint8_t src_desc[256];
+ uint8_t dst_desc[256];
+
+ ixcf.fname[0] = '\0';
+ oxcf.fname[0] = '\0';
+ ixcf.num_sect = -1;
+ oxcf.num_sect = -1;
+
+ if (argc < 2) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ - 1);
+ str[STR_SZ - 1] = '\0';
+ } else
+ continue;
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = (int)strlen(key);
+ if (0 == strncmp(key, "app", 3)) {
+ ixcf.append = !! sg_get_num(buf);
+ oxcf.append = ixcf.append;
+ } else if (0 == strcmp(key, "bpt")) {
+ bpt = sg_get_num(buf);
+ if (-1 == bpt) {
+ pr2serr(ME "bad argument to 'bpt='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key, "bs")) {
+ blk_sz = sg_get_num(buf);
+ if (-1 == blk_sz) {
+ pr2serr(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) {
+ pr2serr(ME "bad argument to 'list_id='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ list_id = (ret & 0xff);
+ list_id_given = true;
+ } 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 if (!strncmp(buf, "disable", 7))
+ list_id_usage = 3;
+ else {
+ pr2serr(ME "bad argument to 'id_usage='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "conv"))
+ pr2serr(ME ">>> ignoring all 'conv=' arguments\n");
+ else if (0 == strcmp(key, "count")) {
+ if (0 != strcmp("-1", buf)) {
+ dd_count = sg_get_llnum(buf);
+ if (-1LL == dd_count) {
+ pr2serr(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")) {
+ n = sg_get_num(buf);
+ if (n < 0 || n > 1) {
+ pr2serr(ME "bad argument to 'cat='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ xcopy_flag_cat = !! n;
+ } else if (0 == strcmp(key, "dc")) {
+ n = sg_get_num(buf);
+ if (n < 0 || n > 1) {
+ pr2serr(ME "bad argument to 'dc='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ xcopy_flag_dc = !! n;
+ } else if (0 == strcmp(key, "fco")) {
+ n = sg_get_num(buf);
+ if (n < 0 || n > 1) {
+ pr2serr(ME "bad argument to 'fco='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ xcopy_flag_fco = !! n;
+ } else if (0 == strcmp(key, "ibs")) {
+ ibs = sg_get_num(buf);
+ } else if (strcmp(key, "if") == 0) {
+ if ('\0' != ixcf.fname[0]) {
+ pr2serr("Second IFILE argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(ixcf.fname, buf, INOUTF_SZ - 1);
+ ixcf.fname[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (process_flags(buf, &ixcf)) {
+ pr2serr(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' != oxcf.fname[0]) {
+ pr2serr("Second OFILE argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(oxcf.fname, buf, INOUTF_SZ - 1);
+ oxcf.fname[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (process_flags(buf, &oxcf)) {
+ pr2serr(ME "bad argument to 'oflag='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "seek")) {
+ seek = sg_get_llnum(buf);
+ if (-1LL == seek) {
+ pr2serr(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) {
+ pr2serr(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);
+ /* look for long options that start with '--' */
+ else if (0 == strncmp(key, "--help", 6))
+ ++num_help;
+ else if (0 == strncmp(key, "--on_dst", 8)) {
+ on_src = false;
+ if (on_src_dst_given) {
+ pr2serr("Syntax error - either specify --on_src OR "
+ "--on_dst\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ on_src_dst_given = true;
+ } else if (0 == strncmp(key, "--on_src", 8)) {
+ on_src = true;
+ if (on_src_dst_given) {
+ pr2serr("Syntax error - either specify --on_src OR "
+ "--on_dst\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ on_src_dst_given = true;
+ } else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ verbose += 1;
+ } else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else if (0 == strncmp(key, "--xcopy", 7))
+ ; /* ignore; for compatibility with ddpt */
+ /* look for short options that start with a single '-', they can be
+ * concaternated (e.g. '-vvvV') */
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ num_help += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ verbose += n;
+ if (n > 0)
+ verbose_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'x');
+ /* accept and ignore; for compatibility with ddpt */
+ res += n;
+ if (res < (keylen - 1)) {
+ pr2serr(ME "Unrecognised short option in '%s', try "
+ "'--help'\n", key);
+ if (0 == num_help)
+ return -1;
+ }
+ } else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ if (num_help)
+ usage(num_help);
+ else
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (num_help) {
+ usage(num_help);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME "%s\n", version_str);
+ return 0;
+ }
+
+ if (! on_src_dst_given) {
+ if (ixcf.xcopy_given == oxcf.xcopy_given) {
+ char * csp;
+ char * cdp;
+
+ csp = getenv(XCOPY_TO_SRC);
+ cdp = getenv(XCOPY_TO_DST);
+ if ((!! csp) == (!! cdp)) {
+#if DEF_XCOPY_SRC0_DST1 == 0
+ on_src = true;
+#else
+ on_src = false;
+#endif
+ } else if (csp)
+ on_src = true;
+ else
+ on_src = false;
+ } else if (ixcf.xcopy_given)
+ on_src = true;
+ else
+ on_src = false;
+ }
+ if (verbose > 1)
+ pr2serr(" >>> Extended Copy(LID1) command will be sent to %s device "
+ "[%s]\n", (on_src ? "src" : "dst"),
+ (on_src ? ixcf.fname : oxcf.fname));
+
+ if ((ibs && blk_sz && (ibs != blk_sz)) ||
+ (obs && blk_sz && (obs != blk_sz))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (blk_sz && !ibs)
+ ibs = blk_sz;
+ if (blk_sz && !obs)
+ obs = blk_sz;
+
+ if ((skip < 0) || (seek < 0)) {
+ pr2serr("skip and seek cannot be negative\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (oxcf.append && (seek > 0)) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (bpt < 1) {
+ pr2serr("bpt must be greater than 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (bpt > MAX_BLOCKS_PER_TRANSFER) {
+ pr2serr("bpt must be less than or equal to %d\n",
+ MAX_BLOCKS_PER_TRANSFER);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (list_id_usage == 3) { /* list_id usage disabled */
+ if (! list_id_given)
+ list_id = 0;
+ if (list_id) {
+ pr2serr("list_id disabled by id_usage flag\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (verbose > 1)
+ pr2serr(" >>> " ME " if=%s skip=%" PRId64 " of=%s seek=%" PRId64
+ " count=%" PRId64 "\n", ixcf.fname, skip, oxcf.fname, seek,
+ dd_count);
+ install_handler(SIGINT, interrupt_handler);
+ install_handler(SIGQUIT, interrupt_handler);
+ install_handler(SIGPIPE, interrupt_handler);
+ install_handler(SIGUSR1, siginfo_handler);
+
+ ixcf.pdt = -1;
+ oxcf.pdt = -1;
+ if (ixcf.fname[0] && ('-' != ixcf.fname[0])) {
+ infd = open_if(&ixcf, verbose);
+ if (infd < 0)
+ return -infd;
+ } else {
+ pr2serr("stdin not acceptable for IFILE\n");
+ return SG_LIB_FILE_ERROR;
+ }
+
+ if (oxcf.fname[0] && ('-' != oxcf.fname[0])) {
+ outfd = open_of(&oxcf, verbose);
+ if (outfd < -1)
+ return -outfd;
+ } else {
+ pr2serr("stdout not acceptable for OFILE\n");
+ return SG_LIB_FILE_ERROR;
+ }
+
+ res = open_sg(&ixcf, verbose);
+ if (res < 0) {
+ if (-1 == res)
+ return SG_LIB_FILE_ERROR;
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+ res = open_sg(&oxcf, verbose);
+ if (res < 0) {
+ if (-1 == res)
+ return SG_LIB_FILE_ERROR;
+ else
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if ((STDIN_FILENO == infd) && (STDOUT_FILENO == outfd)) {
+ pr2serr("Can't have both 'if' as stdin _and_ 'of' as stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+
+ res = scsi_read_capacity(&ixcf);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention (%s in), continuing\n", read_cap_str);
+ res = scsi_read_capacity(&ixcf);
+ } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+ pr2serr("Aborted command (%s in), continuing\n", read_cap_str);
+ res = scsi_read_capacity(&ixcf);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("%s command not supported on %s\n", read_cap_str,
+ ixcf.fname);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("%s failed on %s - not ready\n", read_cap_str,
+ ixcf.fname);
+ else
+ pr2serr("Unable to %s on %s\n", read_cap_str, ixcf.fname);
+ ixcf.num_sect = -1;
+ } else if (ibs && ixcf.sect_sz != ibs) {
+ pr2serr(">> warning: block size on %s confusion: "
+ "ibs=%d, device claims=%d\n", ixcf.fname, ibs, ixcf.sect_sz);
+ }
+ if (skip && ixcf.num_sect < skip) {
+ pr2serr("argument to 'skip=' exceeds device size (max %" PRId64 ")\n",
+ ixcf.num_sect);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ res = scsi_read_capacity(&oxcf);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention (%s out), continuing\n", read_cap_str);
+ res = scsi_read_capacity(&oxcf);
+ } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+ pr2serr("Aborted command (%s out), continuing\n", read_cap_str);
+ res = scsi_read_capacity(&oxcf);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("%s command not supported on %s\n", read_cap_str,
+ oxcf.fname);
+ else
+ pr2serr("Unable to %s on %s\n", read_cap_str, oxcf.fname);
+ oxcf.num_sect = -1;
+ } else if (obs && obs != oxcf.sect_sz) {
+ pr2serr(">> warning: block size on %s confusion: obs=%d, device "
+ "claims=%d\n", oxcf.fname, obs, oxcf.sect_sz);
+ }
+ if (seek && oxcf.num_sect < seek) {
+ pr2serr("argument to 'seek=' exceeds device size (max %" PRId64 ")\n",
+ oxcf.num_sect);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((dd_count < 0) || ((verbose > 0) && (0 == dd_count))) {
+ if (xcopy_flag_dc == 0) {
+ dd_count = ixcf.num_sect - skip;
+ if ((dd_count * ixcf.sect_sz) >
+ ((oxcf.num_sect - seek) * oxcf.sect_sz))
+ dd_count = (oxcf.num_sect - seek) * oxcf.sect_sz /
+ ixcf.sect_sz;
+ } else {
+ dd_count = oxcf.num_sect - seek;
+ if ((dd_count * oxcf.sect_sz) >
+ ((ixcf.num_sect - skip) * ixcf.sect_sz))
+ dd_count = (ixcf.num_sect - skip) * ixcf.sect_sz /
+ oxcf.sect_sz;
+ }
+ } else {
+ int64_t dd_bytes;
+
+ if (xcopy_flag_dc)
+ dd_bytes = dd_count * oxcf.sect_sz;
+ else
+ dd_bytes = dd_count * ixcf.sect_sz;
+
+ if (dd_bytes > ixcf.num_sect * ixcf.sect_sz) {
+ pr2serr("access beyond end of source device (max %" PRId64 ")\n",
+ ixcf.num_sect);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (dd_bytes > oxcf.num_sect * oxcf.sect_sz) {
+ pr2serr("access beyond end of target device (max %" PRId64 ")\n",
+ oxcf.num_sect);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ res = scsi_operating_parameter(&ixcf, 0);
+ if (res < 0) {
+ if (SG_LIB_CAT_UNIT_ATTENTION == -res) {
+ pr2serr("Unit attention (%s), continuing\n",
+ rec_copy_op_params_str);
+ res = scsi_operating_parameter(&ixcf, 0);
+ }
+ if (-res == SG_LIB_CAT_INVALID_OP) {
+ pr2serr("%s command not supported on %s\n",
+ rec_copy_op_params_str, ixcf.fname);
+ ret = sg_convert_errno(EINVAL);
+ goto fini;
+ } else if (-res == SG_LIB_CAT_NOT_READY)
+ pr2serr("%s failed on %s - not ready\n",
+ rec_copy_op_params_str, ixcf.fname);
+ else {
+ pr2serr("Unable to %s on %s\n", rec_copy_op_params_str,
+ ixcf.fname);
+ ret = -res;
+ goto fini;
+ }
+ } else if (res == 0) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ goto fini;
+ }
+
+ if (res & TD_VPD) {
+ if (verbose)
+ pr2serr(" >> using VPD identification for source %s\n",
+ ixcf.fname);
+ src_desc_len = desc_from_vpd_id(ixcf.sg_fd, src_desc,
+ sizeof(src_desc), ixcf.sect_sz, ixcf.pad);
+ if (src_desc_len > (int)sizeof(src_desc)) {
+ pr2serr("source descriptor too large (%d bytes)\n", res);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ } else {
+ ret = SG_LIB_CAT_INVALID_OP;
+ goto fini;
+ }
+
+ res = scsi_operating_parameter(&oxcf, 1);
+ if (res < 0) {
+ if (SG_LIB_CAT_UNIT_ATTENTION == -res) {
+ pr2serr("Unit attention (%s), continuing\n",
+ rec_copy_op_params_str);
+ res = scsi_operating_parameter(&oxcf, 1);
+ }
+ if (-res == SG_LIB_CAT_INVALID_OP) {
+ pr2serr("%s command not supported on %s\n",
+ rec_copy_op_params_str, oxcf.fname);
+ ret = sg_convert_errno(EINVAL);
+ goto fini;
+ } else if (-res == SG_LIB_CAT_NOT_READY)
+ pr2serr("%s failed on %s - not ready\n",
+ rec_copy_op_params_str, oxcf.fname);
+ else {
+ pr2serr("Unable to %s on %s\n", rec_copy_op_params_str,
+ oxcf.fname);
+ ret = -res;
+ goto fini;
+ }
+ } else if (res == 0) {
+ ret = SG_LIB_CAT_INVALID_OP;
+ goto fini;
+ }
+
+ if (res & TD_VPD) {
+ if (verbose)
+ pr2serr(" >> using VPD identification for destination %s\n",
+ oxcf.fname);
+ dst_desc_len = desc_from_vpd_id(oxcf.sg_fd, dst_desc,
+ sizeof(dst_desc), oxcf.sect_sz, oxcf.pad);
+ if (dst_desc_len > (int)sizeof(dst_desc)) {
+ pr2serr("destination descriptor too large (%d bytes)\n", res);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ } else {
+ ret = SG_LIB_CAT_INVALID_OP;
+ goto fini;
+ }
+
+ if (dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (dd_count < (ixcf.min_bytes / (uint32_t)ixcf.sect_sz)) {
+ pr2serr("not enough data to read (min %" PRIu32 " bytes)\n",
+ oxcf.min_bytes);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (dd_count < (oxcf.min_bytes / (uint32_t)oxcf.sect_sz)) {
+ pr2serr("not enough data to write (min %" PRIu32 " bytes)\n",
+ oxcf.min_bytes);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (bpt_given) {
+ if (xcopy_flag_dc) {
+ if ((uint32_t)(bpt * oxcf.sect_sz) > oxcf.max_bytes) {
+ pr2serr("bpt too large (max %" PRIu32 " blocks)\n",
+ oxcf.max_bytes / (uint32_t)oxcf.sect_sz);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ if ((uint32_t)(bpt * ixcf.sect_sz) > ixcf.max_bytes) {
+ pr2serr("bpt too large (max %" PRIu32 " blocks)\n",
+ ixcf.max_bytes / (uint32_t)ixcf.sect_sz);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ } else {
+ uint32_t r;
+
+ if (xcopy_flag_dc)
+ r = oxcf.max_bytes / (uint32_t)oxcf.sect_sz;
+ else
+ r = ixcf.max_bytes / (uint32_t)ixcf.sect_sz;
+ bpt = (r > MAX_BLOCKS_PER_TRANSFER) ? MAX_BLOCKS_PER_TRANSFER : r;
+ }
+
+ seg_desc_type = seg_desc_from_dd_type(simplified_ft(&ixcf), 0,
+ simplified_ft(&oxcf), 0);
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+
+ if (verbose)
+ pr2serr("Start of loop, count=%" PRId64 ", bpt=%d, lba_in=%" PRId64
+ ", lba_out=%" PRId64 "\n", dd_count, bpt, skip, seek);
+
+ xcopy_fd = (on_src) ? infd : outfd;
+
+ while (dd_count > 0) {
+ if (dd_count > bpt)
+ blocks = bpt;
+ else
+ blocks = dd_count;
+ res = scsi_extended_copy(xcopy_fd, list_id, src_desc, src_desc_len,
+ dst_desc, dst_desc_len, seg_desc_type,
+ blocks, skip, seek);
+ if (res != 0)
+ break;
+ in_full += blocks;
+ skip += blocks;
+ seek += blocks;
+ dd_count -= blocks;
+ num_xcopy++;
+ }
+
+ if (do_time)
+ calc_duration_throughput(0);
+ if (res)
+ pr2serr("sg_xcopy: failed with error %d (%" PRId64 " blocks left)\n",
+ res, dd_count);
+ else
+ pr2serr("sg_xcopy: %" PRId64 " blocks, %d command%s\n", in_full,
+ num_xcopy, ((num_xcopy > 1) ? "s" : ""));
+ ret = res;
+
+fini:
+ /* file handles not explicitly closed; let process cleanup do that */
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_xcopy failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_z_act_query.c b/src/sg_z_act_query.c
new file mode 100644
index 00000000..372c27c9
--- /dev/null
+++ b/src/sg_z_act_query.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 2014-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ *
+ * This program issues either a SCSI ZONE ACTIVATE command or a ZONE QUERY
+ * command to the given SCSI device. Based on zbc2r12.pdf .
+ */
+
+static const char * version_str = "1.04 20220729";
+
+#define SG_ZBC_IN_CMDLEN 16
+#define Z_ACTIVATE_SA 0x8
+#define Z_QUERY_SA 0x9
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+#define DEF_ALLOC_LEN 8192
+#define Z_ACT_DESC_LEN 32
+#define MAX_ACT_QUERY_BUFF_LEN (16 * 1024 * 1024)
+
+struct opts_t {
+ bool do_all;
+ bool do_activate;
+ bool do_force;
+ bool do_query;
+ bool do_raw;
+ bool maxlen_given;
+ uint8_t other_zdid;
+ uint16_t max_alloc;
+ uint16_t num_zones;
+ int hex_count;
+ int vb;
+ uint64_t st_lba; /* Zone ID */
+ const char * device_name;
+ const char * inhex_fn;
+};
+
+static struct option long_options[] = {
+ {"activate", no_argument, 0, 'A'},
+ {"all", no_argument, 0, 'a'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"in", required_argument, 0, 'i'}, /* silent, same as --inhex= */
+ {"inhex", required_argument, 0, 'i'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"num", required_argument, 0, 'n'},
+ {"other", required_argument, 0, 'o'},
+ {"query", no_argument, 0, 'q'},
+ {"raw", no_argument, 0, 'r'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"zone", required_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_z_act_query [--activate] [--all] [--force] [--help] "
+ "[--hex]\n"
+ " [--inhex=FN] [--maxlen=LEN] [--num=ZS] "
+ "[--other=ZDID]\n"
+ " [--query] [--raw] [--verbose] "
+ "[--version]\n"
+ " [--zone=ID] DEVICE\n");
+ pr2serr(" where:\n"
+ " --activate|-A do ZONE ACTIVATE command (def: ZONE "
+ "QUERY)\n"
+ " --all|-a sets the ALL flag in the cdb\n"
+ " --force|-f bypass some sanity checks\n"
+ " --help|-h print out usage message\n"
+ " --hex|-H print out response in hexadecimal\n"
+ " --inhex=FN|-i FN decode contents of FN, ignore DEVICE\n"
+ " --maxlen=LEN|-m LEN LEN place in cdb's allocation "
+ "length field\n"
+ " (def: 8192 (bytes))\n"
+ " --num=ZS|-n ZS ZS is the number of zones and is placed "
+ "in the cdb;\n"
+ " default value is 1, ignored if --all "
+ "given\n"
+ " --other=ZDID|-o ZDID ZDID is placed in Other zone domain "
+ "ID field\n"
+ " --query|-q do ZONE QUERY command (def: ZONE "
+ "QUERY)\n"
+ " --raw|-r output response in binary, or if "
+ "--inhex=FN is\n"
+ " given, then FN's contents are binary\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --zone=ID|-z ID ID is the starting LBA of the zone "
+ "(def: 0)\n\n"
+ "Performs either a SCSI ZONE ACTIVATE command, or a ZONE QUERY "
+ "command.\nArguments to options are decimal by default, for hex "
+ "use a leading '0x'\nor a trailing 'h'. The default action is to "
+ "send a ZONE QUERY command.\n");
+}
+
+/* Invokes a ZBC IN command (with either a ZONE ACTIVATE or a ZONE QUERY
+ * service action). Return of 0 -> success, various SG_LIB_CAT_* positive
+ * values or -1 -> other errors */
+static int
+sg_ll_zone_act_query(int sg_fd, const struct opts_t * op, void * resp,
+ int * residp)
+{
+ uint8_t sa = op->do_activate ? Z_ACTIVATE_SA : Z_QUERY_SA;
+ int ret, res, sense_cat;
+ struct sg_pt_base * ptvp;
+ uint8_t zi_cdb[SG_ZBC_IN_CMDLEN] =
+ {SG_ZBC_IN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ char b[64];
+
+ zi_cdb[1] = 0x1f & sa;
+ if (op->do_all)
+ zi_cdb[1] |= 0x80;
+
+ sg_put_unaligned_be64(op->st_lba, zi_cdb + 2);
+ sg_put_unaligned_be16(op->num_zones, zi_cdb + 10);
+ sg_put_unaligned_be16(op->max_alloc, zi_cdb + 12);
+ zi_cdb[14] = op->other_zdid;
+ sg_get_opcode_sa_name(zi_cdb[0], sa, -1, sizeof(b), b);
+ if (op->vb) {
+ char d[128];
+
+ pr2serr(" %s cdb: %s\n", b,
+ sg_get_command_str(zi_cdb, SG_ZBC_IN_CMDLEN, false,
+ sizeof(d), d));
+ }
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", b);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, zi_cdb, sizeof(zi_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, op->max_alloc);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, op->vb);
+ ret = sg_cmds_process_resp(ptvp, b, res, true /* noisy */,
+ op->vb, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ if (residp)
+ *residp = get_scsi_pt_resid(ptvp);
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+static const char *
+zone_condition_str(int zc, char * b, int blen, int vb)
+{
+ const char * cp;
+
+ if (NULL == b)
+ return "zone_condition_str: NULL ptr)";
+ switch (zc) {
+ case 0:
+ cp = "Not write pointer";
+ break;
+ case 1:
+ cp = "Empty";
+ break;
+ case 2:
+ cp = "Implicitly opened";
+ break;
+ case 3:
+ cp = "Explicitly opened";
+ break;
+ case 4:
+ cp = "Closed";
+ break;
+ case 5:
+ cp = "Inactive";
+ break;
+ case 0xd:
+ cp = "Read only";
+ break;
+ case 0xe:
+ cp = "Full";
+ break;
+ case 0xf:
+ cp = "Offline";
+ break;
+ default:
+ cp = NULL;
+ break;
+ }
+ if (cp) {
+ if (vb)
+ snprintf(b, blen, "%s [0x%x]", cp, zc);
+ else
+ snprintf(b, blen, "%s", cp);
+ } else
+ snprintf(b, blen, "Reserved [0x%x]", zc);
+ return b;
+}
+
+/* The allocation length field in each cdb cannot be less than 64 but the
+ * transport could still trim the response. */
+static int
+decode_z_act_query(const uint8_t * ziBuff, int act_len, uint32_t zar_len,
+ const struct opts_t * op)
+{
+ uint8_t zt;
+ int k, zc, num_desc;
+ const uint8_t * bp;
+ char b[80];
+
+ if ((uint32_t)act_len < zar_len) {
+ num_desc = (act_len >= 64) ? ((act_len - 64) / Z_ACT_DESC_LEN) : 0;
+ if (act_len == op->max_alloc) {
+ if (op->maxlen_given)
+ pr2serr("response length [%u bytes] may be constrained by "
+ "given --maxlen value, try increasing\n", zar_len);
+ else
+ pr2serr("perhaps --maxlen=%u needs to be used\n", zar_len);
+ } else if (op->inhex_fn)
+ pr2serr("perhaps %s has been truncated\n", op->inhex_fn);
+ } else
+ num_desc = (zar_len - 64) / Z_ACT_DESC_LEN;
+ if (act_len <= 8)
+ return 0;
+ if (0x80 & ziBuff[8]) {
+ printf(" Nz_valid=1\n");
+ if (act_len > 19)
+ printf(" Number of zones: %u\n",
+ sg_get_unaligned_be32(ziBuff + 16));
+ } else
+ printf(" Nz_valid=0\n");
+ if (0x40 & ziBuff[8]) {
+ printf(" Ziwup_valid=1\n");
+ if (act_len > 31)
+ printf(" Zone ID with unmet prerequisite: 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(ziBuff + 24));
+ } else
+ printf(" Ziwup_valid=0\n");
+ printf(" Activated=%d\n", (0x1 & ziBuff[8]));
+ if (act_len <= 9)
+ return 0;
+ printf(" Unmet prerequisites:\n");
+ if (0 == ziBuff[9])
+ printf(" none\n");
+ else {
+ if (0x40 & ziBuff[9])
+ printf(" security\n");
+ if (0x20 & ziBuff[9])
+ printf(" mult domn\n");
+ if (0x10 & ziBuff[9])
+ printf(" rlm rstct\n");
+ if (0x8 & ziBuff[9])
+ printf(" mult ztyp\n");
+ if (0x4 & ziBuff[9])
+ printf(" rlm align\n");
+ if (0x2 & ziBuff[9])
+ printf(" not empty\n");
+ if (0x1 & ziBuff[9])
+ printf(" not inact\n");
+ }
+ if (act_len <= 10)
+ return 0;
+ printf(" Other zone domain ID: %u\n", ziBuff[10]);
+ if (act_len <= 11)
+ return 0;
+ printf(" All: %d\n", (0x1 & ziBuff[11]));
+
+ if (((uint32_t)act_len < zar_len) &&
+ ((num_desc * Z_ACT_DESC_LEN) + 64 > act_len)) {
+ pr2serr("Skip due to truncated response, try using --num= to a "
+ "value less than %d\n", num_desc);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ for (k = 0, bp = ziBuff + 64; k < num_desc; ++k, bp += Z_ACT_DESC_LEN) {
+ printf(" Zone activation descriptor: %d\n", k);
+ if (op->hex_count) {
+ hex2stdout(bp, Z_ACT_DESC_LEN, -1);
+ continue;
+ }
+ zt = bp[0] & 0xf;
+ zc = (bp[1] >> 4) & 0xf;
+ printf(" Zone type: %s\n", sg_get_zone_type_str(zt, sizeof(b),
+ b));
+ printf(" Zone condition: %s\n", zone_condition_str(zc, b,
+ sizeof(b), op->vb));
+ printf(" Zone domain ID: %u\n", bp[2]);
+ printf(" Zone range size: %" PRIu64 "\n",
+ sg_get_unaligned_be64(bp + 8));
+ printf(" Starting zone locator: 0x%" PRIx64 "\n",
+ sg_get_unaligned_be64(bp + 16));
+ }
+ return 0;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool no_final_msg = false;
+ bool version_given = false;
+ int res, c, n, in_len, rlen, act_len;
+ int sg_fd = -1;
+ int resid = 0;
+ int verbose = 0;
+ int ret = 0;
+ uint32_t zar_len, zarr_len;
+ int64_t ll;
+ uint8_t * ziBuff = NULL;
+ uint8_t * free_zibp = NULL;
+ const char * sa_name;
+ char b[80];
+ struct opts_t opts SG_C_CPP_ZERO_INIT;
+ struct opts_t * op = &opts;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "aAfhHi:m:n:o:qrvVz:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ op->do_all = true;
+ break;
+ case 'A':
+ op->do_activate = true;
+ break;
+ case 'f':
+ op->do_force = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++op->hex_count;
+ break;
+ case 'i':
+ op->inhex_fn = optarg;
+ break;
+ case 'm':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("--maxlen= expects an argument between 0 and 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->maxlen_given = true;
+ op->max_alloc = (uint16_t)n;
+ break;
+ case 'n':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("--num=ZS expects an argument between 0 and 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->num_zones = (uint16_t)n;
+ break;
+ case 'o':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xff)) {
+ pr2serr("--other=ZDID expects an argument between 0 and 0xff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->other_zdid = (uint8_t)n;
+ break;
+ case 'q':
+ op->do_query = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'v':
+ ++op->vb;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'z':
+ if ((2 == strlen(optarg)) && (0 == memcmp("-1", optarg, 2))) {
+ op->st_lba = UINT64_MAX;
+ break;
+ }
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--zone=ID'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->st_lba = (uint64_t)ll; /* Zone ID is starting LBA */
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if ((! op->do_all) && (0 == op->num_zones))
+ op->num_zones = 1;
+ if (op->do_activate && op->do_query){
+ pr2serr("only one of these options: --activate and --query may be "
+ "given\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ sa_name = op->do_activate ? "Zone activate" : "Zone query";
+ if (op->device_name && op->inhex_fn) {
+ pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
+ "not both\n");
+ op->device_name = NULL;
+ }
+ if (op->max_alloc < 4) {
+ if (op->max_alloc > 0)
+ pr2serr("Won't accept --maxlen= of 1, 2 or 3, using %d "
+ "instead\n", DEF_ALLOC_LEN);
+ op->max_alloc = DEF_ALLOC_LEN;
+ }
+ ziBuff = (uint8_t *)sg_memalign(op->max_alloc, 0, &free_zibp, op->vb > 3);
+ if (NULL == ziBuff) {
+ pr2serr("unable to sg_memalign %d bytes\n", op->max_alloc);
+ return sg_convert_errno(ENOMEM);
+ }
+
+ if (NULL == op->device_name) {
+ if (op->inhex_fn) {
+ if ((ret = sg_f2hex_arr(op->inhex_fn, op->do_raw, false, ziBuff,
+ &in_len, op->max_alloc))) {
+ if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
+ no_final_msg = true;
+ pr2serr("... decode what we have, --maxlen=%d needs to "
+ "be increased\n", op->max_alloc);
+ } else
+ goto the_end;
+ }
+ if (verbose > 2)
+ pr2serr("Read %d [0x%x] bytes of user supplied data\n",
+ in_len, in_len);
+ if (op->do_raw)
+ op->do_raw = false; /* can interfere on decode */
+ if (in_len < 4) {
+ pr2serr("--inhex=%s only decoded %d bytes (needs 4 at "
+ "least)\n", op->inhex_fn, in_len);
+ ret = SG_LIB_SYNTAX_ERROR;
+ goto the_end;
+ }
+ res = 0;
+ goto start_response;
+ } else {
+ pr2serr("missing device name!\n\n");
+ usage();
+ ret = SG_LIB_FILE_ERROR;
+ no_final_msg = true;
+ goto the_end;
+ }
+ } else
+ in_len = 0;
+
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ sg_fd = sg_cmds_open_device(op->device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ int err = -sg_fd;
+ if (verbose)
+ pr2serr("open error: %s: %s\n", op->device_name,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto the_end;
+ }
+
+ res = sg_ll_zone_act_query(sg_fd, op, ziBuff, &resid);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", sa_name);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", sa_name, b);
+ }
+ }
+
+start_response:
+ if (0 == res) {
+ if ((resid < 0) || (resid > op->max_alloc)) {
+ pr2serr("Unexpected resid=%d\n", resid);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ rlen = op->inhex_fn ? in_len : (op->max_alloc - resid);
+ if (rlen < 4) {
+ pr2serr("Decoded response length (%d) too short\n", rlen);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ zar_len = sg_get_unaligned_be32(ziBuff + 0) + 64;
+ zarr_len = sg_get_unaligned_be32(ziBuff + 4) + 64;
+ if ((zar_len > MAX_ACT_QUERY_BUFF_LEN) ||
+ (zarr_len > MAX_ACT_QUERY_BUFF_LEN) || (zarr_len > zar_len)) {
+ if (! op->do_force) {
+ pr2serr("zar or zarr length [%u/%u bytes] seems wild, use "
+ "--force override\n", zar_len, zarr_len);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ }
+ if (zarr_len > (uint32_t)rlen) {
+ pr2serr("zarr response length is %u bytes, but system "
+ "reports %d bytes received??\n", zarr_len, rlen);
+ if (op->do_force)
+ act_len = rlen;
+ else {
+ pr2serr("Exiting, use --force to override\n");
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ } else
+ act_len = zarr_len;
+ if (op->do_raw) {
+ dStrRaw(ziBuff, act_len);
+ goto the_end;
+ }
+ if (op->hex_count && (2 != op->hex_count)) {
+ hex2stdout(ziBuff, act_len, ((1 == op->hex_count) ? 1 : -1));
+ goto the_end;
+ }
+ printf("%s response:\n", sa_name);
+ if (act_len < 64) {
+ pr2serr("Zone length [%d] too short (perhaps after truncation\n)",
+ act_len);
+ ret = SG_LIB_CAT_MALFORMED;
+ goto the_end;
+ }
+ ret = decode_z_act_query(ziBuff, act_len, zar_len, op);
+ } else if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", sa_name);
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, op->vb);
+ pr2serr("%s command: %s\n", sa_name, b);
+ }
+
+the_end:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (free_zibp)
+ free(free_zibp);
+ if ((0 == verbose) && (! no_final_msg)) {
+ if (! sg_if_can2stderr("sg_z_act_query failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_zone.c b/src/sg_zone.c
new file mode 100644
index 00000000..6da6d0ac
--- /dev/null
+++ b/src/sg_zone.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2014-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_pt.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ *
+ * This program issues one of the following SCSI commands:
+ * - CLOSE ZONE
+ * - FINISH ZONE
+ * - OPEN ZONE
+ * - REMOVE ELEMENT AND MODIFY ZONES
+ * - SEQUENTIALIZE ZONE
+ */
+
+static const char * version_str = "1.18 20220609";
+
+#define SG_ZONING_OUT_CMDLEN 16
+#define CLOSE_ZONE_SA 0x1
+#define FINISH_ZONE_SA 0x2
+#define OPEN_ZONE_SA 0x3
+#define SEQUENTIALIZE_ZONE_SA 0x10
+#define REM_ELEM_MOD_ZONES_SA 0x1a /* uses SERVICE ACTION IN(16) */
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"all", no_argument, 0, 'a'},
+ {"close", no_argument, 0, 'c'},
+ {"count", required_argument, 0, 'C'},
+ {"element", required_argument, 0, 'e'},
+ {"finish", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"open", no_argument, 0, 'o'},
+ {"quick", no_argument, 0, 'q'},
+ {"remove", no_argument, 0, 'r'},
+ {"reset-all", no_argument, 0, 'R'}, /* same as --all */
+ {"reset_all", no_argument, 0, 'R'},
+ {"sequentialize", no_argument, 0, 'S'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"zone", required_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+/* Indexed by service action of opcode 0x94 (Zone out) unless noted */
+static const char * sa_name_arr[] = {
+ "no SA=0", /* 0x0 */
+ "Close zone",
+ "Finish zone",
+ "Open zone",
+ "-", "-", "-", "-",
+ "-",
+ "-", "-", "-", "-",
+ "-",
+ "-",
+ "-",
+ "Sequentialize zone", /* 0x10 */
+ "-", "-", "-", "-",
+ "-", "-", "-", "-",
+ "-",
+ "Remove element and modify zones", /* service action in(16), 0x1a */
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_zone [--all] [--close] [--count=ZC] [--element=EID] "
+ "[--finish]\n"
+ " [--help] [--open] [--quick] [--remove] "
+ "[--sequentialize]\n"
+ " [--verbose] [--version] [--zone=ID] DEVICE\n");
+ pr2serr(" where:\n"
+ " --all|-a sets the ALL flag in the cdb\n"
+ " --close|-c issue CLOSE ZONE command\n"
+ " --count=ZC|-C ZC set zone count field (def: 0)\n"
+ " --element=EID|-e EID EID is the element identifier to "
+ "remove;\n"
+ " default is 0 which is an invalid "
+ "EID\n"
+ " --finish|-f issue FINISH ZONE command\n"
+ " --help|-h print out usage message\n"
+ " --open|-o issue OPEN ZONE command\n"
+ " --quick|-q bypass 15 second warn and wait "
+ "(for --remove)\n"
+ " --remove|-r issue REMOVE ELEMENT AND MODIFY ZONES "
+ "command\n"
+ " --sequentialize|-S issue SEQUENTIALIZE ZONE command\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --zone=ID|-z ID ID is the starting LBA of the zone "
+ "(def: 0)\n\n"
+ "Performs a SCSI OPEN ZONE, CLOSE ZONE, FINISH ZONE, "
+ "REMOVE ELEMENT AND\nMODIFY ZONES or SEQUENTIALIZE ZONE "
+ "command. Either --close, --finish,\n--open, --remove or "
+ "--sequentialize option needs to be given.\n");
+}
+
+/* Invokes the zone out command indicated by 'sa' (ZBC). Return of 0
+ * -> success, various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_zone_out(int sg_fd, int sa, uint64_t zid, uint16_t zc, bool all,
+ bool noisy, int verbose)
+{
+ int ret, res, sense_cat;
+ struct sg_pt_base * ptvp;
+ uint8_t zo_cdb[SG_ZONING_OUT_CMDLEN] =
+ {SG_ZONING_OUT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ char b[64];
+
+ zo_cdb[1] = 0x1f & sa;
+ if (REM_ELEM_MOD_ZONES_SA == sa) { /* zid carries element identifier */
+ zo_cdb[0] = SG_SERVICE_ACTION_IN_16; /* N.B. changing opcode */
+ sg_put_unaligned_be32((uint32_t)zid, zo_cdb + 10); /* element id */
+ } else {
+ sg_put_unaligned_be64(zid, zo_cdb + 2);
+ sg_put_unaligned_be16(zc, zo_cdb + 12);
+ if (all)
+ zo_cdb[14] = 0x1;
+ }
+ sg_get_opcode_sa_name(zo_cdb[0], sa, -1, sizeof(b), b);
+ if (verbose) {
+ char d[128];
+
+ pr2serr(" %s cdb: %s\n", b,
+ sg_get_command_str(zo_cdb, SG_ZONING_OUT_CMDLEN,
+ false, sizeof(d), d));
+ }
+
+ ptvp = construct_scsi_pt_obj();
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", b);
+ return -1;
+ }
+ set_scsi_pt_cdb(ptvp, zo_cdb, sizeof(zo_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, b, res, noisy,
+ verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else
+ ret = 0;
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool all = false;
+ bool close = false;
+ bool finish = false;
+ bool open = false;
+ bool quick = false;
+ bool reamz = false;
+ bool element_id_given = false;
+ bool sequentialize = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c, n;
+ int sg_fd = -1;
+ int verbose = 0;
+ int ret = 0;
+ int sa = 0;
+ uint16_t zc = 0;
+ uint64_t zid = 0;
+ int64_t ll;
+ const char * device_name = NULL;
+ const char * sa_name;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "acC:e:fhoqrRSvVz:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ case 'R':
+ all = true;
+ break;
+ case 'c':
+ close = true;
+ sa = CLOSE_ZONE_SA;
+ break;
+ case 'C':
+ n = sg_get_num(optarg);
+ if ((n < 0) || (n > 0xffff)) {
+ pr2serr("--count= expects an argument between 0 and 0xffff "
+ "inclusive\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ zc = (uint16_t)n;
+ break;
+ case 'e':
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--element=EID'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == ll)
+ pr2serr("Warning: 0 is an invalid element identifier\n");
+ zid = (uint64_t)ll; /* putting element_id in zid */
+ element_id_given = true;
+ break;
+ case 'f':
+ finish = true;
+ sa = FINISH_ZONE_SA;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'o':
+ open = true;
+ sa = OPEN_ZONE_SA;
+ break;
+ case 'q':
+ quick = true;
+ break;
+ case 'r':
+ reamz = true;
+ sa = REM_ELEM_MOD_ZONES_SA;
+ break;
+ case 'S':
+ sequentialize = true;
+ sa = SEQUENTIALIZE_ZONE_SA;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'z':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("bad argument to '--zone=ID'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ zid = (uint64_t)ll;
+ break;
+ default:
+ pr2serr("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)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (1 != ((int)close + (int)finish + (int)open + (int)sequentialize +
+ (int)reamz)) {
+ pr2serr("One, and only one, of these options needs to be given:\n"
+ " --close, --finish, --open, --remove or --sequentialize "
+ "\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if (element_id_given && (! reamz)) {
+ pr2serr("The --element=EID option should only be used with the "
+ "--remove option\n\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ sa_name = sa_name_arr[sa];
+
+ if (NULL == device_name) {
+ pr2serr("missing device name!\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
+ if (sg_fd < 0) {
+ int err = -sg_fd;
+ if (verbose)
+ pr2serr("open error: %s: %s\n", device_name,
+ safe_strerror(err));
+ ret = sg_convert_errno(err);
+ goto fini;
+ }
+ if (reamz && (! quick))
+ sg_warn_and_wait(sa_name_arr[REM_ELEM_MOD_ZONES_SA], device_name,
+ false);
+
+ res = sg_ll_zone_out(sg_fd, sa, zid, zc, all, true, verbose);
+ ret = res;
+ if (res) {
+ if (SG_LIB_CAT_INVALID_OP == res)
+ pr2serr("%s command not supported\n", sa_name);
+ else {
+ char b[80];
+
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s command: %s\n", sa_name, b);
+ }
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_zone failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' or '-vv' for "
+ "more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sginfo.c b/src/sginfo.c
new file mode 100644
index 00000000..0937e689
--- /dev/null
+++ b/src/sginfo.c
@@ -0,0 +1,3999 @@
+/* * This program reads various mode pages and bits of other
+ * information from a scsi device and interprets the raw data for you
+ * with a report written to stdout. Usage:
+ *
+ * ./sginfo [options] /dev/sg2 [replace parameters]
+ *
+ * Options are:
+ * -6 do 6 byte mode sense + select (default: 10 byte)
+ * -a display all mode pages reported by the device: equivalent to '-t 63'.
+ * -A display all mode pages and subpages reported by the device: equivalent
+ * to '-t 63,255'.
+ * -c access Cache control page.
+ * -C access Control Page.
+ * -d display defect lists (default format: index).
+ * -D access disconnect-reconnect page.
+ * -e access Read-Write error recovery page.
+ * -E access Control Extension page.
+ * -f access Format Device Page.
+ * -Farg defect list format (-Flogical, -flba64, -Fphysical, -Findex, -Fhead)
+ * -g access rigid disk geometry page.
+ * -G display only "grown" defect list (default format: index)
+ * -i display information from Inquiry command.
+ * -I access Informational Exceptions page.
+ * -l list known scsi devices on the system [deprecated]
+ * -n access notch parameters page.
+ * -N Negate (stop) storing to saved page (active with -R)
+ * -P access Power Condition Page.
+ * -r list known raw scsi devices on the system
+ * -s display serial number (from INQUIRY VPD page)
+ * -t <n[,spn]> access page number <n> [and subpage <spn>], try to decode
+ * -u <n[,spn]> access page number <n> [and subpage <spn>], output in hex
+ * -v show this program's version number
+ * -V access Verify Error Recovery Page.
+ * -T trace commands (for debugging, double for more debug)
+ * -z do a single fetch for mode pages (rather than double fetch)
+ *
+ * Only one of the following three options can be specified.
+ * None of these three implies the current values are returned.
+ * -m Display modifiable fields instead of current values
+ * -M Display manufacturer defaults instead of current values
+ * -S Display saved defaults instead of current values
+ *
+ * -X Display output values in a list.
+ * -R Replace parameters - best used with -X
+ *
+ * Eric Youngdale - 11/1/93. Version 1.0.
+ *
+ * Version 1.1: Ability to change parameters on cache page, support for
+ * X front end.
+ *
+ * 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 distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Michael Weller (eowmob at exp-math dot uni-essen dot de)
+ * 11/23/94 massive extensions from 1.4a
+ * 08/23/97 fix problems with defect lists
+ *
+ * Douglas Gilbert (dgilbert at interlog dot com)
+ * 990628 port to sg .... (version 1.81)
+ * up 4KB limit on defect list to 32KB
+ * 'sginfo -l' also shows sg devices and mapping to other
+ * scsi devices
+ * 'sginfo' commands can take either an sd, sr (scd), st
+ * or an sg device (all non-sg devices converted to a
+ * sg device)
+ *
+ * 001208 Add Kurt Garloff's "-uno" flag for displaying info
+ * from a page number. [version 1.90]
+ *
+ * Kurt Garloff
+ * 20000715 allow displaying and modification of vendor specific pages
+ * (unformatted - @ hexdatafield)
+ * accept vendor lengths for those pages
+ * enabled page saving
+ * cleaned parameter parsing a bit (it's still a terrible mess!)
+ * Use sr (instead of scd) and sg%d (instead of sga,b,...) in -l
+ * and support much more devs in -l (incl. nosst)
+ * Fix segfault in defect list (len=0xffff) and adapt formatting
+ * to large disks. Support up to 256kB defect lists with
+ * 0xB7 (12byte) command if necessary and fallback to 0x37
+ * (10byte) in case of failure. Report truncation.
+ * sizeof(buffer) (which is sizeof(char*) == 4 or 32 bit archs)
+ * was used incorrectly all over the place. Fixed.
+ * [version 1.95]
+ * Douglas Gilbert (dgilbert at interlog dot com)
+ * 20020113 snprintf() type cleanup [version 1.96]
+ * 20021211 correct sginfo MODE_SELECT, protect against block devices
+ * that answer sg's ioctls. [version 1.97]
+ * 20021228 scan for some "scd<n>" as well as "sr<n>" device names [1.98]
+ * 20021020 Update control page [1.99]
+ *
+ * Thomas Steudten (thomas at steudten dot com)
+ * 20040521 add -Fhead feature [version 2.04]
+ *
+ * Tim Hunt (tim at timhunt dot net)
+ * 20050427 increase number of mapped SCSI disks devices
+ *
+ * Dave Johnson (djj at ccv dot brown dot edu)
+ * 20051218 improve disk defect list handling
+ */
+
+
+/*
+ * N.B. This utility is in maintenance mode only. This means that serious
+ * bugs will be fixed but no new features or mode page changes will be
+ * added. Please use the sdparm utility. D. Gilbert 20090316
+ */
+
+#define _XOPEN_SOURCE 500
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+static const char * version_str = "2.45 [20220425]";
+
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <ctype.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_io_linux.h"
+
+
+static int glob_fd;
+static char *device_name;
+
+#define MAX_SG_DEVS 8192
+#define MAX_RESP6_SIZE 252
+#define MAX_RESP10_SIZE (4*1024)
+#define MAX_BUFFER_SIZE MAX_RESP10_SIZE
+
+#define INQUIRY_RESP_INITIAL_LEN 36
+#define MAX_INQFIELD_LEN 17
+
+#define MAX_HEADS 127
+#define HEAD_SORT_TOKEN 0x55
+
+#define SIZEOF_BUFFER (16*1024)
+#define SIZEOF_BUFFER1 (16*1024)
+static uint8_t cbuffer[SIZEOF_BUFFER];
+static uint8_t cbuffer1[SIZEOF_BUFFER1];
+static uint8_t cbuffer2[SIZEOF_BUFFER1];
+
+static char defect = 0;
+static char defectformat = 0x4;
+static char grown_defect = 0;
+static char negate_sp_bit = 0;
+static char replace = 0;
+static char serial_number = 0;
+static char x_interface = 0;
+static char single_fetch = 0;
+
+static char mode6byte = 0; /* defaults to 10 byte mode sense + select */
+static char trace_cmd = 0;
+
+struct mpage_info {
+ int page;
+ int subpage;
+ int page_control;
+ int peri_type;
+ int inq_byte6; /* EncServ and MChngr bits of interest */
+ int resp_len;
+};
+
+/* declarations of functions decoding known mode pages */
+static int common_disconnect_reconnect(struct mpage_info * mpi,
+ const char * prefix);
+static int common_control(struct mpage_info * mpi, const char * prefix);
+static int common_control_extension(struct mpage_info * mpi,
+ const char * prefix);
+static int common_proto_spec_lu(struct mpage_info * mpi, const char * prefix);
+static int common_proto_spec_port(struct mpage_info * mpi,
+ const char * prefix);
+static int common_proto_spec_port_sp1(struct mpage_info * mpi,
+ const char * prefix);
+static int common_proto_spec_port_sp2(struct mpage_info * mpi,
+ const char * prefix);
+static int common_power_condition(struct mpage_info * mpi,
+ const char * prefix);
+static int common_informational(struct mpage_info * mpi, const char * prefix);
+static int disk_error_recovery(struct mpage_info * mpi, const char * prefix);
+static int disk_format(struct mpage_info * mpi, const char * prefix);
+static int disk_verify_error_recovery(struct mpage_info * mpi,
+ const char * prefix);
+static int disk_geometry(struct mpage_info * mpi, const char * prefix);
+static int disk_notch_parameters(struct mpage_info * mpi, const char * prefix);
+static int disk_cache(struct mpage_info * mpi, const char * prefix);
+static int disk_xor_control(struct mpage_info * mpi, const char * prefix);
+static int disk_background(struct mpage_info * mpi, const char * prefix);
+static int optical_memory(struct mpage_info * mpi, const char * prefix);
+static int cdvd_error_recovery(struct mpage_info * mpi, const char * prefix);
+static int cdvd_mrw(struct mpage_info * mpi, const char * prefix);
+static int cdvd_write_param(struct mpage_info * mpi, const char * prefix);
+static int cdvd_audio_control(struct mpage_info * mpi, const char * prefix);
+static int cdvd_timeout(struct mpage_info * mpi, const char * prefix);
+static int cdvd_device_param(struct mpage_info * mpi, const char * prefix);
+static int cdvd_cache(struct mpage_info * mpi, const char * prefix);
+static int cdvd_mm_capab(struct mpage_info * mpi, const char * prefix);
+static int cdvd_feature(struct mpage_info * mpi, const char * prefix);
+static int tape_data_compression(struct mpage_info * mpi, const char * prefix);
+static int tape_dev_config(struct mpage_info * mpi, const char * prefix);
+static int tape_medium_part1(struct mpage_info * mpi, const char * prefix);
+static int tape_medium_part2_4(struct mpage_info * mpi, const char * prefix);
+static int ses_services_manag(struct mpage_info * mpi, const char * prefix);
+static int spi4_training_config(struct mpage_info * mpi, const char * prefix);
+static int spi4_negotiated(struct mpage_info * mpi, const char * prefix);
+static int spi4_report_xfer(struct mpage_info * mpi, const char * prefix);
+
+enum page_class {PC_COMMON, PC_DISK, PC_TAPE, PC_CDVD, PC_SES, PC_SMC};
+
+struct mpage_name_func {
+ int page;
+ int subpage;
+ enum page_class pg_class;
+ const char * name;
+ int (*func)(struct mpage_info *, const char *);
+};
+
+#define MP_LIST_PAGES 0x3f
+#define MP_LIST_SUBPAGES 0xff
+
+static struct mpage_name_func mpage_common[] =
+{
+ { 0, 0, PC_COMMON, "Vendor (non-page format)", NULL},
+ { 2, 0, PC_COMMON, "Disconnect-Reconnect", common_disconnect_reconnect},
+ { 9, 0, PC_COMMON, "Peripheral device (obsolete)", NULL},
+ { 0xa, 0, PC_COMMON, "Control", common_control},
+ { 0xa, 1, PC_COMMON, "Control Extension", common_control_extension},
+ { 0x15, 0, PC_COMMON, "Extended", NULL},
+ { 0x16, 0, PC_COMMON, "Extended, device-type specific", NULL},
+ { 0x18, 0, PC_COMMON, "Protocol specific lu", common_proto_spec_lu},
+ { 0x19, 0, PC_COMMON, "Protocol specific port", common_proto_spec_port},
+ { 0x19, 1, PC_COMMON, "Protocol specific port, subpage 1 overload",
+ common_proto_spec_port_sp1},
+ { 0x19, 2, PC_COMMON, "Protocol specific port, subpage 2 overload",
+ common_proto_spec_port_sp2},
+/* { 0x19, 2, PC_COMMON, "SPI-4 Saved Training configuration",
+ spi4_training_config}, */
+ { 0x19, 3, PC_COMMON, "SPI-4 Negotiated Settings", spi4_negotiated},
+ { 0x19, 4, PC_COMMON, "SPI-4 Report transfer capabilities",
+ spi4_report_xfer},
+ { 0x1a, 0, PC_COMMON, "Power Condition", common_power_condition},
+ { 0x1c, 0, PC_COMMON, "Informational Exceptions", common_informational},
+ { MP_LIST_PAGES, 0, PC_COMMON, "Return all pages", NULL},
+};
+static const int mpage_common_len = sizeof(mpage_common) /
+ sizeof(mpage_common[0]);
+
+static struct mpage_name_func mpage_disk[] =
+{
+ { 1, 0, PC_DISK, "Read-Write Error Recovery", disk_error_recovery},
+ { 3, 0, PC_DISK, "Format Device", disk_format},
+ { 4, 0, PC_DISK, "Rigid Disk Geometry", disk_geometry},
+ { 5, 0, PC_DISK, "Flexible Disk", NULL},
+ { 6, 0, PC_DISK, "Optical memory", optical_memory},
+ { 7, 0, PC_DISK, "Verify Error Recovery", disk_verify_error_recovery},
+ { 8, 0, PC_DISK, "Caching", disk_cache},
+ { 0xa, 0xf1, PC_DISK, "Parallel ATA control (SAT)", NULL},
+ { 0xb, 0, PC_DISK, "Medium Types Supported", NULL},
+ { 0xc, 0, PC_DISK, "Notch and Partition", disk_notch_parameters},
+ { 0x10, 0, PC_DISK, "XOR control", disk_xor_control},
+ { 0x1c, 1, PC_DISK, "Background control", disk_background},
+};
+static const int mpage_disk_len = sizeof(mpage_disk) / sizeof(mpage_disk[0]);
+
+static struct mpage_name_func mpage_cdvd[] =
+{
+ { 1, 0, PC_CDVD, "Read-Write Error Recovery (cdvd)",
+ cdvd_error_recovery},
+ { 3, 0, PC_CDVD, "MRW", cdvd_mrw},
+ { 5, 0, PC_CDVD, "Write parameters", cdvd_write_param},
+ { 8, 0, PC_CDVD, "Caching", cdvd_cache},
+ { 0xd, 0, PC_CDVD, "CD device parameters", cdvd_device_param},
+ { 0xe, 0, PC_CDVD, "CD audio control", cdvd_audio_control},
+ { 0x18, 0, PC_CDVD, "Feature set support & version", cdvd_feature},
+ { 0x1a, 0, PC_CDVD, "Power Condition", common_power_condition},
+ { 0x1c, 0, PC_CDVD, "Fault/failure reporting control",
+ common_informational},
+ { 0x1d, 0, PC_CDVD, "Time-out & protect", cdvd_timeout},
+ { 0x2a, 0, PC_CDVD, "MM capabilities & mechanical status", cdvd_mm_capab},
+};
+static const int mpage_cdvd_len = sizeof(mpage_cdvd) / sizeof(mpage_cdvd[0]);
+
+static struct mpage_name_func mpage_tape[] =
+{
+ { 1, 0, PC_TAPE, "Read-Write Error Recovery", disk_error_recovery},
+ { 0xf, 0, PC_TAPE, "Data compression", tape_data_compression},
+ { 0x10, 0, PC_TAPE, "Device configuration", tape_dev_config},
+ { 0x10, 1, PC_TAPE, "Device configuration extension", NULL},
+ { 0x11, 0, PC_TAPE, "Medium partition(1)", tape_medium_part1},
+ { 0x12, 0, PC_TAPE, "Medium partition(2)", tape_medium_part2_4},
+ { 0x13, 0, PC_TAPE, "Medium partition(3)", tape_medium_part2_4},
+ { 0x14, 0, PC_TAPE, "Medium partition(4)", tape_medium_part2_4},
+ { 0x1c, 0, PC_TAPE, "Informational Exceptions", common_informational},
+ { 0x1d, 0, PC_TAPE, "Medium configuration", NULL},
+};
+static const int mpage_tape_len = sizeof(mpage_tape) / sizeof(mpage_tape[0]);
+
+static struct mpage_name_func mpage_ses[] =
+{
+ { 0x14, 0, PC_SES, "Enclosure services management", ses_services_manag},
+};
+static const int mpage_ses_len = sizeof(mpage_ses) / sizeof(mpage_ses[0]);
+
+static struct mpage_name_func mpage_smc[] =
+{
+ { 0x1d, 0, PC_SMC, "Element address assignment", NULL},
+ { 0x1e, 0, PC_SMC, "Transport geometry parameters", NULL},
+ { 0x1f, 0, PC_SMC, "Device capabilities", NULL},
+ { 0x1f, 1, PC_SMC, "Extended device capabilities", NULL},
+};
+static const int mpage_smc_len = sizeof(mpage_smc) / sizeof(mpage_smc[0]);
+
+
+#define MAXPARM 64
+
+static int next_parameter;
+static int n_replacement_values;
+static uint64_t replacement_values[MAXPARM];
+static char is_hex[MAXPARM];
+
+#define SMODE_SENSE 0x1a
+#define SMODE_SENSE_10 0x5a
+#define SMODE_SELECT 0x15
+#define SMODE_SELECT_10 0x55
+
+#define MPHEADER6_LEN 4
+#define MPHEADER10_LEN 8
+
+
+/* forward declarations */
+static void usage(const char *);
+static void dump(void *buffer, unsigned int length);
+
+#define DXFER_NONE 0
+#define DXFER_FROM_DEVICE 1
+#define DXFER_TO_DEVICE 2
+
+
+struct scsi_cmnd_io
+{
+ uint8_t * cmnd; /* ptr to SCSI command block (cdb) */
+ size_t cmnd_len; /* number of bytes in SCSI command */
+ int dxfer_dir; /* DXFER_NONE, DXFER_FROM_DEVICE, or
+ DXFER_TO_DEVICE */
+ uint8_t * dxferp; /* ptr to outgoing/incoming data */
+ size_t dxfer_len; /* bytes to be transferred to/from dxferp */
+};
+
+#define SENSE_BUFF_LEN 64
+#define CMD_TIMEOUT 60000 /* 60,000 milliseconds (60 seconds) */
+#define EBUFF_SZ 512
+
+
+#define GENERAL_ERROR 1
+#define UNKNOWN_OPCODE 2
+#define BAD_CDB_FIELD 3
+#define UNSUPPORTED_PARAM 4
+#define DEVICE_ATTENTION 5
+#define DEVICE_NOT_READY 6
+
+#define DECODE_FAILED_TRY_HEX 9999
+
+/* Returns 0 -> ok, 1 -> general error, 2 -> unknown opcode,
+ 3 -> unsupported field in cdb, 4 -> unsupported param in data-in */
+static int
+do_scsi_io(struct scsi_cmnd_io * sio)
+{
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+ struct sg_scsi_sense_hdr ssh;
+ int res;
+
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sio->cmnd_len;
+ io_hdr.mx_sb_len = sizeof(sense_b);
+ if (DXFER_NONE == sio->dxfer_dir)
+ io_hdr.dxfer_direction = SG_DXFER_NONE;
+ else
+ io_hdr.dxfer_direction = (DXFER_TO_DEVICE == sio->dxfer_dir) ?
+ SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = sio->dxfer_len;
+ io_hdr.dxferp = sio->dxferp;
+ io_hdr.cmdp = sio->cmnd;
+ io_hdr.sbp = sense_b;
+ io_hdr.timeout = CMD_TIMEOUT;
+
+ if (trace_cmd) {
+ printf(" cdb:");
+ dump(sio->cmnd, sio->cmnd_len);
+ }
+ if ((trace_cmd > 1) && (DXFER_TO_DEVICE == sio->dxfer_dir)) {
+ printf(" additional data:\n");
+ dump(sio->dxferp, sio->dxfer_len);
+ }
+
+ if (ioctl(glob_fd, SG_IO, &io_hdr) < 0) {
+ perror("do_scsi_cmd: SG_IO error");
+ return GENERAL_ERROR;
+ }
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("do_scsi_cmd, continuing", &io_hdr, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ return 0;
+ default:
+ if (trace_cmd) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "do_scsi_io: opcode=0x%x", sio->cmnd[0]);
+ sg_chk_n_print3(ebuff, &io_hdr, true);
+ }
+ if (sg_normalize_sense(&io_hdr, &ssh)) {
+ if (ILLEGAL_REQUEST == ssh.sense_key) {
+ if (0x20 == ssh.asc)
+ return UNKNOWN_OPCODE;
+ else if (0x24 == ssh.asc)
+ return BAD_CDB_FIELD;
+ else if (0x26 == ssh.asc)
+ return UNSUPPORTED_PARAM;
+ } else if (UNIT_ATTENTION == ssh.sense_key)
+ return DEVICE_ATTENTION;
+ else if (NOT_READY == ssh.sense_key)
+ return DEVICE_NOT_READY;
+ }
+ return GENERAL_ERROR;
+ }
+}
+
+struct mpage_name_func * get_mpage_info(int page_no, int subpage_no,
+ struct mpage_name_func * mpp, int elems)
+{
+ int k;
+
+ for (k = 0; k < elems; ++k, ++mpp) {
+ if ((mpp->page == page_no) && (mpp->subpage == subpage_no))
+ return mpp;
+ if (mpp->page > page_no)
+ break;
+ }
+ return NULL;
+}
+
+enum page_class get_page_class(struct mpage_info * mpi)
+{
+ switch (mpi->peri_type)
+ {
+ case 0:
+ case 4:
+ case 7:
+ case 0xe: /* should be RBC */
+ return PC_DISK;
+ case 1:
+ case 2:
+ return PC_TAPE;
+ case 8:
+ return PC_SMC;
+ case 5:
+ return PC_CDVD;
+ case 0xd:
+ return PC_SES;
+ default:
+ return PC_COMMON;
+ }
+}
+
+struct mpage_name_func * get_mpage_name_func(struct mpage_info * mpi)
+{
+ struct mpage_name_func * mpf = NULL;
+
+ switch (get_page_class(mpi))
+ {
+ case PC_DISK:
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_disk,
+ mpage_disk_len);
+ break;
+ case PC_CDVD:
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_cdvd,
+ mpage_cdvd_len);
+ break;
+ case PC_TAPE:
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_tape,
+ mpage_tape_len);
+ break;
+ case PC_SES:
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_ses,
+ mpage_ses_len);
+ break;
+ case PC_SMC:
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_smc,
+ mpage_smc_len);
+ break;
+ case PC_COMMON:
+ /* picked up it catch all next */
+ break;
+ }
+ if (NULL == mpf) {
+ if ((PC_SES != get_page_class(mpi)) && (mpi->inq_byte6 & 0x40)) {
+ /* check for attached enclosure services processor */
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_ses,
+ mpage_ses_len);
+ }
+ if ((PC_SMC != get_page_class(mpi)) && (mpi->inq_byte6 & 0x8)) {
+ /* check for attached medium changer device */
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_smc,
+ mpage_smc_len);
+ }
+ }
+ if (NULL == mpf)
+ mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_common,
+ mpage_common_len);
+ return mpf;
+}
+
+
+static char unkn_page_str[64];
+
+static const char *
+get_page_name(struct mpage_info * mpi)
+{
+ struct mpage_name_func * mpf;
+
+ if (MP_LIST_PAGES == mpi->page) {
+ if (MP_LIST_SUBPAGES == mpi->subpage)
+ return "List supported pages and subpages";
+ else
+ return "List supported pages";
+ }
+ mpf = get_mpage_name_func(mpi);
+ if ((NULL == mpf) || (NULL == mpf->name)) {
+ if (mpi->subpage)
+ snprintf(unkn_page_str, sizeof(unkn_page_str),
+ "page number=0x%x, subpage number=0x%x",
+ mpi->page, mpi->subpage);
+ else
+ snprintf(unkn_page_str, sizeof(unkn_page_str),
+ "page number=0x%x", mpi->page);
+ return unkn_page_str;
+ }
+ return mpf->name;
+}
+
+static void
+dump(void *buffer, unsigned int length)
+{
+ unsigned int i;
+
+ printf(" ");
+ for (i = 0; i < length; i++) {
+#if 0
+ if (((uint8_t *) buffer)[i] > 0x20)
+ printf(" %c ", (unsigned int) ((uint8_t *) buffer)[i]);
+ else
+#endif
+ printf("%02x ", (unsigned int) ((uint8_t *) buffer)[i]);
+ if ((i % 16 == 15) && (i < (length - 1))) {
+ printf("\n ");
+ }
+ }
+ printf("\n");
+
+}
+
+static int
+getnbyte(const uint8_t *pnt, int nbyte)
+{
+ unsigned int result;
+ int i;
+
+ if (nbyte > 4)
+ fprintf(stderr, "getnbyte() limited to 32 bits, nbyte=%d\n", nbyte);
+ result = 0;
+ for (i = 0; i < nbyte; i++)
+ result = (result << 8) | (pnt[i] & 0xff);
+ return result;
+}
+
+static int64_t
+getnbyte_ll(const uint8_t *pnt, int nbyte)
+{
+ int64_t result;
+ int i;
+
+ if (nbyte > 8)
+ fprintf(stderr, "getnbyte_ll() limited to 64 bits, nbyte=%d\n",
+ nbyte);
+ result = 0;
+ for (i = 0; i < nbyte; i++)
+ result = (result << 8) + (pnt[i] & 0xff);
+ return result;
+}
+
+static int
+putnbyte(uint8_t *pnt, unsigned int value,
+ unsigned int nbyte)
+{
+ int i;
+
+ for (i = nbyte - 1; i >= 0; i--) {
+ pnt[i] = value & 0xff;
+ value = value >> 8;
+ }
+ return 0;
+}
+
+#define REASON_SZ 128
+
+static void
+check_parm_type(int i)
+{
+ char reason[REASON_SZ];
+
+ if (i == 1 && is_hex[next_parameter] != 1) {
+ snprintf(reason, REASON_SZ,
+ "simple number (pos %i) instead of @ hexdatafield: %"
+ PRIu64 , next_parameter, replacement_values[next_parameter]);
+ usage(reason);
+ }
+ if (i != 1 && is_hex[next_parameter]) {
+ snprintf(reason, REASON_SZ,
+ "@ hexdatafield (pos %i) instead of a simple number: %"
+ PRIu64 , next_parameter, replacement_values[next_parameter]);
+ usage(reason);
+ }
+}
+
+static void
+bitfield(uint8_t *pageaddr, const char * text, int mask, int shift)
+{
+ if (x_interface && replace) {
+ check_parm_type(0);
+ *pageaddr = (*pageaddr & ~(mask << shift)) |
+ ((replacement_values[next_parameter++] & mask) << shift);
+ } else if (x_interface)
+ printf("%d ", (*pageaddr >> shift) & mask);
+ else
+ printf("%-35s%d\n", text, (*pageaddr >> shift) & mask);
+}
+
+#if 0
+static void
+notbitfield(uint8_t *pageaddr, char * text, int mask,
+ int shift)
+{
+ if (modifiable) {
+ bitfield(pageaddr, text, mask, shift);
+ return;
+ }
+ if (x_interface && replace) {
+ check_parm_type(0);
+ *pageaddr = (*pageaddr & ~(mask << shift)) |
+ (((!replacement_values[next_parameter++]) & mask) << shift);
+ } else if (x_interface)
+ printf("%d ", !((*pageaddr >> shift) & mask));
+ else
+ printf("%-35s%d\n", text, !((*pageaddr >> shift) & mask));
+}
+#endif
+
+static void
+intfield(uint8_t * pageaddr, int nbytes, const char * text)
+{
+ if (x_interface && replace) {
+ check_parm_type(0);
+ putnbyte(pageaddr, replacement_values[next_parameter++], nbytes);
+ } else if (x_interface)
+ printf("%d ", getnbyte(pageaddr, nbytes));
+ else
+ printf("%-35s%d\n", text, getnbyte(pageaddr, nbytes));
+}
+
+static void
+hexfield(uint8_t * pageaddr, int nbytes, const char * text)
+{
+ if (x_interface && replace) {
+ check_parm_type(0);
+ putnbyte(pageaddr, replacement_values[next_parameter++], nbytes);
+ } else if (x_interface)
+ printf("%d ", getnbyte(pageaddr, nbytes));
+ else
+ printf("%-35s0x%x\n", text, getnbyte(pageaddr, nbytes));
+}
+
+static void
+hexdatafield(uint8_t * pageaddr, int nbytes, const char * text)
+{
+ if (x_interface && replace) {
+ uint8_t *ptr;
+ unsigned tmp;
+
+ /* Though in main we ensured that a @string has the right format,
+ we have to check that we are working on a @ hexdata field */
+
+ check_parm_type(1);
+
+ ptr = (uint8_t *) (unsigned long)
+ (replacement_values[next_parameter++]);
+ ptr++; /* Skip @ */
+
+ while (*ptr) {
+ if (!nbytes)
+ goto illegal;
+ tmp = (*ptr >= 'a') ? (*ptr - 'a' + 'A') : *ptr;
+ tmp -= (tmp >= 'A') ? 'A' - 10 : '0';
+
+ *pageaddr = tmp << 4;
+ ptr++;
+
+ tmp = (*ptr >= 'a') ? (*ptr - 'a' + 'A') : *ptr;
+ tmp -= (tmp >= 'A') ? 'A' - 10 : '0';
+
+ *pageaddr++ += tmp;
+ ptr++;
+ nbytes--;
+ }
+
+ if (nbytes) {
+ illegal:
+ fputs("sginfo: incorrect number of bytes in @hexdatafield.\n",
+ stdout);
+ exit(2);
+ }
+ } else if (x_interface) {
+ putchar('@');
+ while (nbytes-- > 0)
+ printf("%02x", *pageaddr++);
+ putchar(' ');
+ } else {
+ printf("%-35s0x", text);
+ while (nbytes-- > 0)
+ printf("%02x", *pageaddr++);
+ putchar('\n');
+ }
+}
+
+
+/* Offset into mode sense (6 or 10 byte) response that actual mode page
+ * starts at (relative to resp[0]). Returns -1 if problem */
+static int
+modePageOffset(const uint8_t * resp, int len, int modese_6)
+{
+ int bd_len;
+ int resp_len = 0;
+ int offset = -1;
+
+ if (resp) {
+ if (modese_6) {
+ resp_len = resp[0] + 1;
+ bd_len = resp[3];
+ offset = bd_len + MPHEADER6_LEN;
+ } else {
+ resp_len = (resp[0] << 8) + resp[1] + 2;
+ bd_len = (resp[6] << 8) + resp[7];
+ /* LongLBA doesn't change this calculation */
+ offset = bd_len + MPHEADER10_LEN;
+ }
+ if ((offset + 2) > len) {
+ printf("modePageOffset: raw_curr too small, offset=%d "
+ "resp_len=%d bd_len=%d\n", offset, resp_len, bd_len);
+ offset = -1;
+ } else if ((offset + 2) > resp_len) {
+ printf("modePageOffset: response length too short, resp_len=%d"
+ " offset=%d bd_len=%d\n", resp_len, offset, bd_len);
+ offset = -1;
+ }
+ }
+ return offset;
+}
+
+/* Reads mode (sub-)page via 6 byte MODE SENSE, returns 0 if ok */
+static int
+get_mode_page6(struct mpage_info * mpi, int dbd, uint8_t * resp,
+ int sngl_fetch)
+{
+ int status, off;
+ uint8_t cmd[6];
+ struct scsi_cmnd_io sci;
+ int initial_len = (sngl_fetch ? MAX_RESP6_SIZE : 4);
+
+ memset(resp, 0, 4);
+ cmd[0] = SMODE_SENSE; /* MODE SENSE (6) */
+ cmd[1] = 0x00 | (dbd ? 0x8 : 0); /* disable block descriptors bit */
+ cmd[2] = (mpi->page_control << 6) | mpi->page;
+ cmd[3] = mpi->subpage; /* subpage code */
+ cmd[4] = initial_len;
+ cmd[5] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = initial_len;
+ sci.dxferp = resp;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
+ "0x%x [mode_sense_6]\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ else
+ fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
+ "[mode_sense_6]\n", get_page_name(mpi), mpi->page);
+ return status;
+ }
+ mpi->resp_len = resp[0] + 1;
+ if (sngl_fetch) {
+ if (trace_cmd > 1) {
+ off = modePageOffset(resp, mpi->resp_len, 1);
+ if (off >= 0) {
+ printf(" cdb response:\n");
+ dump(resp, mpi->resp_len);
+ }
+ }
+ return status;
+ }
+
+ cmd[4] = mpi->resp_len;
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = mpi->resp_len;
+ sci.dxferp = resp;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
+ "0x%x [mode_sense_6]\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ else
+ fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
+ "[mode_sense_6]\n", get_page_name(mpi), mpi->page);
+ } else if (trace_cmd > 1) {
+ off = modePageOffset(resp, mpi->resp_len, 1);
+ if (off >= 0) {
+ printf(" cdb response:\n");
+ dump(resp, mpi->resp_len);
+ }
+ }
+ return status;
+}
+
+/* Reads mode (sub-)page via 10 byte MODE SENSE, returns 0 if ok */
+static int
+get_mode_page10(struct mpage_info * mpi, int llbaa, int dbd,
+ uint8_t * resp, int sngl_fetch)
+{
+ int status, off;
+ uint8_t cmd[10];
+ struct scsi_cmnd_io sci;
+ int initial_len = (sngl_fetch ? MAX_RESP10_SIZE : 4);
+
+ memset(resp, 0, 4);
+ cmd[0] = SMODE_SENSE_10; /* MODE SENSE (10) */
+ cmd[1] = 0x00 | (llbaa ? 0x10 : 0) | (dbd ? 0x8 : 0);
+ cmd[2] = (mpi->page_control << 6) | mpi->page;
+ cmd[3] = mpi->subpage;
+ cmd[4] = 0x00; /* (reserved) */
+ cmd[5] = 0x00; /* (reserved) */
+ cmd[6] = 0x00; /* (reserved) */
+ cmd[7] = (initial_len >> 8) & 0xff;
+ cmd[8] = initial_len & 0xff;
+ cmd[9] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = initial_len;
+ sci.dxferp = resp;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
+ "0x%x [mode_sense_10]\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ else {
+ fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
+ "[mode_sense_10]\n", get_page_name(mpi), mpi->page);
+ return status;
+ }
+ }
+ mpi->resp_len = (resp[0] << 8) + resp[1] + 2;
+ if (sngl_fetch) {
+ if (trace_cmd > 1) {
+ off = modePageOffset(resp, mpi->resp_len, 0);
+ if (off >= 0) {
+ printf(" cdb response:\n");
+ dump(resp, mpi->resp_len);
+ }
+ }
+ return status;
+ }
+
+ cmd[7] = (mpi->resp_len >> 8) & 0xff;
+ cmd[8] = (mpi->resp_len & 0xff);
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = mpi->resp_len;
+ sci.dxferp = resp;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
+ "0x%x [mode_sense_10]\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ else
+ fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
+ "[mode_sense_10]\n", get_page_name(mpi), mpi->page);
+ } else if (trace_cmd > 1) {
+ off = modePageOffset(resp, mpi->resp_len, 0);
+ if (off >= 0) {
+ printf(" cdb response:\n");
+ dump(resp, mpi->resp_len);
+ }
+ }
+ return status;
+}
+
+static int
+get_mode_page(struct mpage_info * mpi, int dbd, uint8_t * resp)
+{
+ int res;
+
+ if (mode6byte)
+ res = get_mode_page6(mpi, dbd, resp, single_fetch);
+ else
+ res = get_mode_page10(mpi, 0, dbd, resp, single_fetch);
+ if (UNKNOWN_OPCODE == res)
+ fprintf(stdout, ">>>>> Try command again with%s '-6' "
+ "argument\n", (mode6byte ? "out the" : " a"));
+ else if (mpi->subpage && (BAD_CDB_FIELD == res))
+ fprintf(stdout, ">>>>> device doesn't seem to support "
+ "subpages\n");
+ else if (DEVICE_ATTENTION == res)
+ fprintf(stdout, ">>>>> device reports UNIT ATTENTION, check it or"
+ " just try again\n");
+ else if (DEVICE_NOT_READY == res)
+ fprintf(stdout, ">>>>> device NOT READY, does it need media?\n");
+ return res;
+}
+
+/* Contents should point to the mode parameter header that we obtained
+ in a prior read operation. This way we do not have to work out the
+ format of the beast. Assume 0 or 1 block descriptors. */
+static int
+put_mode_page6(struct mpage_info * mpi, const uint8_t * msense6_resp,
+ int sp_bit)
+{
+ int status;
+ int bdlen, resplen;
+ uint8_t cmd[6];
+ struct scsi_cmnd_io sci;
+
+ bdlen = msense6_resp[3];
+ resplen = msense6_resp[0] + 1;
+
+ cmd[0] = SMODE_SELECT;
+ cmd[1] = 0x10 | (sp_bit ? 1 : 0); /* always set PF bit */
+ cmd[2] = 0x00;
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = resplen; /* parameter list length */
+ cmd[5] = 0x00; /* (reserved) */
+
+ memcpy(cbuffer1, msense6_resp, resplen);
+ cbuffer1[0] = 0; /* Mask off the mode data length
+ - reserved field */
+ cbuffer1[2] = 0; /* device-specific parameter is not defined
+ and/or reserved for mode select */
+
+#if 0 /* leave block descriptor alone */
+ if (bdlen > 0) {
+ memset(cbuffer1 + MPHEADER6_LEN, 0, 4); /* clear 'number of blocks'
+ for DAD device */
+ cbuffer1[MPHEADER6_LEN + 4] = 0; /* clear DAD density code. Why? */
+ /* leave DAD block length */
+ }
+#endif
+ cbuffer1[MPHEADER6_LEN + bdlen] &= 0x7f; /* Mask PS bit */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_TO_DEVICE;
+ sci.dxfer_len = resplen;
+ sci.dxferp = cbuffer1;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to store %s mode page 0x%x,"
+ " subpage 0x%x [msel_6]\n", get_page_name(mpi),
+ mpi->page, mpi->subpage);
+ else
+ fprintf(stdout, ">>> Unable to store %s mode page 0x%x [msel_6]\n",
+ get_page_name(mpi), mpi->page);
+ }
+ return status;
+}
+
+/* Contents should point to the mode parameter header that we obtained
+ in a prior read operation. This way we do not have to work out the
+ format of the beast. Assume 0 or 1 block descriptors. */
+static int
+put_mode_page10(struct mpage_info * mpi, const uint8_t * msense10_resp,
+ int sp_bit)
+{
+ int status;
+ int bdlen, resplen;
+ uint8_t cmd[10];
+ struct scsi_cmnd_io sci;
+
+ bdlen = (msense10_resp[6] << 8) + msense10_resp[7];
+ resplen = (msense10_resp[0] << 8) + msense10_resp[1] + 2;
+
+ cmd[0] = SMODE_SELECT_10;
+ cmd[1] = 0x10 | (sp_bit ? 1 : 0); /* always set PF bit */
+ cmd[2] = 0x00; /* (reserved) */
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = 0x00; /* (reserved) */
+ cmd[5] = 0x00; /* (reserved) */
+ cmd[6] = 0x00; /* (reserved) */
+ cmd[7] = (resplen >> 8) & 0xff;
+ cmd[8] = resplen & 0xff;
+ cmd[9] = 0x00; /* (reserved) */
+
+ memcpy(cbuffer1, msense10_resp, resplen);
+ cbuffer1[0] = 0; /* Mask off the mode data length */
+ cbuffer1[1] = 0; /* Mask off the mode data length */
+ cbuffer1[3] = 0; /* device-specific parameter is not defined
+ and/or reserved for mode select */
+#if 0 /* leave block descriptor alone */
+ if (bdlen > 0) {
+ memset(cbuffer1 + MPHEADER10_LEN, 0, 4); /* clear 'number of blocks'
+ for DAD device */
+ cbuffer1[MPHEADER10_LEN + 4] = 0; /* clear DAD density code. Why? */
+ /* leave DAD block length */
+ }
+#endif
+ cbuffer1[MPHEADER10_LEN + bdlen] &= 0x7f; /* Mask PS bit */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_TO_DEVICE;
+ sci.dxfer_len = resplen;
+ sci.dxferp = cbuffer1;
+ status = do_scsi_io(&sci);
+ if (status) {
+ if (mpi->subpage)
+ fprintf(stdout, ">>> Unable to store %s mode page 0x%x,"
+ " subpage 0x%x [msel_10]\n", get_page_name(mpi),
+ mpi->page, mpi->subpage);
+ else
+ fprintf(stdout, ">>> Unable to store %s mode page 0x%x "
+ "[msel_10]\n", get_page_name(mpi), mpi->page);
+ }
+ return status;
+}
+
+static int
+put_mode_page(struct mpage_info * mpi, const uint8_t * msense_resp)
+{
+ if (mode6byte)
+ return put_mode_page6(mpi, msense_resp, ! negate_sp_bit);
+ else
+ return put_mode_page10(mpi, msense_resp, ! negate_sp_bit);
+}
+
+static int
+setup_mode_page(struct mpage_info * mpi, int nparam, uint8_t * buff,
+ uint8_t ** o_pagestart)
+{
+ int status, offset, rem_pglen;
+ uint8_t * pgp;
+
+ status = get_mode_page(mpi, 0, buff);
+ if (status) {
+ printf("\n");
+ return status;
+ }
+ offset = modePageOffset(buff, mpi->resp_len, mode6byte);
+ if (offset < 0) {
+ fprintf(stdout, "mode page=0x%x has bad page format\n", mpi->page);
+ fprintf(stdout, " perhaps '-z' switch may help\n");
+ return -1;
+ }
+ pgp = buff + offset;
+ *o_pagestart = pgp;
+ rem_pglen = (0x40 & pgp[0]) ? ((pgp[2] << 8) + pgp[3]) : pgp[1];
+
+ if (x_interface && replace) {
+ if ((nparam && (n_replacement_values != nparam)) ||
+ ((! nparam) && (n_replacement_values != rem_pglen))) {
+ fprintf(stdout, "Wrong number of replacement values (%i instead "
+ "of %i)\n", n_replacement_values,
+ nparam ? nparam : rem_pglen);
+ return 1;
+ }
+ next_parameter = 1;
+ }
+ return 0;
+}
+
+static int
+get_protocol_id(int port_not_lu, uint8_t * buff, int * proto_idp,
+ int * offp)
+{
+ int status, off, proto_id, spf;
+ struct mpage_info mp_i;
+ char b[64];
+
+ memset(&mp_i, 0, sizeof(mp_i));
+ mp_i.page = (port_not_lu ? 0x19 : 0x18);
+ /* N.B. getting port or lu specific mode page (not subpage) */
+ status = get_mode_page(&mp_i, 0, buff);
+ if (status)
+ return status;
+ off = modePageOffset(buff, mp_i.resp_len, mode6byte);
+ if (off < 0)
+ return off;
+ spf = (buff[off] & 0x40) ? 1 : 0; /* subpages won't happen here */
+ proto_id = buff[off + (spf ? 5 : 2)] & 0xf;
+ if (trace_cmd > 0)
+ printf("Protocol specific %s, protocol_id=%s\n",
+ (port_not_lu ? "port" : "lu"),
+ sg_get_trans_proto_str(proto_id, sizeof(b), b));
+ if (proto_idp)
+ *proto_idp = proto_id;
+ if (offp)
+ *offp = off;
+ return 0;
+}
+
+static int
+disk_geometry(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 9, cbuffer, &pagestart);
+ if (status)
+ return status;
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------------\n");
+ };
+ intfield(pagestart + 2, 3, "Number of cylinders");
+ intfield(pagestart + 5, 1, "Number of heads");
+ intfield(pagestart + 6, 3, "Starting cyl. write precomp");
+ intfield(pagestart + 9, 3, "Starting cyl. reduced current");
+ intfield(pagestart + 12, 2, "Device step rate");
+ intfield(pagestart + 14, 3, "Landing Zone Cylinder");
+ bitfield(pagestart + 17, "RPL", 3, 0);
+ intfield(pagestart + 18, 1, "Rotational Offset");
+ intfield(pagestart + 20, 2, "Rotational Rate");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_disconnect_reconnect(struct mpage_info * mpi,
+ const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 11, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("------------------------------------\n");
+ };
+ intfield(pagestart + 2, 1, "Buffer full ratio");
+ intfield(pagestart + 3, 1, "Buffer empty ratio");
+ intfield(pagestart + 4, 2, "Bus Inactivity Limit (SAS: 100us)");
+ intfield(pagestart + 6, 2, "Disconnect Time Limit");
+ intfield(pagestart + 8, 2, "Connect Time Limit (SAS: 100us)");
+ intfield(pagestart + 10, 2, "Maximum Burst Size");
+ bitfield(pagestart + 12, "EMDP", 1, 7);
+ bitfield(pagestart + 12, "Fair Arbitration (fcp:faa,fab,fac)", 0x7, 4);
+ bitfield(pagestart + 12, "DIMM", 1, 3);
+ bitfield(pagestart + 12, "DTDC", 0x7, 0);
+ intfield(pagestart + 14, 2, "First Burst Size");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+
+}
+
+static int
+common_control(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 21, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------\n");
+ }
+ bitfield(pagestart + 2, "TST", 0x7, 5);
+ bitfield(pagestart + 2, "TMF_ONLY", 1, 4);
+ bitfield(pagestart + 2, "D_SENSE", 1, 2);
+ bitfield(pagestart + 2, "GLTSD", 1, 1);
+ bitfield(pagestart + 2, "RLEC", 1, 0);
+ bitfield(pagestart + 3, "Queue Algorithm Modifier", 0xf, 4);
+ bitfield(pagestart + 3, "QErr", 0x3, 1);
+ bitfield(pagestart + 3, "DQue [obsolete]", 1, 0);
+ bitfield(pagestart + 4, "TAS", 1, 7);
+ bitfield(pagestart + 4, "RAC", 1, 6);
+ bitfield(pagestart + 4, "UA_INTLCK_CTRL", 0x3, 4);
+ bitfield(pagestart + 4, "SWP", 1, 3);
+ bitfield(pagestart + 4, "RAERP [obs.]", 1, 2);
+ bitfield(pagestart + 4, "UAAERP [obs.]", 1, 1);
+ bitfield(pagestart + 4, "EAERP [obs.]", 1, 0);
+ bitfield(pagestart + 5, "ATO", 1, 7);
+ bitfield(pagestart + 5, "TAS", 1, 6);
+ bitfield(pagestart + 5, "AUTOLOAD MODE", 0x7, 0);
+ intfield(pagestart + 6, 2, "Ready AER Holdoff Period [obs.]");
+ intfield(pagestart + 8, 2, "Busy Timeout Period");
+ intfield(pagestart + 10, 2, "Extended self-test completion time");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_control_extension(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ bitfield(pagestart + 4, "TCMOS", 1, 2);
+ bitfield(pagestart + 4, "SCSIP", 1, 1);
+ bitfield(pagestart + 4, "IALUAE", 1, 0);
+ bitfield(pagestart + 5, "Initial Priority", 0xf, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_informational(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "PERF", 1, 7);
+ bitfield(pagestart + 2, "EBF", 1, 5);
+ bitfield(pagestart + 2, "EWASC", 1, 4);
+ bitfield(pagestart + 2, "DEXCPT", 1, 3);
+ bitfield(pagestart + 2, "TEST", 1, 2);
+ bitfield(pagestart + 2, "EBACKERR", 1, 1);
+ bitfield(pagestart + 2, "LOGERR", 1, 0);
+ bitfield(pagestart + 3, "MRIE", 0xf, 0);
+ intfield(pagestart + 4, 4, "Interval Timer");
+ intfield(pagestart + 8, 4, "Report Count");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+disk_error_recovery(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 14, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "AWRE", 1, 7);
+ bitfield(pagestart + 2, "ARRE", 1, 6);
+ bitfield(pagestart + 2, "TB", 1, 5);
+ bitfield(pagestart + 2, "RC", 1, 4);
+ bitfield(pagestart + 2, "EER", 1, 3);
+ bitfield(pagestart + 2, "PER", 1, 2);
+ bitfield(pagestart + 2, "DTE", 1, 1);
+ bitfield(pagestart + 2, "DCR", 1, 0);
+ intfield(pagestart + 3, 1, "Read Retry Count");
+ intfield(pagestart + 4, 1, "Correction Span");
+ intfield(pagestart + 5, 1, "Head Offset Count");
+ intfield(pagestart + 6, 1, "Data Strobe Offset Count");
+ intfield(pagestart + 8, 1, "Write Retry Count");
+ intfield(pagestart + 10, 2, "Recovery Time Limit (ms)");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_error_recovery(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("------------------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "AWRE", 1, 7);
+ bitfield(pagestart + 2, "ARRE", 1, 6);
+ bitfield(pagestart + 2, "TB", 1, 5);
+ bitfield(pagestart + 2, "RC", 1, 4);
+ bitfield(pagestart + 2, "PER", 1, 2);
+ bitfield(pagestart + 2, "DTE", 1, 1);
+ bitfield(pagestart + 2, "DCR", 1, 0);
+ intfield(pagestart + 3, 1, "Read Retry Count");
+ bitfield(pagestart + 7, "EMCDR", 3, 0);
+ intfield(pagestart + 8, 1, "Write Retry Count");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_mrw(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("------------------------------------------------\n");
+ }
+ bitfield(pagestart + 3, "LBA space", 1, 0);
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+disk_notch_parameters(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
+ if (status) {
+ fprintf(stdout, "Special case: only give 6 fields to '-XR' since"
+ " 'Pages Notched' is unchangeable\n");
+ return status;
+ }
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------------\n");
+ };
+ bitfield(pagestart + 2, "Notched Drive", 1, 7);
+ bitfield(pagestart + 2, "Logical or Physical Notch", 1, 6);
+ intfield(pagestart + 4, 2, "Max # of notches");
+ intfield(pagestart + 6, 2, "Active Notch");
+ if (pagestart[2] & 0x40) {
+ intfield(pagestart + 8, 4, "Starting Boundary");
+ intfield(pagestart + 12, 4, "Ending Boundary");
+ } else { /* Hex is more meaningful for physical notches */
+ hexfield(pagestart + 8, 4, "Starting Boundary");
+ hexfield(pagestart + 12, 4, "Ending Boundary");
+ }
+
+ if (x_interface && !replace) {
+#if 1
+ ; /* do nothing, skip this field */
+#else
+ if (1 == mpi->page_control) /* modifiable */
+ printf("0");
+ else
+ printf("0x%8.8x%8.8x", getnbyte(pagestart + 16, 4),
+ getnbyte(pagestart + 20, 4));
+#endif
+ };
+ if (!x_interface)
+ printf("Pages Notched %8.8x %8.8x\n",
+ getnbyte(pagestart + 16, 4), getnbyte(pagestart + 20, 4));
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static const char *
+formatname(int format)
+{
+ switch(format) {
+ case 0x0: return "logical block addresses (32 bit)";
+ case 0x3: return "logical block addresses (64 bit)";
+ case 0x4: return "bytes from index [Cyl:Head:Off]\n"
+ "Offset -1 marks whole track as bad.\n";
+ case 0x5: return "physical blocks [Cyl:Head:Sect]\n"
+ "Sector -1 marks whole track as bad.\n";
+ }
+ return "Weird, unknown format";
+}
+
+static int
+read_defect_list(int grown_only)
+{
+ int i, len, reallen, table, k, defect_format;
+ int status = 0;
+ int header = 1;
+ int sorthead = 0;
+ uint8_t cmd[10];
+ uint8_t cmd12[12];
+ uint8_t *df = NULL;
+ uint8_t *bp = NULL;
+ uint8_t *heapp = NULL;
+ unsigned int *headsp = NULL;
+ int trunc;
+ struct scsi_cmnd_io sci;
+
+ if (defectformat == HEAD_SORT_TOKEN) {
+ defectformat = 0x04;
+ sorthead = 1;
+ headsp = (unsigned int *)calloc(MAX_HEADS, sizeof(unsigned int));
+ if (headsp == NULL) {
+ perror("malloc failed");
+ return status;
+ }
+ }
+ for (table = grown_only; table < 2; table++) {
+ if (heapp) {
+ free(heapp);
+ heapp = NULL;
+ }
+ bp = cbuffer;
+ memset(bp, 0, 4);
+ trunc = 0;
+ reallen = -1;
+
+ cmd[0] = 0x37; /* READ DEFECT DATA (10) */
+ cmd[1] = 0x00;
+ cmd[2] = (table ? 0x08 : 0x10) | defectformat; /* List, Format */
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = 0x00; /* (reserved) */
+ cmd[5] = 0x00; /* (reserved) */
+ cmd[6] = 0x00; /* (reserved) */
+ cmd[7] = 0x00; /* Alloc len */
+ cmd[8] = 0x04; /* Alloc len (size finder) */
+ cmd[9] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = 4;
+ sci.dxferp = bp;
+ i = do_scsi_io(&sci);
+ if (i) {
+ fprintf(stdout, ">>> Unable to read %s defect data.\n",
+ (table ? "grown (GLIST)" : "primary (PLIST)"));
+ status |= i;
+ continue;
+ }
+ if (trace_cmd > 1) {
+ printf(" cdb response:\n");
+ dump(bp, 4);
+ }
+ /*
+ * Check validity of response:
+ * bp[0] reserved, must be zero
+ * bp[1] bits 7-5 reserved, must be zero
+ * bp[1] bits 4-3 should match table requested
+ */
+ if (0 != bp[0] || (table ? 0x08 : 0x10) != (bp[1] & 0xf8)) {
+ fprintf(stdout, ">>> Invalid header for %s defect list.\n",
+ (table ? "grown (GLIST)" : "primary (PLIST)"));
+ status |= 1;
+ continue;
+ }
+ if (header) {
+ printf("Defect Lists\n"
+ "------------\n");
+ header = 0;
+ }
+ len = (bp[2] << 8) + bp[3];
+ if (len < 0xfff8)
+ reallen = len;
+ else {
+ /*
+ * List length is at or over capacity of READ DEFECT DATA (10)
+ * Try to get actual length with READ DEFECT DATA (12)
+ */
+ bp = cbuffer;
+ memset(bp, 0, 8);
+ cmd12[0] = 0xB7; /* READ DEFECT DATA (12) */
+ cmd12[1] = (table ? 0x08 : 0x10) | defectformat;/* List, Format */
+ cmd12[2] = 0x00; /* (reserved) */
+ cmd12[3] = 0x00; /* (reserved) */
+ cmd12[4] = 0x00; /* (reserved) */
+ cmd12[5] = 0x00; /* (reserved) */
+ cmd12[6] = 0x00; /* Alloc len */
+ cmd12[7] = 0x00; /* Alloc len */
+ cmd12[8] = 0x00; /* Alloc len */
+ cmd12[9] = 0x08; /* Alloc len (size finder) */
+ cmd12[10] = 0x00; /* reserved */
+ cmd12[11] = 0x00; /* control */
+
+ sci.cmnd = cmd12;
+ sci.cmnd_len = sizeof(cmd12);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = 8;
+ sci.dxferp = bp;
+ i = do_scsi_io(&sci);
+ if (i) {
+ if (trace_cmd) {
+ fprintf(stdout, ">>> No 12 byte command support, "
+ "but list is too long for 10 byte version.\n"
+ "List will be truncated at 8191 elements\n");
+ }
+ goto trytenbyte;
+ }
+ if (trace_cmd > 1) {
+ printf(" cdb response:\n");
+ dump(bp, 8);
+ }
+ /*
+ * Check validity of response:
+ * bp[0], bp[2] and bp[3] reserved, must be zero
+ * bp[1] bits 7-5 reserved, must be zero
+ * bp[1] bits 4-3 should match table we requested
+ */
+ if (0 != bp[0] || 0 != bp[2] || 0 != bp[3] ||
+ ((table ? 0x08 : 0x10) != (bp[1] & 0xf8))) {
+ if (trace_cmd)
+ fprintf(stdout,
+ ">>> Invalid header for %s defect list.\n",
+ (table ? "grown (GLIST)" : "primary (PLIST)"));
+ goto trytenbyte;
+ }
+ len = (bp[4] << 24) + (bp[5] << 16) + (bp[6] << 8) + bp[7];
+ reallen = len;
+ }
+
+ if (len > 0) {
+ k = len + 8; /* length of defect list + header */
+ if (k > (int)sizeof(cbuffer)) {
+ heapp = (uint8_t *)malloc(k);
+
+ if (len > 0x80000 && NULL == heapp) {
+ len = 0x80000; /* go large: 512 KB */
+ k = len + 8;
+ heapp = (uint8_t *)malloc(k);
+ }
+ if (heapp != NULL)
+ bp = heapp;
+ }
+ if (len > 0xfff0 && heapp != NULL) {
+ cmd12[0] = 0xB7; /* READ DEFECT DATA (12) */
+ cmd12[1] = (table ? 0x08 : 0x10) | defectformat;
+ /* List, Format */
+ cmd12[2] = 0x00; /* (reserved) */
+ cmd12[3] = 0x00; /* (reserved) */
+ cmd12[4] = 0x00; /* (reserved) */
+ cmd12[5] = 0x00; /* (reserved) */
+ cmd12[6] = 0x00; /* Alloc len */
+ cmd12[7] = (k >> 16) & 0xff; /* Alloc len */
+ cmd12[8] = (k >> 8) & 0xff; /* Alloc len */
+ cmd12[9] = (k & 0xff); /* Alloc len */
+ cmd12[10] = 0x00; /* reserved */
+ cmd12[11] = 0x00; /* control */
+
+ sci.cmnd = cmd12;
+ sci.cmnd_len = sizeof(cmd12);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = k;
+ sci.dxferp = bp;
+ i = do_scsi_io(&sci);
+ if (i)
+ goto trytenbyte;
+ if (trace_cmd > 1) {
+ printf(" cdb response:\n");
+ dump(bp, 8);
+ }
+ reallen = (bp[4] << 24) + (bp[5] << 16) + (bp[6] << 8) +
+ bp[7];
+ if (reallen > len) {
+ trunc = 1;
+ }
+ df = (uint8_t *) (bp + 8);
+ }
+ else {
+trytenbyte:
+ if (len > 0xfff8) {
+ len = 0xfff8;
+ trunc = 1;
+ }
+ k = len + 4; /* length of defect list + header */
+ if (k > (int)sizeof(cbuffer) && NULL == heapp) {
+ heapp = (uint8_t *)malloc(k);
+ if (heapp != NULL)
+ bp = heapp;
+ }
+ if (k > (int)sizeof(cbuffer) && NULL == heapp) {
+ bp = cbuffer;
+ k = sizeof(cbuffer);
+ len = k - 4;
+ trunc = 1;
+ }
+ cmd[0] = 0x37; /* READ DEFECT DATA (10) */
+ cmd[1] = 0x00;
+ cmd[2] = (table ? 0x08 : 0x10) | defectformat;
+ /* List, Format */
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = 0x00; /* (reserved) */
+ cmd[5] = 0x00; /* (reserved) */
+ cmd[6] = 0x00; /* (reserved) */
+ cmd[7] = (k >> 8); /* Alloc len */
+ cmd[8] = (k & 0xff); /* Alloc len */
+ cmd[9] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = k;
+ sci.dxferp = bp;
+ i = do_scsi_io(&sci);
+ df = (uint8_t *) (bp + 4);
+ }
+ }
+ if (i) {
+ fprintf(stdout, ">>> Unable to read %s defect data.\n",
+ (table ? "grown (GLIST)" : "primary (PLIST)"));
+ status |= i;
+ continue;
+ }
+ else {
+ if (table && !status && !sorthead)
+ printf("\n");
+ defect_format = (bp[1] & 0x7);
+ if (-1 == reallen) {
+ printf("at least ");
+ reallen = len;
+ }
+ printf("%d entries (%d bytes) in %s table.\n",
+ reallen / ((0 == defect_format) ? 4 : 8), reallen,
+ table ? "grown (GLIST)" : "primary (PLIST)");
+ if (!sorthead)
+ printf("Format (%x) is: %s\n", defect_format,
+ formatname(defect_format));
+ i = 0;
+ switch (defect_format) {
+ case 4: /* bytes from index */
+ while (len > 0) {
+ snprintf((char *)cbuffer1, 40, "%6d:%3u:%8d",
+ getnbyte(df, 3), df[3], getnbyte(df + 4, 4));
+ if (sorthead == 0)
+ printf("%19s", (char *)cbuffer1);
+ else
+ if (df[3] < MAX_HEADS) headsp[df[3]]++;
+ len -= 8;
+ df += 8;
+ i++;
+ if (i >= 4 && !sorthead) {
+ printf("\n");
+ i = 0;
+ }
+ else if (!sorthead) printf("|");
+ }
+ break;
+ case 5: /* physical sector */
+ while (len > 0) {
+ snprintf((char *)cbuffer1, 40, "%6d:%2u:%5d",
+ getnbyte(df, 3),
+ df[3], getnbyte(df + 4, 4));
+ if (sorthead == 0)
+ printf("%15s", (char *)cbuffer1);
+ else
+ if (df[3] < MAX_HEADS) headsp[df[3]]++;
+ len -= 8;
+ df += 8;
+ i++;
+ if (i >= 5 && !sorthead) {
+ printf("\n");
+ i = 0;
+ }
+ else if (!sorthead) printf("|");
+ }
+ break;
+ case 0: /* lba (32 bit) */
+ while (len > 0) {
+ printf("%10d", getnbyte(df, 4));
+ len -= 4;
+ df += 4;
+ i++;
+ if (i >= 7) {
+ printf("\n");
+ i = 0;
+ }
+ else
+ printf("|");
+ }
+ break;
+ case 3: /* lba (64 bit) */
+ while (len > 0) {
+ printf("%15" PRId64 , getnbyte_ll(df, 8));
+ len -= 8;
+ df += 8;
+ i++;
+ if (i >= 5) {
+ printf("\n");
+ i = 0;
+ }
+ else
+ printf("|");
+ }
+ break;
+ default:
+ printf("unknown defect list format: %d\n", defect_format);
+ break;
+ }
+ if (i && !sorthead)
+ printf("\n");
+ }
+ if (trunc)
+ printf("[truncated]\n");
+ }
+ if (heapp) {
+ free(heapp);
+ heapp = NULL;
+ }
+ if (sorthead) {
+ printf("Format is: [head:# entries for this head in list]\n\n");
+ for (i=0; i<MAX_HEADS; i++) {
+ if (headsp[i] > 0) {
+ printf("%3d: %u\n", i, headsp[i]);
+ }
+ }
+ free(headsp);
+ }
+ printf("\n");
+ return status;
+}
+
+static int
+disk_cache(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 21, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------\n");
+ };
+ bitfield(pagestart + 2, "Initiator Control", 1, 7);
+ bitfield(pagestart + 2, "ABPF", 1, 6);
+ bitfield(pagestart + 2, "CAP", 1, 5);
+ bitfield(pagestart + 2, "DISC", 1, 4);
+ bitfield(pagestart + 2, "SIZE", 1, 3);
+ bitfield(pagestart + 2, "Write Cache Enabled", 1, 2);
+ bitfield(pagestart + 2, "MF", 1, 1);
+ bitfield(pagestart + 2, "Read Cache Disabled", 1, 0);
+ bitfield(pagestart + 3, "Demand Read Retention Priority", 0xf, 4);
+ bitfield(pagestart + 3, "Demand Write Retention Priority", 0xf, 0);
+ intfield(pagestart + 4, 2, "Disable Pre-fetch Transfer Length");
+ intfield(pagestart + 6, 2, "Minimum Pre-fetch");
+ intfield(pagestart + 8, 2, "Maximum Pre-fetch");
+ intfield(pagestart + 10, 2, "Maximum Pre-fetch Ceiling");
+ bitfield(pagestart + 12, "FSW", 1, 7);
+ bitfield(pagestart + 12, "LBCSS", 1, 6);
+ bitfield(pagestart + 12, "DRA", 1, 5);
+ bitfield(pagestart + 12, "NV_DIS", 1, 0);
+ intfield(pagestart + 13, 1, "Number of Cache Segments");
+ intfield(pagestart + 14, 2, "Cache Segment size");
+ intfield(pagestart + 17, 3, "Non-Cache Segment size");
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+disk_format(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 13, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------\n");
+ };
+ intfield(pagestart + 2, 2, "Tracks per Zone");
+ intfield(pagestart + 4, 2, "Alternate sectors per zone");
+ intfield(pagestart + 6, 2, "Alternate tracks per zone");
+ intfield(pagestart + 8, 2, "Alternate tracks per lu");
+ intfield(pagestart + 10, 2, "Sectors per track");
+ intfield(pagestart + 12, 2, "Data bytes per physical sector");
+ intfield(pagestart + 14, 2, "Interleave");
+ intfield(pagestart + 16, 2, "Track skew factor");
+ intfield(pagestart + 18, 2, "Cylinder skew factor");
+ bitfield(pagestart + 20, "Supports Soft Sectoring", 1, 7);
+ bitfield(pagestart + 20, "Supports Hard Sectoring", 1, 6);
+ bitfield(pagestart + 20, "Removable Medium", 1, 5);
+ bitfield(pagestart + 20, "Surface", 1, 4);
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+
+}
+
+static int
+disk_verify_error_recovery(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 7, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "EER", 1, 3);
+ bitfield(pagestart + 2, "PER", 1, 2);
+ bitfield(pagestart + 2, "DTE", 1, 1);
+ bitfield(pagestart + 2, "DCR", 1, 0);
+ intfield(pagestart + 3, 1, "Verify Retry Count");
+ intfield(pagestart + 4, 1, "Verify Correction Span (bits)");
+ intfield(pagestart + 10, 2, "Verify Recovery Time Limit (ms)");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+#if 0
+static int
+peripheral_device_page(struct mpage_info * mpi, const char * prefix)
+{
+ static char *idents[] =
+ {
+ "X3.131: Small Computer System Interface",
+ "X3.91M-1987: Storage Module Interface",
+ "X3.170: Enhanced Small Device Interface",
+ "X3.130-1986; X3T9.3/87-002: IPI-2",
+ "X3.132-1987; X3.147-1988: IPI-3"
+ };
+ int status;
+ unsigned ident;
+ uint8_t *pagestart;
+ char *name;
+
+ status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("---------------------------------\n");
+ };
+
+#if 0
+ dump(pagestart, 20);
+ pagestart[1] += 2; /*TEST */
+ cbuffer[8] += 2; /*TEST */
+#endif
+
+ ident = getnbyte(pagestart + 2, 2);
+ if (ident < (sizeof(idents) / sizeof(char *)))
+ name = idents[ident];
+ else if (ident < 0x8000)
+ name = "Reserved";
+ else
+ name = "Vendor Specific";
+
+#ifdef DPG_CHECK_THIS_OUT
+ bdlen = pagestart[1] - 6;
+ if (bdlen < 0)
+ bdlen = 0;
+ else {
+ status = setup_mode_page(mpi, 2, cbuffer, &bdlen,
+ &pagestart);
+ if (status)
+ return status;
+ }
+
+ hexfield(pagestart + 2, 2, "Interface Identifier");
+ if (!x_interface) {
+ for (ident = 0; ident < 35; ident++)
+ putchar(' ');
+ puts(name);
+ }
+ hexdatafield(pagestart + 8, bdlen, "Vendor Specific Data");
+#endif
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ if (x_interface)
+ puts(name);
+ return 0;
+}
+#endif
+
+static int
+common_power_condition(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("--------------------------------\n");
+ }
+ bitfield(pagestart + 3, "Idle", 1, 1);
+ bitfield(pagestart + 3, "Standby", 1, 0);
+ intfield(pagestart + 4, 4, "Idle Condition counter (100ms)");
+ intfield(pagestart + 8, 4, "Standby Condition counter (100ms)");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+disk_xor_control(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 5, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("--------------------------------\n");
+ }
+ bitfield(pagestart + 2, "XORDS", 1, 1);
+ intfield(pagestart + 4, 4, "Maximum XOR write size");
+ intfield(pagestart + 12, 4, "Maximum regenerate size");
+ intfield(pagestart + 16, 4, "Maximum rebuild transfer size");
+ intfield(pagestart + 22, 2, "Rebuild delay");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+disk_background(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ bitfield(pagestart + 4, "Enable background medium scan", 1, 0);
+ bitfield(pagestart + 5, "Enable pre-scan", 1, 0);
+ intfield(pagestart + 6, 2, "BMS interval time (hour)");
+ intfield(pagestart + 8, 2, "Pre-scan timeout value (hour)");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+optical_memory(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("--------------------------------\n");
+ }
+ bitfield(pagestart + 2, "RUBR", 1, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_write_param(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 20, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("--------------------------------\n");
+ }
+ bitfield(pagestart + 2, "BUFE", 1, 6);
+ bitfield(pagestart + 2, "LS_V", 1, 5);
+ bitfield(pagestart + 2, "Test Write", 1, 4);
+ bitfield(pagestart + 2, "Write Type", 0xf, 0);
+ bitfield(pagestart + 3, "MultiSession", 3, 6);
+ bitfield(pagestart + 3, "FP", 1, 5);
+ bitfield(pagestart + 3, "Copy", 1, 4);
+ bitfield(pagestart + 3, "Track Mode", 0xf, 0);
+ bitfield(pagestart + 4, "Data Block type", 0xf, 0);
+ intfield(pagestart + 5, 1, "Link size");
+ bitfield(pagestart + 7, "Initiator app. code", 0x3f, 0);
+ intfield(pagestart + 8, 1, "Session Format");
+ intfield(pagestart + 10, 4, "Packet size");
+ intfield(pagestart + 14, 2, "Audio Pause Length");
+ hexdatafield(pagestart + 16, 16, "Media Catalog number");
+ hexdatafield(pagestart + 32, 16, "Int. standard recording code");
+ hexdatafield(pagestart + 48, 1, "Subheader byte 1");
+ hexdatafield(pagestart + 49, 1, "Subheader byte 2");
+ hexdatafield(pagestart + 50, 1, "Subheader byte 3");
+ hexdatafield(pagestart + 51, 1, "Subheader byte 4");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_audio_control(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("--------------------------------\n");
+ }
+ bitfield(pagestart + 2, "IMMED", 1, 2);
+ bitfield(pagestart + 2, "SOTC", 1, 1);
+ bitfield(pagestart + 8, "CDDA out port 0, channel select", 0xf, 0);
+ intfield(pagestart + 9, 1, "Channel port 0 volume");
+ bitfield(pagestart + 10, "CDDA out port 1, channel select", 0xf, 0);
+ intfield(pagestart + 11, 1, "Channel port 1 volume");
+ bitfield(pagestart + 12, "CDDA out port 2, channel select", 0xf, 0);
+ intfield(pagestart + 13, 1, "Channel port 2 volume");
+ bitfield(pagestart + 14, "CDDA out port 3, channel select", 0xf, 0);
+ intfield(pagestart + 15, 1, "Channel port 3 volume");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_timeout(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------------------\n");
+ }
+ bitfield(pagestart + 4, "G3Enable", 1, 3);
+ bitfield(pagestart + 4, "TMOE", 1, 2);
+ bitfield(pagestart + 4, "DISP", 1, 1);
+ bitfield(pagestart + 4, "SWPP", 1, 0);
+ intfield(pagestart + 6, 2, "Group 1 minimum time-out");
+ intfield(pagestart + 8, 2, "Group 2 minimum time-out");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_device_param(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 3, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("------------------------------------\n");
+ }
+ bitfield(pagestart + 3, "Inactivity timer multiplier", 0xf, 0);
+ intfield(pagestart + 4, 2, "MSF-S units per MSF_M unit");
+ intfield(pagestart + 6, 2, "MSF-F units per MSF_S unit");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+/* This is not a standard t10.org MMC mode page (it is now "protocol specific
+ lu" mode page). This definition was found in Hitachi GF-2050/GF-2055
+ DVD-RAM drive SCSI reference manual. */
+static int
+cdvd_feature(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 12, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------\n");
+ }
+ intfield(pagestart + 2, 2, "DVD feature set");
+ intfield(pagestart + 4, 2, "CD audio");
+ intfield(pagestart + 6, 2, "Embedded changer");
+ intfield(pagestart + 8, 2, "Packet SMART");
+ intfield(pagestart + 10, 2, "Persistent prevent(MESN)");
+ intfield(pagestart + 12, 2, "Event status notification");
+ intfield(pagestart + 14, 2, "Digital output");
+ intfield(pagestart + 16, 2, "CD sequential recordable");
+ intfield(pagestart + 18, 2, "DVD sequential recordable");
+ intfield(pagestart + 20, 2, "Random recordable");
+ intfield(pagestart + 22, 2, "Key management");
+ intfield(pagestart + 24, 2, "Partial recorded CD media read");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_mm_capab(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 49, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "DVD-RAM read", 1, 5);
+ bitfield(pagestart + 2, "DVD-R read", 1, 4);
+ bitfield(pagestart + 2, "DVD-ROM read", 1, 3);
+ bitfield(pagestart + 2, "Method 2", 1, 2);
+ bitfield(pagestart + 2, "CD-RW read", 1, 1);
+ bitfield(pagestart + 2, "CD-R read", 1, 0);
+ bitfield(pagestart + 3, "DVD-RAM write", 1, 5);
+ bitfield(pagestart + 3, "DVD-R write", 1, 4);
+ bitfield(pagestart + 3, "DVD-ROM write", 1, 3);
+ bitfield(pagestart + 3, "Test Write", 1, 2);
+ bitfield(pagestart + 3, "CD-RW write", 1, 1);
+ bitfield(pagestart + 3, "CD-R write", 1, 0);
+ bitfield(pagestart + 4, "BUF", 1, 7);
+ bitfield(pagestart + 4, "MultiSession", 1, 6);
+ bitfield(pagestart + 4, "Mode 2 Form 2", 1, 5);
+ bitfield(pagestart + 4, "Mode 2 Form 1", 1, 4);
+ bitfield(pagestart + 4, "Digital port (2)", 1, 3);
+ bitfield(pagestart + 4, "Digital port (1)", 1, 2);
+ bitfield(pagestart + 4, "Composite", 1, 1);
+ bitfield(pagestart + 4, "Audio play", 1, 0);
+ bitfield(pagestart + 5, "Read bar code", 1, 7);
+ bitfield(pagestart + 5, "UPC", 1, 6);
+ bitfield(pagestart + 5, "ISRC", 1, 5);
+ bitfield(pagestart + 5, "C2 pointers supported", 1, 4);
+ bitfield(pagestart + 5, "R-W de-interleaved & corrected", 1, 3);
+ bitfield(pagestart + 5, "R-W supported", 1, 2);
+ bitfield(pagestart + 5, "CD-DA stream is accurate", 1, 1);
+ bitfield(pagestart + 5, "CD-DA commands supported", 1, 0);
+ bitfield(pagestart + 6, "Loading mechanism type", 7, 5);
+ bitfield(pagestart + 6, "Eject (individual or magazine)", 1, 3);
+ bitfield(pagestart + 6, "Prevent jumper", 1, 2);
+ bitfield(pagestart + 6, "Lock state", 1, 1);
+ bitfield(pagestart + 6, "Lock", 1, 0);
+ bitfield(pagestart + 7, "R-W in lead-in", 1, 5);
+ bitfield(pagestart + 7, "Side change capable", 1, 4);
+ bitfield(pagestart + 7, "S/W slot selection", 1, 3);
+ bitfield(pagestart + 7, "Changer supports disc present", 1, 2);
+ bitfield(pagestart + 7, "Separate channel mute", 1, 1);
+ bitfield(pagestart + 7, "Separate volume levels", 1, 0);
+ intfield(pagestart + 10, 2, "number of volume level supported");
+ intfield(pagestart + 12, 2, "Buffer size supported");
+ bitfield(pagestart + 17, "Length", 3, 4);
+ bitfield(pagestart + 17, "LSBF", 1, 3);
+ bitfield(pagestart + 17, "RCK", 1, 2);
+ bitfield(pagestart + 17, "BCKF", 1, 1);
+ intfield(pagestart + 22, 2, "Copy management revision supported");
+ bitfield(pagestart + 27, "Rotation control selected", 3, 0);
+ intfield(pagestart + 28, 2, "Current write speed selected");
+ intfield(pagestart + 30, 2, "# of lu speed performance tables");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+cdvd_cache(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("-----------------------\n");
+ };
+ bitfield(pagestart + 2, "Write Cache Enabled", 1, 2);
+ bitfield(pagestart + 2, "Read Cache Disabled", 1, 0);
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+tape_data_compression(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "DCE", 1, 7);
+ bitfield(pagestart + 2, "DCC", 1, 6);
+ bitfield(pagestart + 3, "DDE", 1, 7);
+ bitfield(pagestart + 3, "RED", 3, 5);
+ intfield(pagestart + 4, 4, "Compression algorithm");
+ intfield(pagestart + 8, 4, "Decompression algorithm");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+tape_dev_config(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 25, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "CAF", 1, 5);
+ bitfield(pagestart + 2, "Active format", 0x1f, 0);
+ intfield(pagestart + 3, 1, "Active partition");
+ intfield(pagestart + 4, 1, "Write object cbuffer full ratio");
+ intfield(pagestart + 5, 1, "Read object cbuffer full ratio");
+ intfield(pagestart + 6, 2, "Wire delay time");
+ bitfield(pagestart + 8, "OBR", 1, 7);
+ bitfield(pagestart + 8, "LOIS", 1, 6);
+ bitfield(pagestart + 8, "RSMK", 1, 5);
+ bitfield(pagestart + 8, "AVC", 1, 4);
+ bitfield(pagestart + 8, "SOCF", 3, 2);
+ bitfield(pagestart + 8, "ROBO", 1, 1);
+ bitfield(pagestart + 8, "REW", 1, 0);
+ intfield(pagestart + 9, 1, "Gap size");
+ bitfield(pagestart + 10, "EOD defined", 7, 5);
+ bitfield(pagestart + 10, "EEG", 1, 4);
+ bitfield(pagestart + 10, "SEW", 1, 3);
+ bitfield(pagestart + 10, "SWP", 1, 2);
+ bitfield(pagestart + 10, "BAML", 1, 1);
+ bitfield(pagestart + 10, "BAM", 1, 0);
+ intfield(pagestart + 11, 3, "Object cbuffer size at early warning");
+ intfield(pagestart + 14, 1, "Select data compression algorithm");
+ bitfield(pagestart + 15, "ASOCWP", 1, 2);
+ bitfield(pagestart + 15, "PERSWO", 1, 1);
+ bitfield(pagestart + 15, "PRMWP", 1, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+tape_medium_part1(struct mpage_info * mpi, const char * prefix)
+{
+ int status, off, len;
+ uint8_t *pagestart;
+
+ /* variable length mode page, need to know its response length */
+ status = get_mode_page(mpi, 0, cbuffer);
+ if (status)
+ return status;
+ off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
+ if (off < 0)
+ return off;
+ len = mpi->resp_len - off;
+
+ status = setup_mode_page(mpi, 12 + ((len - 10) / 2), cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ intfield(pagestart + 2, 1, "Maximum additional partitions");
+ intfield(pagestart + 3, 1, "Additional partitions defined");
+ bitfield(pagestart + 4, "FDP", 1, 7);
+ bitfield(pagestart + 4, "SDP", 1, 6);
+ bitfield(pagestart + 4, "IDP", 1, 5);
+ bitfield(pagestart + 4, "PSUM", 3, 3);
+ bitfield(pagestart + 4, "POFM", 1, 2);
+ bitfield(pagestart + 4, "CLEAR", 1, 1);
+ bitfield(pagestart + 4, "ADDP", 1, 0);
+ intfield(pagestart + 5, 1, "Medium format recognition");
+ bitfield(pagestart + 6, "Partition units", 0xf, 0);
+ intfield(pagestart + 8, 2, "Partition size");
+
+ for (off = 10; off < len; off += 2)
+ intfield(pagestart + off, 2, "Partition size");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+tape_medium_part2_4(struct mpage_info * mpi, const char * prefix)
+{
+ int status, off, len;
+ uint8_t *pagestart;
+
+ /* variable length mode page, need to know its response length */
+ status = get_mode_page(mpi, 0, cbuffer);
+ if (status)
+ return status;
+ off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
+ if (off < 0)
+ return off;
+ len = mpi->resp_len - off;
+
+ status = setup_mode_page(mpi, 1 + ((len - 4) / 2), cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ intfield(pagestart + 2, 2, "Partition size");
+
+ for (off = 4; off < len; off += 2)
+ intfield(pagestart + off, 2, "Partition size");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+ses_services_manag(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 5, "ENBLTC", 1, 0);
+ intfield(pagestart + 6, 2, "Maximum time to completion (100 ms units)");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+fcp_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", "Fibre Channel logical unit",
+ mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 3, "EPDC", 1, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+sas_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", "SAS logical unit", mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "Transport Layer Retries", 1, 4);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ int proto_id = 0;
+
+ status = get_protocol_id(0, cbuffer, &proto_id, NULL);
+ if (status)
+ return status;
+ if (0 == proto_id)
+ return fcp_proto_spec_lu(mpi, prefix);
+ else if (6 == proto_id)
+ return sas_proto_spec_lu(mpi, prefix);
+ else
+ return DECODE_FAILED_TRY_HEX;
+}
+
+static int
+fcp_proto_spec_port(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", "Fibre Channel port control",
+ mpi->page);
+ printf("----------------------------------------------------\n");
+ }
+ bitfield(pagestart + 3, "DTFD", 1, 7);
+ bitfield(pagestart + 3, "PLPB", 1, 6);
+ bitfield(pagestart + 3, "DDIS", 1, 5);
+ bitfield(pagestart + 3, "DLM", 1, 4);
+ bitfield(pagestart + 3, "RHA", 1, 3);
+ bitfield(pagestart + 3, "ALWI", 1, 2);
+ bitfield(pagestart + 3, "DTIPE", 1, 1);
+ bitfield(pagestart + 3, "DTOLI", 1, 0);
+ bitfield(pagestart + 6, "RR_TOV units", 7, 0);
+ intfield(pagestart + 7, 1, "Resource recovery time-out");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+spi4_proto_spec_port(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", "SPI-4 port control", mpi->page);
+ printf("-----------------------------------\n");
+ }
+ intfield(pagestart + 4, 2, "Synchronous transfer time-out");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+/* Protocol specific mode page for SAS, short format (subpage 0) */
+static int
+sas_proto_spec_port(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 3, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode page (0x%x)\n", "SAS SSP port control", mpi->page);
+ printf("-------------------------------------\n");
+ }
+ bitfield(pagestart + 2, "Ready LED meaning", 0x1, 4);
+ intfield(pagestart + 4, 2, "I_T Nexus Loss time");
+ intfield(pagestart + 6, 2, "Initiator response time-out");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_proto_spec_port(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ int proto_id = 0;
+
+ status = get_protocol_id(1, cbuffer, &proto_id, NULL);
+ if (status)
+ return status;
+ if (0 == proto_id)
+ return fcp_proto_spec_port(mpi, prefix);
+ else if (1 == proto_id)
+ return spi4_proto_spec_port(mpi, prefix);
+ else if (6 == proto_id)
+ return sas_proto_spec_port(mpi, prefix);
+ else
+ return DECODE_FAILED_TRY_HEX;
+}
+
+static int
+spi4_margin_control(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 5, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", "SPI-4 Margin control",
+ mpi->page, mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ bitfield(pagestart + 5, "Protocol identifier", 0xf, 0);
+ bitfield(pagestart + 7, "Driver Strength", 0xf, 4);
+ bitfield(pagestart + 8, "Driver Asymmetry", 0xf, 4);
+ bitfield(pagestart + 8, "Driver Precompensation", 0xf, 0);
+ bitfield(pagestart + 9, "Driver Slew rate", 0xf, 4);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+/* Protocol specific mode page for SAS, phy control + discover (subpage 1) */
+static int
+sas_phy_control_discover(struct mpage_info * mpi, const char * prefix)
+{
+ int status, off, num_phys, k;
+ uint8_t *pagestart;
+ uint8_t *p;
+
+ /* variable length mode page, need to know its response length */
+ status = get_mode_page(mpi, 0, cbuffer);
+ if (status)
+ return status;
+ off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
+ if (off < 0)
+ return off;
+ num_phys = cbuffer[off + 7];
+
+ status = setup_mode_page(mpi, 1 + (16 * num_phys), cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", "SAS Phy Control and "
+ "Discover", mpi->page, mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ intfield(pagestart + 7, 1, "Number of phys");
+ for (k = 0, p = pagestart + 8; k < num_phys; ++k, p += 48) {
+ intfield(p + 1, 1, "Phy Identifier");
+ bitfield(p + 4, "Attached Device type", 0x7, 4);
+ bitfield(p + 5, "Negotiated Logical Link rate", 0xf, 0);
+ bitfield(p + 6, "Attached SSP Initiator port", 0x1, 3);
+ bitfield(p + 6, "Attached STP Initiator port", 0x1, 2);
+ bitfield(p + 6, "Attached SMP Initiator port", 0x1, 1);
+ bitfield(p + 7, "Attached SSP Target port", 0x1, 3);
+ bitfield(p + 7, "Attached STP Target port", 0x1, 2);
+ bitfield(p + 7, "Attached SMP Target port", 0x1, 1);
+ hexdatafield(p + 8, 8, "SAS address");
+ hexdatafield(p + 16, 8, "Attached SAS address");
+ intfield(p + 24, 1, "Attached Phy identifier");
+ bitfield(p + 32, "Programmed Min Physical Link rate", 0xf, 4);
+ bitfield(p + 32, "Hardware Min Physical Link rate", 0xf, 0);
+ bitfield(p + 33, "Programmed Max Physical Link rate", 0xf, 4);
+ bitfield(p + 33, "Hardware Max Physical Link rate", 0xf, 0);
+ }
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+
+static int
+common_proto_spec_port_sp1(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ int proto_id = 0;
+
+ status = get_protocol_id(1, cbuffer, &proto_id, NULL);
+ if (status)
+ return status;
+ if (1 == proto_id)
+ return spi4_margin_control(mpi, prefix);
+ else if (6 == proto_id)
+ return sas_phy_control_discover(mpi, prefix);
+ else
+ return DECODE_FAILED_TRY_HEX;
+}
+
+static int
+spi4_training_config(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 27, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", "training configuration",
+ mpi->page, mpi->subpage);
+ printf("----------------------------------------------------------\n");
+ }
+ hexdatafield(pagestart + 10, 4, "DB(0) value");
+ hexdatafield(pagestart + 14, 4, "DB(1) value");
+ hexdatafield(pagestart + 18, 4, "DB(2) value");
+ hexdatafield(pagestart + 22, 4, "DB(3) value");
+ hexdatafield(pagestart + 26, 4, "DB(4) value");
+ hexdatafield(pagestart + 30, 4, "DB(5) value");
+ hexdatafield(pagestart + 34, 4, "DB(6) value");
+ hexdatafield(pagestart + 38, 4, "DB(7) value");
+ hexdatafield(pagestart + 42, 4, "DB(8) value");
+ hexdatafield(pagestart + 46, 4, "DB(9) value");
+ hexdatafield(pagestart + 50, 4, "DB(10) value");
+ hexdatafield(pagestart + 54, 4, "DB(11) value");
+ hexdatafield(pagestart + 58, 4, "DB(12) value");
+ hexdatafield(pagestart + 62, 4, "DB(13) value");
+ hexdatafield(pagestart + 66, 4, "DB(14) value");
+ hexdatafield(pagestart + 70, 4, "DB(15) value");
+ hexdatafield(pagestart + 74, 4, "P_CRCA value");
+ hexdatafield(pagestart + 78, 4, "P1 value");
+ hexdatafield(pagestart + 82, 4, "BSY value");
+ hexdatafield(pagestart + 86, 4, "SEL value");
+ hexdatafield(pagestart + 90, 4, "RST value");
+ hexdatafield(pagestart + 94, 4, "REQ value");
+ hexdatafield(pagestart + 98, 4, "ACK value");
+ hexdatafield(pagestart + 102, 4, "ATN value");
+ hexdatafield(pagestart + 106, 4, "C/D value");
+ hexdatafield(pagestart + 110, 4, "I/O value");
+ hexdatafield(pagestart + 114, 4, "MSG value");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+/* SAS(2) SSP, shared protocol specific port mode subpage (subpage 2) */
+static int
+sas_shared_spec_port(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", "SAS SSP shared protocol "
+ "specific port", mpi->page, mpi->subpage);
+ printf("-----------------------------------------------------\n");
+ }
+ intfield(pagestart + 6, 2, "Power loss timeout(ms)");
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+common_proto_spec_port_sp2(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ int proto_id = 0;
+
+ status = get_protocol_id(1, cbuffer, &proto_id, NULL);
+ if (status)
+ return status;
+ if (1 == proto_id)
+ return spi4_training_config(mpi, prefix);
+ else if (6 == proto_id)
+ return sas_shared_spec_port(mpi, prefix);
+ else
+ return DECODE_FAILED_TRY_HEX;
+}
+
+static int
+spi4_negotiated(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 7, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ intfield(pagestart + 6, 1, "Transfer period");
+ intfield(pagestart + 8, 1, "REQ/ACK offset");
+ intfield(pagestart + 9, 1, "Transfer width exponent");
+ bitfield(pagestart + 10, "Protocol option bits", 0x7f, 0);
+ bitfield(pagestart + 11, "Transceiver mode", 3, 2);
+ bitfield(pagestart + 11, "Sent PCOMP_EN", 1, 1);
+ bitfield(pagestart + 11, "Received PCOMP_EN", 1, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static int
+spi4_report_xfer(struct mpage_info * mpi, const char * prefix)
+{
+ int status;
+ uint8_t *pagestart;
+
+ status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
+ if (status)
+ return status;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (!x_interface && !replace) {
+ printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
+ mpi->subpage);
+ printf("--------------------------------------------\n");
+ }
+ intfield(pagestart + 6, 1, "Minimum transfer period factor");
+ intfield(pagestart + 8, 1, "Maximum REQ/ACK offset");
+ intfield(pagestart + 9, 1, "Maximum transfer width exponent");
+ bitfield(pagestart + 10, "Protocol option bits supported", 0xff, 0);
+
+ if (x_interface && replace)
+ return put_mode_page(mpi, cbuffer);
+ else
+ printf("\n");
+ return 0;
+}
+
+static void
+print_hex_page(struct mpage_info * mpi, const char * prefix,
+ uint8_t *pagestart, int off, int len)
+{
+ int k;
+ const char * pg_name;
+
+ if (prefix[0])
+ printf("%s", prefix);
+ if (! x_interface) {
+ pg_name = get_page_name(mpi);
+ if (mpi->subpage) {
+ if (pg_name && (unkn_page_str != pg_name))
+ printf("mode page: 0x%02x subpage: 0x%02x [%s]\n",
+ mpi->page, mpi->subpage, pg_name);
+ else
+ printf("mode page: 0x%02x subpage: 0x%02x\n", mpi->page,
+ mpi->subpage);
+ printf("------------------------------\n");
+ } else {
+ if (pg_name && (unkn_page_str != pg_name))
+ printf("mode page: 0x%02x [%s]\n", mpi->page,
+ pg_name);
+ else
+ printf("mode page: 0x%02x\n", mpi->page);
+ printf("---------------\n");
+ }
+ }
+ for (k = off; k < len; k++)
+ {
+ char nm[8];
+
+ snprintf(nm, sizeof(nm), "0x%02x", (unsigned char)k);
+ hexdatafield(pagestart + k, 1, nm);
+ }
+ printf("\n");
+}
+
+static int
+do_user_page(struct mpage_info * mpi, int decode_in_hex)
+{
+ int status = 0;
+ int len, off, res, done;
+ int offset = 0;
+ uint8_t *pagestart;
+ char prefix[96];
+ struct mpage_info local_mp_i;
+ struct mpage_name_func * mpf;
+ int multiple = ((MP_LIST_PAGES == mpi->page) ||
+ (MP_LIST_SUBPAGES == mpi->subpage));
+
+ if (replace && multiple) {
+ printf("Can't list all (sub)pages and use replace (-R) together\n");
+ return 1;
+ }
+ status = get_mode_page(mpi, 0, cbuffer2);
+ if (status) {
+ printf("\n");
+ return status;
+ } else {
+ offset = modePageOffset(cbuffer2, mpi->resp_len, mode6byte);
+ if (offset < 0) {
+ fprintf(stdout, "mode page=0x%x has bad page format\n",
+ mpi->page);
+ fprintf(stdout, " perhaps '-z' switch may help\n");
+ return -1;
+ }
+ pagestart = cbuffer2 + offset;
+ }
+
+ memset(&local_mp_i, 0, sizeof(local_mp_i));
+ local_mp_i.page_control = mpi->page_control;
+ local_mp_i.peri_type = mpi->peri_type;
+ local_mp_i.inq_byte6 = mpi->inq_byte6;
+ local_mp_i.resp_len = mpi->resp_len;
+
+ do {
+ local_mp_i.page = (pagestart[0] & 0x3f);
+ local_mp_i.subpage = (pagestart[0] & 0x40) ? pagestart[1] : 0;
+ if(0 == local_mp_i.page) { /* page==0 vendor (unknown) format */
+ off = 0;
+ len = mpi->resp_len - offset; /* should be last listed page */
+ } else if (local_mp_i.subpage) {
+ off = 4;
+ len = (pagestart[2] << 8) + pagestart[3] + 4;
+ } else {
+ off = 2;
+ len = pagestart[1] + 2;
+ }
+
+ prefix[0] = '\0';
+ done = 0;
+ if ((! decode_in_hex) && ((mpf = get_mpage_name_func(&local_mp_i))) &&
+ mpf->func) {
+ if (multiple && x_interface && !replace) {
+ if (local_mp_i.subpage)
+ snprintf(prefix, sizeof(prefix), "sginfo -t 0x%x,0x%x"
+ " -XR %s ", local_mp_i.page, local_mp_i.subpage,
+ device_name);
+ else
+ snprintf(prefix, sizeof(prefix), "sginfo -t 0x%x -XR %s ",
+ local_mp_i.page, device_name);
+ }
+ res = mpf->func(&local_mp_i, prefix);
+ if (DECODE_FAILED_TRY_HEX != res) {
+ done = 1;
+ status |= res;
+ }
+ }
+ if (! done) {
+ if (x_interface && replace)
+ return put_mode_page(&local_mp_i, cbuffer2);
+ else {
+ if (multiple && x_interface && !replace) {
+ if (local_mp_i.subpage)
+ snprintf(prefix, sizeof(prefix), "sginfo -u 0x%x,0x%x"
+ " -XR %s ", local_mp_i.page,
+ local_mp_i.subpage, device_name);
+ else
+ snprintf(prefix, sizeof(prefix), "sginfo -u 0x%x -XR "
+ "%s ", local_mp_i.page, device_name);
+ }
+ print_hex_page(&local_mp_i, prefix, pagestart, off, len);
+ }
+ }
+ offset += len;
+ pagestart = cbuffer2 + offset;
+ } while (multiple && (offset < mpi->resp_len));
+ return status;
+}
+
+static void
+inqfieldname(uint8_t *deststr, const uint8_t *srcbuf, int maxlen)
+{
+ int i;
+
+ memset(deststr, '\0', MAX_INQFIELD_LEN);
+ for (i = maxlen - 1; i >= 0 && isspace(srcbuf[i]); --i)
+ ;
+ memcpy(deststr, srcbuf, i + 1);
+}
+
+static int
+do_inquiry(int * peri_type, int * resp_byte6, int inquiry_verbosity)
+{
+ int status;
+ uint8_t cmd[6];
+ uint8_t fieldname[MAX_INQFIELD_LEN];
+ uint8_t *pagestart;
+ struct scsi_cmnd_io sci;
+
+ memset(cbuffer, 0, INQUIRY_RESP_INITIAL_LEN);
+ cbuffer[0] = 0x7f;
+
+ cmd[0] = 0x12; /* INQUIRY */
+ cmd[1] = 0x00; /* evpd=0 */
+ cmd[2] = 0x00; /* page code = 0 */
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = INQUIRY_RESP_INITIAL_LEN; /* allocation length */
+ cmd[5] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = INQUIRY_RESP_INITIAL_LEN;
+ sci.dxferp = cbuffer;
+ status = do_scsi_io(&sci);
+ if (status) {
+ printf("Error doing INQUIRY (1)\n");
+ return status;
+ }
+ if (trace_cmd > 1) {
+ printf(" inquiry response:\n");
+ dump(cbuffer, INQUIRY_RESP_INITIAL_LEN);
+ }
+ pagestart = cbuffer;
+ if (peri_type)
+ *peri_type = pagestart[0] & 0x1f;
+ if (resp_byte6)
+ *resp_byte6 = pagestart[6];
+ if (0 == inquiry_verbosity)
+ return 0;
+ if ((pagestart[4] + 5) < INQUIRY_RESP_INITIAL_LEN) {
+ printf("INQUIRY response too short: expected 36 bytes, got %d\n",
+ pagestart[4] + 5);
+ return -EINVAL;
+ }
+
+ if (!x_interface && !replace) {
+ printf("INQUIRY response (cmd: 0x12)\n");
+ printf("----------------------------\n");
+ };
+ bitfield(pagestart + 0, "Device Type", 0x1f, 0);
+ if (2 == inquiry_verbosity) {
+ bitfield(pagestart + 0, "Peripheral Qualifier", 0x7, 5);
+ bitfield(pagestart + 1, "Removable", 1, 7);
+ bitfield(pagestart + 2, "Version", 0xff, 0);
+ bitfield(pagestart + 3, "NormACA", 1, 5);
+ bitfield(pagestart + 3, "HiSup", 1, 4);
+ bitfield(pagestart + 3, "Response Data Format", 0xf, 0);
+ bitfield(pagestart + 5, "SCCS", 1, 7);
+ bitfield(pagestart + 5, "ACC", 1, 6);
+ bitfield(pagestart + 5, "ALUA", 3, 4);
+ bitfield(pagestart + 5, "3PC", 1, 3);
+ bitfield(pagestart + 5, "Protect", 1, 0);
+ bitfield(pagestart + 6, "BQue", 1, 7);
+ bitfield(pagestart + 6, "EncServ", 1, 6);
+ bitfield(pagestart + 6, "MultiP", 1, 4);
+ bitfield(pagestart + 6, "MChngr", 1, 3);
+ bitfield(pagestart + 6, "Addr16", 1, 0);
+ bitfield(pagestart + 7, "Relative Address", 1, 7);
+ bitfield(pagestart + 7, "Wide bus 16", 1, 5);
+ bitfield(pagestart + 7, "Synchronous neg.", 1, 4);
+ bitfield(pagestart + 7, "Linked Commands", 1, 3);
+ bitfield(pagestart + 7, "Command Queueing", 1, 1);
+ }
+ if (x_interface)
+ printf("\n");
+
+ inqfieldname(fieldname, pagestart + 8, 8);
+ printf("%s%s\n", (!x_interface ? "Vendor: " : ""),
+ fieldname);
+
+ inqfieldname(fieldname, pagestart + 16, 16);
+ printf("%s%s\n", (!x_interface ? "Product: " : ""),
+ fieldname);
+
+ inqfieldname(fieldname, pagestart + 32, 4);
+ printf("%s%s\n", (!x_interface ? "Revision level: " : ""),
+ fieldname);
+
+ printf("\n");
+ return status;
+
+}
+
+static int
+do_serial_number(void)
+{
+ int status, pagelen;
+ uint8_t cmd[6];
+ uint8_t *pagestart;
+ struct scsi_cmnd_io sci;
+ const uint8_t serial_vpd = 0x80;
+ const uint8_t supported_vpd = 0x0;
+
+ /* check supported VPD pages + unit serial number well formed */
+ cmd[0] = 0x12; /* INQUIRY */
+ cmd[1] = 0x01; /* evpd=1 */
+ cmd[2] = supported_vpd;
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = 0x04; /* allocation length */
+ cmd[5] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = 4;
+ sci.dxferp = cbuffer;
+ status = do_scsi_io(&sci);
+ if (status) {
+ printf("No serial number (error doing INQUIRY, supported VPDs)\n\n");
+ return status;
+ }
+ if (! ((supported_vpd == cbuffer[1]) && (0 == cbuffer[2]))) {
+ printf("No serial number (bad format for supported VPDs)\n\n");
+ return -1;
+ }
+
+ cmd[0] = 0x12; /* INQUIRY */
+ cmd[1] = 0x01; /* evpd=1 */
+ cmd[2] = serial_vpd;
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = 0x04; /* allocation length */
+ cmd[5] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = 4;
+ sci.dxferp = cbuffer;
+ status = do_scsi_io(&sci);
+ if (status) {
+ printf("No serial number (error doing INQUIRY, serial number)\n\n");
+ return status;
+ }
+ if (! ((serial_vpd == cbuffer[1]) && (0 == cbuffer[2]))) {
+ printf("No serial number (bad format for serial number)\n\n");
+ return -1;
+ }
+
+ pagestart = cbuffer;
+
+ pagelen = 4 + pagestart[3];
+
+ cmd[0] = 0x12; /* INQUIRY */
+ cmd[1] = 0x01; /* evpd=1 */
+ cmd[2] = serial_vpd;
+ cmd[3] = 0x00; /* (reserved) */
+ cmd[4] = (uint8_t)pagelen; /* allocation length */
+ cmd[5] = 0x00; /* control */
+
+ sci.cmnd = cmd;
+ sci.cmnd_len = sizeof(cmd);
+ sci.dxfer_dir = DXFER_FROM_DEVICE;
+ sci.dxfer_len = pagelen;
+ sci.dxferp = cbuffer;
+ status = do_scsi_io(&sci);
+ if (status) {
+ printf("No serial number (error doing INQUIRY, serial number)\n\n");
+ return status;
+ }
+ if (trace_cmd > 1) {
+ printf(" inquiry (vpd page 0x80) response:\n");
+ dump(cbuffer, pagelen);
+ }
+
+ pagestart[pagestart[3] + 4] = '\0';
+ printf("Serial Number '%s'\n\n", pagestart + 4);
+ return status;
+}
+
+
+typedef struct sg_map {
+ int bus;
+ int channel;
+ int target_id;
+ int lun;
+ char * dev_name;
+} Sg_map;
+
+typedef struct my_scsi_idlun
+{
+ int mux4;
+ int host_unique_id;
+
+} My_scsi_idlun;
+
+#define MDEV_NAME_SZ 256
+
+static void
+make_dev_name(char * fname, int k, int do_numeric)
+{
+ char buff[MDEV_NAME_SZ];
+ size_t len;
+
+ strncpy(fname, "/dev/sg", MDEV_NAME_SZ);
+ fname[MDEV_NAME_SZ - 1] = '\0';
+ len = strlen(fname);
+ if (do_numeric)
+ snprintf(fname + len, MDEV_NAME_SZ - len, "%d", k);
+ else {
+ if (k <= 26) {
+ buff[0] = 'a' + (char)k;
+ buff[1] = '\0';
+ strcat(fname, buff);
+ }
+ else
+ strcat(fname, "xxxx");
+ }
+}
+
+
+static Sg_map sg_map_arr[MAX_SG_DEVS + 1];
+
+#define MAX_HOLES 4
+
+/* Print out a list of the known devices on the system */
+static void
+show_devices(int raw)
+{
+ int k, j, fd, err, bus, n;
+ My_scsi_idlun m_idlun;
+ char name[MDEV_NAME_SZ];
+ char dev_name[MDEV_NAME_SZ + 6];
+ char ebuff[EBUFF_SZ];
+ int do_numeric = 1;
+ int max_holes = MAX_HOLES;
+ DIR *dir_ptr;
+ struct dirent *entry;
+ char *tmpptr;
+
+ dir_ptr=opendir("/dev");
+ if ( dir_ptr == NULL ) {
+ perror("/dev");
+ exit(1);
+ }
+
+ j=0;
+ while ( (entry=readdir(dir_ptr)) != NULL ) {
+ switch(entry->d_type) {
+ case DT_LNK:
+ case DT_CHR:
+ case DT_BLK:
+ break;
+ default:
+ continue;
+ }
+
+ switch(entry->d_name[0]) {
+ case 's':
+ case 'n':
+ break;
+ default:
+ continue;
+ }
+
+ if ( strncmp("sg",entry->d_name,2) == 0 ) {
+ continue;
+ }
+ if ( strncmp("sd",entry->d_name,2) == 0 ) {
+ continue;
+ }
+ if ( isdigit(entry->d_name[strlen(entry->d_name)-1]) ) {
+ continue;
+ }
+ if ( strncmp("snapshot",entry->d_name,8) == 0 ) {
+ continue;
+ }
+
+ snprintf(dev_name, sizeof(dev_name),"/dev/%s",entry->d_name);
+
+ fd = open(dev_name, O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ continue;
+ err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &(sg_map_arr[j].bus));
+ if (err < 0) {
+#if 0
+ snprintf(ebuff, EBUFF_SZ,
+ "SCSI(1) ioctl on %s failed", dev_name);
+ perror(ebuff);
+#endif
+ close(fd);
+ continue;
+ }
+ err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
+ if (err < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "SCSI(2) ioctl on %s failed", dev_name);
+ perror(ebuff);
+ close(fd);
+ continue;
+ }
+ sg_map_arr[j].channel = (m_idlun.mux4 >> 16) & 0xff;
+ sg_map_arr[j].lun = (m_idlun.mux4 >> 8) & 0xff;
+ sg_map_arr[j].target_id = m_idlun.mux4 & 0xff;
+ n = strlen(dev_name);
+ /* memory leak ... no free()s for this malloc() */
+ tmpptr = (char *)malloc(n + 1);
+ snprintf(tmpptr, n + 1, "%.*s", n, dev_name);
+ /* strncpy(tmpptr,dev_name,strlen(dev_name)+1); */
+
+ sg_map_arr[j].dev_name = tmpptr;
+#if 0
+ printf("[scsi%d ch=%d id=%d lun=%d %s] ", sg_map_arr[j].bus,
+ sg_map_arr[j].channel, sg_map_arr[j].target_id, sg_map_arr[j].lun,
+ sg_map_arr[j].dev_name);
+#endif
+ printf("%s ", dev_name);
+ close(fd);
+ if (++j >= MAX_SG_DEVS)
+ break;
+ }
+ closedir(dir_ptr);
+
+ printf("\n"); /* <<<<<<<<<<<<<<<<<<<<< */
+ for (k = 0; k < MAX_SG_DEVS; k++) {
+ if ( raw ) {
+ sprintf(name,"/dev/raw/raw%d",k);
+ fd = open(name, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ continue;
+ }
+ }
+ else {
+ make_dev_name(name, k, do_numeric);
+ fd = open(name, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ if ((ENOENT == errno) && (0 == k)) {
+ do_numeric = 0;
+ make_dev_name(name, k, do_numeric);
+ fd = open(name, O_RDWR | O_NONBLOCK);
+ }
+ if (fd < 0) {
+ if (EBUSY == errno)
+ continue; /* step over if O_EXCL already on it */
+ else {
+#if 0
+ snprintf(ebuff, EBUFF_SZ,
+ "open on %s failed (%d)", name, errno);
+ perror(ebuff);
+#endif
+ if (max_holes-- > 0)
+ continue;
+ else
+ break;
+ }
+ }
+ }
+ }
+ max_holes = MAX_HOLES;
+ err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
+ if (err < 0) {
+ if ( ! raw ) {
+ snprintf(ebuff, EBUFF_SZ, "SCSI(3) ioctl on %s failed", name);
+ perror(ebuff);
+ }
+ close(fd);
+ continue;
+ }
+ err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
+ if (err < 0) {
+ if ( ! raw ) {
+ snprintf(ebuff, EBUFF_SZ, "SCSI(3) ioctl on %s failed", name);
+ perror(ebuff);
+ }
+ close(fd);
+ continue;
+ }
+#if 0
+ printf("[scsi%d ch=%d id=%d lun=%d %s]", bus,
+ (m_idlun.mux4 >> 16) & 0xff, m_idlun.mux4 & 0xff,
+ (m_idlun.mux4 >> 8) & 0xff, name);
+#endif
+ for (j = 0; sg_map_arr[j].dev_name; ++j) {
+ if ((bus == sg_map_arr[j].bus) &&
+ ((m_idlun.mux4 & 0xff) == sg_map_arr[j].target_id) &&
+ (((m_idlun.mux4 >> 16) & 0xff) == sg_map_arr[j].channel) &&
+ (((m_idlun.mux4 >> 8) & 0xff) == sg_map_arr[j].lun)) {
+ printf("%s [=%s scsi%d ch=%d id=%d lun=%d]\n", name,
+ sg_map_arr[j].dev_name, bus,
+ ((m_idlun.mux4 >> 16) & 0xff), m_idlun.mux4 & 0xff,
+ ((m_idlun.mux4 >> 8) & 0xff));
+ break;
+ }
+ }
+ if (NULL == sg_map_arr[j].dev_name)
+ printf("%s [scsi%d ch=%d id=%d lun=%d]\n", name, bus,
+ ((m_idlun.mux4 >> 16) & 0xff), m_idlun.mux4 & 0xff,
+ ((m_idlun.mux4 >> 8) & 0xff));
+ close(fd);
+ }
+ printf("\n");
+}
+
+#define DEVNAME_SZ 256
+
+static int
+open_sg_io_dev(char * devname)
+{
+ int fd, fdrw, err, bus, bbus, k, v;
+ My_scsi_idlun m_idlun, mm_idlun;
+ int do_numeric = 1;
+ char name[DEVNAME_SZ];
+ struct stat a_st;
+ int block_dev = 0;
+
+ strncpy(name, devname, DEVNAME_SZ);
+ name[DEVNAME_SZ - 1] = '\0';
+ fd = open(name, O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ return fd;
+ if ((ioctl(fd, SG_GET_VERSION_NUM, &v) >= 0) && (v >= 30000)) {
+ fdrw = open(name, O_RDWR | O_NONBLOCK);
+ if (fdrw >= 0) {
+ close(fd);
+ return fdrw;
+ }
+ return fd;
+ }
+ if (fstat(fd, &a_st) < 0) {
+ fprintf(stderr, "could do fstat() on fd ??\n");
+ close(fd);
+ return -9999;
+ }
+ if (S_ISBLK(a_st.st_mode))
+ block_dev = 1;
+
+ if (block_dev || (ioctl(fd, SG_GET_TIMEOUT, 0) < 0)) {
+ err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
+ if (err < 0) {
+ fprintf(stderr, "A device name that understands SCSI commands "
+ "is required\n");
+ close(fd);
+ return -9999;
+ }
+ err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
+ if (err < 0) {
+ fprintf(stderr, "A SCSI device name is required(2)\n");
+ close(fd);
+ return -9999;
+ }
+ close(fd);
+
+ for (k = 0; k < MAX_SG_DEVS; k++) {
+ make_dev_name(name, k, do_numeric);
+ fd = open(name, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ if ((ENOENT == errno) && (0 == k)) {
+ do_numeric = 0;
+ make_dev_name(name, k, do_numeric);
+ fd = open(name, O_RDWR | O_NONBLOCK);
+ }
+ if (fd < 0) {
+ if (EBUSY == errno)
+ continue; /* step over if O_EXCL already on it */
+ else
+ break;
+ }
+ }
+ err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bbus);
+ if (err < 0) {
+ perror("sg ioctl failed");
+ close(fd);
+ fd = -9999;
+ }
+ err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &mm_idlun);
+ if (err < 0) {
+ perror("sg ioctl failed");
+ close(fd);
+ fd = -9999;
+ }
+ if ((bus == bbus) &&
+ ((m_idlun.mux4 & 0xff) == (mm_idlun.mux4 & 0xff)) &&
+ (((m_idlun.mux4 >> 8) & 0xff) ==
+ ((mm_idlun.mux4 >> 8) & 0xff)) &&
+ (((m_idlun.mux4 >> 16) & 0xff) ==
+ ((mm_idlun.mux4 >> 16) & 0xff)))
+ break;
+ else {
+ close(fd);
+ fd = -9999;
+ }
+ }
+ }
+ if (fd >= 0) {
+ if ((ioctl(fd, SG_GET_VERSION_NUM, &v) < 0) || (v < 30000)) {
+ fprintf(stderr, "requires lk 2.4 (sg driver), lk 2.6 or lk 3 "
+ "series\n");
+ close(fd);
+ return -9999;
+ }
+ close(fd);
+ return open(name, O_RDWR | O_NONBLOCK);
+ }
+ else
+ return fd;
+}
+
+static void
+usage(const char *errtext)
+{
+ if (errtext)
+ fprintf(stderr, "Error: sginfo: %s\n", errtext);
+ fprintf(stderr, "Usage: sginfo [-options] [device] "
+ "[replacement_values]\n");
+ fputs("\tAllowed options are:\n"
+ "\t-6 Do 6 byte mode sense and select commands (def: 10 "
+ "bytes).\n"
+ "\t-a Display inquiry info, serial # and all mode pages.\n"
+ "\t-A Similar to '-a' but displays all subpages as well.\n"
+ "\t-c Access Caching Page.\n"
+ "\t-C Access Control Mode Page.\n"
+ "\t-d Display defect lists (default format: index).\n"
+ "\t-D Access Disconnect-Reconnect Page.\n"
+ "\t-e Access Read-Write Error Recovery page.\n"
+ "\t-E Access Control Extension page.\n"
+ "\t-f Access Format Device Page.\n"
+ "\t-Farg Format of the defect list:\n"
+ "\t\t-Flogical - logical block addresses (32 bit)\n"
+ "\t\t-Flba64 - logical block addresses (64 bit)\n"
+ "\t\t-Fphysical - physical blocks\n"
+ "\t\t-Findex - defect bytes from index\n"
+ "\t\t-Fhead - sort by head\n", stdout);
+ fputs("\t-g Access Rigid Disk Drive Geometry Page.\n"
+ "\t-G Display 'grown' defect list (default format: index).\n"
+ "\t-i Display information from INQUIRY command.\n"
+ "\t-I Access Informational Exception page.\n"
+ "\t-l List known scsi devices on the system [DEPRECATED]\n"
+ "\t-n Access Notch and Partition Page.\n"
+ "\t-N Negate (stop) storing to saved page (active with -R).\n"
+ "\t-P Access Power Condition Page.\n"
+ "\t-r List known raw scsi devices on the system\n"
+ "\t-s Display serial number (from INQUIRY VPD page).\n"
+ "\t-t<pn[,sp]> Access mode page <pn> [subpage <sp>] and decode.\n"
+ "\t-T Trace commands (for debugging, double for more)\n"
+ "\t-u<pn[,sp]> Access mode page <pn> [subpage <sp>], output in hex\n"
+ "\t-v Show version number\n"
+ "\t-V Access Verify Error Recovery Page.\n"
+ "\t-z single fetch mode pages (rather than double fetch)\n"
+ "\n", stdout);
+ fputs("\tOnly one of the following three options can be specified.\n"
+ "\tNone of these three implies the current values are returned.\n", stdout);
+ fputs("\t-m Access modifiable fields instead of current values\n"
+ "\t-M Access manufacturer defaults instead of current values\n"
+ "\t-S Access saved defaults instead of current values\n\n"
+ "\t-X Use list (space separated values) rather than table.\n"
+ "\t-R Replace parameters - best used with -X (expert use only)\n"
+ "\t [replacement parameters placed after device on command line]\n\n",
+ stdout);
+ printf("\t sginfo version: %s; See man page for more details.\n",
+ version_str);
+ exit(2);
+}
+
+int main(int argc, char *argv[])
+{
+ int k, j, n;
+ unsigned int unum, unum2;
+ int decode_in_hex = 0;
+ char c;
+ char * cp;
+ int status = 0;
+ long tmp;
+ struct mpage_info mp_i;
+ int inquiry_verbosity = 0;
+ int show_devs = 0, show_raw = 0;
+ int found = 0;
+
+ if (argc < 2)
+ usage(NULL);
+ memset(&mp_i, 0, sizeof(mp_i));
+ while ((k = getopt(argc, argv, "6aAcCdDeEfgGiIlmMnNPrRsSTvVXzF:t:u:")) !=
+ EOF) {
+ c = (char)k;
+ switch (c) {
+ case '6':
+ mode6byte = 1;
+ break;
+ case 'a':
+ inquiry_verbosity = 1;
+ serial_number = 1;
+ mp_i.page = MP_LIST_PAGES;
+ break;
+ case 'A':
+ inquiry_verbosity = 1;
+ serial_number = 1;
+ mp_i.page = MP_LIST_PAGES;
+ mp_i.subpage = MP_LIST_SUBPAGES;
+ break;
+ case 'c':
+ mp_i.page = 0x8;
+ break;
+ case 'C':
+ mp_i.page = 0xa;
+ break;
+ case 'd':
+ defect = 1;
+ break;
+ case 'D':
+ mp_i.page = 0x2;
+ break;
+ case 'e':
+ mp_i.page = 0x1;
+ break;
+ case 'E':
+ mp_i.page = 0xa;
+ mp_i.subpage = 0x1;
+ break;
+ case 'f':
+ mp_i.page = 0x3;
+ break;
+ case 'F':
+ if (!strcasecmp(optarg, "logical"))
+ defectformat = 0x0;
+ else if (!strcasecmp(optarg, "lba64"))
+ defectformat = 0x3;
+ else if (!strcasecmp(optarg, "physical"))
+ defectformat = 0x5;
+ else if (!strcasecmp(optarg, "index"))
+ defectformat = 0x4;
+ else if (!strcasecmp(optarg, "head"))
+ defectformat = HEAD_SORT_TOKEN;
+ else
+ usage("Illegal -F parameter, must be one of logical, "
+ "physical, index or head");
+ break;
+ case 'g':
+ mp_i.page = 0x4;
+ break;
+ case 'G':
+ grown_defect = 1;
+ break;
+ case 'i': /* just vendor, product and revision for '-i -i' */
+ inquiry_verbosity = (2 == inquiry_verbosity) ? 1 : 2;
+ break;
+ case 'I':
+ mp_i.page = 0x1c;
+ break;
+ case 'l':
+ show_devs = 1;
+ break;
+ case 'm': /* modifiable page control */
+ if (0 == mp_i.page_control)
+ mp_i.page_control = 1;
+ else
+ usage("can only have one of 'm', 'M' and 'S'");
+ break;
+ case 'M': /* manufacturer's==default page control */
+ if (0 == mp_i.page_control)
+ mp_i.page_control = 2;
+ else
+ usage("can only have one of 'M', 'm' and 'S'");
+ break;
+ case 'n':
+ mp_i.page = 0xc;
+ break;
+ case 'N':
+ negate_sp_bit = 1;
+ break;
+ case 'P':
+ mp_i.page = 0x1a;
+ break;
+ case 'r':
+ show_raw = 1;
+ break;
+ case 'R':
+ replace = 1;
+ break;
+ case 's':
+ serial_number = 1;
+ break;
+ case 'S': /* saved page control */
+ if (0 == mp_i.page_control)
+ mp_i.page_control = 3;
+ else
+ usage("can only have one of 'S', 'm' and 'M'");
+ break;
+ case 'T':
+ trace_cmd++;
+ break;
+ case 't':
+ case 'u':
+ if ('u' == c)
+ decode_in_hex = 1;
+ while (' ' == *optarg)
+ optarg++;
+ if ('0' == *optarg) {
+ unum = 0;
+ unum2 = 0;
+ j = sscanf(optarg, "0x%x,0x%x", &unum, &unum2);
+ mp_i.page = unum;
+ if (1 == j) {
+ cp = strchr(optarg, ',');
+ if (cp && (1 == sscanf(cp, ",%d", &mp_i.subpage)))
+ j = 2;
+ } else
+ mp_i.subpage = unum2;
+ } else
+ j = sscanf(optarg, "%d,%d", &mp_i.page, &mp_i.subpage);
+ if (1 == j)
+ mp_i.subpage = 0;
+ else if (j < 1)
+ usage("argument following '-u' should be of form "
+ "<pg>[,<subpg>]");
+ if ((mp_i.page < 0) || (mp_i.page > MP_LIST_PAGES) ||
+ (mp_i.subpage < 0) || (mp_i.subpage > MP_LIST_SUBPAGES))
+ usage("mode pages range from 0 .. 63, subpages from "
+ "1 .. 255");
+ found = 1;
+ break;
+ case 'v':
+ fprintf(stdout, "sginfo version: %s\n", version_str);
+ return 0;
+ case 'V':
+ mp_i.page = 0x7;
+ break;
+ case 'X':
+ x_interface = 1;
+ break;
+ case 'z':
+ single_fetch = 1;
+ break;
+ case '?':
+ usage("Unknown option");
+ break;
+ default:
+ fprintf(stdout, "Unknown option '-%c' (ascii 0x%02x)\n", c, c);
+ usage("bad option");
+ }
+ }
+
+ if (replace && !x_interface)
+ usage("-R requires -X");
+ if (replace && mp_i.page_control)
+ usage("-R not allowed for -m, -M or -S");
+ if (x_interface && replace && ((MP_LIST_PAGES == mp_i.page) ||
+ (MP_LIST_SUBPAGES == mp_i.subpage)))
+ usage("-XR can be used only with exactly one page.");
+
+ if (replace && (3 != mp_i.page_control)) {
+ memset (is_hex, 0, 32);
+ for (j = 1; j < argc - optind; j++) {
+ if (strncmp(argv[optind + j], "0x", 2) == 0) {
+ char *pnt = argv[optind + j] + 2;
+ replacement_values[j] = 0;
+ /* This is a kluge, but we can handle 64 bit quantities this way. */
+ while (*pnt) {
+ if (*pnt >= 'a' && *pnt <= 'f')
+ *pnt -= 32;
+ replacement_values[j] = (replacement_values[j] << 4) |
+ (*pnt > '9' ? (*pnt - 'A' + 10) : (*pnt - '0'));
+ pnt++;
+ }
+ continue;
+ }
+ if (argv[optind + j][0] == '@') {
+ /*Ensure that this string contains an even number of hex-digits */
+ int len = strlen(argv[optind + j] + 1);
+
+ if ((len & 1) || (len != (int)strspn(argv[optind + j] + 1,
+ "0123456789ABCDEFabcdef")))
+ usage("Odd number of chars or non-hex digit in "
+ "@hexdatafield");
+
+ replacement_values[j] = (unsigned long) argv[optind + j];
+ is_hex[j] = 1;
+ continue;
+ }
+ /* Using a tmp here is silly but the most clean approach */
+ n = sscanf(argv[optind + j], "%ld", &tmp);
+ replacement_values[j] = ((1 == n) ? tmp : 0);
+ }
+ n_replacement_values = argc - optind - 1;
+ }
+ if (show_devs) {
+ show_devices(0);
+ exit(0);
+ }
+ if (show_raw) {
+ show_devices(1);
+ exit(0);
+ }
+ if (optind >= argc)
+ usage("no device name given");
+ glob_fd = open_sg_io_dev(device_name = argv[optind]);
+ if (glob_fd < 0) {
+ if (-9999 == glob_fd)
+ fprintf(stderr, "Couldn't find sg device corresponding to %s\n",
+ device_name);
+ else {
+ perror("sginfo(open)");
+ fprintf(stderr, "file=%s, or no corresponding sg device found\n",
+ device_name);
+ fprintf(stderr, "Is sg driver loaded?\n");
+ }
+ exit(1);
+ }
+
+#if 0
+ if (!x_interface)
+ printf("\n");
+#endif
+ if (! (found || mp_i.page || mp_i.subpage || inquiry_verbosity ||
+ serial_number)) {
+ if (trace_cmd > 0)
+ fprintf(stdout, "nothing selected so do a short INQUIRY\n");
+ inquiry_verbosity = 1;
+ }
+
+ status |= do_inquiry(&mp_i.peri_type, &mp_i.inq_byte6,
+ inquiry_verbosity);
+ if (serial_number)
+ do_serial_number(); /* ignore error */
+ if (mp_i.page > 0)
+ status |= do_user_page(&mp_i, decode_in_hex);
+ if (defect)
+ status |= read_defect_list(0);
+ if (grown_defect)
+ status |= read_defect_list(1);
+
+ return status ? 1 : 0;
+}
diff --git a/src/sgm_dd.c b/src/sgm_dd.c
new file mode 100644
index 00000000..8c9723a1
--- /dev/null
+++ b/src/sgm_dd.c
@@ -0,0 +1,1474 @@
+/* A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 1999 - 2022 D. Gilbert and P. Allworth
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ This program is a specialisation of the Unix "dd" command in which
+ either the input or the output file is a scsi generic device. 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 uses memory-mapped transfers (i.e. mmap() call from the user
+ space) to speed transfers. If both sides of copy are sg devices
+ then only the read side will be mmap-ed, while the write side will
+ use normal IO.
+
+ This version is designed for the Linux kernel 2.4, 2.6, 3, 4 and 5 series.
+*/
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <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/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "1.19 20220118";
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SCSI_CDBSZ 10
+#define MAX_SCSI_CDBSZ 16
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define ME "sgm_dd: "
+
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define FT_OTHER 1 /* filetype other than one of following */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_RAW 4 /* filetype is raw char device */
+#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */
+#define FT_ST 16 /* filetype is st char device (tape) */
+#define FT_BLOCK 32 /* filetype is a block device */
+#define FT_ERROR 64 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+
+#define MIN_RESERVED_SIZE 8192
+
+static int sum_of_resids = 0;
+
+static int64_t dd_count = -1;
+static int64_t req_count = 0;
+static int64_t in_full = 0;
+static int in_partial = 0;
+static int64_t out_full = 0;
+static int out_partial = 0;
+static int verbose = 0;
+static int dry_run = 0;
+static int progress = 0; /* accept --progress or -p, does nothing */
+
+static bool do_time = false;
+static bool start_tm_valid = false;
+static struct timeval start_tm;
+static int blk_sz = 0;
+static uint32_t glob_pack_id = 0; /* pre-increment */
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+struct flags_t {
+ bool append;
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool fua;
+};
+
+
+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()
+{
+ if (0 != dd_count)
+ pr2serr(" remaining block count=%" PRId64 "\n", dd_count);
+ pr2serr("%" PRId64 "+%d records in\n", in_full - in_partial, in_partial);
+ pr2serr("%" PRId64 "+%d records out\n", out_full - out_partial,
+ out_partial);
+}
+
+static void
+calc_duration_throughput(bool contin)
+{
+ double a, b;
+ struct timeval end_tm, res_tm;
+
+ if (start_tm_valid && (start_tm.tv_sec || start_tm.tv_usec)) {
+ 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 * (req_count - dd_count);
+ pr2serr("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))
+ pr2serr(" at %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+ }
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ print_stats ();
+ if (do_time)
+ calc_duration_throughput(false);
+ kill (getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ print_stats();
+ if (do_time)
+ calc_duration_throughput(true);
+}
+
+static int
+dd_filetype(const char * filename)
+{
+ size_t len = strlen(filename);
+ struct stat st;
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if ((MEM_MAJOR == major(st.st_rdev)) &&
+ (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+ return FT_DEV_NULL;
+ if (RAW_MAJOR == major(st.st_rdev))
+ return FT_RAW;
+ if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+ return FT_ST;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ return FT_OTHER;
+}
+
+static char *
+dd_filetype_str(int ft, char * buff)
+{
+ int off = 0;
+
+ if (FT_DEV_NULL & ft)
+ off += sg_scnpr(buff + off, 32, "null device ");
+ if (FT_SG & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI generic (sg) device ");
+ if (FT_BLOCK & ft)
+ off += sg_scnpr(buff + off, 32, "block device ");
+ if (FT_ST & ft)
+ off += sg_scnpr(buff + off, 32, "SCSI tape device ");
+ if (FT_RAW & ft)
+ off += sg_scnpr(buff + off, 32, "raw device ");
+ if (FT_OTHER & ft)
+ off += sg_scnpr(buff + off, 32, "other (perhaps ordinary file) ");
+ if (FT_ERROR & ft)
+ sg_scnpr(buff + off, 32, "unable to 'stat' file ");
+ return buff;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sgm_dd [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");
+ pr2serr(" [bpt=BPT] [cdbsz=6|10|12|16] [dio=0|1] "
+ "[fua=0|1|2|3]\n"
+ " [sync=0|1] [time=0|1] [verbose=VERB] "
+ "[--dry-run] [--verbose]\n\n"
+ " where:\n"
+ " bpt is blocks_per_transfer (default is 128)\n"
+ " bs must be device logical block size (default "
+ "512)\n"
+ " cdbsz size of SCSI READ or WRITE cdb (default is 10)\n"
+ " count number of blocks to copy (def: device size)\n"
+ " dio 0->indirect IO on write, 1->direct IO on write\n"
+ " (only when read side is sg device (using mmap))\n"
+ " fua force unit access: 0->don't(def), 1->OFILE, "
+ "2->IFILE,\n"
+ " 3->OFILE+IFILE\n"
+ " if file or device to read from (def: stdin)\n");
+ pr2serr(" iflag comma separated list from: [direct,dpo,dsync,"
+ "excl,fua,\n"
+ " null]\n"
+ " of file or device to write to (def: stdout), "
+ "OFILE of '.'\n"
+ " treated as /dev/null\n"
+ " oflag comma separated list from: [append,dio,direct,"
+ "dpo,dsync,\n"
+ " excl,fua,null]\n"
+ " seek block position to start writing to OFILE\n"
+ " skip block position to start reading from IFILE\n"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE "
+ "after copy\n"
+ " time 0->no timing(def), 1->time plus calculate "
+ "throughput\n"
+ " verbose 0->quiet(def), 1->some noise, 2->more noise, "
+ "etc\n"
+ " --dry-run|-d prepare but bypass copy/read\n"
+ " --help|-h print usage message then exit\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version information then exit\n\n"
+ "Copy from IFILE to OFILE, similar to dd command\n"
+ "specialized for SCSI devices for which mmap-ed IO attempted\n");
+}
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res, verb;
+ unsigned int ui;
+ uint8_t rcBuff[RCAP16_REPLY_LEN];
+
+ verb = (verbose ? verbose - 1: 0);
+ res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false,
+ verb);
+ if (0 != res)
+ return res;
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+
+ res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false,
+ verb);
+ if (0 != res)
+ return res;
+ *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ ui = sg_get_unaligned_be32(rcBuff + 0);
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)ui + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ if (verb)
+ pr2serr(" number of blocks=%" PRId64 " [0x%" PRIx64 "], block "
+ "size=%d\n", *num_sect, *num_sect, *sect_sz);
+ return 0;
+}
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ if (verbose > 1)
+ pr2serr(" [bgs64] number of blocks=%" PRId64 " [0x%" PRIx64
+ "], logical block size=%d\n", *num_sect, *num_sect,
+ *sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ if (verbose > 1)
+ pr2serr(" [bgs] number of blocks=%" PRId64 " [0x%" PRIx64
+ "], logical block size=%d\n", *num_sect, *num_sect,
+ *sect_sz);
+ #endif
+ }
+ return 0;
+#else
+ if (verbose)
+ pr2serr(" BLKSSZGET+BLKGETSIZE ioctl not available\n");
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool write_true, bool fua, bool dpo)
+{
+ int sz_ind;
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+
+ memset(cdbp, 0, cdb_sz);
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr(ME "for 6 byte commands, maximum number of blocks is "
+ "256\n");
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr(ME "for 6 byte commands, can't address blocks beyond "
+ "%d\n", 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr(ME "for 6 byte commands, neither dpo nor fua bits "
+ "supported\n");
+ return 1;
+ }
+ break;
+ case 10:
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr(ME "for 10 byte commands, maximum number of blocks is "
+ "%d\n", 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr(ME "expected cdb size of 6, 10, 12, or 16 but got %d\n",
+ cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+/* Returns 0 -> successful, various SG_LIB_CAT_* positive values,
+ * -2 -> recoverable (ENOMEM), -1 -> unrecoverable error */
+static int
+sg_read(int sg_fd, uint8_t * buff, int blocks, int64_t from_block,
+ int bs, int cdbsz, bool fua, bool dpo, bool do_mmap)
+{
+ bool print_cdb_after = false;
+ int res;
+ uint8_t rdCmd[MAX_SCSI_CDBSZ];
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+
+ if (sg_build_scsi_cdb(rdCmd, cdbsz, blocks, from_block, false, fua,
+ dpo)) {
+ pr2serr(ME "bad rd cdb build, from_block=%" PRId64 ", blocks=%d\n",
+ from_block, blocks);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = cdbsz;
+ io_hdr.cmdp = rdCmd;
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = bs * blocks;
+ if (! do_mmap)
+ io_hdr.dxferp = buff;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.pack_id = (int)++glob_pack_id;
+ if (do_mmap)
+ io_hdr.flags |= SG_FLAG_MMAP_IO;
+ if (verbose > 2) {
+ char b[128];
+
+ pr2serr(" Read cdb: %s\n",
+ sg_get_command_str(rdCmd, cdbsz, false, sizeof(b), b));
+ }
+
+#if 1
+ while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ sleep(1);
+ if (res < 0) {
+ perror(ME "SG_IO error (sg_read)");
+ return -1;
+ }
+#else
+ while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return -2;
+ perror("reading (wr) on sg device, error");
+ return -1;
+ }
+
+ while (((res = read(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ perror("reading (rd) on sg device, error");
+ return -1;
+ }
+#endif
+ if (verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("Reading, continuing", &io_hdr, verbose > 1);
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ case SG_LIB_CAT_MEDIUM_HARD:
+ return res;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (verbose)
+ print_cdb_after = true;
+ /* FALL THROUGH */
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ default:
+ sg_chk_n_print3("reading", &io_hdr, verbose > 1);
+ if (print_cdb_after)
+ sg_print_command_len(rdCmd, cdbsz);
+ return res;
+ }
+ sum_of_resids += io_hdr.resid;
+#ifdef DEBUG
+ pr2serr("duration=%u ms\n", io_hdr.duration);
+#endif
+ return 0;
+}
+
+/* Returns 0 -> successful, various SG_LIB_CAT_* positive values,
+ * -2 -> recoverable (ENOMEM), -1 -> unrecoverable error */
+static int
+sg_write(int sg_fd, uint8_t * buff, int blocks, int64_t to_block,
+ int bs, int cdbsz, bool fua, bool dpo, bool do_mmap, bool * diop)
+{
+ bool print_cdb_after = false;
+ int res;
+ uint8_t wrCmd[MAX_SCSI_CDBSZ];
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr SG_C_CPP_ZERO_INIT;
+
+ if (sg_build_scsi_cdb(wrCmd, cdbsz, blocks, to_block, true, fua, dpo)) {
+ pr2serr(ME "bad wr cdb build, to_block=%" PRId64 ", blocks=%d\n",
+ to_block, blocks);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = cdbsz;
+ io_hdr.cmdp = wrCmd;
+ io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ io_hdr.dxfer_len = bs * blocks;
+ if (! do_mmap)
+ io_hdr.dxferp = buff;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.pack_id = (int)++glob_pack_id;
+ if (do_mmap)
+ io_hdr.flags |= SG_FLAG_MMAP_IO;
+ else if (diop && *diop)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+ if (verbose > 2) {
+ char b[128];
+
+ pr2serr(" Write cdb: %s\n",
+ sg_get_command_str(wrCmd, cdbsz, false, sizeof(b), b));
+ }
+
+#if 1
+ while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ sleep(1);
+ if (res < 0) {
+ perror(ME "SG_IO error (sg_write)");
+ return -1;
+ }
+#else
+ while (((res = write(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return -2;
+ perror("writing (wr) on sg device, error");
+ return -1;
+ }
+
+ while (((res = read(sg_fd, &io_hdr, sizeof(io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ perror("writing (rd) on sg device, error");
+ return -1;
+ }
+#endif
+ if (verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("Writing, continuing", &io_hdr, verbose > 1);
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ case SG_LIB_CAT_MEDIUM_HARD:
+ return res;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (verbose)
+ print_cdb_after = true;
+ /* FALL THROUGH */
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ default:
+ sg_chk_n_print3("writing", &io_hdr, verbose > 1);
+ if (print_cdb_after)
+ sg_print_command_len(wrCmd, cdbsz);
+ return res;
+ }
+ if (diop && *diop &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ *diop = false; /* flag that dio not done (completely) */
+ return 0;
+}
+
+static int
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return 1;
+ }
+ cp = np;
+ } while (cp);
+ return 0;
+}
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+#define EBUFF_SZ 768
+
+int
+main(int argc, char * argv[])
+{
+ bool bpt_given = false;
+ bool cdbsz_given = false;
+ bool do_coe = false; /* dummy, just accept + ignore */
+ bool do_sync = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, k, t, infd, outfd, blocks, n, flags, blocks_per, err, keylen;
+ int bpt = DEF_BLOCKS_PER_TRANSFER;
+ int ibs = 0;
+ int in_res_sz = 0;
+ int in_sect_sz;
+ int in_type = FT_OTHER;
+ int obs = 0;
+ int out_res_sz = 0;
+ int out_sect_sz;
+ int out_type = FT_OTHER;
+ int num_dio_not_done = 0;
+ int ret = 0;
+ int scsi_cdbsz_in = DEF_SCSI_CDBSZ;
+ int scsi_cdbsz_out = DEF_SCSI_CDBSZ;
+ size_t psz;
+ int64_t in_num_sect = -1;
+ int64_t out_num_sect = -1;
+ int64_t skip = 0;
+ int64_t seek = 0;
+ char * buf;
+ char * key;
+ uint8_t * wrkPos;
+ uint8_t * wrkBuff = NULL;
+ uint8_t * wrkMmap = NULL;
+ char inf[INOUTF_SZ];
+ char str[STR_SZ];
+ char outf[INOUTF_SZ];
+ char ebuff[EBUFF_SZ];
+ char b[80];
+ struct flags_t in_flags;
+ struct flags_t out_flags;
+
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+#else
+ psz = 4096; /* give up, pick likely figure */
+#endif
+ inf[0] = '\0';
+ outf[0] = '\0';
+ memset(&in_flags, 0, sizeof(in_flags));
+ memset(&out_flags, 0, sizeof(out_flags));
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k])
+ snprintf(str, STR_SZ, "%s", argv[k]);
+ else
+ continue;
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key,"bpt")) {
+ bpt = sg_get_num(buf);
+ if (-1 == bpt) {
+ pr2serr(ME "bad argument to 'bpt'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key,"bs")) {
+ blk_sz = sg_get_num(buf);
+ if (-1 == blk_sz) {
+ pr2serr(ME "bad argument to 'bs'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"cdbsz")) {
+ scsi_cdbsz_in = sg_get_num(buf);
+ if ((scsi_cdbsz_in < 6) || (scsi_cdbsz_in > 32)) {
+ pr2serr(ME "'cdbsz' expects 6, 10, 12, 16 or 32\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ scsi_cdbsz_out = scsi_cdbsz_in;
+ cdbsz_given = true;
+ } else if (0 == strcmp(key,"coe")) {
+ do_coe = !! sg_get_num(buf); /* dummy, just accept + ignore */
+ if (do_coe) { ; } /* unused, dummy to suppress warning */
+ } else if (0 == strcmp(key,"count")) {
+ if (0 != strcmp("-1", buf)) {
+ dd_count = sg_get_llnum(buf);
+ if ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(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,"dio"))
+ out_flags.dio = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"fua")) {
+ n = sg_get_num(buf);
+ if (n & 1)
+ out_flags.fua = true;
+ if (n & 2)
+ in_flags.fua = true;
+ } else if (0 == strcmp(key,"ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr(ME "bad argument to 'ibs'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"if") == 0) {
+ if ('\0' != inf[0]) {
+ pr2serr("Second 'if=' argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(inf, buf, INOUTF_SZ);
+ inf[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (process_flags(buf, &in_flags)) {
+ pr2serr(ME "bad argument to 'iflag'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"of") == 0) {
+ if ('\0' != outf[0]) {
+ pr2serr("Second 'of=' argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(outf, buf, INOUTF_SZ);
+ outf[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (process_flags(buf, &out_flags)) {
+ pr2serr(ME "bad argument to 'oflag'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr(ME "bad argument to 'obs'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"seek")) {
+ seek = sg_get_llnum(buf);
+ if ((seek < 0) || (seek > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(ME "bad argument to 'seek'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"skip")) {
+ skip = sg_get_llnum(buf);
+ if ((skip < 0) || (skip > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr(ME "bad argument to 'skip'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"sync"))
+ do_sync = !! sg_get_num(buf);
+ 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 ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'd');
+ dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ if (n > 0) {
+ usage();
+ return 0;
+ }
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ progress += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ verbose += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?"))) {
+ usage();
+ return 0;
+ } else if (0 == strncmp(key, "--prog", 6))
+ ++progress;
+ else if (0 == strncmp(key, "--verb", 6))
+ ++verbose;
+ else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr(ME ": %s\n", version_str);
+ return 0;
+ }
+
+ if (blk_sz <= 0) {
+ blk_sz = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ blk_sz);
+ }
+ if ((ibs && (ibs != blk_sz)) || (obs && (obs != blk_sz))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage();
+ return SG_LIB_CONTRADICT;
+ }
+ if ((skip < 0) || (seek < 0)) {
+ pr2serr("skip and seek cannot be negative\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (out_flags.append && (seek > 0)) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if ((bpt < 1) || (bpt > MAX_BPT_VALUE)) {
+ pr2serr("bpt must be > 0 and <= %d\n", MAX_BPT_VALUE);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ for the block layer in lk 2.6 and results in an EIO on the
+ SG_IO ioctl. So reduce it in that case. */
+ if ((blk_sz >= 2048) && (! bpt_given))
+ bpt = DEF_BLOCKS_PER_2048TRANSFER;
+
+#ifdef DEBUG
+ pr2serr(ME "if=%s skip=%" PRId64 " of=%s seek=%" PRId64 " count=%" PRId64
+ "\n", inf, skip, outf, 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;
+ if (inf[0] && ('-' != inf[0])) {
+ in_type = dd_filetype(inf);
+ if (verbose > 1)
+ pr2serr(" >> Input file type: %s\n",
+ dd_filetype_str(in_type, ebuff));
+
+ if (FT_ERROR == in_type) {
+ pr2serr(ME "unable to access %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == in_type) {
+ pr2serr(ME "unable to use scsi tape device %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == in_type) {
+ flags = O_RDWR | O_NONBLOCK;
+ if (in_flags.direct)
+ flags |= O_DIRECT;
+ if (in_flags.excl)
+ flags |= O_EXCL;
+ if (in_flags.dsync)
+ flags |= O_SYNC;
+ if ((infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for sg reading", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ res = ioctl(infd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30122)) {
+ pr2serr(ME "sg driver prior to 3.1.22\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ in_res_sz = blk_sz * bpt;
+ if (0 != (in_res_sz % psz)) /* round up to next page */
+ in_res_sz = ((in_res_sz / psz) + 1) * psz;
+ if (ioctl(infd, SG_GET_RESERVED_SIZE, &t) < 0) {
+ err = errno;
+ perror(ME "SG_GET_RESERVED_SIZE error");
+ return sg_convert_errno(err);
+ }
+ if (t < MIN_RESERVED_SIZE)
+ t = MIN_RESERVED_SIZE;
+ if (in_res_sz > t) {
+ if (ioctl(infd, SG_SET_RESERVED_SIZE, &in_res_sz) < 0) {
+ err = errno;
+ perror(ME "SG_SET_RESERVED_SIZE error");
+ return sg_convert_errno(err);
+ }
+ }
+ wrkMmap = (uint8_t *)mmap(NULL, in_res_sz,
+ PROT_READ | PROT_WRITE, MAP_SHARED, infd, 0);
+ if (MAP_FAILED == wrkMmap) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "error using mmap() on file: %s", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ } else {
+ flags = O_RDONLY;
+ if (in_flags.direct)
+ flags |= O_DIRECT;
+ if (in_flags.excl)
+ flags |= O_EXCL;
+ if (in_flags.dsync)
+ flags |= O_SYNC;
+ if ((infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for reading", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ else if (skip > 0) {
+ off64_t offset = skip;
+
+ offset *= blk_sz; /* could exceed 32 bits here! */
+ if (lseek64(infd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't skip to "
+ "required position on %s", inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (verbose > 1)
+ pr2serr(" >> skip: lseek64 SEEK_SET, byte offset=0x%"
+ PRIx64 "\n", (uint64_t)offset);
+ }
+ }
+ }
+
+ if (outf[0] && ('-' != outf[0])) {
+ out_type = dd_filetype(outf);
+ if (verbose > 1)
+ pr2serr(" >> Output file type: %s\n",
+ dd_filetype_str(out_type, ebuff));
+
+ if (FT_ST == out_type) {
+ pr2serr(ME "unable to use scsi tape device %s\n", outf);
+ return SG_LIB_FILE_ERROR;
+ }
+ else if (FT_SG == out_type) {
+ flags = O_RDWR | O_NONBLOCK;
+ if (out_flags.direct)
+ flags |= O_DIRECT;
+ if (out_flags.excl)
+ flags |= O_EXCL;
+ if (out_flags.dsync)
+ flags |= O_SYNC;
+ if ((outfd = open(outf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "could not open %s for "
+ "sg writing", outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ res = ioctl(outfd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30122)) {
+ pr2serr(ME "sg driver prior to 3.1.22\n");
+ return SG_LIB_FILE_ERROR;
+ }
+ if (ioctl(outfd, SG_GET_RESERVED_SIZE, &t) < 0) {
+ err = errno;
+ perror(ME "SG_GET_RESERVED_SIZE error");
+ return sg_convert_errno(err);
+ }
+ if (t < MIN_RESERVED_SIZE)
+ t = MIN_RESERVED_SIZE;
+ out_res_sz = blk_sz * bpt;
+ if (out_res_sz > t) {
+ if (ioctl(outfd, SG_SET_RESERVED_SIZE, &out_res_sz) < 0) {
+ err = errno;
+ perror(ME "SG_SET_RESERVED_SIZE error");
+ return sg_convert_errno(err);
+ }
+ }
+ if (NULL == wrkMmap) {
+ wrkMmap = (uint8_t *)mmap(NULL, out_res_sz,
+ PROT_READ | PROT_WRITE, MAP_SHARED, outfd, 0);
+ if (MAP_FAILED == wrkMmap) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "error using mmap() on file: %s", outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ else if (FT_DEV_NULL == out_type)
+ outfd = -1; /* don't bother opening */
+ else {
+ if (FT_RAW != out_type) {
+ flags = O_WRONLY | O_CREAT;
+ if (out_flags.direct)
+ flags |= O_DIRECT;
+ if (out_flags.excl)
+ flags |= O_EXCL;
+ if (out_flags.dsync)
+ flags |= O_SYNC;
+ if (out_flags.append)
+ flags |= O_APPEND;
+ if ((outfd = open(outf, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ,
+ ME "could not open %s for writing", outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ else {
+ if ((outfd = open(outf, O_WRONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "could not open %s "
+ "for raw writing", outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ if (seek > 0) {
+ off64_t offset = seek;
+
+ offset *= blk_sz; /* could exceed 32 bits here! */
+ if (lseek64(outfd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, ME "couldn't seek to "
+ "required position on %s", outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (verbose > 1)
+ pr2serr(" >> seek: lseek64 SEEK_SET, byte offset=0x%"
+ PRIx64 "\n", (uint64_t)offset);
+ }
+ }
+ }
+ if ((STDIN_FILENO == infd) && (STDOUT_FILENO == outfd)) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to as "
+ "stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_CONTRADICT;
+ }
+ if (dd_count < 0) {
+ in_num_sect = -1;
+ if (FT_SG == in_type) {
+ res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention(in), continuing\n");
+ res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+ } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+ pr2serr("Aborted command(in), continuing\n");
+ res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+ }
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Read capacity (if=%s): %s\n", inf, b);
+ in_num_sect = -1;
+ }
+ } else if (FT_BLOCK == in_type) {
+ if (0 != read_blkdev_capacity(infd, &in_num_sect, &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ if (blk_sz != in_sect_sz) {
+ pr2serr("logical block size on %s confusion; bs=%d, from "
+ "device=%d\n", inf, blk_sz, in_sect_sz);
+ in_num_sect = -1;
+ }
+ }
+ if (in_num_sect > skip)
+ in_num_sect -= skip;
+
+ out_num_sect = -1;
+ if (FT_SG == out_type) {
+ res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention(out), continuing\n");
+ res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+ } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+ pr2serr("Aborted command(out), continuing\n");
+ res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+ }
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Read capacity (of=%s): %s\n", inf, b);
+ out_num_sect = -1;
+ }
+ } else if (FT_BLOCK == out_type) {
+ if (0 != read_blkdev_capacity(outfd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outf);
+ out_num_sect = -1;
+ }
+ if (blk_sz != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, from "
+ "device=%d\n", outf, blk_sz, out_sect_sz);
+ out_num_sect = -1;
+ }
+ }
+ if (out_num_sect > seek)
+ out_num_sect -= seek;
+#ifdef DEBUG
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64 ", "
+ "out_num_sect=%" PRId64 "\n", dd_count, in_num_sect,
+ out_num_sect);
+#endif
+ if (in_num_sect > 0) {
+ if (out_num_sect > 0)
+ dd_count = (in_num_sect > out_num_sect) ? out_num_sect :
+ in_num_sect;
+ else
+ dd_count = in_num_sect;
+ }
+ else
+ dd_count = out_num_sect;
+ }
+
+ if (dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (! cdbsz_given) {
+ if ((FT_SG == in_type) && (MAX_SCSI_CDBSZ != scsi_cdbsz_in) &&
+ (((dd_count + skip) > UINT_MAX) || (bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ scsi_cdbsz_in = MAX_SCSI_CDBSZ;
+ }
+ if ((FT_SG == out_type) && (MAX_SCSI_CDBSZ != scsi_cdbsz_out) &&
+ (((dd_count + seek) > UINT_MAX) || (bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ scsi_cdbsz_out = MAX_SCSI_CDBSZ;
+ }
+ }
+
+ if (out_flags.dio && (FT_SG != in_type)) {
+ out_flags.dio = false;
+ pr2serr(">>> dio only performed on 'of' side when 'if' is an sg "
+ "device\n");
+ }
+ if (out_flags.dio) {
+ int fd;
+ char c;
+
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+
+ if (wrkMmap) {
+ wrkPos = wrkMmap;
+ } else {
+ wrkPos = (uint8_t *)sg_memalign(blk_sz * bpt, 0, &wrkBuff,
+ verbose > 3);
+ if (NULL == wrkPos) {
+ pr2serr("Not enough user memory\n");
+ return sg_convert_errno(ENOMEM);
+ }
+ }
+
+ blocks_per = bpt;
+#ifdef DEBUG
+ pr2serr("Start of loop, count=%" PRId64 ", blocks_per=%d\n", dd_count,
+ blocks_per);
+#endif
+ if (dry_run > 0)
+ goto fini;
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+ req_count = dd_count;
+
+ if (verbose && (dd_count > 0) && (! out_flags.dio) &&
+ (FT_SG == in_type) && (FT_SG == out_type))
+ pr2serr("Since both 'if' and 'of' are sg devices, only do mmap-ed "
+ "transfers on 'if'\n");
+
+ while (dd_count > 0) {
+ blocks = (dd_count > blocks_per) ? blocks_per : dd_count;
+ if (FT_SG == in_type) {
+ ret = sg_read(infd, wrkPos, blocks, skip, blk_sz, scsi_cdbsz_in,
+ in_flags.fua, in_flags.dpo, true);
+ if ((SG_LIB_CAT_UNIT_ATTENTION == ret) ||
+ (SG_LIB_CAT_ABORTED_COMMAND == ret)) {
+ pr2serr("Unit attention or aborted command, continuing "
+ "(r)\n");
+ ret = sg_read(infd, wrkPos, blocks, skip, blk_sz,
+ scsi_cdbsz_in, in_flags.fua, in_flags.dpo,
+ true);
+ }
+ if (0 != ret) {
+ pr2serr("sg_read failed, skip=%" PRId64 "\n", skip);
+ break;
+ }
+ else
+ in_full += blocks;
+ }
+ else {
+ while (((res = read(infd, wrkPos, blocks * blk_sz)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("read(unix): count=%d, res=%d\n", blocks * blk_sz,
+ res);
+ if (ret < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "reading, skip=%" PRId64 " ",
+ skip);
+ perror(ebuff);
+ ret = -1;
+ break;
+ }
+ else if (res < blocks * blk_sz) {
+ dd_count = 0;
+ blocks = res / blk_sz;
+ if ((res % blk_sz) > 0) {
+ blocks++;
+ in_partial++;
+ }
+ }
+ in_full += blocks;
+ }
+
+ if (0 == blocks)
+ break; /* read nothing so leave loop */
+
+ if (FT_SG == out_type) {
+ bool dio_res = out_flags.dio;
+ bool do_mmap = (FT_SG != in_type);
+
+ ret = sg_write(outfd, wrkPos, blocks, seek, blk_sz, scsi_cdbsz_out,
+ out_flags.fua, out_flags.dpo, do_mmap, &dio_res);
+ if ((SG_LIB_CAT_UNIT_ATTENTION == ret) ||
+ (SG_LIB_CAT_ABORTED_COMMAND == ret)) {
+ pr2serr("Unit attention or aborted command, continuing (w)\n");
+ dio_res = out_flags.dio;
+ ret = sg_write(outfd, wrkPos, blocks, seek, blk_sz,
+ scsi_cdbsz_out, out_flags.fua, out_flags.dpo,
+ do_mmap, &dio_res);
+ }
+ if (0 != ret) {
+ pr2serr("sg_write failed, seek=%" PRId64 "\n", seek);
+ break;
+ }
+ else {
+ out_full += blocks;
+ if (out_flags.dio && (! dio_res))
+ num_dio_not_done++;
+ }
+ }
+ else if (FT_DEV_NULL == out_type)
+ out_full += blocks; /* act as if written out without error */
+ else {
+ while (((res = write(outfd, wrkPos, blocks * blk_sz)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) ||
+ (EBUSY == errno)))
+ ;
+ if (verbose > 2)
+ pr2serr("write(unix): count=%d, res=%d\n", blocks * blk_sz,
+ res);
+ if (res < 0) {
+ snprintf(ebuff, EBUFF_SZ, ME "writing, seek=%" PRId64 " ",
+ seek);
+ perror(ebuff);
+ break;
+ }
+ else if (res < blocks * blk_sz) {
+ pr2serr("output file probably full, seek=%" PRId64 " ", seek);
+ blocks = res / blk_sz;
+ out_full += blocks;
+ if ((res % blk_sz) > 0)
+ out_partial++;
+ break;
+ }
+ else
+ out_full += blocks;
+ }
+ if (dd_count > 0)
+ dd_count -= blocks;
+ skip += blocks;
+ seek += blocks;
+ }
+
+ if (do_time)
+ calc_duration_throughput(false);
+ if (do_sync) {
+ if (FT_SG == out_type) {
+ pr2serr(">> Synchronizing cache on %s\n", outf);
+ res = sg_ll_sync_cache_10(outfd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention(out), continuing\n");
+ res = sg_ll_sync_cache_10(outfd, 0, 0, 0, 0, 0, false, 0);
+ }
+ if (0 != res) {
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("Synchronize cache(out): %s\n", b);
+ }
+ }
+ }
+
+fini:
+ if (wrkBuff)
+ free(wrkBuff);
+ if ((STDIN_FILENO != infd) && (infd >= 0))
+ close(infd);
+ if ((STDOUT_FILENO != outfd) && (FT_DEV_NULL != out_type)) {
+ if (outfd >= 0)
+ close(outfd);
+ }
+ if ((0 != dd_count) && (0 == dry_run)) {
+ pr2serr("Some error occurred,");
+ if (0 == ret)
+ ret = SG_LIB_CAT_OTHER;
+ }
+ print_stats();
+ if (sum_of_resids)
+ pr2serr(">> Non-zero sum of residual counts=%d\n", sum_of_resids);
+ if (num_dio_not_done)
+ pr2serr(">> dio requested but _not_ done %d times\n",
+ num_dio_not_done);
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sgp_dd.c b/src/sgp_dd.c
new file mode 100644
index 00000000..6a039f43
--- /dev/null
+++ b/src/sgp_dd.c
@@ -0,0 +1,2019 @@
+/* A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 1999 - 2022 D. Gilbert and P. Allworth
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a specialisation of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device or a raw
+ * device. A logical block size ('bs') is assumed to be 512 if not given.
+ * This program complains if 'ibs' or 'obs' are given with some other value
+ * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If
+ * 'of' is not given or 'of=-' then stdout assumed.
+ *
+ * A non-standard argument "bpt" (blocks per transfer) is added to control
+ * the maximum number of blocks in each transfer. The default value is 128.
+ * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16 KiB
+ * in this case) are transferred to or from the sg device in a single SCSI
+ * command.
+ *
+ * This version is designed for the Linux kernel 2.4, 2.6, 3, 4 and 5 series.
+ *
+ * sgp_dd is a Posix threads specialization of the sg_dd utility. Both
+ * sgp_dd and sg_dd only perform special tasks when one or both of the given
+ * devices belong to the Linux sg driver
+ */
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <sys/time.h>
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __STDC_VERSION__
+#if __STDC_VERSION__ >= 201112L && defined(HAVE_STDATOMIC_H)
+#ifndef __STDC_NO_ATOMICS__
+
+#define HAVE_C11_ATOMICS
+#include <stdatomic.h>
+
+#endif
+#endif
+#endif
+
+#ifndef HAVE_C11_ATOMICS
+#warning "Don't have C11 Atomics, using mutex with pack_id"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "5.84 20220118";
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SCSI_CDBSZ 10
+#define MAX_SCSI_CDBSZ 16
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#define SGP_READ10 0x28
+#define SGP_WRITE10 0x2a
+#define DEF_NUM_THREADS 4
+#define MAX_NUM_THREADS 1024 /* was SG_MAX_QUEUE (16) but no longer applies */
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define FT_OTHER 1 /* filetype other than one of the following */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_RAW 4 /* filetype is raw char device */
+#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */
+#define FT_ST 16 /* filetype is st char device (tape) */
+#define FT_BLOCK 32 /* filetype is a block device */
+#define FT_ERROR 64 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+
+#define EBUFF_SZ 768
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+
+
+struct flags_t {
+ bool append;
+ bool coe;
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool fua;
+ bool mmap;
+};
+
+struct opts_t
+{ /* one instance visible to all threads */
+ int infd;
+ int64_t skip;
+ int in_type;
+ int cdbsz_in;
+ struct flags_t in_flags;
+ int64_t in_blk; /* next block address to read */
+ int64_t in_count; /* blocks remaining for next read */
+ int64_t in_rem_count; /* count of remaining in blocks */
+ int in_partial;
+ pthread_mutex_t inout_mutex;
+ int outfd;
+ int64_t seek;
+ int out_type;
+ int cdbsz_out;
+ struct flags_t out_flags;
+ int64_t out_blk; /* next block address to write */
+ int64_t out_count; /* blocks remaining for next write */
+ int64_t out_rem_count; /* count of remaining out blocks */
+ int out_partial;
+ pthread_cond_t out_sync_cv;
+ int bs;
+ int bpt;
+ int num_threads;
+ int dio_incomplete_count;
+ int sum_of_resids;
+ bool mmap_active;
+ int chkaddr; /* check read data contains 4 byte, big endian block
+ * addresses, once: check only 4 bytes per block */
+ int progress; /* accept --progress or -p, does nothing */
+ int debug;
+ int dry_run;
+};
+
+struct thread_arg
+{ /* pointer to this argument passed to thread */
+ int id;
+ int64_t seek_skip;
+};
+
+typedef struct request_element
+{ /* one instance per worker thread */
+ bool wr;
+ bool in_stop;
+ bool in_err;
+ bool out_err;
+ bool use_no_dxfer;
+ int infd;
+ int outfd;
+ int64_t blk;
+ int num_blks;
+ uint8_t * buffp;
+ uint8_t * alloc_bp;
+ struct sg_io_hdr io_hdr;
+ uint8_t cdb[MAX_SCSI_CDBSZ];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int bs;
+ int dio_incomplete_count;
+ int resid;
+ int cdbsz_in;
+ int cdbsz_out;
+ struct flags_t in_flags;
+ struct flags_t out_flags;
+ int debug;
+ uint32_t pack_id;
+} Rq_elem;
+
+static sigset_t signal_set;
+static pthread_t sig_listen_thread_id;
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+static void sg_in_operation(struct opts_t * clp, Rq_elem * rep);
+static void sg_out_operation(struct opts_t * clp, Rq_elem * rep,
+ bool bump_out_blk);
+static void normal_in_operation(struct opts_t * clp, Rq_elem * rep,
+ int blocks);
+static void normal_out_operation(struct opts_t * clp, Rq_elem * rep,
+ int blocks, bool bump_out_blk);
+static int sg_start_io(Rq_elem * rep);
+static int sg_finish_io(bool wr, Rq_elem * rep, pthread_mutex_t * a_mutp);
+
+#ifdef HAVE_C11_ATOMICS
+
+/* Assume initialized to 0, but want to start at 1, hence adding 1 in macro */
+static atomic_uint ascending_val;
+
+static atomic_uint num_eintr;
+static atomic_uint num_eagain;
+static atomic_uint num_ebusy;
+static atomic_bool exit_threads;
+
+#define GET_NEXT_PACK_ID(_v) (atomic_fetch_add(&ascending_val, _v) + (_v))
+
+#else
+
+static pthread_mutex_t av_mut = PTHREAD_MUTEX_INITIALIZER;
+static int ascending_val = 1;
+static volatile bool exit_threads;
+
+static unsigned int
+GET_NEXT_PACK_ID(unsigned int val)
+{
+ int res;
+
+ pthread_mutex_lock(&av_mut);
+ res = ascending_val;
+ ascending_val += val;
+ pthread_mutex_unlock(&av_mut);
+ return res;
+}
+
+#endif
+
+#define STRERR_BUFF_LEN 128
+
+static pthread_mutex_t strerr_mut = PTHREAD_MUTEX_INITIALIZER;
+
+static pthread_t threads[MAX_NUM_THREADS];
+static struct thread_arg thr_arg_a[MAX_NUM_THREADS];
+
+static bool shutting_down = false;
+static bool do_sync = false;
+static bool do_time = false;
+static struct opts_t my_opts;
+static struct timeval start_tm;
+static int64_t dd_count = -1;
+static int exit_status = 0;
+static char infn[INOUTF_SZ];
+static char outfn[INOUTF_SZ];
+
+static const char * my_name = "sgp_dd: ";
+
+
+static void
+calc_duration_throughput(int contin)
+{
+ struct timeval end_tm, res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+ b = (double)my_opts.bs * (dd_count - my_opts.out_rem_count);
+ pr2serr("time to transfer data %s %d.%06d secs",
+ (contin ? "so far" : "was"), (int)res_tm.tv_sec,
+ (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+}
+
+static void
+print_stats(const char * str)
+{
+ int64_t infull, outfull;
+
+ if (0 != my_opts.out_rem_count)
+ pr2serr(" remaining block count=%" PRId64 "\n",
+ my_opts.out_rem_count);
+ infull = dd_count - my_opts.in_rem_count;
+ pr2serr("%s%" PRId64 "+%d records in\n", str,
+ infull - my_opts.in_partial, my_opts.in_partial);
+
+ outfull = dd_count - my_opts.out_rem_count;
+ pr2serr("%s%" PRId64 "+%d records out\n", str,
+ outfull - my_opts.out_partial, my_opts.out_partial);
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ if (do_time)
+ calc_duration_throughput(0);
+ print_stats("");
+ kill(getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+static void
+install_handler(int sig_num, void (*sig_handler) (int sig))
+{
+ struct sigaction sigact;
+ sigaction (sig_num, NULL, &sigact);
+ if (sigact.sa_handler != SIG_IGN)
+ {
+ sigact.sa_handler = sig_handler;
+ sigemptyset (&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction (sig_num, &sigact, NULL);
+ }
+}
+
+#ifdef SG_LIB_ANDROID
+static void
+thread_exit_handler(int sig)
+{
+ pthread_exit(0);
+}
+#endif
+
+/* Make safe_strerror() thread safe */
+static char *
+tsafe_strerror(int code, char * ebp)
+{
+ int status;
+ char * cp;
+
+ status = pthread_mutex_lock(&strerr_mut);
+ if (0 != status) pr2serr("lock strerr_mut");
+ cp = safe_strerror(code);
+ strncpy(ebp, cp, STRERR_BUFF_LEN);
+ status = pthread_mutex_unlock(&strerr_mut);
+ if (0 != status) pr2serr("unlock strerr_mut");
+ ebp[STRERR_BUFF_LEN - 1] = '\0';
+ return ebp;
+}
+
+
+/* Following macro from D.R. Butenhof's POSIX threads book:
+ * ISBN 0-201-63392-2 . [Highly recommended book.] Changed __FILE__
+ * to __func__ */
+#define err_exit(code,text) do { \
+ char _strerr_buff[STRERR_BUFF_LEN + 1]; \
+ pr2serr("%s at \"%s\":%d: %s\n", \
+ text, __func__, __LINE__, tsafe_strerror(code, _strerr_buff)); \
+ exit(1); \
+ } while (0)
+
+
+static int
+dd_filetype(const char * filename)
+{
+ struct stat st;
+ size_t len = strlen(filename);
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if ((MEM_MAJOR == major(st.st_rdev)) &&
+ (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+ return FT_DEV_NULL;
+ if (RAW_MAJOR == major(st.st_rdev))
+ return FT_RAW;
+ if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+ return FT_ST;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ return FT_OTHER;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sgp_dd [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");
+ pr2serr(" [bpt=BPT] [cdbsz=6|10|12|16] [coe=0|1] "
+ "[deb=VERB] [dio=0|1]\n"
+ " [fua=0|1|2|3] [sync=0|1] [thr=THR] "
+ "[time=0|1] [verbose=VERB]\n"
+ " [--dry-run] [--verbose]\n"
+ " where:\n"
+ " bpt is blocks_per_transfer (default is 128)\n"
+ " bs must be device logical block size (default "
+ "512)\n"
+ " cdbsz size of SCSI READ or WRITE cdb (default is 10)\n"
+ " coe continue on error, 0->exit (def), "
+ "1->zero + continue\n"
+ " count number of blocks to copy (def: device size)\n"
+ " deb for debug, 0->none (def), > 0->varying degrees "
+ "of debug\n");
+ pr2serr(" dio is direct IO, 1->attempt, 0->indirect IO (def)\n"
+ " fua force unit access: 0->don't(def), 1->OFILE, "
+ "2->IFILE,\n"
+ " 3->OFILE+IFILE\n"
+ " if file or device to read from (def: stdin)\n"
+ " iflag comma separated list from: [coe,dio,direct,dpo,"
+ "dsync,excl,\n"
+ " fua,mmap,null]\n"
+ " of file or device to write to (def: stdout), "
+ "OFILE of '.'\n"
+ " treated as /dev/null\n"
+ " oflag comma separated list from: [append,coe,dio,"
+ "direct,dpo,\n"
+ " dsync,excl,fua,mmap,null]\n"
+ " seek block position to start writing to OFILE\n"
+ " skip block position to start reading from IFILE\n"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE "
+ "after copy\n"
+ " thr is number of threads, must be > 0, default 4, "
+ "max 1024\n"
+ " time 0->no timing(def), 1->time plus calculate "
+ "throughput\n"
+ " verbose same as 'deb=VERB': increase verbosity\n"
+ " --chkaddr|-c check read data contains blk address\n"
+ " --dry-run|-d prepare but bypass copy/read\n"
+ " --help|-h output this usage message then exit\n"
+ " --verbose|-v increase verbosity of utility\n"
+ " --version|-V output version string then exit\n"
+ "Copy from IFILE to OFILE, similar to dd command\n"
+ "specialized for SCSI devices, uses multiple POSIX threads\n");
+}
+
+static int
+sgp_mem_mmap(int fd, int res_sz, uint8_t ** mmpp)
+{
+ int t;
+
+ if (ioctl(fd, SG_GET_RESERVED_SIZE, &t) < 0) {
+ perror("SG_GET_RESERVED_SIZE error");
+ return -1;
+ }
+ if (t < (int)sg_get_page_size())
+ t = sg_get_page_size();
+ if (res_sz > t) {
+ if (ioctl(fd, SG_SET_RESERVED_SIZE, &res_sz) < 0) {
+ perror("SG_SET_RESERVED_SIZE error");
+ return -1;
+ }
+ }
+ *mmpp = (uint8_t *)mmap(NULL, res_sz,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (MAP_FAILED == *mmpp) {
+ perror("mmap() failed");
+ return -1;
+ }
+ return 0;
+}
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rcBuff[RCAP16_REPLY_LEN];
+
+ res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false, 0);
+ if (0 != res)
+ return res;
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+
+ res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false,
+ 0);
+ if (0 != res)
+ return res;
+ *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)sg_get_unaligned_be32(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ return 0;
+}
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ #endif
+ }
+ return 0;
+#else
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+static void *
+sig_listen_thread(void * v_clp)
+{
+ struct opts_t * clp = (struct opts_t *)v_clp;
+ int sig_number;
+
+ while (1) {
+ sigwait(&signal_set, &sig_number);
+ if (shutting_down)
+ break;
+ if (SIGINT == sig_number) {
+ pr2serr("%sinterrupted by SIGINT\n", my_name);
+#ifdef HAVE_C11_ATOMICS
+ atomic_store(&exit_threads, true);
+#else
+ exit_threads = true;
+#endif
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ }
+ }
+ return NULL;
+}
+
+static void
+cleanup_in(void * v_clp)
+{
+ struct opts_t * clp = (struct opts_t *)v_clp;
+
+ pr2serr("thread cancelled while in mutex held\n");
+ pthread_mutex_unlock(&clp->inout_mutex);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+}
+
+static void
+cleanup_out(void * v_clp)
+{
+ struct opts_t * clp = (struct opts_t *)v_clp;
+
+ pr2serr("thread cancelled while out mutex held\n");
+ pthread_mutex_unlock(&clp->inout_mutex);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+}
+
+static int
+sg_prepare(int fd, int bs, int bpt)
+{
+ int res, t;
+
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ pr2serr("%ssg driver prior to 3.x.y\n", my_name);
+ return 1;
+ }
+ t = bs * bpt;
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
+ if (res < 0)
+ perror("sgp_dd: SG_SET_RESERVED_SIZE error");
+ t = 1;
+ res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
+ if (res < 0)
+ perror("sgp_dd: SG_SET_FORCE_PACK_ID error");
+ return 0;
+}
+
+static int
+sg_in_open(const char * fnp, struct flags_t * flagp, int bs, int bpt)
+{
+ int flags = O_RDWR;
+ int fd, err;
+ char ebuff[800];
+
+ if (flagp->direct)
+ flags |= O_DIRECT;
+ if (flagp->excl)
+ flags |= O_EXCL;
+ if (flagp->dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(fnp, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for sg "
+ "reading", my_name, fnp);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ if (sg_prepare(fd, bs, bpt)) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ return fd;
+}
+
+static int
+sg_out_open(const char * fnp, struct flags_t * flagp, int bs, int bpt)
+{
+ int flags = O_RDWR;
+ int fd, err;
+ char ebuff[800];
+
+ if (flagp->direct)
+ flags |= O_DIRECT;
+ if (flagp->excl)
+ flags |= O_EXCL;
+ if (flagp->dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(fnp, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for sg "
+ "writing", my_name, fnp);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ if (sg_prepare(fd, bs, bpt)) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ return fd;
+}
+
+static void *
+read_write_thread(void * v_tap)
+{
+ struct thread_arg * tap = (struct thread_arg *)v_tap;
+ struct opts_t * clp = &my_opts;
+ Rq_elem rel;
+ Rq_elem * rep = &rel;
+ volatile bool stop_after_write, bb;
+ bool enforce_write_ordering;
+ int sz, c_addr;
+ int64_t out_blk, out_count;
+ int64_t seek_skip = tap->seek_skip;
+ int blocks, status;
+
+ stop_after_write = false;
+ enforce_write_ordering = (FT_DEV_NULL != clp->out_type) &&
+ (FT_SG != clp->out_type);
+ c_addr = clp->chkaddr;
+ memset(rep, 0, sizeof(*rep));
+ /* Following clp members are constant during lifetime of thread */
+ rep->bs = clp->bs;
+ if ((clp->num_threads > 1) && clp->mmap_active) {
+ /* sg devices need separate file descriptor */
+ if (clp->in_flags.mmap && (FT_SG == clp->in_type)) {
+ rep->infd = sg_in_open(infn, &clp->in_flags, rep->bs, clp->bpt);
+ if (rep->infd < 0) err_exit(-rep->infd, "error opening infn");
+ } else
+ rep->infd = clp->infd;
+ if (clp->out_flags.mmap && (FT_SG == clp->out_type)) {
+ rep->outfd = sg_out_open(outfn, &clp->out_flags, rep->bs,
+ clp->bpt);
+ if (rep->outfd < 0) err_exit(-rep->outfd, "error opening outfn");
+
+ } else
+ rep->outfd = clp->outfd;
+ } else {
+ rep->infd = clp->infd;
+ rep->outfd = clp->outfd;
+ }
+ sz = clp->bpt * rep->bs;
+ rep->debug = clp->debug;
+ rep->cdbsz_in = clp->cdbsz_in;
+ rep->cdbsz_out = clp->cdbsz_out;
+ rep->in_flags = clp->in_flags;
+ rep->out_flags = clp->out_flags;
+ rep->use_no_dxfer = (FT_DEV_NULL == clp->out_type);
+ if (clp->mmap_active) {
+ int fd = clp->in_flags.mmap ? rep->infd : rep->outfd;
+
+ status = sgp_mem_mmap(fd, sz, &rep->buffp);
+ if (status) err_exit(status, "sgp_mem_mmap() failed");
+ } else {
+ rep->buffp = sg_memalign(sz, 0 /* page align */, &rep->alloc_bp,
+ false);
+ if (NULL == rep->buffp)
+ err_exit(ENOMEM, "out of memory creating user buffers\n");
+ }
+
+ while(1) {
+ if ((rep->in_stop) || (rep->in_err) || (rep->out_err))
+ break;
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+#ifdef HAVE_C11_ATOMICS
+ bb = atomic_load(&exit_threads);
+#else
+ bb = exit_threads;
+#endif
+ if (bb || (clp->in_count <= 0)) {
+ /* no more to do, exit loop then thread */
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ break;
+ }
+ blocks = (clp->in_count > clp->bpt) ? clp->bpt : clp->in_count;
+ rep->wr = false;
+ rep->blk = clp->in_blk;
+ rep->num_blks = blocks;
+ clp->in_blk += blocks;
+ clp->in_count -= blocks;
+ /* while we have this lock, find corresponding out_blk */
+ out_blk = rep->blk + seek_skip;
+ out_count = clp->out_count;
+ if (! enforce_write_ordering)
+ clp->out_blk += blocks;
+ clp->out_count -= blocks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+
+ pthread_cleanup_push(cleanup_in, (void *)clp);
+ if (FT_SG == clp->in_type)
+ sg_in_operation(clp, rep);
+ else
+ normal_in_operation(clp, rep, blocks);
+ if (c_addr && (rep->bs > 3)) {
+ int k, j, off, num;
+ uint32_t addr = (uint32_t)rep->blk;
+
+ num = (1 == c_addr) ? 4 : (rep->bs - 3);
+ for (k = 0, off = 0; k < blocks; ++k, ++addr, off += rep->bs) {
+ for (j = 0; j < num; j += 4) {
+ if (addr != sg_get_unaligned_be32(rep->buffp + off + j))
+ break;
+ }
+ if (j < num)
+ break;
+ }
+ if (k < blocks) {
+ pr2serr("%s: chkaddr failure at addr=0x%x\n", __func__, addr);
+ rep->in_err = true;
+ }
+ }
+ pthread_cleanup_pop(0);
+ if (rep->in_err) {
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ /* write-side not done, so undo changes to out_blk + out_count */
+ if (! enforce_write_ordering)
+ clp->out_blk -= blocks;
+ clp->out_count += blocks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ break;
+ }
+
+ if (enforce_write_ordering) {
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+#ifdef HAVE_C11_ATOMICS
+ bb = atomic_load(&exit_threads);
+#else
+ bb = exit_threads;
+#endif
+ while ((! bb) && (out_blk != clp->out_blk)) {
+ /* if write would be out of sequence then wait */
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ status = pthread_cond_wait(&clp->out_sync_cv,
+ &clp->inout_mutex);
+ if (0 != status) err_exit(status, "cond out_sync_cv");
+ pthread_cleanup_pop(0);
+ }
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ }
+
+#ifdef HAVE_C11_ATOMICS
+ bb = atomic_load(&exit_threads);
+#else
+ bb = exit_threads;
+#endif
+ if (bb || (out_count <= 0))
+ break;
+
+ rep->wr = true;
+ rep->blk = out_blk;
+
+ if (0 == rep->num_blks) {
+ break; /* read nothing so leave loop */
+ }
+
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ if (FT_SG == clp->out_type)
+ sg_out_operation(clp, rep, enforce_write_ordering);
+ else if (FT_DEV_NULL == clp->out_type) {
+ /* skip actual write operation */
+ clp->out_rem_count -= blocks;
+ }
+ else
+ normal_out_operation(clp, rep, blocks, enforce_write_ordering);
+ pthread_cleanup_pop(0);
+
+ if (enforce_write_ordering)
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ } /* end of while loop */
+
+ if (rep->alloc_bp)
+ free(rep->alloc_bp);
+ if (rep->in_err || rep->out_err) {
+ stop_after_write = true;
+#ifdef HAVE_C11_ATOMICS
+ if (! atomic_load(&exit_threads))
+ atomic_store(&exit_threads, true);
+#else
+ if (! exit_threads)
+ exit_threads = true;
+#endif
+ }
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ return (stop_after_write || rep->in_stop) ? NULL : clp;
+}
+
+static void
+normal_in_operation(struct opts_t * clp, Rq_elem * rep, int blocks)
+{
+ int res, status;
+ char strerr_buff[STRERR_BUFF_LEN + 1];
+
+ while (((res = read(rep->infd, rep->buffp, blocks * rep->bs)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno)))
+ ;
+ if (res < 0) {
+ if (rep->in_flags.coe) {
+ memset(rep->buffp, 0, rep->num_blks * rep->bs);
+ pr2serr(">> substituted zeros for in blk=%" PRId64 " for %d "
+ "bytes, %s\n", rep->blk,
+ rep->num_blks * rep->bs,
+ tsafe_strerror(errno, strerr_buff));
+ res = rep->num_blks * rep->bs;
+ }
+ else {
+ pr2serr("error in normal read, %s\n",
+ tsafe_strerror(errno, strerr_buff));
+ rep->in_stop = true;
+ rep->in_err = true;
+ return;
+ }
+ }
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ if (res < blocks * rep->bs) {
+ int o_blocks = blocks;
+
+ rep->in_stop = true;
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->in_partial++;
+ }
+ /* Reverse out + re-apply blocks on clp */
+ clp->in_blk -= o_blocks;
+ clp->in_count += o_blocks;
+ rep->num_blks = blocks;
+ clp->in_blk += blocks;
+ clp->in_count -= blocks;
+ }
+ clp->in_rem_count -= blocks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+}
+
+static void
+normal_out_operation(struct opts_t * clp, Rq_elem * rep, int blocks,
+ bool bump_out_blk)
+{
+ int res, status;
+ char strerr_buff[STRERR_BUFF_LEN + 1];
+
+ while (((res = write(rep->outfd, rep->buffp, rep->num_blks * rep->bs))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ ;
+ if (res < 0) {
+ if (rep->out_flags.coe) {
+ pr2serr(">> ignored error for out blk=%" PRId64 " for %d bytes, "
+ "%s\n", rep->blk, rep->num_blks * rep->bs,
+ tsafe_strerror(errno, strerr_buff));
+ res = rep->num_blks * rep->bs;
+ }
+ else {
+ pr2serr("error normal write, %s\n",
+ tsafe_strerror(errno, strerr_buff));
+ rep->out_err = true;
+ return;
+ }
+ }
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ if (res < blocks * rep->bs) {
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->out_partial++;
+ }
+ rep->num_blks = blocks;
+ }
+ clp->out_rem_count -= blocks;
+ if (bump_out_blk)
+ clp->out_blk += blocks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool write_true, bool fua, bool dpo)
+{
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+ int sz_ind;
+
+ memset(cdbp, 0, cdb_sz);
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr("%sfor 6 byte commands, maximum number of blocks is "
+ "256\n", my_name);
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr("%sfor 6 byte commands, can't address blocks beyond "
+ "%d\n", my_name, 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr("%sfor 6 byte commands, neither dpo nor fua bits "
+ "supported\n", my_name);
+ return 1;
+ }
+ break;
+ case 10:
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr("%sfor 10 byte commands, maximum number of blocks is "
+ "%d\n", my_name, 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr("%sexpected cdb size of 6, 10, 12, or 16 but got %d\n",
+ my_name, cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+sg_in_operation(struct opts_t * clp, Rq_elem * rep)
+{
+ int res;
+ int status;
+
+ while (1) {
+ res = sg_start_io(rep);
+ if (1 == res)
+ err_exit(ENOMEM, "sg starting in command");
+ else if (res < 0) {
+ pr2serr("%sinputting to sg failed, blk=%" PRId64 "\n", my_name,
+ rep->blk);
+ rep->in_stop = true;
+ rep->in_err = true;
+ return;
+ }
+ res = sg_finish_io(rep->wr, rep, &clp->inout_mutex);
+ switch (res) {
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ /* try again with same addr, count info */
+ /* now re-acquire in mutex for balance */
+ /* N.B. This re-read could now be out of read sequence */
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (0 == rep->in_flags.coe) {
+ pr2serr("error finishing sg in command (medium)\n");
+ if (exit_status <= 0)
+ exit_status = res;
+ rep->in_stop = true;
+ rep->in_err = true;
+ return;
+ } else {
+ memset(rep->buffp, 0, rep->num_blks * rep->bs);
+ pr2serr(">> substituted zeros for in blk=%" PRId64 " for %d "
+ "bytes\n", rep->blk, rep->num_blks * rep->bs);
+ }
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case 0:
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ if (rep->dio_incomplete_count || rep->resid) {
+ clp->dio_incomplete_count += rep->dio_incomplete_count;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->in_rem_count -= rep->num_blks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ return;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (clp->debug)
+ sg_print_command_len(rep->cdb, rep->cdbsz_in);
+ /* FALL THROUGH */
+ default:
+ pr2serr("error finishing sg in command (%d)\n", res);
+ if (exit_status <= 0)
+ exit_status = res;
+ rep->in_stop = true;
+ rep->in_err = true;
+ return;
+ }
+ } /* end of while loop */
+}
+
+static void
+sg_out_operation(struct opts_t * clp, Rq_elem * rep, bool bump_out_blk)
+{
+ int res;
+ int status;
+
+ while (1) {
+ res = sg_start_io(rep);
+ if (1 == res)
+ err_exit(ENOMEM, "sg starting out command");
+ else if (res < 0) {
+ pr2serr("%soutputting from sg failed, blk=%" PRId64 "\n",
+ my_name, rep->blk);
+ rep->out_err = true;
+ return;
+ }
+ res = sg_finish_io(rep->wr, rep, &clp->inout_mutex);
+ switch (res) {
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ /* try again with same addr, count info */
+ /* now re-acquire out mutex for balance */
+ /* N.B. This re-write could now be out of write sequence */
+ break;
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (0 == rep->out_flags.coe) {
+ pr2serr("error finishing sg out command (medium)\n");
+ if (exit_status <= 0)
+ exit_status = res;
+ rep->out_err = true;
+ return;
+ } else
+ pr2serr(">> ignored error for out blk=%" PRId64 " for %d "
+ "bytes\n", rep->blk, rep->num_blks * rep->bs);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case 0:
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ if (rep->dio_incomplete_count || rep->resid) {
+ clp->dio_incomplete_count += rep->dio_incomplete_count;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->out_rem_count -= rep->num_blks;
+ if (bump_out_blk)
+ clp->out_blk += rep->num_blks;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ return;
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ if (clp->debug)
+ sg_print_command_len(rep->cdb, rep->cdbsz_out);
+ /* FALL THROUGH */
+ default:
+ rep->out_err = true;
+ pr2serr("error finishing sg out command (%d)\n", res);
+ if (exit_status <= 0)
+ exit_status = res;
+ return;
+ }
+ }
+}
+
+static int
+sg_start_io(Rq_elem * rep)
+{
+ struct sg_io_hdr * hp = &rep->io_hdr;
+ bool fua = rep->wr ? rep->out_flags.fua : rep->in_flags.fua;
+ bool dpo = rep->wr ? rep->out_flags.dpo : rep->in_flags.dpo;
+ bool dio = rep->wr ? rep->out_flags.dio : rep->in_flags.dio;
+ bool mmap = rep->wr ? rep->out_flags.mmap : rep->in_flags.mmap;
+ bool no_dxfer = rep->wr ? false : rep->use_no_dxfer;
+ int cdbsz = rep->wr ? rep->cdbsz_out : rep->cdbsz_in;
+ int res;
+
+ if (sg_build_scsi_cdb(rep->cdb, cdbsz, rep->num_blks, rep->blk,
+ rep->wr, fua, dpo)) {
+ pr2serr("%sbad cdb build, start_blk=%" PRId64 ", blocks=%d\n",
+ my_name, rep->blk, rep->num_blks);
+ return -1;
+ }
+ memset(hp, 0, sizeof(struct sg_io_hdr));
+ hp->interface_id = 'S';
+ hp->cmd_len = cdbsz;
+ hp->cmdp = rep->cdb;
+ hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ hp->dxfer_len = rep->bs * rep->num_blks;
+ hp->dxferp = mmap ? NULL : rep->buffp;
+ hp->mx_sb_len = sizeof(rep->sb);
+ hp->sbp = rep->sb;
+ hp->timeout = DEF_TIMEOUT;
+ hp->usr_ptr = rep;
+ rep->pack_id = GET_NEXT_PACK_ID(1);
+ hp->pack_id = (int)rep->pack_id;
+ if (dio)
+ hp->flags |= SG_FLAG_DIRECT_IO;
+ if (mmap)
+ hp->flags |= SG_FLAG_MMAP_IO;
+ if (no_dxfer)
+ hp->flags |= SG_FLAG_NO_DXFER;
+ if (rep->debug > 8) {
+ pr2serr("%s: SCSI %s, blk=%" PRId64 " num_blks=%d\n", __func__,
+ rep->wr ? "WRITE" : "READ", rep->blk, rep->num_blks);
+ sg_print_command(hp->cmdp);
+ }
+
+ while (((res = write(rep->wr ? rep->outfd : rep->infd, hp,
+ sizeof(struct sg_io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno))) {
+#ifdef HAVE_C11_ATOMICS
+ if (EINTR == errno)
+ atomic_fetch_add(&num_eintr, 1);
+ else if (EAGAIN == errno)
+ atomic_fetch_add(&num_eagain, 1);
+ else
+ atomic_fetch_add(&num_ebusy, 1);
+#endif
+ }
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return 1;
+ perror("starting io on sg device, error");
+ return -1;
+ }
+ return 0;
+}
+
+/* 0 -> successful, SG_LIB_CAT_UNIT_ATTENTION or SG_LIB_CAT_ABORTED_COMMAND
+ -> try again, SG_LIB_CAT_NOT_READY, SG_LIB_CAT_MEDIUM_HARD,
+ -1 other errors */
+static int
+sg_finish_io(bool wr, Rq_elem * rep, pthread_mutex_t * a_mutp)
+{
+ int res, status;
+ struct sg_io_hdr io_hdr;
+ struct sg_io_hdr * hp;
+#if 0
+ static int testing = 0; /* thread dubious! */
+#endif
+
+ memset(&io_hdr, 0 , sizeof(struct sg_io_hdr));
+ /* FORCE_PACK_ID active set only read packet with matching pack_id */
+ io_hdr.interface_id = 'S';
+ io_hdr.dxfer_direction = wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ io_hdr.pack_id = (int)rep->pack_id;
+
+ while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr,
+ sizeof(struct sg_io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ if (res < 0) {
+ perror("finishing io on sg device, error");
+ return -1;
+ }
+ if (rep != (Rq_elem *)io_hdr.usr_ptr)
+ err_exit(0, "sg_finish_io: bad usr_ptr, request-response mismatch\n");
+ memcpy(&rep->io_hdr, &io_hdr, sizeof(struct sg_io_hdr));
+ hp = &rep->io_hdr;
+
+ res = sg_err_category3(hp);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3((wr ? "writing continuing":
+ "reading continuing"), hp, false);
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ if (rep->debug)
+ sg_chk_n_print3((wr ? "writing": "reading"), hp, false);
+ return res;
+ case SG_LIB_CAT_NOT_READY:
+ default:
+ rep->out_err = false;
+ if (rep->debug) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s blk=%" PRId64,
+ wr ? "writing": "reading", rep->blk);
+ status = pthread_mutex_lock(a_mutp);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ sg_chk_n_print3(ebuff, hp, false);
+ status = pthread_mutex_unlock(a_mutp);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+ }
+ return res;
+ }
+#if 0
+ if (0 == (++testing % 100)) return -1;
+#endif
+ if ((wr ? rep->out_flags.dio : rep->in_flags.dio) &&
+ ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ rep->dio_incomplete_count = 1; /* count dios done as indirect IO */
+ else
+ rep->dio_incomplete_count = 0;
+ rep->resid = hp->resid;
+ if (rep->debug > 8)
+ pr2serr("%s: completed %s\n", __func__, wr ? "WRITE" : "READ");
+ return 0;
+}
+
+static int
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "coe"))
+ fp->coe = true;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "mmap"))
+ fp->mmap = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return 1;
+ }
+ cp = np;
+ } while (cp);
+ return 0;
+}
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ int64_t skip = 0;
+ int64_t seek = 0;
+ int ibs = 0;
+ int obs = 0;
+ int bpt_given = 0;
+ int cdbsz_given = 0;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ int res, k, err, keylen;
+ int64_t in_num_sect = 0;
+ int64_t out_num_sect = 0;
+ int64_t seek_skip;
+ int in_sect_sz, out_sect_sz, status, n, flags;
+ void * vp;
+ struct opts_t * clp = &my_opts;
+ char ebuff[EBUFF_SZ];
+#if SG_LIB_ANDROID
+ struct sigaction actions;
+
+ memset(&actions, 0, sizeof(actions));
+ sigemptyset(&actions.sa_mask);
+ actions.sa_flags = 0;
+ actions.sa_handler = thread_exit_handler;
+ sigaction(SIGUSR1, &actions, NULL);
+#endif
+ memset(clp, 0, sizeof(*clp));
+ clp->num_threads = DEF_NUM_THREADS;
+ clp->bpt = DEF_BLOCKS_PER_TRANSFER;
+ clp->in_type = FT_OTHER;
+ clp->out_type = FT_OTHER;
+ clp->cdbsz_in = DEF_SCSI_CDBSZ;
+ clp->cdbsz_out = DEF_SCSI_CDBSZ;
+ infn[0] = '\0';
+ outfn[0] = '\0';
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ }
+ else
+ continue;
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key,"bpt")) {
+ clp->bpt = sg_get_num(buf);
+ if ((clp->bpt < 0) || (clp->bpt > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bpt='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpt_given = 1;
+ } else if (0 == strcmp(key,"bs")) {
+ clp->bs = sg_get_num(buf);
+ if ((clp->bs < 0) || (clp->bs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"cdbsz")) {
+ clp->cdbsz_in = sg_get_num(buf);
+ if ((clp->cdbsz_in < 6) || (clp->cdbsz_in > 32)) {
+ pr2serr("%s'cdbsz' expects 6, 10, 12, 16 or 32\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->cdbsz_out = clp->cdbsz_in;
+ cdbsz_given = 1;
+ } else if (0 == strcmp(key,"coe")) {
+ clp->in_flags.coe = !! sg_get_num(buf);
+ clp->out_flags.coe = clp->in_flags.coe;
+ } else if (0 == strcmp(key,"count")) {
+ if (0 != strcmp("-1", buf)) {
+ dd_count = sg_get_llnum(buf);
+ if ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("%sbad argument to 'count='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } /* treat 'count=-1' as calculate count (same as not given) */
+ } else if ((0 == strncmp(key,"deb", 3)) ||
+ (0 == strncmp(key,"verb", 4)))
+ clp->debug = sg_get_num(buf);
+ else if (0 == strcmp(key,"dio")) {
+ clp->in_flags.dio = !! sg_get_num(buf);
+ clp->out_flags.dio = clp->in_flags.dio;
+ } else if (0 == strcmp(key,"fua")) {
+ n = sg_get_num(buf);
+ if (n & 1)
+ clp->out_flags.fua = true;
+ if (n & 2)
+ clp->in_flags.fua = true;
+ } else if (0 == strcmp(key,"ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'ibs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"if") == 0) {
+ if ('\0' != infn[0]) {
+ pr2serr("Second 'if=' argument??\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ memcpy(infn, buf, INOUTF_SZ);
+ infn[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (process_flags(buf, &clp->in_flags)) {
+ pr2serr("%sbad argument to 'iflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'obs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"of") == 0) {
+ if ('\0' != outfn[0]) {
+ pr2serr("Second 'of=' argument??\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ memcpy(outfn, buf, INOUTF_SZ);
+ outfn[INOUTF_SZ - 1] = '\0';
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (process_flags(buf, &clp->out_flags)) {
+ pr2serr("%sbad argument to 'oflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"seek")) {
+ seek = sg_get_llnum(buf);
+ if ((seek < 0) || (seek > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"skip")) {
+ skip = sg_get_llnum(buf);
+ if ((skip < 0) || (skip > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("%sbad argument to 'skip='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"sync"))
+ do_sync = !! sg_get_num(buf);
+ else if (0 == strcmp(key,"thr"))
+ clp->num_threads = sg_get_num(buf);
+ else if (0 == strcmp(key,"time"))
+ do_time = !! sg_get_num(buf);
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'c');
+ clp->chkaddr += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'd');
+ clp->dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ if (n > 0) {
+ usage();
+ return 0;
+ }
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ clp->progress += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ clp->debug += n; /* -v ---> --verbose */
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp(key, "--chkaddr", 9))
+ ++clp->chkaddr;
+ else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++clp->dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?"))) {
+ usage();
+ return 0;
+ } else if (0 == strncmp(key, "--prog", 6))
+ ++clp->progress;
+ else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++clp->debug; /* --verbose */
+ } else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ clp->debug = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ clp->debug = 2;
+ } else
+ pr2serr("keep verbose=%d\n", clp->debug);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("%s%s\n", my_name, version_str);
+ return 0;
+ }
+
+ if (clp->bs <= 0) {
+ clp->bs = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ clp->bs);
+ }
+ if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((skip < 0) || (seek < 0)) {
+ pr2serr("skip and seek cannot be negative\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->out_flags.append && (seek > 0)) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->bpt < 1) || (clp->bpt > MAX_BPT_VALUE)) {
+ pr2serr("bpt must be > 0 and <= %d\n", MAX_BPT_VALUE);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->in_flags.mmap && clp->out_flags.mmap) {
+ pr2serr("can only use mmap flag in iflag= or oflag=, not both\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (clp->in_flags.mmap || clp->out_flags.mmap)
+ clp->mmap_active = true;
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ for the block layer in lk 2.6 and results in an EIO on the
+ SG_IO ioctl. So reduce it in that case. */
+ if ((clp->bs >= 2048) && (0 == bpt_given))
+ clp->bpt = DEF_BLOCKS_PER_2048TRANSFER;
+ if ((clp->num_threads < 1) || (clp->num_threads > MAX_NUM_THREADS)) {
+ pr2serr("too few or too many threads requested\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->debug > 2)
+ pr2serr("%sif=%s skip=%" PRId64 " of=%s seek=%" PRId64 " count=%"
+ PRId64 "\n", my_name, infn, skip, outfn, seek, dd_count);
+
+ install_handler(SIGINT, interrupt_handler);
+ install_handler(SIGQUIT, interrupt_handler);
+ install_handler(SIGPIPE, interrupt_handler);
+ install_handler(SIGUSR1, siginfo_handler);
+
+ clp->infd = STDIN_FILENO;
+ clp->outfd = STDOUT_FILENO;
+ if (infn[0] && ('-' != infn[0])) {
+ clp->in_type = dd_filetype(infn);
+
+ if (FT_ERROR == clp->in_type) {
+ pr2serr("%sunable to access %s\n", my_name, infn);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == clp->in_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, infn);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->in_type) {
+ clp->infd = sg_in_open(infn, &clp->in_flags, clp->bs, clp->bpt);
+ if (clp->infd < 0)
+ return -clp->infd;
+ }
+ else {
+ flags = O_RDONLY;
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((clp->infd = open(infn, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for reading",
+ my_name, infn);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ else if (skip > 0) {
+ off64_t offset = skip;
+
+ offset *= clp->bs; /* could exceed 32 bits here! */
+ if (lseek64(clp->infd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't skip to required "
+ "position on %s", my_name, infn);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ }
+ if (outfn[0] && ('-' != outfn[0])) {
+ clp->out_type = dd_filetype(outfn);
+
+ if (FT_ST == clp->out_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, outfn);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->out_type) {
+ clp->outfd = sg_out_open(outfn, &clp->out_flags, clp->bs,
+ clp->bpt);
+ if (clp->outfd < 0)
+ return -clp->outfd;
+ } else if (FT_DEV_NULL == clp->out_type)
+ clp->outfd = -1; /* don't bother opening */
+ else {
+ if (FT_RAW != clp->out_type) {
+ flags = O_WRONLY | O_CREAT;
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+
+ if ((clp->outfd = open(outfn, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for "
+ "writing", my_name, outfn);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ else { /* raw output file */
+ if ((clp->outfd = open(outfn, O_WRONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw "
+ "writing", my_name, outfn);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ if (seek > 0) {
+ off64_t offset = seek;
+
+ offset *= clp->bs; /* could exceed 32 bits here! */
+ if (lseek64(clp->outfd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required "
+ "position on %s", my_name, outfn);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ }
+ if ((STDIN_FILENO == clp->infd) && (STDOUT_FILENO == clp->outfd)) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to stdout\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (dd_count < 0) {
+ in_num_sect = -1;
+ if (FT_SG == clp->in_type) {
+ res = scsi_read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(in), continuing\n");
+ res = scsi_read_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", infn);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", infn);
+ else
+ pr2serr("Unable to read capacity on %s\n", infn);
+ in_num_sect = -1;
+ }
+ } else if (FT_BLOCK == clp->in_type) {
+ if (0 != read_blkdev_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", infn);
+ in_num_sect = -1;
+ }
+ if (clp->bs != in_sect_sz) {
+ pr2serr("logical block size on %s confusion; bs=%d, from "
+ "device=%d\n", infn, clp->bs, in_sect_sz);
+ in_num_sect = -1;
+ }
+ }
+ if (in_num_sect > skip)
+ in_num_sect -= skip;
+
+ out_num_sect = -1;
+ if (FT_SG == clp->out_type) {
+ res = scsi_read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(out), continuing\n");
+ res = scsi_read_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", outfn);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", outfn);
+ else
+ pr2serr("Unable to read capacity on %s\n", outfn);
+ out_num_sect = -1;
+ }
+ } else if (FT_BLOCK == clp->out_type) {
+ if (0 != read_blkdev_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outfn);
+ out_num_sect = -1;
+ }
+ if (clp->bs != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, from "
+ "device=%d\n", outfn, clp->bs, out_sect_sz);
+ out_num_sect = -1;
+ }
+ }
+ if (out_num_sect > seek)
+ out_num_sect -= seek;
+
+ if (in_num_sect > 0) {
+ if (out_num_sect > 0)
+ dd_count = (in_num_sect > out_num_sect) ? out_num_sect :
+ in_num_sect;
+ else
+ dd_count = in_num_sect;
+ }
+ else
+ dd_count = out_num_sect;
+ }
+ if (clp->debug > 1)
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64
+ ", out_num_sect=%" PRId64 "\n", dd_count, in_num_sect,
+ out_num_sect);
+ if (dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (! cdbsz_given) {
+ if ((FT_SG == clp->in_type) && (MAX_SCSI_CDBSZ != clp->cdbsz_in) &&
+ (((dd_count + skip) > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ clp->cdbsz_in = MAX_SCSI_CDBSZ;
+ }
+ if ((FT_SG == clp->out_type) && (MAX_SCSI_CDBSZ != clp->cdbsz_out) &&
+ (((dd_count + seek) > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ clp->cdbsz_out = MAX_SCSI_CDBSZ;
+ }
+ }
+
+ clp->in_count = dd_count;
+ clp->in_rem_count = dd_count;
+ clp->skip = skip;
+ clp->in_blk = skip;
+ clp->out_count = dd_count;
+ clp->out_rem_count = dd_count;
+ clp->seek = seek;
+ status = pthread_mutex_init(&clp->inout_mutex, NULL);
+ if (0 != status) err_exit(status, "init inout_mutex");
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock inout_mutex");
+ clp->out_blk = seek;
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock inout_mutex");
+
+ status = pthread_cond_init(&clp->out_sync_cv, NULL);
+ if (0 != status) err_exit(status, "init out_sync_cv");
+
+ if (clp->dry_run > 0) {
+ pr2serr("Due to --dry-run option, bypass copy/read\n");
+ goto fini;
+ }
+ sigemptyset(&signal_set);
+ sigaddset(&signal_set, SIGINT);
+ status = pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
+ if (0 != status) err_exit(status, "pthread_sigmask");
+ status = pthread_create(&sig_listen_thread_id, NULL,
+ sig_listen_thread, (void *)clp);
+ if (0 != status) err_exit(status, "pthread_create, sig...");
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+
+/* vvvvvvvvvvv Start worker threads vvvvvvvvvvvvvvvvvvvvvvvv */
+ if ((clp->out_rem_count > 0) && (clp->num_threads > 0)) {
+ /* Run 1 work thread to shake down infant retryable stuff */
+ status = pthread_mutex_lock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ seek_skip = clp->seek - clp->skip;
+ thr_arg_a[0].id = 0;
+ thr_arg_a[0].seek_skip = seek_skip;
+ status = pthread_create(&threads[0], NULL, read_write_thread,
+ (void *)(thr_arg_a + 0));
+ if (0 != status) err_exit(status, "pthread_create");
+ if (clp->debug)
+ pr2serr("Starting worker thread k=0\n");
+
+ /* wait for any broadcast */
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ status = pthread_cond_wait(&clp->out_sync_cv, &clp->inout_mutex);
+ if (0 != status) err_exit(status, "cond out_sync_cv");
+ pthread_cleanup_pop(0);
+ status = pthread_mutex_unlock(&clp->inout_mutex);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+
+ /* now start the rest of the threads */
+ for (k = 1; k < clp->num_threads; ++k) {
+
+ thr_arg_a[k].id = k;
+ thr_arg_a[k].seek_skip = seek_skip;
+ status = pthread_create(&threads[k], NULL, read_write_thread,
+ (void *)(thr_arg_a + k));
+ if (0 != status) err_exit(status, "pthread_create");
+ if (clp->debug > 2)
+ pr2serr("Starting worker thread k=%d\n", k);
+ }
+
+ /* now wait for worker threads to finish */
+ for (k = 0; k < clp->num_threads; ++k) {
+ status = pthread_join(threads[k], &vp);
+ if (0 != status) err_exit(status, "pthread_join");
+ if (clp->debug > 2)
+ pr2serr("Worker thread k=%d terminated\n", k);
+ }
+ } /* started worker threads and here after they have all exited */
+
+ if (do_time && (start_tm.tv_sec || start_tm.tv_usec))
+ calc_duration_throughput(0);
+
+ if (do_sync) {
+ if (FT_SG == clp->out_type) {
+ pr2serr(">> Synchronizing cache on %s\n", outfn);
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr("Unit attention(out), continuing\n");
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr("Unable to synchronize cache\n");
+ }
+ }
+
+#if 0
+#if SG_LIB_ANDROID
+ /* Android doesn't have pthread_cancel() so use pthread_kill() instead.
+ * Also there is no need to link with -lpthread in Android */
+ status = pthread_kill(sig_listen_thread_id, SIGUSR1);
+ if (0 != status) err_exit(status, "pthread_kill");
+#else
+ status = pthread_cancel(sig_listen_thread_id);
+ if (0 != status) err_exit(status, "pthread_cancel");
+#endif
+#endif /* 0, because always do pthread_kill() next */
+
+ shutting_down = true;
+ status = pthread_kill(sig_listen_thread_id, SIGINT);
+ if (0 != status) err_exit(status, "pthread_kill");
+ /* valgrind says the above _kill() leaks; web says it needs a following
+ * _join() to clear heap taken by associated _create() */
+
+fini:
+ if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0))
+ close(clp->infd);
+ if ((STDOUT_FILENO != clp->outfd) && (FT_DEV_NULL != clp->out_type)) {
+ if (clp->outfd >= 0)
+ close(clp->outfd);
+ }
+ res = exit_status;
+ if ((0 != clp->out_count) && (0 == clp->dry_run)) {
+ pr2serr(">>>> Some error occurred, remaining blocks=%" PRId64 "\n",
+ clp->out_count);
+ if (0 == res)
+ res = SG_LIB_CAT_OTHER;
+ }
+ print_stats("");
+ if (clp->dio_incomplete_count) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ clp->dio_incomplete_count);
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (clp->sum_of_resids)
+ pr2serr(">> Non-zero sum of residual counts=%d\n",
+ clp->sum_of_resids);
+#ifdef HAVE_C11_ATOMICS
+ {
+ unsigned int ui;
+
+ if ((ui = atomic_load(&num_eagain)) > 0)
+ pr2serr(">> number of IO call yielding EAGAIN %u\n", ui);
+ if ((ui = atomic_load(&num_ebusy)) > 0)
+ pr2serr(">> number of IO call yielding EBUSY %u\n", ui);
+ if ((ui = atomic_load(&num_eintr)) > 0)
+ pr2serr(">> number of IO call yielding EINTR %u\n", ui);
+ }
+#endif
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+}
diff --git a/suse/sg3_utils.changes b/suse/sg3_utils.changes
new file mode 100644
index 00000000..4279a292
--- /dev/null
+++ b/suse/sg3_utils.changes
@@ -0,0 +1,393 @@
+-------------------------------------------------------------------
+Thu Jan 23 15:00:00 EST 2014 - dgilbert@interlog.com
+
+- import Suse build files into sg3_utils in the suse directory
+ * change suse spec file to be patch-less
+ * henceforth see ChangeLog in main directory
+
+-------------------------------------------------------------------
+Thu Jan 23 08:57:56 CET 2014 - hare@suse.de
+
+- Update to inofficial release 1.38r546
+ * sg_ses: error and warning message cleanup
+ - fix --data=- problem with large buffers
+ - new --data=@FN to read hex data from file FN
+ - add --maxlen= option
+ * sg_inq:
+ - add LU_CONG to standard inquiry response
+ - sync version descriptors dated 20131126
+ - fix overflow in encode_whitespaces
+ * sg_vpd: add LU_CONG to standard inquiry response output
+ - decode Third Party Copy (tpc) page
+ * sg_persist: add PROUT: Replace Lost Reservation (spc4r36)
+ * sg_readcap: for --16 show physical block size if
+ * sg_xcopy:
+ - environment variables: XCOPY_TO_SRC and
+ XCOPY_TO_DST indicate where xcopy command is sent
+ - change default to send xcopy to dst (was src)
+ - improve CL handling of short options (e.g. '-vv')
+ * sg_write_same: repeat if unit attention
+ * sg_rtpg: fix indexing bug with --extended option
+ * sg_lib_data: sync asc/ascq codes with T10 dated 20131110
+ * sg_cmds_extra: fix sa bug in sg_ll_3party_copy_out()
+- Update tarball to 1.38b7r537
+- Add sg3_utils-1.38r546.patch
+
+-------------------------------------------------------------------
+Mon Nov 4 01:59:38 UTC 2013 - jengelh@inai.de
+
+- Update to new upstream release 1.37
+* sg_compare_and_write: add --quiet option to suppress miscompare
+ report
+* sg_persist: fix core dump on -Q option
+* sg_unmap: fix core dump on -g option
+* sg_ses: add --nickname and --nickid options
+- Remove sg3_utils-Fixup-T10-Vendor-designator-display.patch
+ (merged upstream)
+
+-------------------------------------------------------------------
+Sun Aug 25 18:45:14 CEST 2013 - ohering@suse.de
+
+- Fixup T10 Vendor designator display (bnc#805059)
+ sg3_utils-Fixup-T10-Vendor-designator-display.patch
+- In rescan-scsi-bus.sh, check if the HBA driver exports issue_lip
+ in sysfs before using it (bnc#780946)
+ sg3_utils-check-if-hba-supports-issue-lip.patch
+
+-------------------------------------------------------------------
+Thu Jun 13 14:15:26 UTC 2013 - jengelh@inai.de
+
+- Implement shlib packaging guidelines; rename sg3_utils-devel
+ to libsgutils-devel (upstream recommendation)
+- More robust make install call; remove redundant %clean section;
+ simplify file lists
+
+-------------------------------------------------------------------
+Tue Jun 11 08:56:39 UTC 2013 - rmilasan@suse.com
+
+- Update to version 1.36
+ - sg_vpd: Protocol-specific port information VPD page
+ for SAS SSP, persistent connection (spl3r2), power
+ disable (spl3r3)
+ - block device characteristics: add FUAB bit
+ - sg_xcopy: handle more descriptor types; handle zero
+ maximum segment length; allow list IDs to be disabled;
+ improve skip/seek handling; allow xcopy on destination
+ - sg_reset: and --no-esc option to stop reset escalation
+ - clean up cli, add long option names
+ - sg_luns: add --test=ALUN option for decoding LUNs
+ - decoded luns output in decimal or hex (if -HH given)
+ - add '--linux' option to show Linux LUN after T10
+ representation, can map one to the other
+ - sg_inq: add --vendor option to show standard inquiry's
+ vendor specific fields in ASCII
+ - take resid into account with response output
+ - sg_sync: add --16 (for 16 byte command) and --timeout=
+ - sg_logs: add data compression page (ssc4)
+ - sg_sat_set_features: increase --lba from 1 to 4 bytes
+ - sg_write_same: add --ndob option (sbc3r35d)
+ - sg_map: mark as deprecated
+ - sginfo: mark as deprecated, especially -l (list)
+ - sg_lib: improve snprintf handling
+ - sg_lib_data: sync asc/ascq codes with T10 20130117
+ - sg_cmds (lib): if noisy given, give more UA info
+ - make code more C++ friendly
+
+-------------------------------------------------------------------
+Tue Mar 12 09:13:45 CET 2013 - hare@suse.de
+
+- Update to version 1.35
+ - sg_compare_and_write: new utility
+ - sg_inq+sg_vpd: block device characteristics VPD page:
+ add product_type, WABEREQ, WACEREQ and VBULS fields
+ - sg_inq: more --export option changes for udev
+ - sg_vpd: add more rdac vendor specific vpd pages
+ - sg_verify: add --ebytchk option for sbc3r34 changes
+ - sg_stpg: --offline option: fix 'Invalid state 0xe'
+ - sg_ses: Door Lock element changed to Door element and
+ abbreviation changed from 'dl' to 'do' (ses3r05)
+ - archive/rescan-scsi-bus.sh: upgrade to version 1.53hr
+ - move rescan-scsi-bus.sh to scripts directory
+ - sync to sbc3r34
+ - sg_lib: sg_ll_verify10+16 expand BYTCHK to 2 bit field
+ - sg_pt_win32, sg_scan(win32): changes for cygwin 1.7.17
+ - clean up man page summary lines
+ - sg_xcopy: new dd like 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 spc4r36 and sbc3r32
+ - sg_inq: add --export so sg_inq can replace udev's scsi_id
+ - decode old EMC Symmetrix abuse of VPD page 0x83
+ - sg_vpd: decode old EMC Symmetrix abuse of VPD page 0x83
+ - sg_ses: increase max dpage response size to 64 KB
+ - allow ident,locate on enclosure controller
+ - more sanity for additional element status descriptor
+ - sg_sanitize: add --ause, --fail and --test=
+ - sg_luns: add long extended flat space addressing format
+ - sg_logs: add ATA pass-through results lpage (SAT-2)
+ - sg_rtpg: add --extended option
+ - sg_senddiag: list rebuild assist diag page name
+ - sg_pt_linux: expand DID_ (host_byte) codes
+ - cope with a transport error plus sense data
+ - prefer major() over MAJOR() macro
+ - sg_lib: fix sg_get_command_name() service actions
+ - report sdat_ovfl bit (if set) in sense data
+ - decode extended_copy and receive_copy service actions
+ - decode read_buffer and write_buffer modes
+ - decode ATA PT fixed format sense (SAT-2)
+ - sg_cmds_extra: add sg_ll_report_tgt_prt_grp2()
+ - ./configure options:
+ - change --enable-no-linux-bsg to --disable-linuxbsg
+ - add --disable-scsistrings to reduce utility sizes
+
+-------------------------------------------------------------------
+Wed Jul 4 07:01:46 UTC 2012 - cfarrell@suse.com
+
+- license update: GPL-2.0+ and BSD-3-Clause
+ Show aggregation and make compatible with Fedora declaration
+
+-------------------------------------------------------------------
+Sun Apr 22 11:50:44 UTC 2012 - puzel@suse.com
+
+- Update to version 1.33
+ - sg_ses: major rework of indexes (again), now two level
+ - sg_write_buffer: new --specific option for mode specific
+ field; new mode 13 (spc4r32)
+ - sg_vpd: add hp3par volume info vendor VPD page
+ - fix 'scsi ports' [0x88] page problem
+ - add 'sinq' pseudo page for standard inquiry response
+ - add power consumption page
+ - sg_format: add --poll= option for request sense polling
+ - improve handling of disks > 2 TB and DIF (protection)
+ - sg_logs: LB provision lpage extra (sbc3r28)
+ - sg_modes: application tag mpage subcode 0xf0->0x2
+ - sg_write_same: no prot fields when wrprotect=0
+ - sg_get_lba_status: reflect change in sbc3r25 to Parameter
+ Data Length response field (offset reduced from 8 to 4)
+ - sg_inq, sg_vpd: sync with spc4r33
+ - win32: change DataBufferOffset type per MSDN; caused
+ problem with 64 bit machines (with buffered interface)
+ - sg_luns: tweak documentation for vendor specific reports
+ - add man pages for scsi_loging_level, scsi_mandat,
+ scsi_satl and scsi_temperature
+
+-------------------------------------------------------------------
+Mon Jan 16 19:59:42 UTC 2012 - tabraham@novell.com
+
+- Update to version 1.32
+ + sg_sanitize: new utility for command added in sb3r27
+ + sg_sat_identify: add '--ident' to output WWN
+ + sg_ses: major rework of descriptor output
+ + add --index, --descriptor, --join, --clear, --get, and --set
+ options
+ + sg_raw: exit status corrections
+ + sg_decode_sense: add --nospace and --hex options
+ + sg_logs: fix bug with large --maxlen
+ + zero response length when resid implies it is invalid
+ + add scope field to lb provisioning lpage (sb3r27)
+ + sg_inq: sync version descriptors with spc4r31
+ + sb_lib_data: sync asc/ascq codes with spc4r31
+ + sg_vpd: add LBPRZ field in LP provisioning VPD page
+ + sg_format: allow format of pdt 7 (some MO drives)
+ + sg_cmd_basic: sg_cmds_process_resp() handle status good
+ with a sense key other than no_sense (e.g. completed)
+ + add README.iscsi
+
+- Updated rescan-scsi-bus.sh to v1.56
+
+-------------------------------------------------------------------
+Thu Mar 10 08:47:43 UTC 2011 - coolo@novell.com
+
+- fix file list
+
+-------------------------------------------------------------------
+Fri Feb 18 16:41:32 CET 2011 - hare@suse.de
+
+- Update to version 1.31:
+ + sg_decode_sense: new utility to decode sense data
+ + sg_vpd: LB provisioning + Block limits pages (sbc3r26)
+ + sync asc/ascq and version descriptors with spc4r28
+ + sg_get_config, sg_rmsn, sg_verify: add --readonly option
+ + sg_lib: implement forwarded sense data descriptor
+ - decode user data segment referral sense data descriptor
+ + sg_lib, sg_turs, sg_format: more precision for progress
+ indication (two places after decimal point)
+ + sg_lib(win32): add runtime selection of SPT direct or
+ indirect interface
+ - sg_read_buffer+sg_write_buffer: set SPT direct
+ + add examples/forwarded_sense.txt + examples/ref_sense.txt
+
+- Changes from version 1.30:
+ + sg_referrals: new utility for REPORT REFERRALS
+ + sbc3r25 renames 'thin' provisioning' to 'logical block
+ provisioning': changes in sg_format, sg_inq, sg_logs,
+ sg_modes, sg_readcap, sg_vpd
+ + sg_inq: update version descriptor list to spc4r27
+ - extended inquiry vpd page add extended self test
+ completion minutes field
+ + sg_lib: sync asc/ascq list to spc4r27
+ - dStrHex(): trim excess trailing spaces
+ + sg_read_long: add --readonly option (open() is rw)
+ + sg_raw: add --readonly option (open() is rw)
+ - allow bidirectional commands
+ + sg_vpd: rdac vendor page [0xc8] parse corrections
+ - extended inquiry vpd page add extended self test
+ -completion minutes field
+ + sg_ses: expand --data (in) buffer to 2048 bytes
+ + sg_opcodes: add extended parameter data for TMFs (spc4r26)
+ + sg_dd: clean count calculation, document nocache flag
+ - treat bsg devices as implicit sg_io
+ + sg_write_same: if READ CAPACITY(16) fails try 10 byte variant
+ - anticipate approval of proposal to allow UNMAP and ANCHOR
+ bits to be set on WRITE SAME(10) with '--10' option
+ + sg3_utils man page: sections added for OS device names
+
+-------------------------------------------------------------------
+Fri Aug 13 11:42:50 CEST 2010 - dimstar@opensuse.org
+
+- Update to version 1.29:
+ + sg_rtpg: new logical block dependent state and bit (spc4r23)
+ + sg_start: add '--readonly' option for ATA disks
+ + sg_lib: update asc/ascq list to spc4r23
+ + sg_inq: update version descriptor list to spc4r23
+ + sg_vpd: block device characteristics page: fix form factor
+ - update Extended Inquiry VPD page to spc4r23
+ - update Block Limits VPD page to sbc3r22
+ - update Thin Provisioning VPD page to sbc3r22
+ - Automation device serial number and Data transfer device
+ element VPD pages (ssc4r01)
+ - add Referrals VPD page (sbc3r22)
+ + sg_logs: add thin provisioning and solid state media log pages
+ - addition of IBM LTO specific log pages
+ + sg_modes: new page names from ssc4r01
+ + sg_ses: sync with ses3r02 (SAS-2.1 connector types)
+ + sg_unmap: add '--anchor' option (sbc3r22)
+ + sg_write_same: add '--anchor' option (sbc3r22)
+ + sg_pt interface: add set_scsi_pt_flags() to permit passing
+ through SCSI_PT_FLAGS_QUEUE_AT_TAIL and AT_HEAD flags
+ + add examples/sg_queue_tst+bsg_queue_tst for SG_FLAG_Q_AT_TAIL
+ + add AM_MAINTAINER_MODE to configure.ac to lessen build issues
+ + add BSD_LICENSE file to this and lib directories, refer to
+ it from source and header files. Some source has GPL license
+- Changes from version 1.28:
+ + sg_unmap: new utility for thin provisioning
+ - add examples/sg_unmap_example.txt
+ + sg_get_lba_status: new utility for thin provisioning
+ + sg_read_block_limits: new utility for tape drives
+ + sg_logs: add cache memory statistics log (sub)page
+ + sg_vpd, sg_inq: extend Block limits VPD page (sbc3r19)
+ + sg_vpd: add Thin provisioning VPD page (sbc3r20) and
+ TapeAlert supported flags VPD page
+ + sg_inq: note VPD page support better in sg_vpd
+ + sg_persist: add transport specific transportID format
+ - allow transportIDs to be read from named file
+ + sg_opcodes: allow --opcode= option to take OP and SA
+ values (comma seperated)
+ - tweak print format, remove test code
+ + sg_requests: remove test code in progress calculation
+ + sg_reset: add target reset option
+ + sg_luns: reduce default maxlen to 8192 (for FreeBSD)
+ + sg_raw: extend max cdb length from 16 to 256 bytes
+ - align heap allocs to page boundaries
+ + sg_lib: sg_set_binary_mode() needs config.h included
+ - add progress indication sense data descriptor (0xa)
+ - change SG3_UTILS_* constants to SG_LIB_*
+ - decode service actions within persistent reserve in/out
+ - sync with spc4r21
+ + sg_cmds_extra: add sg_ll_unmap() and sg_ll_get_lba_status()
+ + sg_pt_linux: fix check condition but empty sense buffer;
+ - major() macro grief, if present include <linux/kdev_t.h> and
+ use MAJOR() instead
+ + scripts/sas_disk_blink: moved from this package to sdparm
+ + utils/hxascdmp: in Windows set binary mode on read files
+ + examples/sg_persist_tst.sh: add PRIN read full status command
+ + sg_raw,sg_write_buffer,sg_write_long,sg_write_same: in Windows
+ set binary mode on read files
+ + sg_pt_win32: default to non-direct variant of SPT interface
+ - use './configure --enable-win32-spt-direct' to override
+ - non-direct data length set to 16 KB, extended if required
+ + debian: incorporate patch from debian sid
+
+-------------------------------------------------------------------
+Mon Jun 28 06:38:35 UTC 2010 - jengelh@medozas.de
+
+- use %_smp_mflags
+
+-------------------------------------------------------------------
+Tue Jul 21 14:00:16 CEST 2009 - hare@suse.de
+
+- Clean up spec file and remove obsolete cruft
+
+-------------------------------------------------------------------
+Fri Apr 17 20:15:58 CEST 2009 - crrodriguez@suse.de
+
+- remove static libraries and "la" files
+
+-------------------------------------------------------------------
+Mon Jan 26 15:30:31 CET 2009 - hare@suse.de
+
+- Fixes to rescan-scsi-bus.sh:
+ * Implement '--forcerescan' to force a rescan of existing devices
+ * Handle LUN changes correctly
+ * Check variables before evaluation
+
+-------------------------------------------------------------------
+Wed Oct 29 11:05:47 CET 2008 - garloff@suse.de
+
+- rescan-scsi-bus.sh 1.29:
+ * Fix error in script (returning "" does not work)
+ * Support systems without /proc/scsi
+- Don't install INSTALL
+
+-------------------------------------------------------------------
+Tue Sep 30 14:11:15 CEST 2008 - hare@suse.de
+
+- Add %insserv_prereq (bnc#423204)
+
+-------------------------------------------------------------------
+Fri Sep 12 20:29:08 CEST 2008 - garloff@suse.de
+
+- Update rescan-scsi-bus.sh script to 1.28:
+ * Merge fixes from Hannes
+ * Minor cleanups
+ * Sort hosts numerically
+
+-------------------------------------------------------------------
+Tue Aug 12 18:25:43 CEST 2008 - garloff@suse.de
+
+- Update to sg3_utils-1.27:
+ * Adapted to linux-2.6.26 (sg_map26)
+ * sg_dd uses flock (rw -- if that fails ro)
+ * sg_get_config: OSSC feature (mmc6r02)
+- Update to sg3_utils-1.26:
+ * Minor fixes and enhancements to
+ sg_sat_phy_event, sg_ses, sg_get_config, sg_verify, sg_vpd,
+ sg_inq, sg_modes, sg_start, sg_request, sg_luns, sg_dd,
+ sg_opcodes, sg_turs.
+ * sg_lib: asc/ascq update for spc4r15, osd2r03 service actions,
+ sense key specific unit attn queue overflow decoding, ...
+ * Restructuring: sg_lib -> sg_lib_data, sg_inq_data, (u)int64_t,
+ sg_io_linux -> lib/.
+ * Documentation enhancements.
+
+-------------------------------------------------------------------
+Wed Jul 16 09:55:33 CEST 2008 - hare@suse.de
+
+- Use correct length parameter for sg_inq (bnc#363438)
+
+-------------------------------------------------------------------
+Fri May 23 10:22:31 CEST 2008 - hare@suse.de
+
+- Use 'Provides' to clean update dependency
+
+-------------------------------------------------------------------
+Fri May 9 17:31:33 CEST 2008 - schwab@suse.de
+
+- Use autoreconf -i.
+
+-------------------------------------------------------------------
+Thu Apr 24 14:14:14 CEST 2008 - hare@suse.de
+
+- Split off from original scsi package.
+
diff --git a/suse/sg3_utils.spec b/suse/sg3_utils.spec
new file mode 100644
index 00000000..f1b15075
--- /dev/null
+++ b/suse/sg3_utils.spec
@@ -0,0 +1,125 @@
+#
+# spec file for package sg3_utils
+#
+# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
+#
+# All modifications and additions to the file contributed by third parties
+# remain the property of their copyright owners, unless otherwise agreed
+# upon. The license for this file, and modifications and additions to the
+# file, is the same license as for the pristine package itself (unless the
+# license for the pristine package is not an Open Source License, in which
+# case the license is the MIT License). An "Open Source License" is a
+# license that conforms to the Open Source Definition (Version 1.9)
+# published by the Open Source Initiative.
+
+# Please submit bugfixes or comments via http://bugs.opensuse.org/
+#
+#
+# No patches, this is the maintainer's version for Suse targets.
+# Patch lines would appear after the "Source:" line and look like:
+# Patch1: sg3_utils-1.38r546.patch
+# then under the "%setup -q" line there would be one or more lines:
+# %patch1 -p1
+
+
+Name: sg3_utils
+%define lname libsgutils2-2
+Version: 1.41
+Release: 0
+Summary: A collection of tools that send SCSI commands to devices
+License: GPL-2.0+ and BSD-3-Clause
+Group: Hardware/Other
+Url: https://sg.danny.cz/sg/sg3_utils.html
+
+Source: https://sg.danny.cz/sg/p/%name-%{version}.tar.xz
+BuildRoot: %{_tmppath}/%{name}-%{version}-build
+BuildRequires: xz
+Requires(pre): %insserv_prereq
+Provides: scsi
+Provides: sg_utils
+Obsoletes: scsi <= 1.7_2.38_1.25_0.19_1.02_0.93
+
+%description
+The sg3_utils package contains utilities that send SCSI commands to
+devices. As well as devices on transports traditionally associated with
+SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI
+Parallel Interface(SPI)) many other devices use SCSI command sets.
+ATAPI cd/dvd drives and SATA disks that connect via a translation layer
+or a bridge device are examples of devices that use SCSI command sets.
+
+%package -n %lname
+Summary: Library to hold functions common to the SCSI utilities
+License: BSD-3-Clause
+Group: System/Libraries
+
+%description -n %lname
+The sg3_utils package contains utilities that send SCSI commands to
+devices. As well as devices on transports traditionally associated with
+SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI
+Parallel Interface(SPI)) many other devices use SCSI command sets.
+ATAPI cd/dvd drives and SATA disks that connect via a translation layer
+or a bridge device are examples of devices that use SCSI command sets.
+
+This subpackage contains the library of common sg_utils code, such as
+SCSI error processing.
+
+%package -n libsgutils-devel
+Summary: A collection of tools that send SCSI commands to devices
+License: BSD-3-Clause
+Group: Development/Libraries/C and C++
+Requires: %lname = %version
+# Added for 13.1
+Obsoletes: %name-devel < %version-%release
+Provides: %name-devel = %version-%release
+
+%description -n libsgutils-devel
+The sg3_utils package contains utilities that send SCSI commands to
+devices. As well as devices on transports traditionally associated with
+SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI
+Parallel Interface(SPI)) many other devices use SCSI command sets.
+ATAPI cd/dvd drives and SATA disks that connect via a translation layer
+or a bridge device are examples of devices that use SCSI command sets.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libsgutils.
+
+%prep
+%setup -q
+
+%build
+%configure --disable-static --with-pic
+make %{?_smp_mflags}
+
+%install
+make install DESTDIR="%buildroot"
+install -m 755 scripts/scsi_logging_level $RPM_BUILD_ROOT%{_bindir}
+install -m 755 scripts/rescan-scsi-bus.sh $RPM_BUILD_ROOT%{_bindir}
+%{__rm} -f %{buildroot}%{_libdir}/*.la
+
+%post -p /sbin/ldconfig -n %lname
+
+%postun -p /sbin/ldconfig -n %lname
+
+%files
+%defattr(-,root,root)
+%doc README README.sg_start
+%doc ChangeLog CREDITS NEWS
+%_bindir/sg_*
+%_bindir/scsi_*
+%_bindir/sginfo
+%_bindir/sgp_dd
+%_bindir/sgm_dd
+%_bindir/scsi_logging_level
+%_bindir/rescan-scsi-bus.sh
+%_mandir/man8/*.8*
+
+%files -n %lname
+%defattr(-,root,root)
+%_libdir/libsgutils2.so.2*
+
+%files -n libsgutils-devel
+%defattr(-,root,root)
+%_libdir/libsgutils2.so
+%_includedir/scsi/
+
+%changelog
diff --git a/testing/Makefile b/testing/Makefile
new file mode 100644
index 00000000..34914916
--- /dev/null
+++ b/testing/Makefile
@@ -0,0 +1,158 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+EXECS = sg_sense_test sg_queue_tst bsg_queue_tst sg_chk_asc sg_tst_nvme \
+ sg_tst_ioctl sg_tst_bidi tst_sg_lib sgs_dd sg_tst_excl \
+ sg_tst_excl2 sg_tst_excl3 sg_tst_context sg_tst_async sgh_dd \
+ sg_mrq_dd sg_iovec_tst sg_take_snap sg_tst_json_builder
+
+EXTRAS =
+
+BSG_EXTRAS =
+
+
+MAN_PGS =
+MAN_PREF = man8
+
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+# For C++/clang testing
+## CC = gcc
+## CXX = g++
+## CC = clang
+## CXX = clang++
+
+LD = $(CXX)
+CXXLD = $(CXX)
+
+CPPFLAGS = -iquote ../include -iquote .. -D_REENTRANT $(LARGE_FILE_FLAGS) -DHAVE_CONFIG_H -DHAVE_NVME
+CXXFLAGS = -std=c++11 -pthread -ggdb -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+## CXXFLAGS = -std=c++14 -pthread -ggdb -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+## CXXFLAGS = -std=c++17 -pthread -ggdb -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+## CXXFLAGS = -std=c++20 -pthread -ggdb -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+## CXXFLAGS = -std=c++2a -pthread -ggdb -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS)
+# CPPFLAGS = -iquote ../include -iquote .. -D_REENTRANT $(LARGE_FILE_FLAGS) -DHAVE_CONFIG_H -DHAVE_NVME -DDEBUG
+CFLAGS = -g -O2 -W -Wall
+# CFLAGS = -ggdb -O2 -W -Wall -DDEBUG
+# CFLAGS = -g -O2 -Wall -DSG_KERNEL_INCLUDES
+# CFLAGS = -g -O2 -Wall -pedantic
+# CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# CFLAGS = -Wall -W -pedantic -std=c++14 -fPIC
+# CFLAGS = -Wall -W -pedantic -std=c++20
+
+LDFLAGS =
+
+LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_io_linux.o \
+ ../lib/sg_json_builder.o ../lib/sg_pr2serr.o
+
+LIBFILESNEW = ../lib/sg_pt_linux_nvme.o ../lib/sg_lib.o ../lib/sg_lib_data.o \
+ ../lib/sg_pt_linux.o ../lib/sg_io_linux.o \
+ ../lib/sg_pt_common.o ../lib/sg_cmds_basic.o \
+ ../lib/sg_cmds_basic2.o ../lib/sg_lib_names.o \
+ ../lib/sg_json_builder.o ../lib/sg_pr2serr.o
+
+all: $(EXECS)
+
+extras: $(EXTRAS)
+
+bsg: $(BSG_EXTRAS)
+
+
+depend dep:
+ for i in *.c; do $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) json_writer core .depend
+
+sg_sense_test: sg_sense_test.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_queue_tst: sg_queue_tst.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+bsg_queue_tst: bsg_queue_tst.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_ioctl: sg_tst_ioctl.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+# building sg_chk_asc depends on a prior successful make in ../lib
+sg_chk_asc: sg_chk_asc.o ../lib/sg_lib.o ../lib/sg_lib_data.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_nvme: sg_tst_nvme.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+tst_sg_lib: tst_sg_lib.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sgs_dd: sgs_dd.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_bidi: sg_tst_bidi.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_excl: sg_tst_excl.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_tst_excl2: sg_tst_excl2.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_tst_excl3: sg_tst_excl3.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_tst_context: sg_tst_context.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_tst_async: sg_tst_async.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+# Next three used to require '-latomic', may not anymore
+sgh_dd: sgh_dd.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_mrq_dd: sg_mrq_dd.o sg_scat_gath.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_iovec_tst: sg_iovec_tst.o sg_scat_gath.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread $^
+
+sg_take_snap: sg_take_snap.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_json_builder: sg_tst_json_builder.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $^; \
+ do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+# Linux uses GNU make and FreeBSD uses Berkely make. The following lines
+# only work in Linux. Possible solutions in FreeBSD:
+# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq'
+# c) build with 'make -f Makefile.freebsd'
+# In Linux one can install bmake (but that won't help here).
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
diff --git a/testing/Makefile.cyg b/testing/Makefile.cyg
new file mode 100644
index 00000000..0dfbb07d
--- /dev/null
+++ b/testing/Makefile.cyg
@@ -0,0 +1,110 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?)
+# the default C compiler is clang. Swap the comment marks (lines starting
+# with '#') on the next 4 (non-blank) lines.
+CC = gcc
+# CC = clang
+
+LD = gcc
+# LD = clang
+
+EXECS = sg_sense_test sg_chk_asc sg_tst_nvme tst_sg_lib
+
+EXTRAS =
+
+BSG_EXTRAS =
+
+
+MAN_PGS =
+MAN_PREF = man8
+
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+
+# For C++/clang testing
+## CC = gcc
+## CC = g++
+## CC = clang
+## CC = clang++
+
+CPPFLAGS = -iquote ../include -iquote .. -D_REENTRANT $(LARGE_FILE_FLAGS) -DHAVE_CONFIG_H -DHAVE_NVME
+CFLAGS = -g -O2 -W -Wall
+# CFLAGS = -g -O2 -Wall -DSG_KERNEL_INCLUDES
+# CFLAGS = -g -O2 -Wall -pedantic
+# CFLAGS = -Wall -W -pedantic -std=c11 --analyze
+# CFLAGS = -Wall -W -pedantic -std=c++14 -fPIC
+
+LDFLAGS =
+
+LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o
+LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o \
+ ../lib/sg_pt_win32.o ../lib/sg_pt_common.o ../lib/sg_cmds_basic.o
+
+all: $(EXECS)
+
+extras: $(EXTRAS)
+
+bsg: $(BSG_EXTRAS)
+
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend
+
+sg_iovec_tst: sg_iovec_tst.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_sense_test: sg_sense_test.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_queue_tst: sg_queue_tst.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+bsg_queue_tst: bsg_queue_tst.o $(LIBFILESOLD)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+# building sg_chk_asc depends on a prior successful make in ../lib
+sg_chk_asc: sg_chk_asc.o ../lib/sg_lib.o ../lib/sg_lib_data.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+sg_tst_nvme: sg_tst_nvme.o $(LIBFILESNEW)
+ $(LD) -o $@ $(LDFLAGS) $^
+
+tst_sg_lib: tst_sg_lib.o ../lib/sg_lib.o ../lib/sg_lib_data.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $^; \
+ do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+# Linux uses GNU make and FreeBSD uses Berkely make. The following lines
+# only work in Linux. Possible solutions in FreeBSD:
+# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq'
+# c) build with 'make -f Makefile.freebsd'
+# In Linux one can install bmake (but that won't help here).
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
diff --git a/testing/Makefile.freebsd b/testing/Makefile.freebsd
new file mode 100644
index 00000000..5b11cd09
--- /dev/null
+++ b/testing/Makefile.freebsd
@@ -0,0 +1,96 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?)
+# the default C compiler is clang. Swap the comment marks (lines starting
+# with '#') on the next 4 (non-blank) lines.
+# CC = gcc
+# CC = clang
+
+# LD = gcc
+# LD = clang
+
+EXECS = sg_sense_test sg_chk_asc sg_tst_nvme tst_sg_lib
+
+EXTRAS =
+
+MAN_PGS =
+MAN_PREF = man8
+
+OS_FLAGS = -DSG_LIB_FREEBSD -DHAVE_NVME
+EXTRA_FLAGS = $(OS_FLAGS)
+
+# For C++/clang testing
+## CC = gcc
+## CC = g++
+## CC = clang
+## CC = clang++
+
+# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) -I ../include
+CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) -I ../include
+# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) -I ../include
+
+CFLAGS_PTHREADS = -D_REENTRANT
+
+# there is no rule to make the following in the parent directory,
+# it is assumed they are already built.
+D_FILES = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_cmds_basic.o ../lib/sg_pt_common.o ../lib/sg_pt_freebsd.o
+
+LDFLAGS = -lcam
+
+all: $(EXECS)
+
+extras: $(EXTRAS)
+
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) $(EXTRAS) core .depend
+
+sg_sense_test: sg_sense_test.o $(D_FILES)
+ $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES)
+
+# building sg_chk_asc depends on a prior successful make in ../lib
+sg_chk_asc: sg_chk_asc.o $(D_FILES)
+ $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES)
+
+sg_tst_nvme: sg_tst_nvme.o $(D_FILES)
+ $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES)
+
+tst_sg_lib: tst_sg_lib.o $(D_FILES)
+ $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES)
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $(EXECS) ; \
+ do install -s -o root -g wheel -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g wheel -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+# Linux uses GNU make and FreeBSD uses Berkely make. The following lines
+# only work in Linux. Possible solutions in FreeBSD:
+# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq'
+# c) build with 'make -f Makefile.freebsd'
+# In Linux one can install bmake (but that won't help here).
+# ifeq (.depend,$(wildcard .depend))
+# include .depend
+# endif
diff --git a/testing/README b/testing/README
new file mode 100644
index 00000000..a144e77b
--- /dev/null
+++ b/testing/README
@@ -0,0 +1,47 @@
+
+
+The utilities in this directory are _not_ built automatically. So:
+ cd <root_of_sg3_utils_src>
+ ./configure ; make ; make install
+will _not_ build and install them. The make command (or some variant
+of it) needs to be run in this directory as outlined below.
+
+Building files in this directory depends on several files being already
+built in the ../lib directory. So to build files here, the ./configure
+needs to be executed in the parent directory followed by changing
+directory to the lib directory and calling 'make' there.
+Another way is to do a top level 'make' after the ./configure which
+will make the libraries followed by all the utilities in the src/
+directory. To make them in FreeBSD use 'make -f Makefile.freebsd' .
+
+The utilities in this directory do not have manpages. They have
+relatively complete but terse help messages, typically seen by using
+the '--help' option one or more times. If called several times, the
+shorter form of the help option is more convenient, for example: '-hhh'.
+And of course there is the source code. Unfortunately where the code
+implements many different options, it can become a bit dense. There
+is also a large amount of error checking, as many of these utilities
+were used to test new features placed in the sg v4 driver in Linux.
+
+The sg_chk_asc utility decodes the SCSI additional sense code table
+found at https://www.t10.org/lists/asc-num.txt and checks it against
+the table found in sg_lib_data.c in the lib/ subdirectory. It is
+designed to keep the table in sg_lib_data.c in "sync" with the
+table at the t10.org web site.
+
+The tst_sg_lib utility exercises several functions found in sg_lib.c
+and related files in the 'lib' sibling directory. Use 'tst_sg_lib -h'
+to get more information.
+
+There are both C and C++ files in this directory, they have extensions
+'.c' and '.cpp' respectively. Now both are built with rules in Makefile
+(at least in Linux). A gcc/g++ compiler of 4.7.3 vintage or later
+(or a recent clang compiler) will be required. To make them in FreeBSD
+use 'make -f Makefile.freebsd'.
+
+The sgh_dd utility (C++) uses 'libatomic' which may not be installed
+on some systems. On Debian based systems 'apt install libatomic1' fixes
+this.
+
+Douglas Gilbert
+17th September 2019
diff --git a/testing/bsg_queue_tst.c b/testing/bsg_queue_tst.c
new file mode 100644
index 00000000..06a35224
--- /dev/null
+++ b/testing/bsg_queue_tst.c
@@ -0,0 +1,171 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* If the following fails the Linux kernel is probably too old */
+#include <linux/bsg.h>
+
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_linux_inc.h"
+
+/* This program was used to test SCSI mid level queue ordering.
+ The default behaviour is to "queue at head" which is useful for
+ error processing but not for streaming READ and WRITE commands.
+
+* Copyright (C) 2010-2021 D. Gilbert
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2, or (at your option)
+* any later version.
+
+ Invocation: bsg_queue_tst [-t] <bsg_device>
+ -t queue at tail
+
+ Version 0.91 (20190111)
+
+*/
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define SDIAG_CMD_LEN 6
+#define SENSE_BUFFER_LEN 96
+
+#define EBUFF_SZ 256
+
+#ifndef BSG_FLAG_Q_AT_TAIL
+#define BSG_FLAG_Q_AT_TAIL 0x10
+#endif
+
+#ifndef BSG_FLAG_Q_AT_HEAD
+#define BSG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+
+int main(int argc, char * argv[])
+{
+ int bsg_fd, k, ok;
+ uint8_t inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t sdiag_cdb[SDIAG_CMD_LEN] =
+ {0x1d, 0x10 /* PF */, 0, 0, 0, 0};
+ uint8_t inqBuff[16][INQ_REPLY_LEN];
+ struct sg_io_v4 io_hdr[16];
+ struct sg_io_v4 rio_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[16][SENSE_BUFFER_LEN] SG_C_CPP_ZERO_INIT;
+ int q_at_tail = 0;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-t", argv[k], 2))
+ ++q_at_tail;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'bsg_queue_tst [-t] <bsg_device>'\n"
+ "where:\n -t queue_at_tail (def: q_at_head)\n");
+ return 1;
+ }
+
+ /* An access mode of O_RDWR is required for write()/read() interface */
+ if ((bsg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "bsg_queue_tst: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+
+ for (k = 0; k < 16; ++k) {
+ /* Prepare INQUIRY command */
+ memset(&io_hdr[k], 0, sizeof(struct sg_io_v4));
+ io_hdr[k].guard = 'Q';
+ /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */
+ if (0 == (k % 3)) {
+ io_hdr[k].request_len = sizeof(sdiag_cdb);
+ io_hdr[k].request = (uint64_t)(long)sdiag_cdb;
+ } else {
+ io_hdr[k].request_len = sizeof(inq_cdb);
+ io_hdr[k].request = (uint64_t)(long)inq_cdb;
+ io_hdr[k].din_xfer_len = INQ_REPLY_LEN;
+ io_hdr[k].din_xferp = (uint64_t)(long)inqBuff[k];
+ }
+ io_hdr[k].response = (uint64_t)(long)sense_buffer[k];
+ io_hdr[k].max_response_len = SENSE_BUFFER_LEN;
+ io_hdr[k].timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr[k].usr_ptr = k;
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ io_hdr[k].flags |= BSG_FLAG_Q_AT_TAIL;
+ else
+ io_hdr[k].flags |= BSG_FLAG_Q_AT_HEAD;
+
+ if (write(bsg_fd, &io_hdr[k], sizeof(struct sg_io_v4)) < 0) {
+ perror("bsg_queue_tst: bsg write error");
+ close(bsg_fd);
+ return 1;
+ }
+ }
+ /* sleep(3); */
+ for (k = 0; k < 16; ++k) {
+ memset(&rio_hdr, 0, sizeof(struct sg_io_v4));
+ rio_hdr.guard = 'Q';
+ if (read(bsg_fd, &rio_hdr, sizeof(struct sg_io_v4)) < 0) {
+ perror("bsg_queue_tst: bsg read error");
+ close(bsg_fd);
+ return 1;
+ }
+ /* now for the error processing */
+ ok = 0;
+ if (0 == rio_hdr.device_status)
+ ok = 1;
+ else {
+ switch (sg_err_category_sense((uint8_t *)(long)
+ rio_hdr.response, rio_hdr.response_len)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ fprintf(stderr, "command error:\n");
+ sg_print_sense(NULL, (uint8_t *)(long)rio_hdr.response,
+ rio_hdr.response_len, 1);
+ break;
+ }
+ }
+
+ if (ok) { /* output result if it is available */
+ /* if (0 == rio_hdr.pack_id) */
+ if (0 == (rio_hdr.usr_ptr % 3))
+ printf("SEND DIAGNOSTIC %d duration=%u\n",
+ (int)rio_hdr.usr_ptr, rio_hdr.duration);
+ else
+ printf("INQUIRY %d duration=%u\n", (int)rio_hdr.usr_ptr,
+ rio_hdr.duration);
+ }
+ }
+
+ close(bsg_fd);
+ return 0;
+}
diff --git a/testing/sg_chk_asc.c b/testing/sg_chk_asc.c
new file mode 100644
index 00000000..1f5f7edb
--- /dev/null
+++ b/testing/sg_chk_asc.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2006-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "sg_lib.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ * This program takes a asc_ascq.txt file from www.t10.org and
+ * checks it against the additional sense codes held in the
+ * sg_lib.c file.
+ * The online version of the asc_ascq codes can be found at:
+ * https://www.t10.org/lists/asc-num.txt
+ */
+
+static const char * version_str = "1.09 20210226";
+
+
+#define MAX_LINE_LEN 1024
+
+
+static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"verbose", 0, 0, 'v'},
+ {"version", 0, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void usage()
+{
+ fprintf(stderr, "Usage: "
+ "sg_chk_asc [--help] [--offset=POS] [--verbose] [--version]\n"
+ " <asc_ascq_file>\n"
+ " where:\n"
+ " --help|-h print out usage message\n"
+ " --offset=POS|-o POS line position in file where "
+ "text starts\n"
+ " origin 0 (def: 24 (was 25))\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Checks asc/ascq codes in <asc_ascq_file> against the sg3_utils "
+ "library.\nThe additional sense codes (asc_ascq) can be found "
+ "at\nwww.t10.org/lists/asc-num.txt .\n"
+ );
+
+}
+
+int main(int argc, char * argv[])
+{
+ int k, j, res, c, num, len;
+ unsigned int asc, ascq;
+ FILE * fp;
+ int offset = 24;
+ int verbose = 0;
+ char file_name[256];
+ char line[MAX_LINE_LEN];
+ char b[MAX_LINE_LEN];
+ char bb[MAX_LINE_LEN];
+ char * cp;
+ int ret = 1;
+
+ memset(file_name, 0, sizeof file_name);
+ memset(line, 0, sizeof file_name);
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "ho:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'o':
+ offset = sg_get_num(optarg);
+ if (offset < 0) {
+ fprintf(stderr, "bad argument to --offset\n");
+ return 1;
+ }
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ fprintf(stderr, "version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised switch code 0x%x ??\n", c);
+ usage();
+ return 1;
+ }
+ }
+ if (optind < argc) {
+ if ('\0' == file_name[0]) {
+ strncpy(file_name, argv[optind], sizeof(file_name) - 1);
+ file_name[sizeof(file_name) - 1] = '\0';
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return 1;
+ }
+ }
+
+ if (0 == file_name[0]) {
+ fprintf(stderr, "missing file name!\n");
+ usage();
+ return 1;
+ }
+ fp = fopen(file_name, "r");
+ if (NULL == fp) {
+ fprintf(stderr, "open error: %s: %s\n", file_name,
+ safe_strerror(errno));
+ return 1;
+ }
+ for (k = 0; (cp = fgets(line, sizeof(line) - 1, fp)); ++k) {
+ len = strlen(line);
+ if (len < 1)
+ continue;
+ if (! isdigit(line[0]))
+ continue;
+ num = sscanf(line, "%xh/%xh", &asc, &ascq);
+ if (1 == num)
+ ascq = 999;
+ if (num < 1) {
+ if (verbose)
+ fprintf(stderr, "Badly formed line number %d (num=%d)\n",
+ k + 1, num);
+ continue;
+ }
+ if (len < 26)
+ continue;
+#if 0
+strncpy(b , line, sizeof(b) - 1);
+b[sizeof(b) - 1] = '\0';
+num = strlen(b);
+if (0xd == b[num - 2]) {
+ b[num - 2] = '\0';
+ b[num - 1] = '\0';
+}
+printf("\"%s\",\n", b);
+#endif
+ strncpy(b , line + offset, sizeof(b) - 1);
+ b[sizeof(b) - 1] = '\0';
+ num = strlen(b);
+ if (0xd == b[num - 2])
+ b[num - 2] = '\0';
+ b[num - 1] = '\0';
+ num = strlen(b);
+ for (j = 0; j < num; ++j)
+ b[j] = toupper(b[j]);
+
+ bb[0] = '\0';
+ if (ascq < 999) {
+ cp = sg_get_asc_ascq_str(asc, ascq, sizeof(bb) - 1, bb);
+ if (NULL == cp) {
+ fprintf(stderr, "no entry for %x,%x : %s\n", asc, ascq, b);
+ continue;
+ }
+ num = strlen(cp);
+// fprintf(stderr, "file: asc=%x acsq=%x strlen=%d %s\n", asc, ascq, num,
+// cp);
+// if (num < 20)
+// continue;
+ if ((num > 6) &&
+ ((0 == memcmp("ASC", cp, 3)) ||
+ (0 == memcmp("vendor", cp, 6)))) {
+ fprintf(stderr, "%x,%x differ, ref: %s, sg_lib_data: "
+ "<missing>\n", asc, ascq, b);
+ continue;
+ }
+ if (num > 20) {
+ cp += 18;
+ num -= 18;
+ for (j = 0; j < num; ++j)
+ cp[j] = toupper(cp[j]);
+ }
+ if (0 != strcmp(b, cp))
+ fprintf(stderr, "%x,%x differ, ref: %s, sg_lib_data: "
+ "%s\n", asc, ascq, b, cp);
+ }
+ }
+ if (NULL == cp) {
+ if (feof(fp)) {
+ if (verbose > 2)
+ fprintf(stderr, "EOF detected\n");
+ } else
+ fprintf(stderr, "fgets: %s\n", safe_strerror(errno));
+ } else
+ fprintf(stderr, "%s\n", line);
+
+ res = fclose(fp);
+ if (EOF == res) {
+ fprintf(stderr, "close error: %s\n", safe_strerror(errno));
+ return 1;
+ }
+ return ret;
+}
diff --git a/testing/sg_iovec_tst.cpp b/testing/sg_iovec_tst.cpp
new file mode 100644
index 00000000..7cc18935
--- /dev/null
+++ b/testing/sg_iovec_tst.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2003-2021 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
+ * device driver.
+ * This C++ program will read a certain number of blocks of a given block
+ * size from a given sg device node using struct sg_iovec and write what is
+ * retrieved out to a normal file. The purpose is to test the sg_iovec
+ * mechanism within the sg_io_hdr and sg_io_v4 structures.
+ *
+ * struct sg_iovec and struct iovec [in include/uapi/uio.h] are basically
+ * the same thing: a pointer followed by a length (of type size_t). If
+ * applied to a disk then the pointer will hold a LBA and 'length' will
+ * be a number of logical blocks (which usually cannot exceed 2**32-1 .
+ *
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <poll.h>
+#include <limits.h>
+#include <time.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <linux/bsg.h>
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+
+// C++ local header
+#include "sg_scat_gath.h"
+
+static const char * version_str = "1.08 20210214";
+
+#define ME "sg_iovec_tst: "
+
+#define IOVEC_ELEMS 1024 /* match current UIO_MAXIOV in <linux/uio.h> */
+
+#define DEF_BLK_SZ 512
+#define SENSE_BUFF_LEN 32
+#define DEF_TIMEOUT 40000 /* 40,000 milliseconds */
+
+static struct sg_iovec iovec[IOVEC_ELEMS];
+
+static int verbose;
+
+static struct option long_options[] = {
+ {"async", no_argument, 0, 'a'},
+ {"bs", required_argument, 0, 'b'},
+ {"elem_size", required_argument, 0, 'e'},
+ {"elem-size", required_argument, 0, 'e'},
+ {"elemsz", required_argument, 0, 'e'},
+ {"fill", required_argument, 0, 'f'},
+ {"from_skip", no_argument, 0, 'F'},
+ {"from-skip", no_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"num", required_argument, 0, 'n'},
+ {"num_blks", required_argument, 0, 'n'},
+ {"num-blks", required_argument, 0, 'n'},
+ {"sgl", required_argument, 0, 'S'},
+ {"sgv4", no_argument, 0, '4'},
+ {"skip", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage(void)
+{
+ printf("Usage: sg_iovec_tst [--async] [--bs=BS] [--elem_sz=ES] "
+ "[--fill=F_ELEMS]\n"
+ " [from_skip] [--help] --num=NUM [--sgl=SFN] "
+ "[--sgv4]\n"
+ " [--skip=SKIP] [--verbose] [--version] "
+ "SG_DEV OUT_F\n");
+ printf("where:\n"
+ " --async|-a async sg usage (def: use ioctl(SG_IO) )\n");
+ printf(" --bs=BS|-b BS logical block size of SG_DEV (def: 512 "
+ "bytes)\n");
+ printf(" --elem_sz=ES|-e ES iovec element size (def: BS bytes)\n");
+ printf(" --fill=F_ELEMS|-f F_ELEMS append F_ELEMS*ES zero bytes "
+ "onto OUT_F\n"
+ " after each iovec element (def: "
+ "0)\n");
+ printf(" --from_skip|-F sgl output starts from SKIP (def: 0)\n");
+ printf(" --help|-h this usage message\n");
+ printf(" --num=NUM|-n NUM number of blocks to read from SG_DEV\n");
+ printf(" --sgl=SFN|-S SFN Sgl FileName (SFN) that is written to, "
+ "with\n"
+ " addresses and lengths having ES as "
+ "their unit\n");
+ printf(" --sgv4|-4 use the sg v4 interface (def: v3 "
+ "interface)\n");
+ printf(" --skip=SKIP|-s SKIP SKIP blocks before reading S_DEV "
+ "(def: 0)\n");
+ printf(" --verbose|-v increase verbosity\n");
+ printf(" --version|-V print version and exit\n\n");
+ printf("Reads from SG_DEV and writes that data to OUT_F in binary. Uses "
+ "iovec\n(a scatter gather list) in linear mode (i.e. it cuts up "
+ "a contiguous\nbuffer). Example:\n"
+ " sg_iovec_tst -n 8k -e 4k /dev/sg3 out.bin\n");
+}
+
+/* Returns 0 if everything ok */
+static int
+sg_read(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
+ int elem_size, int async)
+{
+ uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+ struct pollfd a_poll;
+ int dxfer_len = bs * num_blocks;
+ int k, pos, rem;
+
+ sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
+ sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
+
+ for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
+ iovec[k].iov_base = buff + pos;
+ iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
+ if (rem <= elem_size)
+ break;
+ pos += elem_size;
+ rem -= elem_size;
+ }
+ if (k >= IOVEC_ELEMS) {
+ fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
+ "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
+ dxfer_len / elem_size);
+ fprintf(stderr, "Try expanding elem_size which is currently %d "
+ "bytes\n", elem_size);
+ return -1;
+ }
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rdCmd);
+ io_hdr.cmdp = rdCmd;
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = dxfer_len;
+ io_hdr.iovec_count = k + 1;
+ io_hdr.dxferp = iovec;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.pack_id = from_block;
+ if (verbose) {
+ char b[128];
+
+ fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
+ sizeof(b), b));
+ }
+
+ if (async) {
+ int res = write(sg_fd, &io_hdr, sizeof(io_hdr));
+
+ if (res < 0) {
+ perror("write(<sg_device>), error");
+ return -1;
+ } else if (res < (int)sizeof(io_hdr)) {
+ fprintf(stderr, "write(<sg_device>) returned %d, expected %d\n",
+ res, (int)sizeof(io_hdr));
+ return -1;
+ }
+ a_poll.fd = sg_fd;
+ a_poll.events = POLLIN;
+ a_poll.revents = 0;
+ res = poll(&a_poll, 1, 2000 /* millisecs */ );
+ if (res < 0) {
+ perror("poll error on <sg_device>");
+ return -1;
+ }
+ if (0 == (POLLIN & a_poll.revents)) {
+ fprintf(stderr, "strange, poll() completed without data to "
+ "read\n");
+ return -1;
+ }
+ res = read(sg_fd, &io_hdr, sizeof(io_hdr));
+ if (res < 0) {
+ perror("read(<sg_device>), error");
+ return -1;
+ } else if (res < (int)sizeof(io_hdr)) {
+ fprintf(stderr, "read(<sg_device>) returned %d, expected %d\n",
+ res, (int)sizeof(io_hdr));
+ return -1;
+ }
+ } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
+ perror("reading (SG_IO) on sg device, error");
+ return -1;
+ }
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
+ from_block, num_blocks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ fprintf(stderr, "Unit attention\n");
+ return -1;
+ default:
+ sg_chk_n_print3("reading", &io_hdr, 1);
+ return -1;
+ }
+ return 0;
+}
+
+/* Returns 0 if everything ok */
+static int
+sg_read_v4(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
+ int elem_size, int async)
+{
+ uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_v4 io_hdr;
+ struct pollfd a_poll;
+ int dxfer_len = bs * num_blocks;
+ int k, pos, rem, res;
+
+ sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
+ sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
+
+ for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
+ iovec[k].iov_base = buff + pos;
+ iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
+ if (rem <= elem_size)
+ break;
+ pos += elem_size;
+ rem -= elem_size;
+ }
+ if (k >= IOVEC_ELEMS) {
+ fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
+ "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
+ dxfer_len / elem_size);
+ fprintf(stderr, "Try expanding elem_size which is currently %d "
+ "bytes\n", elem_size);
+ return -1;
+ }
+ memset(&io_hdr, 0, sizeof(struct sg_io_v4));
+ io_hdr.guard = 'Q';
+ io_hdr.request_len = sizeof(rdCmd);
+ io_hdr.request = (uint64_t)(uintptr_t)rdCmd;
+ io_hdr.din_xfer_len = dxfer_len;
+ io_hdr.din_xferp = (uint64_t)(uintptr_t)iovec;
+ io_hdr.din_iovec_count = k + 1;
+ io_hdr.max_response_len = SG_DXFER_FROM_DEV;
+ io_hdr.response = (uint64_t)(uintptr_t)senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.request_extra = from_block; /* pack_id */
+ if (verbose) {
+ char b[128];
+
+ fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
+ sizeof(b), b));
+ }
+ if (async) {
+ res = ioctl(sg_fd, SG_IOSUBMIT, &io_hdr);
+ if (res < 0) {
+ perror("ioctl(SG_IOSUBMIT <sg_device>), error");
+ return -1;
+ }
+ a_poll.fd = sg_fd;
+ a_poll.events = POLLIN;
+ a_poll.revents = 0;
+ res = poll(&a_poll, 1, 2000 /* millisecs */ );
+ if (res < 0) {
+ perror("poll error on <sg_device>");
+ return -1;
+ }
+ if (0 == (POLLIN & a_poll.revents)) {
+ fprintf(stderr, "strange, poll() completed without data to "
+ "read\n");
+ return -1;
+ }
+ res = ioctl(sg_fd, SG_IORECEIVE, &io_hdr);
+ if (res < 0) {
+ perror("ioctl(SG_IORECEIVE <sg_device>), error");
+ return -1;
+ }
+ } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
+ perror("ioctl(SG_IO) on sg device, error");
+ return -1;
+ }
+
+ res = sg_err_category_new(io_hdr.device_status, io_hdr.transport_status,
+ io_hdr.driver_status,
+ (const uint8_t *)(unsigned long)io_hdr.response,
+ io_hdr.response_len);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
+ from_block, num_blocks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ fprintf(stderr, "Unit attention\n");
+ return -1;
+ default:
+ sg_linux_sense_print("reading", io_hdr.device_status,
+ io_hdr.transport_status, io_hdr.driver_status,
+ senseBuff, io_hdr.response_len, true);
+ return -1;
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_sgv4 = false;
+ bool do_async = false;
+ bool do_help = false;
+ bool from_skip = false;
+ bool blk_size_given = false;
+ bool elem_size_given = false;
+ int sg_fd, fd, c, res, res2, err, dxfer_len;
+ unsigned int k;
+ int blk_size = DEF_BLK_SZ;
+ int elem_size = blk_size;
+ int num_blks = 0;
+ int f_elems = 0;
+ int64_t start_blk = 0;
+ char * sg_dev_name = 0;
+ char * out_file_name = 0;
+ char * sgl_fn = 0;
+ uint8_t * buffp;
+ uint8_t * fillp = NULL;
+ FILE * fp = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "4ab:e:f:Fhn:s:S:vV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '4':
+ do_sgv4 = true;
+ break;
+ case 'a':
+ do_async = true;
+ break;
+ case 'b':
+ blk_size = sg_get_num(optarg);
+ if (blk_size < 1) {
+ printf("Couldn't decode positive number after '--bs=' "
+ "option\n");
+ sg_dev_name = 0;
+ } else
+ blk_size_given = true;
+ break;
+ case 'e':
+ elem_size = sg_get_num(optarg);
+ if (elem_size < 1) {
+ printf("Couldn't decode positive number after '--elem_size=' "
+ "option\n");
+ sg_dev_name = 0;
+ } else
+ elem_size_given = true;
+ break;
+ case 'f':
+ f_elems = sg_get_num(optarg);
+ if (f_elems < 0) {
+ printf("Couldn't decode number after '--fill=' option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 'F':
+ from_skip = true;
+ break;
+ case 'h':
+ do_help = true;
+ break;
+ case 'n':
+ num_blks = sg_get_num(optarg);
+ if (num_blks < 1) {
+ printf("Couldn't decode positive number after '--num=' "
+ "option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 's':
+ start_blk = sg_get_llnum(optarg);
+ if ((start_blk < 0) || (start_blk > INT_MAX)) {
+ printf("Couldn't decode number after '--skip=' option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 'S':
+ if (sgl_fn) {
+ printf("Looks like --sgl=SFN has been given twice\n");
+ sg_dev_name = 0;
+ } else
+ sgl_fn = optarg;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ printf("Version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == sg_dev_name) {
+ sg_dev_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ if (sg_dev_name) {
+ out_file_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 (do_help) {
+ usage();
+ return 0;
+ }
+ if (NULL == sg_dev_name) {
+ printf(">>> need sg node name (e.g. /dev/sg3)\n\n");
+ usage();
+ return 1;
+ }
+ if (NULL == out_file_name) {
+ printf(">>> need out filename (to place what is fetched by READ\n\n");
+ usage();
+ return 1;
+ }
+ if (0 == num_blks) {
+ printf(">>> need number of blocks to READ\n\n");
+ usage();
+ return 1;
+ }
+
+ if ((! elem_size_given) && blk_size_given)
+ elem_size = blk_size;
+
+ if (do_async)
+ sg_fd = open(sg_dev_name, O_RDWR);
+ else
+ sg_fd = open(sg_dev_name, O_RDONLY);
+ if (sg_fd < 0) {
+ perror(ME "sg device node open error");
+ return 1;
+ }
+ /* Don't worry, being very careful not to write to a none-sg file ... */
+ res = ioctl(sg_fd, SG_GET_VERSION_NUM, &k);
+ if ((res < 0) || (k < 30000)) {
+ printf(ME "not a sg device, or driver prior to 3.x\n");
+ return 1;
+ }
+ fd = open(out_file_name, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0) {
+ perror(ME "output file open error");
+ return 1;
+ }
+ if (f_elems > 0) {
+ fillp = (uint8_t *)calloc(f_elems, elem_size);
+ if (NULL == fillp) {
+ fprintf(stderr, "fill calloc for %d bytes failed\n",
+ f_elems * elem_size);
+ goto fini;
+ }
+ }
+ if (sgl_fn) {
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+ char s[128];
+
+ fp = fopen(sgl_fn, "w");
+ if (NULL == fp) {
+ err = errno;
+ fprintf(stderr, "Unable to open %s, error: %s\n", sgl_fn,
+ strerror(err));
+ res = sg_convert_errno(err);
+ goto fini;
+ }
+ strftime(s, sizeof(s), "%c", tm);
+ fprintf(fp, "# Scatter gather list generated by sg_iovec_tst "
+ "%s\n#\n", s);
+ }
+
+ dxfer_len = num_blks * blk_size;
+ buffp = (uint8_t *)calloc(num_blks, blk_size);
+ if (buffp) {
+ int dx_len;
+ int64_t curr_blk = from_skip ? start_blk : 0;
+
+ if (do_sgv4) {
+ if (sg_read(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
+ elem_size, do_async))
+ goto free_buff;
+ } else {
+ if (sg_read_v4(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
+ elem_size, do_async))
+ goto free_buff;
+ }
+ if (f_elems > 0) {
+ int fill_len = f_elems * elem_size;
+
+ for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size) {
+ if (write(fd, buffp + dx_len, elem_size) < 0) {
+ perror(ME "partial dxfer output write failed");
+ break;
+ }
+ if (sgl_fn) {
+ fprintf(fp, "%" PRId64 ",1\n", curr_blk);
+ curr_blk += f_elems + 1;
+ }
+ if (write(fd, fillp, fill_len) < 0) {
+ perror(ME "partial fill output write failed");
+ break;
+ }
+ }
+ } else if (write(fd, buffp, dxfer_len) < 0)
+ perror(ME "full output write failed");
+ else if (sgl_fn) {
+ for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size)
+ fprintf(fp, "%" PRId64 ",1\n", curr_blk++);
+ }
+free_buff:
+ free(buffp);
+ } else
+ fprintf(stderr, "user space calloc for %d bytes failed\n",
+ dxfer_len);
+ res = close(fd);
+ if (res < 0) {
+ perror(ME "output file close error");
+ close(sg_fd);
+ return 1;
+ }
+fini:
+ res2 = close(sg_fd);
+ if (res2 < 0) {
+ err = errno;
+ perror(ME "sg device close error");
+ if (0 == res)
+ res = sg_convert_errno(err);
+ }
+ if (fp)
+ fclose(fp);
+ return res;
+}
diff --git a/testing/sg_json_builder_test.c b/testing/sg_json_builder_test.c
new file mode 100644
index 00000000..a43b9f78
--- /dev/null
+++ b/testing/sg_json_builder_test.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
+/*
+ * Simple streaming JSON writer
+ *
+ * This takes care of the annoying bits of JSON syntax like the commas
+ * after elements
+ *
+ * Authors: Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Borrowed from Linux kernel [5.17.0]: tools/bpf/bpftool/json_writer.[hc]
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <malloc.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "../lib/sg_json_builder.h"
+
+int main(int argc, char **argv)
+{
+ json_writer_t *wr = jsonw_new(stdout);
+
+ jsonw_start_object(wr);
+ jsonw_pretty(wr, true);
+ jsonw_name(wr, "Vyatta");
+ jsonw_start_object(wr);
+ jsonw_string_field(wr, "url", "http://vyatta.com");
+ jsonw_uint_field(wr, "downloads", 2000000ul);
+ jsonw_float_field(wr, "stock", 8.16);
+
+ jsonw_name(wr, "ARGV");
+ jsonw_start_array(wr);
+ while (--argc)
+ jsonw_string(wr, *++argv);
+ jsonw_end_array(wr);
+
+ jsonw_name(wr, "empty");
+ jsonw_start_array(wr);
+ jsonw_end_array(wr);
+
+ jsonw_name(wr, "NIL");
+ jsonw_start_object(wr);
+ jsonw_end_object(wr);
+
+ jsonw_null_field(wr, "my_null");
+
+ jsonw_name(wr, "special chars");
+ jsonw_start_array(wr);
+ jsonw_string_field(wr, "slash", "/");
+ jsonw_string_field(wr, "newline", "\n");
+ jsonw_string_field(wr, "tab", "\t");
+ jsonw_string_field(wr, "ff", "\f");
+ jsonw_string_field(wr, "quote", "\"");
+ jsonw_string_field(wr, "tick", "\'");
+ jsonw_string_field(wr, "backslash", "\\");
+ jsonw_end_array(wr);
+
+jsonw_name(wr, "ARGV");
+jsonw_start_array(wr);
+jsonw_string(wr, "boo: appended or new entry?");
+jsonw_end_array(wr);
+
+ jsonw_end_object(wr);
+
+ jsonw_end_object(wr);
+ jsonw_destroy(&wr);
+ return 0;
+}
+
diff --git a/testing/sg_mrq_dd.cpp b/testing/sg_mrq_dd.cpp
new file mode 100644
index 00000000..01b38e79
--- /dev/null
+++ b/testing/sg_mrq_dd.cpp
@@ -0,0 +1,4664 @@
+/*
+ * A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 2018-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a specialisation of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device.
+ * A logical block size ('bs') is assumed to be 512 if not given. This
+ * program complains if 'ibs' or 'obs' are given with some other value
+ * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If
+ * 'of' is not given or 'of=-' then stdout assumed.
+ *
+ * A non-standard argument "bpt" (blocks per transfer) is added to control
+ * the maximum number of blocks in each transfer. The default value is 128.
+ * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16 KiB
+ * in this case) are transferred to or from the sg device in a single SCSI
+ * command.
+ *
+ * This version is designed for the linux kernel 4 and 5 series.
+ *
+ * sg_mrq_dd uses C++ threads and MRQ (multiple requests (in one invocation))
+ * facilities in the sg version 4 driver to do "dd" type copies and verifies.
+ *
+ */
+
+static const char * version_str = "1.44 20221020";
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h> /* for nanosleep() */
+#include <poll.h>
+#include <limits.h>
+// #include <pthread.h>
+#include <signal.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <sys/time.h>
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+#include <sys/mman.h> /* for mmap() system call */
+
+#include <vector>
+#include <array>
+#include <atomic> // C++ header replacing <stdatomic.h>
+#include <random>
+#include <thread> // needed for std::this_thread::yield()
+#include <mutex>
+#include <condition_variable> // for infant_cv: copy/verify first segment
+ // single threaded
+#include <chrono>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h> /* for getrandom() system call */
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+// C++ local header
+#include "sg_scat_gath.h"
+
+// C headers associated with sg3_utils library
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+using namespace std;
+
+// #ifdef __GNUC__
+// #ifndef __clang__
+// #pragma GCC diagnostic ignored "-Wclobbered"
+// #endif
+// #endif
+
+
+#ifndef SGV4_FLAG_POLLED
+#define SGV4_FLAG_POLLED 0x800
+#endif
+
+#define MAX_SGL_NUM_VAL (INT32_MAX - 1) /* should reduce for testing */
+// #define MAX_SGL_NUM_VAL 7 /* should reduce for testing */
+#if MAX_SGL_NUM_VAL > INT32_MAX
+#error "MAX_SGL_NUM_VAL cannot exceed 2^31 - 1"
+#endif
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SDT_ICT_MS 300
+#define DEF_SDT_CRT_SEC 3
+#define DEF_SCSI_CDB_SZ 10
+#define MAX_SCSI_CDB_SZ 16 /* could be 32 */
+#define PACK_ID_TID_MULTIPLIER (0x1000000) /* 16,777,216 */
+#define MAX_SLICES 16 /* number of IFILE,OFILE pairs */
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#define SGP_READ10 0x28
+#define SGP_PRE_FETCH10 0x34
+#define SGP_PRE_FETCH16 0x90
+#define SGP_VERIFY10 0x2f
+#define SGP_WRITE10 0x2a
+#define DEF_NUM_THREADS 4
+#define MAX_NUM_THREADS 1024 /* was SG_MAX_QUEUE with v3 driver */
+#define DEF_MRQ_NUM 16
+
+#define FT_UNKNOWN 0 /* yet to be checked */
+#define FT_OTHER 1 /* filetype other than one of the following */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_DEV_NULL 4 /* either /dev/null, /dev/zero, or "." */
+#define FT_ST 8 /* filetype is st char device (tape) */
+#define FT_BLOCK 16 /* filetype is a block device */
+#define FT_FIFO 32 /* fifo (named or unnamed pipe (stdout)) */
+#define FT_CHAR 64 /* fifo (named or unnamed pipe (stdout)) */
+#define FT_RANDOM_0_FF 128 /* iflag=00, iflag=ff and iflag=random
+ override if=IFILE */
+#define FT_ERROR 256 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+#define DEV_ZERO_MINOR_NUM 5
+
+#define EBUFF_SZ 768
+
+#define PROC_SCSI_SG_VERSION "/proc/scsi/sg/version"
+#define SYS_SCSI_SG_VERSION "/sys/module/sg/version"
+
+
+struct flags_t {
+ bool append;
+ bool coe;
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool ff;
+ bool fua;
+ bool masync; /* more async sg v4 driver fd flag */
+ bool mout_if; /* META_OUT_IF flag at mrq level */
+ bool nocreat;
+ bool no_dur;
+ bool no_thresh;
+ bool no_waitq; /* dummy, no longer supported, just warn */
+ bool order_wr;
+ bool polled; /* was previously 'hipri' */
+ bool qhead;
+ bool qtail;
+ bool random;
+ bool serial;
+ bool same_fds;
+ bool wq_excl;
+ bool zero;
+ int cdl; /* command duration limits, 0 --> no cdl */
+ int mmap;
+};
+
+typedef pair<int64_t, int> get_next_res_t; /* LBA, num */
+typedef array<uint8_t, MAX_SCSI_CDB_SZ> cdb_arr_t;
+
+struct cp_ver_pair_t {
+ cp_ver_pair_t() {}
+
+ get_next_res_t get_next(int desired_num_blks);
+
+ enum class my_state {empty,
+ init,
+ underway,
+ ignore,
+ finished} state = {my_state::empty};
+
+ int my_index = 0;
+ int in_fd = -1;
+ int in_type = FT_UNKNOWN;
+ int out_fd = -1;
+ int out_type = FT_UNKNOWN;
+
+ int64_t dd_count = 0;
+ atomic<int64_t> next_count_pos {};
+ atomic<int64_t> in_rem_count {};
+ atomic<int64_t> out_rem_count {};
+ atomic<int> in_partial {};
+ atomic<int> out_partial {};
+ atomic<int> sum_of_resids {};
+};
+
+typedef array<cp_ver_pair_t, MAX_SLICES> cp_ver_arr_t;
+
+/* There is one instance of this structure and it is at file scope so it is
+ * initialized to zero. The design of this copy multi-threaded copy algorithm
+ * attempts to have no locks on the fast path. Contention in gcoll.get_next()
+ * is resolved by the loser repeating its operation. Statistics and error
+ * information is held in each thread until it shuts down and contention
+ * can occur at that point. */
+struct global_collection /* one instance visible to all threads */
+{
+ cp_ver_arr_t cp_ver_arr;
+
+ /* get_next() is the pivotal function for multi-threaded safety. It can
+ * be safely called from all threads with the desired number of blocks
+ * (typically mrq*bpt) and this function returns a pair. The first pair
+ * value is the starting count value/index [0..dd_count) and the second
+ * pair value is the number of blocks to copy. If desired_num_blks is
+ * negative this flags an error has occurred. If the second value in the
+ * returned pair is 0 then the calling thread should shutdown; a
+ * negative value indicates an error has occurred (e.g. in another
+ * thread) and the calling thread should shutdown. */
+
+ int in0fd;
+ int64_t dd_count;
+ int in_type; /* expect all IFILEs to have same type */
+ int cdbsz_in;
+ int help;
+ struct flags_t in_flags;
+ atomic<int> in_partial; /* | */
+ off_t in_st_size; /* Only for FT_OTHER (regular) file */
+ int mrq_num; /* if user gives 0, set this to 1 */
+ int out0fd;
+ int out_type;
+ int cdbsz_out;
+ struct flags_t out_flags;
+ atomic<int> out_partial; /* | */
+ off_t out_st_size; /* Only for FT_OTHER (regular) file */
+ condition_variable infant_cv; /* after thread:0 does first segment */
+ mutex infant_mut;
+ int bs;
+ int bpt;
+ int cmd_timeout; /* in milliseconds */
+ int elem_sz;
+ int outregfd;
+ int outreg_type;
+ off_t outreg_st_size;
+ atomic<int> dio_incomplete_count;
+ atomic<int> sum_of_resids;
+ atomic<int> reason_res;
+ atomic<int> most_recent_pack_id;
+ uint32_t sdt_ict; /* stall detection; initial check time (milliseconds) */
+ uint32_t sdt_crt; /* check repetition time (seconds), after first stall */
+ int dry_run;
+ int verbose;
+ bool mrq_eq_0; /* true when user gives mrq=0 */
+ bool processed;
+ bool cdbsz_given;
+ bool cdl_given;
+ bool count_given;
+ bool ese;
+ bool flexible;
+ bool mrq_polled;
+ bool ofile_given;
+ bool unit_nanosec; /* default duration unit is millisecond */
+ bool verify; /* don't copy, verify like Unix: cmp */
+ bool prefetch; /* for verify: do PF(b),RD(a),V(b)_a_data */
+ vector<string> inf_v;
+ vector<string> outf_v;
+ const char * infp;
+ const char * outfp;
+ class scat_gath_list i_sgl;
+ class scat_gath_list o_sgl;
+};
+
+typedef struct request_element
+{ /* one instance per worker thread */
+ struct global_collection *clp;
+ bool has_share;
+ bool both_sg;
+ bool same_sg;
+ bool only_in_sg;
+ bool only_out_sg;
+ bool stop_after_write;
+ bool stop_now;
+ int id;
+ int bs;
+ int infd;
+ int outfd;
+ int outregfd;
+ uint8_t * buffp;
+ uint8_t * alloc_bp;
+ struct sg_io_v4 io_hdr4[2];
+ uint8_t cmd[MAX_SCSI_CDB_SZ];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int dio_incomplete_count;
+ int mmap_active;
+ int rd_p_id;
+ int rep_count;
+ int rq_id;
+ int mmap_len;
+ int mrq_id;
+ int mrq_index;
+ int mrq_pack_id_off;
+ uint32_t a_mrq_din_blks;
+ uint32_t a_mrq_dout_blks;
+ int64_t in_follow_on;
+ int64_t out_follow_on;
+ int64_t in_local_count;
+ int64_t out_local_count;
+ int64_t in_rem_count;
+ int64_t out_rem_count;
+ int in_local_partial;
+ int out_local_partial;
+ int in_resid_bytes;
+ long seed;
+#ifdef HAVE_SRAND48_R /* gcc extension. N.B. non-reentrant version slower */
+ struct drand48_data drand;/* opaque, used by srand48_r and mrand48_r */
+#endif
+} Rq_elem;
+
+/* Additional parameters for sg_start_io() and sg_finish_io() */
+struct sg_io_extra {
+ bool prefetch;
+ bool dout_is_split;
+ int hpv4_ind;
+ int blk_offset;
+ int blks;
+};
+
+#define MONO_MRQ_ID_INIT 0x10000
+
+
+
+/* Use this class to wrap C++11 <random> features to produce uniform random
+ * unsigned ints in the range [lo, hi] (inclusive) given a_seed */
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed)
+ : uid(lo, hi), dre(a_seed) { }
+ /* uid ctor takes inclusive range when integral type */
+
+ unsigned int get() { return uid(dre); }
+
+private:
+ uniform_int_distribution<unsigned int> uid;
+ default_random_engine dre;
+};
+
+static atomic<int> num_ebusy(0);
+static atomic<int> num_start_eagain(0);
+static atomic<int> num_fin_eagain(0);
+static atomic<int> num_miscompare(0);
+static atomic<int> num_fallthru_sigusr2(0);
+static atomic<bool> vb_first_time(true);
+
+static sigset_t signal_set;
+static sigset_t orig_signal_set;
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+static int do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4);
+static int do_both_sg_segment_mrq0(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks);
+static int do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4);
+static int do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks);
+
+#define STRERR_BUFF_LEN 128
+
+static mutex strerr_mut;
+
+static bool have_sg_version = false;
+static int sg_version = 0;
+static bool sg_version_ge_40045 = false;
+static atomic<bool> shutting_down{false};
+static bool do_sync = false;
+static int do_time = 1;
+static struct global_collection gcoll;
+static struct timeval start_tm;
+static int num_threads = DEF_NUM_THREADS;
+static bool after1 = false;
+static int listen_t_tid;
+
+static const char * my_name = "sg_mrq_dd: ";
+
+// static const char * mrq_blk_s = "mrq: ordinary blocking";
+static const char * mrq_svb_s = "mrq: shared variable blocking (svb)";
+static const char * mrq_ob_s = "mrq: ordered blocking";
+static const char * mrq_vb_s = "mrq: variable blocking";
+
+
+#ifdef __GNUC__
+static int pr2serr_lk(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#else
+static int pr2serr_lk(const char * fmt, ...);
+#endif
+
+
+static int
+pr2serr_lk(const char * fmt, ...)
+{
+ int n;
+ va_list args;
+ lock_guard<mutex> lk(strerr_mut);
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+static void
+usage(int pg_num)
+{
+ if (pg_num > 4)
+ goto page5;
+ if (pg_num > 3)
+ goto page4;
+ else if (pg_num > 2)
+ goto page3;
+ else if (pg_num > 1)
+ goto page2;
+
+ pr2serr("Usage: sg_mrq_dd [bs=BS] [conv=CONV] [count=COUNT] [ibs=BS] "
+ "[if=IFILE*]\n"
+ " [iflag=FLAGS] [obs=BS] [of=OFILE*] "
+ "[oflag=FLAGS]\n"
+ " [seek=SEEK] [skip=SKIP] [--help] [--verify] "
+ "[--version]\n\n");
+ pr2serr(" [bpt=BPT] [cdbsz=6|10|12|16] [cdl=CDL] "
+ "[dio=0|1]\n"
+ " [elemsz_kb=EKB] [ese=0|1] [fua=0|1|2|3] "
+ "[polled=NRQS]\n"
+ " [mrq=NRQS] [ofreg=OFREG] [sdt=SDT] "
+ "[sync=0|1]\n"
+ " [thr=THR] [time=0|1|2[,TO]] [verbose=VERB] "
+ "[--dry-run]\n"
+ " [--pre-fetch] [--verbose] [--version]\n\n"
+ " where: operands have the form name=value and are pecular to "
+ "'dd'\n"
+ " style commands, and options start with one or "
+ "two hyphens;\n"
+ " the main operands and options (shown in first group "
+ "above) are:\n"
+ " bs must be device logical block size (default "
+ "512)\n"
+ " conv comma separated list from: [nocreat,noerror,"
+ "notrunc,\n"
+ " null,sync]\n"
+ " count number of blocks to copy (def: device size)\n"
+ " if file(s) or device(s) to read from (def: "
+ "stdin)\n"
+ " iflag comma separated list from: [00,coe,dio,"
+ "direct,dpo,\n"
+ " dsync,excl,ff,fua,masync,mmap,mout_if,nodur,"
+ "null,\n"
+ " order,qhead,qtail,random,same_fds,serial,"
+ "wq_excl]\n"
+ " of file(s) or device(s) to write to (def: "
+ "/dev/null)\n"
+ " 'of=.' also outputs to /dev/null\n"
+ " oflag comma separated list from: [append,nocreat,\n"
+ " <<list from iflag>>]\n"
+ " seek block position to start writing to OFILE\n"
+ " skip block position to start reading from IFILE\n"
+ " --help|-h output this usage message then exit\n"
+ " --verify|-x do a verify (compare) operation [def: do a "
+ "copy]\n"
+ " --version|-V output version string then exit\n\n"
+ "Copy IFILE to OFILE, similar to dd command. A comma separated "
+ "list of files\n may be given for IFILE*, ditto for OFILE*. "
+ "This utility is specialized for\nSCSI devices and uses the "
+ "'multiple requests' (mrq) in a single invocation\nfacility in "
+ "version 4 of the sg driver unless mrq=0. Usually one or both\n"
+ "IFILE and OFILE will be sg devices. With the --verify option "
+ "it does a\nverify/compare operation instead of a copy. This "
+ "utility is Linux specific.\nUse '-hh', '-hhh', '-hhhh' or "
+ "'-hhhhh' for more information.\n"
+ );
+ return;
+page2:
+ pr2serr("Syntax: sg_mrq_dd [operands] [options]\n\n"
+ " the lesser used operands and option are:\n\n"
+ " bpt is blocks_per_transfer (default is 128)\n"
+ " cdbsz size of SCSI READ, WRITE or VERIFY cdb_s "
+ "(default is 10)\n"
+ " cdl command duration limits value 0 to 7 (def: "
+ "0 (no cdl))\n"
+ " dio is direct IO, 1->attempt, 0->indirect IO (def)\n"
+ " elemsz_kb=EKB scatter gather list element size in "
+ "kibibytes;\n"
+ " must be power of two, >= page_size "
+ "(typically 4)\n"
+ " ese=0|1 exit on secondary error when 1, else continue\n"
+ " fua force unit access: 0->don't(def), 1->OFILE, "
+ "2->IFILE,\n"
+ " 3->OFILE+IFILE\n"
+ " ibs IFILE logical block size, cannot differ from "
+ "obs or bs\n"
+ " hipri same as polled=NRQS; name 'hipri' is deprecated\n"
+ " mrq NRQS is number of cmds placed in each sg "
+ "ioctl\n"
+ " (def: 16). Does not set mrq hipri flag.\n"
+ " if mrq=0 does one-by-one, blocking "
+ "ioctl(SG_IO)s\n"
+ " obs OFILE logical block size, cannot differ from "
+ "ibs or bs\n"
+ " ofreg OFREG is regular file or pipe to send what is "
+ "read from\n"
+ " polled similar to mrq=NRQS operand but also sets "
+ "polled flag\n"
+ " IFILE in the first half of each shared element\n"
+ " sdt stall detection times: CRT[,ICT]. CRT: check "
+ "repetition\n"
+ " time (after first) in seconds; ICT: initial "
+ "check time\n"
+ " in milliseconds. Default: 3,300 . Use CRT=0 "
+ "to disable\n"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE "
+ "after copy\n"
+ " thr is number of threads, must be > 0, default 4, "
+ "max 1024\n"
+ " time 0->no timing; 1/2->millisec/nanosec precision "
+ "(def: 1);\n"
+ " TO is command timeout in seconds (def: 60)\n"
+ " verbose increase verbosity (def: VERB=0)\n"
+ " --dry-run|-d prepare but bypass copy/read\n"
+ " --prefetch|-p with verify: do pre-fetch first\n"
+ " --verbose|-v increase verbosity of utility\n\n"
+ "Use '-hhh', '-hhhh' or '-hhhhh' for more information about "
+ "flags.\n"
+ );
+ return;
+page3:
+ pr2serr("Syntax: sg_mrq_dd [operands] [options]\n\n"
+ " where: 'iflag=<arg>' and 'oflag=<arg>' arguments are listed "
+ "below:\n\n"
+ " 00 use all zeros instead of if=IFILE (only in "
+ "iflag)\n"
+ " 00,ff generates blocks that contain own (32 bit be) "
+ "blk addr\n"
+ " append append output to OFILE (assumes OFILE is "
+ "regular file)\n"
+ " coe continue of error (reading, fills with zeros)\n"
+ " dio sets the SG_FLAG_DIRECT_IO in sg requests\n"
+ " direct sets the O_DIRECT flag on open()\n"
+ " dpo sets the DPO (disable page out) in SCSI READs "
+ "and WRITEs\n"
+ " dsync sets the O_SYNC flag on open()\n"
+ " excl sets the O_EXCL flag on open()\n"
+ " ff use all 0xff bytes instead of if=IFILE (only in "
+ "iflag)\n"
+ " fua sets the FUA (force unit access) in SCSI READs "
+ "and WRITEs\n"
+ " hipri same as 'polled'; name 'hipri' is deprecated\n"
+ " masync set 'more async' flag on this sg device\n"
+ " mmap setup mmap IO on IFILE or OFILE\n"
+ " mmap,mmap when used twice, doesn't call munmap()\n"
+ " mout_if set META_OUT_IF flag on control object\n"
+ " nocreat will fail rather than create OFILE\n"
+ " nodur turns off command duration calculations\n"
+ " no_thresh skip checking per fd max data xfer size\n"
+ " order require write ordering on sg->sg copy; only "
+ "for oflag\n"
+ " polled set POLLED flag and use blk_poll() for "
+ "completions\n"
+ " qhead queue new request at head of block queue\n"
+ " qtail queue new request at tail of block queue (def: "
+ "q at head)\n"
+ " random use random data instead of if=IFILE (only in "
+ "iflag)\n"
+ " same_fds each thread of a IOFILE pair uses same fds\n"
+ " serial serialize sg command execution (def: overlap)\n"
+ " wq_excl set SG_CTL_FLAGM_EXCL_WAITQ on this sg fd\n"
+ "\n"
+ "Copies IFILE to OFILE (and to OFILE2 if given). If IFILE and "
+ "OFILE are sg\ndevices 'shared' mode is selected. "
+ "When sharing, the data stays in a\nsingle "
+ "in-kernel buffer which is copied (or mmap-ed) to the user "
+ "space\nif the 'ofreg=OFREG' is given. Use '-hhhh' or '-hhhhh' "
+ "for more information.\n"
+ );
+ return;
+page4:
+ pr2serr("pack_id:\n"
+ "These are ascending integers, starting at 1, associated with "
+ "each issued\nSCSI command. When both IFILE and OFILE are sg "
+ "devices, then the READ in\neach read-write pair is issued an "
+ "even pack_id and its WRITE pair is\ngiven the pack_id one "
+ "higher (i.e. an odd number). This enables a\n'dmesg -w' "
+ "user to see that progress is being "
+ "made.\n\n");
+ pr2serr("Debugging:\n"
+ "Apart from using one or more '--verbose' options which gets a "
+ "bit noisy\n'dmesg -w' can give a good overview "
+ "of what is happening.\nThat does a sg driver object tree "
+ "traversal that does minimal locking\nto make sure that each "
+ "traversal is 'safe'. So it is important to note\nthe whole "
+ "tree is not locked. This means for fast devices the overall\n"
+ "tree state may change while the traversal is occurring. For "
+ "example,\nit has been observed that both the read- and write- "
+ "sides of a request\nshare show they are in 'active' state "
+ "which should not be possible.\nIt occurs because the read-side "
+ "probably jumped out of active state and\nthe write-side "
+ "request entered it while some other nodes were being "
+ "printed.\n\n");
+ pr2serr("Busy state:\n"
+ "Busy state (abbreviated to 'bsy' in the dmesg "
+ "output)\nis entered during request setup and completion. It "
+ "is intended to be\na temporary state. It should not block "
+ "but does sometimes (e.g. in\nblock_get_request()). Even so "
+ "that blockage should be short and if not\nthere is a "
+ "problem.\n\n");
+ pr2serr("--verify :\n"
+ "For comparing IFILE with OFILE. Does repeated sequences of: "
+ "READ(ifile)\nand uses data returned to send to VERIFY(ofile, "
+ "BYTCHK=1). So the OFILE\ndevice/disk is doing the actual "
+ "comparison. Stops on first miscompare\nunless oflag=coe is "
+ "given\n\n");
+ pr2serr("--prefetch :\n"
+ "Used with --verify option. Prepends a PRE-FETCH(ofile, IMMED) "
+ "to verify\nsequence. This should speed the trailing VERIFY by "
+ "making sure that\nthe data it needs for the comparison is "
+ "already in its cache.\n");
+ return;
+page5:
+ pr2serr(" IFILE and/or OFILE lists\n\n"
+ "For dd, its if= operand takes a single file (or device), ditto "
+ "for the of=\noperand. This utility extends that to "
+ "allowing a comma separated list\nof files. Ideally if multiple "
+ "IFILEs are given, the same number of OFILEs\nshould be given. "
+ "Simple expansions occur to make the list lengths equal\n"
+ "(e.g. if 5 IFILEs are given but no OFILEs, then OFILEs is "
+ "expanded to 5\n'/dev/null' files). IFILE,OFILE pairs with "
+ "the same list position are\ncalled a 'slice'. Each slice is "
+ "processed (i.e. copy or verify) in one or\nmore threads. The "
+ "number of threads must be >= the number of slices. Best\nif "
+ "the number of threads is an integer multiple of the number of "
+ "slices.\nThe file type of multiple IFILEs must be the same, "
+ "ditto for OFILEs.\nSupport for slices is for testing rather "
+ "than a general mechanism.\n");
+}
+
+static void
+lk_print_command_len(const char *prefix, uint8_t * cmdp, int len, bool lock)
+{
+ if (lock) {
+ lock_guard<mutex> lk(strerr_mut);
+
+ if (prefix && *prefix)
+ fputs(prefix, stderr);
+ sg_print_command_len(cmdp, len);
+ } else {
+ if (prefix && *prefix)
+ fputs(prefix, stderr);
+ sg_print_command_len(cmdp, len);
+ }
+}
+
+static void
+lk_chk_n_print4(const char * leadin, const struct sg_io_v4 * h4p,
+ bool raw_sinfo)
+{
+ lock_guard<mutex> lk(strerr_mut);
+
+ if (h4p->usr_ptr) {
+ const cdb_arr_t * cdbp = (const cdb_arr_t *)h4p->usr_ptr;
+
+ pr2serr("Failed cdb: ");
+ sg_print_command(cdbp->data());
+ } else
+ pr2serr("cdb: <null>\n");
+ sg_linux_sense_print(leadin, h4p->device_status, h4p->transport_status,
+ h4p->driver_status, (const uint8_t *)h4p->response,
+ h4p->response_len, raw_sinfo);
+}
+
+static void
+hex2stderr_lk(const uint8_t * b_str, int len, int no_ascii)
+{
+ lock_guard<mutex> lk(strerr_mut);
+
+ hex2stderr(b_str, len, no_ascii);
+}
+
+static int
+system_wrapper(const char * cmd)
+{
+ int res;
+
+ res = system(cmd);
+ if (WIFSIGNALED(res) &&
+ (WTERMSIG(res) == SIGINT || WTERMSIG(res) == SIGQUIT))
+ raise(WTERMSIG(res));
+ return WEXITSTATUS(res);
+}
+
+/* Flags decoded into abbreviations for those that are set, separated by
+ * '|' . */
+static char *
+sg_flags_str(int flags, int b_len, char * b)
+{
+ int n = 0;
+
+ if ((b_len < 1) || (! b))
+ return b;
+ b[0] = '\0';
+ if (SG_FLAG_DIRECT_IO & flags) { /* 0x1 */
+ n += sg_scnpr(b + n, b_len - n, "DIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_MMAP_IO & flags) { /* 0x4 */
+ n += sg_scnpr(b + n, b_len - n, "MMAP|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_YIELD_TAG & flags) { /* 0x8 */
+ n += sg_scnpr(b + n, b_len - n, "YTAG|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_TAIL & flags) { /* 0x10 */
+ n += sg_scnpr(b + n, b_len - n, "QTAI|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_HEAD & flags) { /* 0x20 */
+ n += sg_scnpr(b + n, b_len - n, "QHEA|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DOUT_OFFSET & flags) { /* 0x40 */
+ n += sg_scnpr(b + n, b_len - n, "DOFF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_EVENTFD & flags) { /* 0x80 */
+ n += sg_scnpr(b + n, b_len - n, "EVFD|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_COMPLETE_B4 & flags) { /* 0x100 */
+ n += sg_scnpr(b + n, b_len - n, "CPL_B4|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SIGNAL & flags) { /* 0x200 */
+ n += sg_scnpr(b + n, b_len - n, "SIGNAL|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_IMMED & flags) { /* 0x400 */
+ n += sg_scnpr(b + n, b_len - n, "IMM|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_POLLED & flags) { /* 0x800 */
+ n += sg_scnpr(b + n, b_len - n, "POLLED|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_STOP_IF & flags) { /* 0x1000 */
+ n += sg_scnpr(b + n, b_len - n, "STOPIF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DEV_SCOPE & flags) { /* 0x2000 */
+ n += sg_scnpr(b + n, b_len - n, "DEV_SC|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SHARE & flags) { /* 0x4000 */
+ n += sg_scnpr(b + n, b_len - n, "SHARE|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DO_ON_OTHER & flags) { /* 0x8000 */
+ n += sg_scnpr(b + n, b_len - n, "DO_OTH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_NO_DXFER & flags) { /* 0x10000 */
+ n += sg_scnpr(b + n, b_len - n, "NOXFER|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_KEEP_SHARE & flags) { /* 0x20000 */
+ n += sg_scnpr(b + n, b_len - n, "KEEP_SH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_MULTIPLE_REQS & flags) { /* 0x40000 */
+ n += sg_scnpr(b + n, b_len - n, "MRQS|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_ORDERED_WR & flags) { /* 0x80000 */
+ n += sg_scnpr(b + n, b_len - n, "OWR|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_REC_ORDER & flags) { /* 0x100000 */
+ n += sg_scnpr(b + n, b_len - n, "REC_O|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_META_OUT_IF & flags) { /* 0x200000 */
+ n += sg_scnpr(b + n, b_len - n, "MOUT_IF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (0 == n)
+ n += sg_scnpr(b + n, b_len - n, "<none>");
+fini:
+ if (n < b_len) { /* trim trailing '\' */
+ if ('|' == b[n - 1])
+ b[n - 1] = '\0';
+ } else if ('|' == b[b_len - 1])
+ b[b_len - 1] = '\0';
+ return b;
+}
+
+/* Info field decoded into abbreviations for those bits that are set,
+ * separated by '|' . */
+static char *
+sg_info_str(int info, int b_len, char * b)
+{
+ int n = 0;
+
+ if ((b_len < 1) || (! b))
+ return b;
+ b[0] = '\0';
+ if (SG_INFO_CHECK & info) { /* 0x1 */
+ n += sg_scnpr(b + n, b_len - n, "CHK|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_DIRECT_IO & info) { /* 0x2 */
+ n += sg_scnpr(b + n, b_len - n, "DIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_MIXED_IO & info) { /* 0x4 */
+ n += sg_scnpr(b + n, b_len - n, "MIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_DEVICE_DETACHING & info) { /* 0x8 */
+ n += sg_scnpr(b + n, b_len - n, "DETA|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_ABORTED & info) { /* 0x10 */
+ n += sg_scnpr(b + n, b_len - n, "ABRT|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_MRQ_FINI & info) { /* 0x20 */
+ n += sg_scnpr(b + n, b_len - n, "MRQF|");
+ if (n >= b_len)
+ goto fini;
+ }
+fini:
+ if (n < b_len) { /* trim trailing '\' */
+ if ('|' == b[n - 1])
+ b[n - 1] = '\0';
+ } else if ('|' == b[b_len - 1])
+ b[b_len - 1] = '\0';
+ return b;
+}
+
+static void
+v4hdr_out_lk(const char * leadin, const sg_io_v4 * h4p, int id, bool chk_info)
+{
+ lock_guard<mutex> lk(strerr_mut);
+ char b[80];
+
+ if (leadin)
+ pr2serr("%s [id=%d]:\n", leadin, id);
+ if (('Q' != h4p->guard) || (0 != h4p->protocol) ||
+ (0 != h4p->subprotocol))
+ pr2serr(" <<<sg_io_v4 _NOT_ properly set>>>\n");
+ pr2serr(" pointers: cdb=%s sense=%s din=%p dout=%p\n",
+ (h4p->request ? "y" : "NULL"), (h4p->response ? "y" : "NULL"),
+ (void *)h4p->din_xferp, (void *)h4p->dout_xferp);
+ pr2serr(" lengths: cdb=%u sense=%u din=%u dout=%u\n",
+ h4p->request_len, h4p->max_response_len, h4p->din_xfer_len,
+ h4p->dout_xfer_len);
+ pr2serr(" flags=0x%x request_extra{pack_id}=%d\n",
+ h4p->flags, h4p->request_extra);
+ pr2serr(" flags set: %s\n", sg_flags_str(h4p->flags, sizeof(b), b));
+ pr2serr(" %s OUT:\n", leadin);
+ pr2serr(" response_len=%d driver/transport/device_status="
+ "0x%x/0x%x/0x%x\n", h4p->response_len, h4p->driver_status,
+ h4p->transport_status, h4p->device_status);
+ pr2serr(" info=0x%x din_resid=%u dout_resid=%u spare_out=%u "
+ "dur=%u\n",
+ h4p->info, h4p->din_resid, h4p->dout_resid, h4p->spare_out,
+ h4p->duration);
+ if (chk_info && (SG_INFO_CHECK & h4p->info))
+ pr2serr(" >>>> info: %s\n", sg_info_str(h4p->info, sizeof(b), b));
+}
+
+static void
+fetch_sg_version(void)
+{
+ FILE * fp;
+ char b[96];
+
+ have_sg_version = false;
+ sg_version = 0;
+ fp = fopen(PROC_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (1 == sscanf(b, "%d", &sg_version))
+ have_sg_version = !!sg_version;
+ } else {
+ int j, k, l;
+
+ if (fp)
+ fclose(fp);
+ fp = fopen(SYS_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (3 == sscanf(b, "%d.%d.%d", &j, &k, &l)) {
+ sg_version = (j * 10000) + (k * 100) + l;
+ have_sg_version = !!sg_version;
+ }
+ }
+ if (NULL == fp)
+ pr2serr("The sg driver may not be loaded\n");
+ }
+ if (fp)
+ fclose(fp);
+}
+
+static void
+calc_duration_throughput(int contin)
+{
+ struct timeval end_tm, res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+
+ b = 0.0;
+ for (auto && cvp : gcoll.cp_ver_arr) {
+ if (cvp.state == cp_ver_pair_t::my_state::empty)
+ break;
+ b += (double)(cvp.dd_count - cvp.out_rem_count.load());
+ }
+ b *= (double)gcoll.bs;
+ pr2serr("time to %s data %s %d.%06d secs",
+ (gcoll.verify ? "verify" : "copy"), (contin ? "so far" : "was"),
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+}
+
+static void
+print_stats(const char * str)
+{
+ bool show_slice = ((gcoll.cp_ver_arr.size() > 1) &&
+ (gcoll.cp_ver_arr[1].state !=
+ cp_ver_pair_t::my_state::empty));
+ int k = 0;
+ int64_t infull, outfull;
+
+ for (auto && cvp : gcoll.cp_ver_arr) {
+ ++k;
+ if (cvp.state == cp_ver_pair_t::my_state::empty)
+ break;
+ if (cvp.state == cp_ver_pair_t::my_state::ignore) {
+ pr2serr(">>> IGNORING slice: %d\n", k);
+ continue;
+ }
+ if (show_slice)
+ pr2serr(">>> slice: %d\n", k);
+ if (0 != cvp.out_rem_count.load())
+ pr2serr(" remaining block count=%" PRId64 "\n",
+ cvp.out_rem_count.load());
+ infull = cvp.dd_count - cvp.in_rem_count.load();
+ pr2serr("%s%" PRId64 "+%d records in\n", str,
+ infull, cvp.in_partial.load());
+
+ if (cvp.out_type == FT_DEV_NULL)
+ pr2serr("%s0+0 records out\n", str);
+ else {
+ outfull = cvp.dd_count - cvp.out_rem_count.load();
+ pr2serr("%s%" PRId64 "+%d records %s\n", str,
+ outfull, cvp.out_partial.load(),
+ (gcoll.verify ? "verified" : "out"));
+ }
+ }
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ if (do_time > 0)
+ calc_duration_throughput(0);
+ print_stats("");
+ kill(getpid(), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time > 0)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+/* Usually this signal (SIGUSR2) will be caught by the timed wait in the
+ * sig_listen_thread thread but some might slip through while the timed
+ * wait is being re-armed or after that thread is finished. This handler
+ * acts as a backstop. */
+static void
+siginfo2_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ ++num_fallthru_sigusr2;
+}
+
+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);
+ }
+}
+
+/* Make safe_strerror() thread safe */
+static char *
+tsafe_strerror(int code, char * ebp)
+{
+ lock_guard<mutex> lk(strerr_mut);
+ char * cp;
+
+ cp = safe_strerror(code);
+ strncpy(ebp, cp, STRERR_BUFF_LEN);
+ ebp[STRERR_BUFF_LEN - 1] = '\0';
+ return ebp;
+}
+
+
+static int
+dd_filetype(const char * filename, off_t & st_size)
+{
+ struct stat st;
+ size_t len = strlen(filename);
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if ((MEM_MAJOR == major(st.st_rdev)) &&
+ ((DEV_NULL_MINOR_NUM == minor(st.st_rdev)) ||
+ (DEV_ZERO_MINOR_NUM == minor(st.st_rdev))))
+ return FT_DEV_NULL; /* treat /dev/null + /dev/zero the same */
+ if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+ return FT_ST;
+ return FT_CHAR;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ else if (S_ISFIFO(st.st_mode))
+ return FT_FIFO;
+ st_size = st.st_size;
+ return FT_OTHER;
+}
+
+/* Returns reserved_buffer_size/mmap_size if success, else 0 for failure */
+static int
+sg_prepare_resbuf(int fd, struct global_collection *clp, bool is_in,
+ uint8_t **mmpp)
+{
+ static bool done = false;
+ bool no_dur = is_in ? clp->in_flags.no_dur : clp->out_flags.no_dur;
+ bool masync = is_in ? clp->in_flags.masync : clp->out_flags.masync;
+ bool wq_excl = is_in ? clp->in_flags.wq_excl : clp->out_flags.wq_excl;
+ bool skip_thresh = is_in ? clp->in_flags.no_thresh :
+ clp->out_flags.no_thresh;
+ int elem_sz = clp->elem_sz;
+ int res, t, num, err;
+ uint8_t *mmp;
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 40000)) {
+ if (ioctl(fd, SG_GET_RESERVED_SIZE, &num) < 0) {
+ perror("SG_GET_RESERVED_SIZE ioctl failed");
+ return 0;
+ }
+ if (! done) {
+ done = true;
+ pr2serr_lk("%ssg driver prior to 4.0.00, reduced functionality\n",
+ my_name);
+ }
+ goto bypass;
+ }
+ if (elem_sz >= 4096) {
+ seip->sei_rd_mask |= SG_SEIM_SGAT_ELEM_SZ;
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("sg_mrq_dd: %s: SG_SET_GET_EXTENDED(SGAT_ELEM_SZ) rd "
+ "error: %s\n", __func__, strerror(errno));
+ if (elem_sz != (int)seip->sgat_elem_sz) {
+ seip->sei_wr_mask |= SG_SEIM_SGAT_ELEM_SZ;
+ seip->sgat_elem_sz = elem_sz;
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("sg_mrq_dd: %s: SG_SET_GET_EXTENDED(SGAT_ELEM_SZ) "
+ "wr error: %s\n", __func__, strerror(errno));
+ }
+ }
+ if (no_dur || masync || skip_thresh) {
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ if (no_dur) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags |= SG_CTL_FLAGM_NO_DURATION;
+ }
+ if (masync) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC;
+ seip->ctl_flags |= SG_CTL_FLAGM_MORE_ASYNC;
+ }
+ if (wq_excl) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_EXCL_WAITQ;
+ seip->ctl_flags |= SG_CTL_FLAGM_EXCL_WAITQ;
+ }
+ if (skip_thresh) {
+ seip->tot_fd_thresh = 0;
+ sei.sei_wr_mask |= SG_SEIM_TOT_FD_THRESH;
+ }
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("sg_mrq_dd: %s: SG_SET_GET_EXTENDED(NO_DURATION) "
+ "error: %s\n", __func__, strerror(errno));
+ }
+bypass:
+ num = clp->bs * clp->bpt;
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &num);
+ if (res < 0) {
+ perror("sg_mrq_dd: SG_SET_RESERVED_SIZE error");
+ return 0;
+ } else {
+ int nn;
+
+ res = ioctl(fd, SG_GET_RESERVED_SIZE, &nn);
+ if (res < 0) {
+ perror("sg_mrq_dd: SG_GET_RESERVED_SIZE error");
+ return 0;
+ }
+ if (nn < num) {
+ pr2serr_lk("%s: SG_GET_RESERVED_SIZE shows size truncated, "
+ "wanted %d got %d\n", __func__, num, nn);
+ return 0;
+ }
+ if (mmpp) {
+ mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (MAP_FAILED == mmp) {
+ err = errno;
+ pr2serr_lk("sg_mrq_dd: %s: sz=%d, fd=%d, mmap() failed: %s\n",
+ __func__, num, fd, strerror(err));
+ return 0;
+ }
+ *mmpp = mmp;
+ }
+ }
+ t = 1;
+ res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
+ if (res < 0)
+ perror("sg_mrq_dd: SG_SET_FORCE_PACK_ID error");
+ if (clp->unit_nanosec) {
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ res = -1;
+ pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+ }
+ if (clp->verbose) {
+ t = 1;
+ /* more info in the kernel log */
+ res = ioctl(fd, SG_SET_DEBUG, &t);
+ if (res < 0)
+ perror("sg_mrq_dd: SG_SET_DEBUG error");
+ }
+ return (res < 0) ? 0 : num;
+}
+
+static int
+sg_in_open(struct global_collection *clp, const string & inf, uint8_t **mmpp,
+ int * mmap_lenp)
+{
+ int fd, err, n;
+ int flags = O_RDWR;
+ char ebuff[EBUFF_SZ];
+ const char * fnp = inf.c_str();
+
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(fnp, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg reading",
+ __func__, fnp);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, clp, true, mmpp);
+ if (n <= 0) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+static int
+sg_out_open(struct global_collection *clp, const string & outf,
+ uint8_t **mmpp, int * mmap_lenp)
+{
+ int fd, err, n;
+ int flags = O_RDWR;
+ char ebuff[EBUFF_SZ];
+ const char * fnp = outf.c_str();
+
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(fnp, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg %s",
+ __func__, fnp, (clp->verify ? "verifying" : "writing"));
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, clp, false, mmpp);
+ if (n <= 0) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+static int
+reg_file_open(struct global_collection *clp, const string & fn_s,
+ bool for_wr)
+{
+ int fd, flags;
+ char ebuff[EBUFF_SZ];
+
+ if (for_wr) {
+ flags = O_WRONLY;
+ if (! clp->out_flags.nocreat)
+ flags |= O_CREAT;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+ } else
+ flags = O_RDONLY;
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if (for_wr)
+ fd = open(fn_s.c_str(), flags, 0666);
+ else
+ fd = open(fn_s.c_str(), flags);
+ if (fd < 0) {
+ int err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for %sing ",
+ my_name, fn_s.c_str(), (for_wr ? "writ" : "read"));
+ perror(ebuff);
+ return -err;
+ }
+ return fd;
+}
+
+get_next_res_t
+cp_ver_pair_t::get_next(int desired_num_blks)
+{
+ int64_t expected, desired;
+
+ if (desired_num_blks <= 0) {
+ if (desired_num_blks < 0) {
+ if (next_count_pos.load() >= 0) /* flag error detection */
+ next_count_pos.store(desired_num_blks);
+ }
+ return make_pair(next_count_pos.load(), 0);
+ }
+
+ expected = next_count_pos.load();
+ do { /* allowed to race with other threads */
+ if (expected < 0)
+ return make_pair(0, (int)expected);
+ else if (expected >= dd_count)
+ return make_pair(expected, 0); /* clean finish */
+ desired = expected + desired_num_blks;
+ if (desired > dd_count)
+ desired = dd_count;
+ } while (! next_count_pos.compare_exchange_strong(expected, desired));
+ return make_pair(expected, desired - expected);
+}
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rcBuff[RCAP16_REPLY_LEN] = {};
+
+ res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false, 0);
+ if (0 != res)
+ goto bad;
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+
+ res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false,
+ 0);
+ if (0 != res)
+ goto bad;
+ *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)sg_get_unaligned_be32(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ return 0;
+bad:
+ *num_sect = 0;
+ *sect_sz = 0;
+ return res;
+}
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ #endif
+ }
+ return 0;
+#else
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+static void
+flag_all_stop(struct global_collection * clp)
+{
+ for (auto && elem : clp->cp_ver_arr) {
+ if (elem.state == cp_ver_pair_t::my_state::empty)
+ break;
+ elem.next_count_pos.store(-1);
+ }
+}
+
+/* Has an infinite loop doing a timed wait for any signals in signal_set.
+ * After each timeout (300 ms) checks if the most_recent_pack_id atomic
+ * integer has changed. If not after another two timeouts announces a stall
+ * has been detected. If shutting down atomic is true breaks out of loop and
+ * shuts down this thread. Other than that, this thread is normally cancelled
+ * by the main thread, after other threads have exited. */
+static void
+sig_listen_thread(struct global_collection * clp)
+{
+ bool stall_reported = false;
+ int prev_pack_id = 0;
+ int sig_number, pack_id;
+ uint32_t ict_ms = (clp->sdt_ict ? clp->sdt_ict : DEF_SDT_ICT_MS);
+ struct timespec ts;
+ struct timespec * tsp = &ts;
+
+ tsp->tv_sec = ict_ms / 1000;
+ tsp->tv_nsec = (ict_ms % 1000) * 1000 * 1000; /* DEF_SDT_ICT_MS */
+ listen_t_tid = gettid(); // to facilitate sending SIGUSR2 to exit
+ while (1) {
+ sig_number = sigtimedwait(&signal_set, NULL, tsp);
+ if (sig_number < 0) {
+ int err = errno;
+
+ /* EAGAIN implies a timeout */
+ if ((EAGAIN == err) && (clp->sdt_crt > 0)) {
+ pack_id = clp->most_recent_pack_id.load();
+ if ((pack_id > 0) && (pack_id == prev_pack_id)) {
+ if (! stall_reported) {
+ stall_reported = true;
+ tsp->tv_sec = clp->sdt_crt;
+ tsp->tv_nsec = 0;
+ pr2serr_lk("%s: first stall at pack_id=%d detected\n",
+ __func__, pack_id);
+ } else
+ pr2serr_lk("%s: subsequent stall at pack_id=%d\n",
+ __func__, pack_id);
+ // following command assumes linux bash or similar shell
+ system_wrapper("cat /proc/scsi/sg/debug >> /dev/stderr\n");
+ // system_wrapper("/usr/bin/dmesg\n");
+ } else
+ prev_pack_id = pack_id;
+ } else if (EAGAIN != err)
+ pr2serr_lk("%s: sigtimedwait() errno=%d\n", __func__, err);
+ }
+ if (SIGINT == sig_number) {
+ pr2serr_lk("%sinterrupted by SIGINT\n", my_name);
+ flag_all_stop(clp);
+ shutting_down.store(true);
+ sigprocmask(SIG_SETMASK, &orig_signal_set, NULL);
+ raise(SIGINT);
+ break;
+ }
+ if (SIGUSR2 == sig_number) {
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: SIGUSR2 received\n", __func__);
+ break;
+ } if (shutting_down)
+ break;
+ } /* end of while loop */
+ if (clp->verbose > 3)
+ pr2serr_lk("%s: exiting\n", __func__);
+}
+
+static bool
+sg_share_prepare(int write_side_fd, int read_side_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_wr_mask |= SG_SEIM_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_SHARE_FD;
+ seip->share_fd = read_side_fd;
+ if (ioctl(write_side_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(shared_fd=%d), failed "
+ "errno=%d %s\n", id, read_side_fd, errno,
+ strerror(errno));
+ return false;
+ }
+ if (vb_b)
+ pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(shared_fd)) ok, "
+ "read_side_fd=%d, write_side_fd=%d\n", __func__, id,
+ read_side_fd, write_side_fd);
+ return true;
+}
+
+static void
+sg_take_snap(int sg_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_SNAP_DEV;
+ seip->ctl_flags &= ~SG_CTL_FLAGM_SNAP_DEV; /* 0 --> append */
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(SNAP_DEV), failed errno=%d %s\n",
+ id, errno, strerror(errno));
+ return;
+ }
+ if (vb_b)
+ pr2serr_lk("tid=%d: ioctl(SNAP_DEV) ok\n", id);
+}
+
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+/* Each thread's "main" function */
+static void
+read_write_thread(struct global_collection * clp, int thr_idx, int slice_idx,
+ bool singleton)
+{
+ Rq_elem rel {};
+ Rq_elem * rep = &rel;
+ int n, sz, fd, vb, err, seg_blks;
+ int res = 0;
+ int num_sg = 0;
+ bool own_infd = false;
+ bool in_is_sg, in_mmap, out_is_sg, out_mmap;
+ bool own_outfd = false;
+ bool only_one_sg = false;
+ struct cp_ver_pair_t & cvp = clp->cp_ver_arr[slice_idx];
+ class scat_gath_iter i_sg_it(clp->i_sgl);
+ class scat_gath_iter o_sg_it(clp->o_sgl);
+ const string & inf = clp->inf_v[slice_idx];
+ const string & outf = clp->outf_v[slice_idx];
+ vector<cdb_arr_t> a_cdb;
+ vector<struct sg_io_v4> a_v4;
+
+ vb = clp->verbose;
+ sz = clp->mrq_num * clp->bpt * clp->bs;
+ in_is_sg = (FT_SG == clp->in_type);
+ in_mmap = (in_is_sg && (clp->in_flags.mmap > 0));
+ out_is_sg = (FT_SG == clp->out_type);
+ out_mmap = (out_is_sg && (clp->out_flags.mmap > 0));
+ rep->clp = clp;
+ rep->id = thr_idx;
+ rep->bs = clp->bs;
+
+ if (in_is_sg && out_is_sg)
+ rep->both_sg = true;
+ else if (in_is_sg || out_is_sg) {
+ only_one_sg = true;
+ if (in_is_sg)
+ rep->only_in_sg = true;
+ else
+ rep->only_out_sg = true;
+ }
+
+ if (vb > 2) {
+ pr2serr_lk("%d <-- Starting worker thread, slice=%d\n", thr_idx,
+ slice_idx);
+ if (vb > 3)
+ pr2serr_lk(" %s ---> %s\n", inf.c_str(), outf.c_str());
+ }
+ if (! (rep->both_sg || in_mmap)) {
+ rep->buffp = sg_memalign(sz, 0 /* page align */, &rep->alloc_bp,
+ false);
+ if (NULL == rep->buffp) {
+ pr2serr_lk("Failed to allocate %d bytes, exiting\n", sz);
+ return;
+ }
+ }
+ rep->infd = clp->in0fd;
+ rep->outfd = clp->out0fd;
+ rep->outregfd = clp->outregfd;
+ rep->rep_count = 0;
+ rep->in_follow_on = -1;
+ rep->out_follow_on = -1;
+ if (cvp.state == cp_ver_pair_t::my_state::init)
+ cvp.state = cp_ver_pair_t::my_state::underway;
+ if (FT_OTHER == cvp.in_type) {
+ fd = reg_file_open(clp, inf, false);
+ if (fd < 0) {
+ pr2serr_lk("[%d]: unable to open IFILE of slice=%d\n", thr_idx,
+ slice_idx);
+ return;
+ }
+ rep->infd = fd;
+ }
+ if (FT_OTHER == cvp.out_type) {
+ fd = reg_file_open(clp, outf, true);
+ if (fd < 0) {
+ pr2serr_lk("[%d]: unable to open OFILE of slice=%d\n", thr_idx,
+ slice_idx);
+ return;
+ }
+ rep->outfd = fd;
+ }
+
+ if (rep->infd == rep->outfd) {
+ if (in_is_sg)
+ rep->same_sg = true;
+ }
+ if (clp->in_flags.random) {
+#ifdef HAVE_GETRANDOM
+ ssize_t ssz = getrandom(&rep->seed, sizeof(rep->seed), GRND_NONBLOCK);
+
+ if (ssz < (ssize_t)sizeof(rep->seed)) {
+ pr2serr_lk("[%d] %s: getrandom() failed, ret=%d\n", thr_idx,
+ __func__, (int)ssz);
+ rep->seed = (long)time(NULL);
+ }
+#else
+ rep->seed = (long)time(NULL); /* use seconds since epoch as proxy */
+#endif
+ if (vb > 1)
+ pr2serr_lk("[%d] %s: seed=%ld\n", thr_idx, __func__, rep->seed);
+#ifdef HAVE_SRAND48_R
+ srand48_r(rep->seed, &rep->drand);
+#else
+ srand48(rep->seed);
+#endif
+ }
+
+ if (in_is_sg && inf.size()) {
+ if ((clp->in_flags.same_fds || (0 == thr_idx)) &&
+ (cvp.in_fd >= 0))
+ fd = cvp.in_fd;
+ else {
+ fd = sg_in_open(clp, inf, (in_mmap ? &rep->buffp : NULL),
+ (in_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ own_infd = true;
+ if (cvp.in_fd < 0)
+ cvp.in_fd = fd;
+ }
+ rep->infd = fd;
+ rep->mmap_active = in_mmap ? clp->in_flags.mmap : 0;
+ if (in_mmap && (vb > 4))
+ pr2serr_lk("[%d] %s: mmap buffp=%p\n", thr_idx, __func__,
+ rep->buffp);
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("[%d]: opened local sg IFILE\n", thr_idx);
+ }
+ if (out_is_sg && outf.size()) {
+ if ((clp->out_flags.same_fds || (0 == thr_idx)) &&
+ (cvp.out_fd >= 0))
+ fd = cvp.out_fd;
+ else {
+ fd = sg_out_open(clp, outf, (out_mmap ? &rep->buffp : NULL),
+ (out_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ own_outfd = true;
+ if (cvp.out_fd < 0)
+ cvp.out_fd = fd;
+ }
+ rep->outfd = fd;
+ if (! rep->mmap_active)
+ rep->mmap_active = out_mmap ? clp->out_flags.mmap : 0;
+ if (out_mmap && (vb > 4))
+ pr2serr_lk("[%d]: mmap buffp=%p\n", thr_idx, rep->buffp);
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("[%d]: opened local sg OFILE\n", thr_idx);
+ }
+ if (vb > 2) {
+ if (in_is_sg && (! own_infd))
+ pr2serr_lk("[%d]: using global sg IFILE, fd=%d\n", thr_idx,
+ rep->infd);
+ if (out_is_sg && (! own_outfd))
+ pr2serr_lk("[%d]: using global sg OFILE, fd=%d\n", thr_idx,
+ rep->outfd);
+ }
+ if (rep->both_sg)
+ rep->has_share = sg_share_prepare(rep->outfd, rep->infd, thr_idx,
+ vb > 9);
+ if (vb > 9)
+ pr2serr_lk("[%d]: has_share=%s\n", thr_idx,
+ (rep->has_share ? "true" : "false"));
+ // share_and_ofreg = (rep->has_share && (rep->outregfd >= 0));
+
+ /* vvvvvvvvvvvvvv Main segment copy loop vvvvvvvvvvvvvvvvvvvvvvv */
+ while (! shutting_down) {
+ get_next_res_t gnr = cvp.get_next(clp->mrq_num * clp->bpt);
+
+ seg_blks = gnr.second;
+ if (seg_blks <= 0) {
+ if (seg_blks < 0)
+ res = -seg_blks;
+ else
+ cvp.state = cp_ver_pair_t::my_state::finished;
+ break;
+ }
+ if (! i_sg_it.set_by_blk_idx(gnr.first)) {
+ lock_guard<mutex> lk(strerr_mut);
+
+ pr2serr_lk("[%d]: input set_by_blk_idx() failed\n", thr_idx);
+ i_sg_it.dbg_print("input after set_by_blk_idx", false, vb > 5);
+ res = 2;
+ break;
+ }
+ if (! o_sg_it.set_by_blk_idx(gnr.first)) {
+ pr2serr_lk("[%d]: output set_by_blk_idx() failed\n", thr_idx);
+ res = 3;
+ break;
+ }
+ if (rep->both_sg) {
+ uint32_t nn = (2 * clp->mrq_num) + 4;
+
+ if (a_cdb.capacity() < nn)
+ a_cdb.reserve(nn);
+ if (a_v4.capacity() < nn)
+ a_v4.reserve(nn);
+ if (clp->mrq_eq_0)
+ res = do_both_sg_segment_mrq0(rep, i_sg_it, o_sg_it,
+ seg_blks);
+ else
+ res = do_both_sg_segment(rep, i_sg_it, o_sg_it, seg_blks,
+ a_cdb, a_v4);
+ if (res < 0)
+ break;
+ } else if (only_one_sg) {
+ uint32_t nn = clp->mrq_num + 4;
+
+ if (a_cdb.capacity() < nn)
+ a_cdb.reserve(nn);
+ if (a_v4.capacity() < nn)
+ a_v4.reserve(nn);
+ res = do_normal_sg_segment(rep, i_sg_it, o_sg_it, seg_blks, a_cdb,
+ a_v4);
+ if (res < 0)
+ break;
+ } else {
+ res = do_normal_normal_segment(rep, i_sg_it, o_sg_it, seg_blks);
+ if (res < 0)
+ break;
+ }
+ if (singleton) {
+ {
+ lock_guard<mutex> lk(clp->infant_mut);
+
+ clp->processed = true;
+ } /* this unlocks lk */
+ clp->infant_cv.notify_one();
+ singleton = false;
+ }
+ if (rep->stop_after_write || rep->stop_now) {
+ shutting_down = true;
+ break;
+ }
+ } /* ^^^^^^^^^^ end of main while loop which copies segments ^^^^^^ */
+
+ if (shutting_down) {
+ if (vb > 3)
+ pr2serr_lk("%s: t=%d: shutting down\n", __func__, rep->id);
+ goto fini;
+ }
+ if (singleton) {
+ {
+ lock_guard<mutex> lk(clp->infant_mut);
+
+ clp->processed = true;
+ } /* this unlocks lk */
+ clp->infant_cv.notify_one();
+ }
+ if (res < 0) {
+ if (seg_blks >= 0)
+ cvp.get_next(-1); /* flag error to main */
+ pr2serr_lk("%s: t=%d: aborting, res=%d\n", __func__, rep->id, res);
+ }
+
+fini:
+
+ if ((1 == rep->mmap_active) && (rep->mmap_len > 0)) {
+ if (munmap(rep->buffp, rep->mmap_len) < 0) {
+ err = errno;
+ char bb[64];
+
+ pr2serr_lk("thread=%d: munmap() failed: %s\n", rep->id,
+ tsafe_strerror(err, bb));
+ }
+ if (vb > 4)
+ pr2serr_lk("thread=%d: munmap(%p, %d)\n", rep->id, rep->buffp,
+ rep->mmap_len);
+ rep->mmap_active = 0;
+ }
+
+ if (own_infd && (rep->infd >= 0)) {
+ if (vb && in_is_sg) {
+ if (ioctl(rep->infd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior close(in)\n",
+ __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->infd);
+ }
+ if (own_outfd && (rep->outfd >= 0)) {
+ if (vb && out_is_sg) {
+ if (ioctl(rep->outfd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior "
+ "close(out)\n", __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->outfd);
+ }
+ /* pass stats back to read-side */
+ if (vb > 3)
+ pr2serr_lk("%s: [%d] leaving: in/out local count=%" PRId64 "/%"
+ PRId64 "\n", __func__, rep->id, rep->in_local_count,
+ rep->out_local_count);
+ cvp.in_rem_count -= rep->in_local_count;
+ cvp.out_rem_count -= rep->out_local_count;
+ cvp.in_partial += rep->in_local_partial;
+ cvp.out_partial += rep->out_local_partial;
+ cvp.sum_of_resids += rep->in_resid_bytes;
+ if (rep->alloc_bp)
+ free(rep->alloc_bp);
+}
+
+/* N.B. Returns 'blocks' is successful, lesser positive number if there was
+ * a short read, or an error code which is negative. */
+static int
+normal_in_rd(Rq_elem * rep, int64_t lba, int blocks, int d_boff)
+{
+ struct global_collection * clp = rep->clp;
+ int res, err;
+ int id = rep->id;
+ uint8_t * bp;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id,
+ __func__, lba, blocks, d_boff);
+ if (FT_RANDOM_0_FF == clp->in_type) {
+ int k, j;
+ const int jbump = sizeof(uint32_t);
+ long rn;
+ uint8_t * bp;
+
+ if (clp->in_flags.zero && clp->in_flags.ff && (rep->bs >= 4)) {
+ uint32_t pos = (uint32_t)lba;
+ uint32_t off;
+
+ for (k = 0, off = 0; k < blocks; ++k, off += rep->bs, ++pos) {
+ for (j = 0; j < (rep->bs - 3); j += 4)
+ sg_put_unaligned_be32(pos, rep->buffp + off + j);
+ }
+ } else if (clp->in_flags.zero)
+ memset(rep->buffp + d_boff, 0, blocks * rep->bs);
+ else if (clp->in_flags.ff)
+ memset(rep->buffp + d_boff, 0xff, blocks * rep->bs);
+ else {
+ bp = rep->buffp + d_boff;
+ for (k = 0; k < blocks; ++k, bp += rep->bs) {
+ for (j = 0; j < rep->bs; j += jbump) {
+ /* mrand48 takes uniformly from [-2^31, 2^31) */
+#ifdef HAVE_SRAND48_R
+ mrand48_r(&rep->drand, &rn);
+#else
+ rn = mrand48();
+#endif
+ *((uint32_t *)(bp + j)) = (uint32_t)rn;
+ }
+ }
+ }
+ return blocks;
+ }
+
+ if (clp->in_type != FT_FIFO) {
+ int64_t pos = lba * rep->bs;
+
+ if (rep->in_follow_on != pos) {
+ if (lseek64(rep->infd, pos, SEEK_SET) < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id,
+ __func__, pos, safe_strerror(err));
+ return -err;
+ }
+ rep->in_follow_on = pos;
+ }
+ }
+ bp = rep->buffp + d_boff;
+ while (((res = read(rep->infd, bp, blocks * rep->bs)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ if (clp->in_flags.coe) {
+ memset(bp, 0, blocks * rep->bs);
+ pr2serr_lk("[%d] %s : >> substituted zeros for in blk=%" PRId64
+ " for %d bytes, %s\n", id, __func__, lba,
+ blocks * rep->bs,
+ tsafe_strerror(err, strerr_buff));
+ res = blocks * rep->bs;
+ } else {
+ pr2serr_lk("[%d] %s: error in normal read, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ }
+ rep->in_follow_on += res;
+ if (res < blocks * rep->bs) {
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ rep->in_local_partial++;
+ rep->in_resid_bytes = res % rep->bs;
+ }
+ }
+ return blocks;
+}
+
+/* N.B. Returns 'blocks' is successful, lesser positive number if there was
+ * a short write, or an error code which is negative. */
+static int
+normal_out_wr(Rq_elem * rep, int64_t lba, int blocks, int d_boff)
+{
+ int res, err;
+ int id = rep->id;
+ struct global_collection * clp = rep->clp;
+ uint8_t * bp = rep->buffp + d_boff;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id,
+ __func__, lba, blocks, d_boff);
+
+ if (clp->in_type != FT_FIFO) {
+ int64_t pos = lba * rep->bs;
+
+ if (rep->out_follow_on != pos) {
+ if (lseek64(rep->outfd, pos, SEEK_SET) < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id,
+ __func__, pos, safe_strerror(err));
+ return -err;
+ }
+ rep->out_follow_on = pos;
+ }
+ }
+ while (((res = write(rep->outfd, bp, blocks * rep->bs))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ if (clp->out_flags.coe) {
+ pr2serr_lk("[%d] %s: >> ignored error for out lba=%" PRId64
+ " for %d bytes, %s\n", id, __func__, lba,
+ blocks * rep->bs, tsafe_strerror(err, strerr_buff));
+ res = blocks * rep->bs;
+ }
+ else {
+ pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ }
+ rep->out_follow_on += res;
+ if (res < blocks * rep->bs) {
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ rep->out_local_partial++;
+ }
+ }
+ return blocks;
+}
+
+static int
+extra_out_wr(Rq_elem * rep, int num_bytes, int d_boff)
+{
+ int res, err;
+ int id = rep->id;
+ struct global_collection * clp = rep->clp;
+ uint8_t * bp = rep->buffp + d_boff;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: num_bytes=%d, d_boff=%d\n", id, __func__,
+ num_bytes, d_boff);
+
+ while (((res = write(clp->out0fd, bp, num_bytes))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ if (res > 0)
+ rep->out_local_partial++;
+ return res;
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool ver_true, bool write_true,
+ bool fua, bool dpo, int cdl)
+{
+ bool normal_rw = true;
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int ve_opcode[] = {0xff /* no VER(6) */, 0x2f, 0xaf, 0x8f};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+ int sz_ind;
+
+ memset(cdbp, 0, cdb_sz);
+ if (ver_true) { /* only support VERIFY(10) */
+ if (cdb_sz < 10) {
+ pr2serr_lk("%s only support VERIFY(10)\n", my_name);
+ return 1;
+ }
+ cdb_sz = 10;
+ fua = false;
+ cdbp[1] |= 0x2; /* BYTCHK=1 --> sending dout for comparison */
+ cdbp[0] = ve_opcode[1];
+ normal_rw = false;
+ }
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr_lk("%sfor 6 byte commands, maximum number of blocks is "
+ "256\n", my_name);
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr_lk("%sfor 6 byte commands, can't address blocks beyond "
+ "%d\n", my_name, 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr_lk("%sfor 6 byte commands, neither dpo nor fua bits "
+ "supported\n", my_name);
+ return 1;
+ }
+ break;
+ case 10:
+ if (! ver_true) {
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ }
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr_lk("%sfor 10 byte commands, maximum number of blocks is "
+ "%d\n", my_name, 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ if (normal_rw && (cdl > 0)) {
+ if (cdl & 0x4)
+ cdbp[1] |= 0x1;
+ if (cdl & 0x3)
+ cdbp[14] |= ((cdl & 0x3) << 6);
+ }
+ break;
+ default:
+ pr2serr_lk("%sexpected cdb size of 6, 10, 12, or 16 but got %d\n",
+ my_name, cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+process_mrq_response(Rq_elem * rep, const struct sg_io_v4 * ctl_v4p,
+ const struct sg_io_v4 * a_v4p, int num_mrq,
+ uint32_t & good_inblks, uint32_t & good_outblks,
+ bool & last_err_on_in)
+{
+ struct global_collection * clp = rep->clp;
+ bool ok, all_good;
+ bool sb_in_co = !!(ctl_v4p->response);
+ int id = rep->id;
+ int resid = ctl_v4p->din_resid;
+ int sres = ctl_v4p->spare_out;
+ int n_subm = num_mrq - ctl_v4p->dout_resid;
+ int n_cmpl = ctl_v4p->info;
+ int n_good = 0;
+ int hole_count = 0;
+ int cat = 0;
+ int vb = clp->verbose;
+ int k, j, f1, slen;
+ char b[160];
+
+ good_inblks = 0;
+ good_outblks = 0;
+ if (vb > 2)
+ pr2serr_lk("[thread_id=%d] %s: num_mrq=%d, n_subm=%d, n_cmpl=%d\n",
+ id, __func__, num_mrq, n_subm, n_cmpl);
+ if (n_subm < 0) {
+ pr2serr_lk("[%d] co.dout_resid(%d) > num_mrq(%d)\n", id,
+ ctl_v4p->dout_resid, num_mrq);
+ return -1;
+ }
+ if (n_cmpl != (num_mrq - resid))
+ pr2serr_lk("[%d] co.info(%d) != (num_mrq(%d) - co.din_resid(%d))\n"
+ "will use co.info\n", id, n_cmpl, num_mrq, resid);
+ if (n_cmpl > n_subm) {
+ pr2serr_lk("[%d] n_cmpl(%d) > n_subm(%d), use n_subm for both\n",
+ id, n_cmpl, n_subm);
+ n_cmpl = n_subm;
+ }
+ if (sres) {
+ pr2serr_lk("[%d] secondary error: %s [%d], info=0x%x\n", id,
+ strerror(sres), sres, ctl_v4p->info);
+ if (E2BIG == sres) {
+ sg_take_snap(rep->infd, id, true);
+ sg_take_snap(rep->outfd, id, true);
+ }
+ }
+ /* Check if those submitted have finished or not. N.B. If there has been
+ * an error then there may be "holes" (i.e. info=0x0) in the array due
+ * to completions being out-of-order. */
+ for (k = 0, j = 0; ((k < num_mrq) && (j < n_subm));
+ ++k, j += f1, ++a_v4p) {
+ slen = a_v4p->response_len;
+ if (! (SG_INFO_MRQ_FINI & a_v4p->info))
+ ++hole_count;
+ ok = true;
+ f1 = !!(a_v4p->info); /* want to skip n_subm count if info is 0x0 */
+ if (SG_INFO_CHECK & a_v4p->info) {
+ if ((0 == k) && (SGV4_FLAG_META_OUT_IF & ctl_v4p->flags) &&
+ (UINT32_MAX == a_v4p->info)) {
+ hole_count = 0;
+ n_good = num_mrq;
+ good_inblks = rep->a_mrq_din_blks;
+ good_outblks = rep->a_mrq_dout_blks;
+ break;
+ }
+ ok = false;
+ pr2serr_lk("[%d] a_v4[%d]: SG_INFO_CHECK set [%s]\n", id, k,
+ sg_info_str(a_v4p->info, sizeof(b), b));
+ }
+ if (sg_scsi_status_is_bad(a_v4p->device_status) ||
+ a_v4p->transport_status || a_v4p->driver_status) {
+ ok = false;
+ last_err_on_in = ! (a_v4p->flags & SGV4_FLAG_DO_ON_OTHER);
+ if (SAM_STAT_CHECK_CONDITION != a_v4p->device_status) {
+ pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
+ if (vb)
+ lk_chk_n_print4(" >>", a_v4p, vb > 4);
+ }
+ }
+ if (slen > 0) {
+ struct sg_scsi_sense_hdr ssh;
+ const uint8_t *sbp = (const uint8_t *)
+ (sb_in_co ? ctl_v4p->response : a_v4p->response);
+
+ if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
+ (ssh.response_code >= 0x70)) {
+ if (ssh.response_code & 0x1) {
+ ok = true;
+ last_err_on_in = false;
+ } else
+ cat = sg_err_category_sense(sbp, slen);
+ if (SPC_SK_MISCOMPARE == ssh.sense_key)
+ ++num_miscompare;
+
+ pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
+ if (vb)
+ lk_chk_n_print4(" >>", a_v4p, vb > 4);
+ }
+ } else if (! ok)
+ cat = SG_LIB_CAT_OTHER;
+ if (ok && f1) {
+ ++n_good;
+ if (a_v4p->dout_xfer_len >= (uint32_t)rep->bs)
+ good_outblks += (a_v4p->dout_xfer_len - a_v4p->dout_resid) /
+ rep->bs;
+ if (a_v4p->din_xfer_len >= (uint32_t)rep->bs)
+ good_inblks += (a_v4p->din_xfer_len - a_v4p->din_resid) /
+ rep->bs;
+ }
+ if (! ok) {
+ if ((a_v4p->dout_xfer_len > 0) || (! clp->in_flags.coe))
+ rep->stop_after_write = true;
+ }
+ } /* end of request array scan loop */
+ if ((n_subm == num_mrq) || (vb < 3))
+ goto fini;
+ pr2serr_lk("[%d] checking response array _beyond_ number of "
+ "submissions [%d] to num_mrq:\n", id, k);
+ for (all_good = true; k < num_mrq; ++k, ++a_v4p) {
+ if (SG_INFO_MRQ_FINI & a_v4p->info) {
+ pr2serr_lk("[%d] a_v4[%d]: unexpected SG_INFO_MRQ_FINI set [%s]\n",
+ id, k, sg_info_str(a_v4p->info, sizeof(b), b));
+ all_good = false;
+ }
+ if (a_v4p->device_status || a_v4p->transport_status ||
+ a_v4p->driver_status) {
+ pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
+ lk_chk_n_print4(" ", a_v4p, vb > 4);
+ all_good = false;
+ }
+ }
+ if (all_good)
+ pr2serr_lk(" ... all good\n");
+fini:
+ if (cat > 0)
+ clp->reason_res.store(cat);
+ return n_good;
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+sg_half_segment_mrq0(Rq_elem * rep, scat_gath_iter & sg_it, bool is_wr,
+ int seg_blks, uint8_t *dp)
+{
+ int k, res, fd, pack_id_base, id, rflags;
+ int num, kk, lin_blks, cdbsz, err;
+ uint32_t q_blks = 0;
+ struct global_collection * clp = rep->clp;
+ cdb_arr_t t_cdb {};
+ struct sg_io_v4 t_v4 {};
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * flagsp = is_wr ? &clp->out_flags : &clp->in_flags;
+ int vb = clp->verbose;
+
+ id = rep->id;
+ pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+ rflags = 0;
+ fd = is_wr ? rep->outfd : rep->infd;
+ if (flagsp->mmap && (rep->outregfd >= 0))
+ rflags |= SGV4_FLAG_MMAP_IO;
+ if (flagsp->dio)
+ rflags |= SGV4_FLAG_DIRECT_IO;
+ if (flagsp->qhead)
+ rflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (flagsp->qtail)
+ rflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (flagsp->polled)
+ rflags |= SGV4_FLAG_POLLED;
+
+ for (k = 0, num = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ lin_blks = sg_it.linear_for_n_blks(kk);
+ num = lin_blks;
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num);
+ break;
+ }
+
+ /* First build the command/request for the read-side */
+ cdbsz = is_wr ? clp->cdbsz_out : clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, sg_it.current_lba(),
+ false, is_wr, flagsp->fua, flagsp->dpo,
+ flagsp->cdl);
+ if (res) {
+ pr2serr_lk("[%d] %s: sg_build_scsi_cdb() failed\n", id, __func__);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("cdb: ", t_cdb.data(), cdbsz, true);
+
+ t_v4p->guard = 'Q';
+ t_v4p->request = (uint64_t)t_cdb.data();
+ t_v4p->usr_ptr = t_v4p->request;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->flags = rflags;
+ t_v4p->request_len = cdbsz;
+ if (is_wr) {
+ t_v4p->dout_xfer_len = num * rep->bs;
+ t_v4p->dout_xferp = (uint64_t)(dp + (q_blks * rep->bs));
+ t_v4p->din_xfer_len = 0;
+ } else {
+ t_v4p->din_xfer_len = num * rep->bs;
+ t_v4p->din_xferp = (uint64_t)(dp + (q_blks * rep->bs));
+ t_v4p->dout_xfer_len = 0;
+ }
+ t_v4p->timeout = clp->cmd_timeout;
+ t_v4p->request_extra = pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+mrq0_again:
+ res = ioctl(fd, SG_IO, t_v4p);
+ err = errno;
+ if (vb > 5)
+ v4hdr_out_lk("sg_half_segment_mrq0: >> after ioctl(SG_IO)",
+ t_v4p, id, false);
+ if (res < 0) {
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* so other threads can progress */
+ goto mrq0_again;
+ }
+ pr2serr_lk("[%d] %s: ioctl(SG_IO)-->%d, errno=%d: %s\n", id,
+ __func__, res, err, strerror(err));
+ return -err;
+ }
+ if (t_v4p->device_status || t_v4p->transport_status ||
+ t_v4p->driver_status) {
+ rep->stop_now = true;
+ pr2serr_lk("[%d] t_v4[%d]:\n", id, k);
+ lk_chk_n_print4(" ", t_v4p, vb > 4);
+ return q_blks;
+ }
+ q_blks += num;
+ sg_it.add_blks(num);
+ }
+ return q_blks;
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+sg_half_segment(Rq_elem * rep, scat_gath_iter & sg_it, bool is_wr,
+ int seg_blks, uint8_t *dp, vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, rflags;
+ int num, kk, lin_blks, cdbsz, num_good, err;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks, out_fin_blks;
+ uint32_t mrq_q_blks = 0;
+ uint32_t in_mrq_q_blks = 0;
+ uint32_t out_mrq_q_blks = 0;
+ const int max_cdb_sz = MAX_SCSI_CDB_SZ;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 ctl_v4 {}; /* MRQ control object */
+ struct global_collection * clp = rep->clp;
+ const char * iosub_str = "SG_IOSUBMIT(variable blocking)";
+ char b[80];
+ cdb_arr_t t_cdb {};
+ struct sg_io_v4 t_v4 {};
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * flagsp = is_wr ? &clp->out_flags : &clp->in_flags;
+ bool serial = flagsp->serial;
+ bool err_on_in = false;
+ int vb = clp->verbose;
+
+ id = rep->id;
+ b_len = sizeof(b);
+ if (serial)
+ iosub_str = "SG_IO(ordered blocking)";
+
+ a_cdb.clear();
+ a_v4.clear();
+ rep->a_mrq_din_blks = 0;
+ rep->a_mrq_dout_blks = 0;
+ mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+
+ rflags = 0;
+ if (flagsp->mmap && (rep->outregfd >= 0))
+ rflags |= SGV4_FLAG_MMAP_IO;
+ if (flagsp->dio)
+ rflags |= SGV4_FLAG_DIRECT_IO;
+ if (flagsp->qhead)
+ rflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (flagsp->qtail)
+ rflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (flagsp->polled)
+ rflags |= SGV4_FLAG_POLLED;
+
+ for (k = 0, num = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ lin_blks = sg_it.linear_for_n_blks(kk);
+ num = lin_blks;
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num);
+ break;
+ }
+
+ /* First build the command/request for the read-side */
+ cdbsz = is_wr ? clp->cdbsz_out : clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, sg_it.current_lba(),
+ false, is_wr, flagsp->fua, flagsp->dpo,
+ flagsp->cdl);
+ if (res) {
+ pr2serr_lk("[%d] %s: sg_build_scsi_cdb() failed\n", id, __func__);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+
+ t_v4p->guard = 'Q';
+ t_v4p->flags = rflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->flags = rflags;
+ t_v4p->usr_ptr = (uint64_t)&a_cdb[a_cdb.size() - 1];
+ if (is_wr) {
+ rep->a_mrq_dout_blks += num;
+ t_v4p->dout_xfer_len = num * rep->bs;
+ t_v4p->dout_xferp = (uint64_t)(dp + (mrq_q_blks * rep->bs));
+ t_v4p->din_xfer_len = 0;
+ } else {
+ rep->a_mrq_din_blks += num;
+ t_v4p->din_xfer_len = num * rep->bs;
+ t_v4p->din_xferp = (uint64_t)(dp + (mrq_q_blks * rep->bs));
+ t_v4p->dout_xfer_len = 0;
+ }
+ t_v4p->timeout = clp->cmd_timeout;
+ mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+ a_v4.push_back(t_v4);
+
+ sg_it.add_blks(num);
+ }
+
+ if (rep->only_in_sg)
+ fd = rep->infd;
+ else if (rep->only_out_sg)
+ fd = rep->outfd;
+ else {
+ pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__);
+ return -EINVAL;
+ }
+ num_mrq = a_v4.size();
+ a_v4p = a_v4.data();
+ res = 0;
+ ctl_v4.guard = 'Q';
+ ctl_v4.request_len = a_cdb.size() * max_cdb_sz;
+ ctl_v4.request = (uint64_t)a_cdb.data();
+ ctl_v4.max_response_len = sizeof(rep->sb);
+ ctl_v4.response = (uint64_t)rep->sb;
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS;
+ if (! flagsp->coe)
+ ctl_v4.flags |= SGV4_FLAG_STOP_IF;
+ if (clp->mrq_polled)
+ ctl_v4.flags |= SGV4_FLAG_POLLED;
+ if (clp->in_flags.mout_if || clp->out_flags.mout_if) {
+ ctl_v4.flags |= SGV4_FLAG_META_OUT_IF;
+ if (num_mrq > 0)
+ a_v4[0].info = UINT32_MAX;
+ }
+ ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */
+ ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */
+ ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ if (false /* allow_mrq_abort */) {
+ ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(ctl_v4.request_extra);
+ }
+
+ if (vb && vb_first_time.load()) {
+ pr2serr_lk("First controlling object output by ioctl(%s), flags: "
+ "%s\n", iosub_str, sg_flags_str(ctl_v4.flags, b_len, b));
+ vb_first_time.store(false);
+ } else if (vb > 4) {
+ pr2serr_lk("[%d] %s: >> Control object _before_ ioctl(%s):\n", id,
+ __func__, iosub_str);
+ }
+ if (vb > 4) {
+ if (vb > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk(">> Control object before", &ctl_v4, id, false);
+ }
+
+try_again:
+ if (!after1 && (vb > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, serial ? mrq_ob_s : mrq_vb_s);
+ }
+ if (serial)
+ res = ioctl(fd, SG_IO, &ctl_v4);
+ else
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4); /* overlapping commands */
+ if (res < 0) {
+ err = errno;
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* allow another thread to progress */
+ goto try_again;
+ }
+ pr2serr_lk("[%d] %s: ioctl(%s, %s)-->%d, errno=%d: %s\n", id,
+ __func__, iosub_str, sg_flags_str(ctl_v4.flags, b_len, b),
+ res, err, strerror(err));
+ return -err;
+ }
+ if (vb > 4) {
+ pr2serr_lk("%s: >> Control object after ioctl(%s) seg_blks=%d:\n",
+ __func__, iosub_str, o_seg_blks);
+ if (vb > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk(">> Control object after", &ctl_v4, id, false);
+ if (vb > 5) {
+ for (k = 0; k < num_mrq; ++k) {
+ if ((vb > 6) || a_v4p[k].info) {
+ snprintf(b, b_len, "a_v4[%d/%d]", k, num_mrq);
+ v4hdr_out_lk(b, (a_v4p + k), id, true);
+ }
+ }
+ }
+ }
+ num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
+ out_fin_blks, err_on_in);
+ if (is_wr)
+ out_mrq_q_blks = mrq_q_blks;
+ else
+ in_mrq_q_blks = mrq_q_blks;
+ if (vb > 2)
+ pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; "
+ "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good,
+ in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks);
+
+ if (clp->ese) {
+ int sres = ctl_v4.spare_out;
+
+ if (sres != 0) {
+ clp->reason_res.store(sg_convert_errno(sres));
+ pr2serr_lk("Exit due to secondary error [%d]\n", sres);
+ return -sres;
+ }
+ }
+ if (num_good < 0)
+ return -ENODATA;
+ else {
+ if (num_good < num_mrq) {
+ int resid_blks = in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0) {
+ rep->in_rem_count += resid_blks;
+ rep->stop_after_write = ! (err_on_in && clp->in_flags.coe);
+ }
+
+ resid_blks = out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0) {
+ rep->out_rem_count += resid_blks;
+ rep->stop_after_write = ! (! err_on_in && clp->out_flags.coe);
+ }
+ }
+ }
+ return is_wr ? out_fin_blks : in_fin_blks;
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks)
+{
+ int k, kk, res, id, num, d_off;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;
+ struct global_collection * clp = rep->clp;
+
+ id = rep->id;
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = i_sg_it.linear_for_n_blks(kk);
+ res = normal_in_rd(rep, i_sg_it.current_lba(), num,
+ d_off * rep->bs);
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ i_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ in_fin_blks = seg_blks;
+
+ if (FT_DEV_NULL == clp->out_type)
+ goto fini;
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = o_sg_it.linear_for_n_blks(kk);
+ res = normal_out_wr(rep, o_sg_it.current_lba(), num,
+ d_off * rep->bs);
+ if (res < num) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ }
+ o_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ if (rep->in_resid_bytes > 0) {
+ res = extra_out_wr(rep, rep->in_resid_bytes, d_off * rep->bs);
+ if (res < 0)
+ pr2serr_lk("[%d] %s: extr out failed d_off=%d, err=%d\n", id,
+ __func__, d_off, -res);
+ rep->in_resid_bytes = 0;
+ }
+ seg_blks = d_off;
+ out_fin_blks = seg_blks;
+
+fini:
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) {
+ int resid_blks = o_seg_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+ resid_blks = o_seg_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ }
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ bool in_is_normal = ! rep->only_in_sg;
+ int k, kk, res, id, num, d_off;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;
+ struct global_collection * clp = rep->clp;
+
+ id = rep->id;
+ a_cdb.clear();
+ a_v4.clear();
+
+ if (in_is_normal) { /* in: normal --> out : sg */
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = i_sg_it.linear_for_n_blks(kk);
+ res = normal_in_rd(rep, i_sg_it.current_lba(), num,
+ d_off * rep->bs);
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ i_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ in_fin_blks = seg_blks;
+
+ if (rep->in_resid_bytes > 0) {
+ ++seg_blks;
+ rep->in_resid_bytes = 0;
+ }
+ if (clp->mrq_eq_0)
+ res = sg_half_segment_mrq0(rep, o_sg_it, true /* is_wr */,
+ seg_blks, rep->buffp);
+ else
+ res = sg_half_segment(rep, o_sg_it, true /* is_wr */, seg_blks,
+ rep->buffp, a_cdb, a_v4);
+ if (res < seg_blks) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: sg out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ goto fini;
+ }
+ rep->stop_after_write = true;
+ }
+ seg_blks = res;
+ out_fin_blks = seg_blks;
+
+ } else { /* in: sg --> out: normal */
+ if (clp->mrq_eq_0)
+ res = sg_half_segment_mrq0(rep, i_sg_it, false, seg_blks,
+ rep->buffp);
+ else
+ res = sg_half_segment(rep, i_sg_it, false, seg_blks, rep->buffp,
+ a_cdb, a_v4);
+ if (res < seg_blks) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: sg in failed, err=%d\n", id, __func__,
+ -res);
+ goto fini;
+ }
+ rep->stop_after_write = true;
+ }
+ seg_blks = res;
+ in_fin_blks = seg_blks;
+
+ if (FT_DEV_NULL == clp->out_type) {
+ out_fin_blks = seg_blks;/* so finish logic doesn't suspect ... */
+ goto bypass;
+ }
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = o_sg_it.linear_for_n_blks(kk);
+ res = normal_out_wr(rep, o_sg_it.current_lba(), num,
+ d_off * rep->bs);
+ if (res < num) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ }
+ o_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ out_fin_blks = seg_blks;
+ }
+bypass:
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) {
+ int resid_blks = o_seg_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+ resid_blks = o_seg_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ }
+fini:
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+/* This function sets up a multiple request (mrq) transaction and sends it
+ * to the pass-through. Returns number of blocks processed (==seg_blks for
+ * all good) or a negative error number. */
+static int
+do_both_sg_segment_mrq0(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks)
+{
+ int k, kk, res, pack_id_base, id, iflags, oflags;
+ int num, i_lin_blks, o_lin_blks, cdbsz, err;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;
+ struct global_collection * clp = rep->clp;
+ int vb = clp->verbose;
+ cdb_arr_t t_cdb {};
+ struct sg_io_v4 t_v4 {};
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * iflagsp = &clp->in_flags;
+ struct flags_t * oflagsp = &clp->out_flags;
+ const char * const a_ioctl_s = "do_both_sg_segment_mrq0: after "
+ "ioctl(SG_IO)";
+
+ id = rep->id;
+ pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+
+ iflags = SGV4_FLAG_SHARE;
+ if (iflagsp->mmap && (rep->outregfd >= 0))
+ iflags |= SGV4_FLAG_MMAP_IO;
+ else
+ iflags |= SGV4_FLAG_NO_DXFER;
+ if (iflagsp->dio)
+ iflags |= SGV4_FLAG_DIRECT_IO;
+ if (iflagsp->qhead)
+ iflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (iflagsp->qtail)
+ iflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (iflagsp->polled)
+ iflags |= SGV4_FLAG_POLLED;
+
+ oflags = SGV4_FLAG_SHARE | SGV4_FLAG_NO_DXFER;
+ if (oflagsp->dio)
+ oflags |= SGV4_FLAG_DIRECT_IO;
+ if (oflagsp->qhead)
+ oflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (oflagsp->qtail)
+ oflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (oflagsp->polled)
+ oflags |= SGV4_FLAG_POLLED;
+
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ i_lin_blks = i_sg_it.linear_for_n_blks(kk);
+ o_lin_blks = o_sg_it.linear_for_n_blks(kk);
+ num = min<int>(i_lin_blks, o_lin_blks);
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: min(i_lin_blks=%d o_lin_blks=%d) < 1\n", id,
+ __func__, i_lin_blks, o_lin_blks);
+ break;
+ }
+
+ /* First build the command/request for the read-side*/
+ cdbsz = clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ i_sg_it.current_lba(), false, false,
+ iflagsp->fua, iflagsp->dpo, iflagsp->cdl);
+ if (res) {
+ pr2serr_lk("%s: t=%d: input sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("input cdb: ", t_cdb.data(), cdbsz, true);
+
+ t_v4p->guard = 'Q';
+ t_v4p->request = (uint64_t)t_cdb.data();
+ t_v4p->usr_ptr = t_v4p->request;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->flags = iflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->din_xfer_len = num * rep->bs;
+ t_v4p->dout_xfer_len = 0;
+ t_v4p->timeout = clp->cmd_timeout;
+ t_v4p->request_extra = pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+mrq0_again:
+ res = ioctl(rep->infd, SG_IO, t_v4p);
+ err = errno;
+ if (vb > 5)
+ v4hdr_out_lk(a_ioctl_s, t_v4p, id, false);
+ if (res < 0) {
+ if (E2BIG == err)
+ sg_take_snap(rep->infd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* so other threads can progress */
+ goto mrq0_again;
+ }
+ pr2serr_lk("[%d] %s: ioctl(SG_IO, read-side)-->%d, errno=%d: "
+ "%s\n", id, __func__, res, err, strerror(err));
+ return -err;
+ }
+ if (t_v4p->device_status || t_v4p->transport_status ||
+ t_v4p->driver_status) {
+ rep->stop_now = true;
+ pr2serr_lk("[%d] t_v4[%d]:\n", id, k);
+ lk_chk_n_print4(" ", t_v4p, vb > 4);
+ return min<int>(in_fin_blks, out_fin_blks);
+ }
+ rep->in_local_count += num;
+ in_fin_blks += num;
+
+ /* Now build the command/request for write-side (WRITE or VERIFY) */
+ cdbsz = clp->cdbsz_out;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ o_sg_it.current_lba(), clp->verify, true,
+ oflagsp->fua, oflagsp->dpo, oflagsp->cdl);
+ if (res) {
+ pr2serr_lk("%s: t=%d: output sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("output cdb: ", t_cdb.data(), cdbsz, true);
+
+ t_v4p->guard = 'Q';
+ t_v4p->request = (uint64_t)t_cdb.data();
+ t_v4p->usr_ptr = t_v4p->request;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->flags = oflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->din_xfer_len = 0;
+ t_v4p->dout_xfer_len = num * rep->bs;
+ t_v4p->timeout = clp->cmd_timeout;
+ t_v4p->request_extra = pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+mrq0_again2:
+ res = ioctl(rep->outfd, SG_IO, t_v4p);
+ err = errno;
+ if (vb > 5)
+ v4hdr_out_lk(a_ioctl_s, t_v4p, id, false);
+ if (res < 0) {
+ if (E2BIG == err)
+ sg_take_snap(rep->outfd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* so other threads can progress */
+ goto mrq0_again2;
+ }
+ pr2serr_lk("[%d] %s: ioctl(SG_IO, write-side)-->%d, errno=%d: "
+ "%s\n", id, __func__, res, err, strerror(err));
+ return -err;
+ }
+ if (t_v4p->device_status || t_v4p->transport_status ||
+ t_v4p->driver_status) {
+ rep->stop_now = true;
+ pr2serr_lk("[%d] t_v4[%d]:\n", id, k);
+ lk_chk_n_print4(" ", t_v4p, vb > 4);
+ return min<int>(in_fin_blks, out_fin_blks);
+ }
+ rep->out_local_count += num;
+ out_fin_blks += num;
+
+ i_sg_it.add_blks(num);
+ o_sg_it.add_blks(num);
+ }
+ return min<int>(in_fin_blks, out_fin_blks);
+}
+
+/* This function sets up a multiple request (mrq) transaction and sends it
+ * to the pass-through. Returns number of blocks processed (==seg_blks for
+ * all good) or a negative error number. */
+static int
+do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ bool err_on_in = false;
+ int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, iflags, oflags;
+ int num, kk, i_lin_blks, o_lin_blks, cdbsz, num_good, err;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;;
+ uint32_t in_mrq_q_blks = 0;
+ uint32_t out_mrq_q_blks = 0;
+ const int max_cdb_sz = MAX_SCSI_CDB_SZ;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 ctl_v4 {}; /* MRQ control object */
+ struct global_collection * clp = rep->clp;
+ const char * iosub_str = "SG_IOSUBMIT(svb)";
+ char b[80];
+ cdb_arr_t t_cdb {};
+ struct sg_io_v4 t_v4 {};
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * iflagsp = &clp->in_flags;
+ struct flags_t * oflagsp = &clp->out_flags;
+ int vb = clp->verbose;
+
+ id = rep->id;
+ b_len = sizeof(b);
+
+ a_cdb.clear();
+ a_v4.clear();
+ rep->a_mrq_din_blks = 0;
+ rep->a_mrq_dout_blks = 0;
+ mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+
+ iflags = SGV4_FLAG_SHARE;
+ if (iflagsp->mmap && (rep->outregfd >= 0))
+ iflags |= SGV4_FLAG_MMAP_IO;
+ else
+ iflags |= SGV4_FLAG_NO_DXFER;
+ if (iflagsp->dio)
+ iflags |= SGV4_FLAG_DIRECT_IO;
+ if (iflagsp->qhead)
+ iflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (iflagsp->qtail)
+ iflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (iflagsp->polled)
+ iflags |= SGV4_FLAG_POLLED;
+
+ oflags = SGV4_FLAG_SHARE | SGV4_FLAG_NO_DXFER;
+ if (oflagsp->dio)
+ oflags |= SGV4_FLAG_DIRECT_IO;
+ if (oflagsp->qhead)
+ oflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (oflagsp->qtail)
+ oflags |= SGV4_FLAG_Q_AT_TAIL;
+ if (oflagsp->polled)
+ oflags |= SGV4_FLAG_POLLED;
+ oflags |= SGV4_FLAG_DO_ON_OTHER;
+
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ i_lin_blks = i_sg_it.linear_for_n_blks(kk);
+ o_lin_blks = o_sg_it.linear_for_n_blks(kk);
+ num = min<int>(i_lin_blks, o_lin_blks);
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: min(i_lin_blks=%d o_lin_blks=%d) < 1\n", id,
+ __func__, i_lin_blks, o_lin_blks);
+ break;
+ }
+
+ /* First build the command/request for the read-side*/
+ cdbsz = clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ i_sg_it.current_lba(), false, false,
+ iflagsp->fua, iflagsp->dpo, iflagsp->cdl);
+ if (res) {
+ pr2serr_lk("%s: t=%d: input sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("input cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+
+ t_v4p->guard = 'Q';
+ t_v4p->flags = iflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->usr_ptr = (uint64_t)&a_cdb[a_cdb.size() - 1];
+ t_v4p->din_xfer_len = num * rep->bs;
+ rep->a_mrq_din_blks += num;
+ t_v4p->dout_xfer_len = 0;
+ t_v4p->timeout = clp->cmd_timeout;
+ in_mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+ a_v4.push_back(t_v4);
+
+ /* Now build the command/request for write-side (WRITE or VERIFY) */
+ cdbsz = clp->cdbsz_out;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ o_sg_it.current_lba(), clp->verify, true,
+ oflagsp->fua, oflagsp->dpo, oflagsp->cdl);
+ if (res) {
+ pr2serr_lk("%s: t=%d: output sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (vb > 3)
+ lk_print_command_len("output cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+ t_v4p->guard = 'Q';
+ t_v4p->flags = oflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->response = (uint64_t)rep->sb;
+ t_v4p->max_response_len = sizeof(rep->sb);
+ t_v4p->usr_ptr = (uint64_t)&a_cdb[a_cdb.size() - 1];
+ t_v4p->din_xfer_len = 0;
+ t_v4p->dout_xfer_len = num * rep->bs;
+ rep->a_mrq_dout_blks += num;
+ t_v4p->timeout = clp->cmd_timeout;
+ out_mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(t_v4p->request_extra);
+ a_v4.push_back(t_v4);
+
+ i_sg_it.add_blks(num);
+ o_sg_it.add_blks(num);
+ }
+
+ if (vb > 6) {
+ pr2serr_lk("%s: t=%d: a_v4 array contents:\n", __func__, id);
+ hex2stderr_lk((const uint8_t *)a_v4.data(),
+ a_v4.size() * sizeof(struct sg_io_v4), 1);
+ }
+ if (rep->both_sg || rep->same_sg)
+ fd = rep->infd; /* assume share to rep->outfd */
+ else {
+ pr2serr_lk("[%d] %s: why am I here? Want 2 sg devices\n", id,
+ __func__);
+ res = -1;
+ goto fini;
+ }
+ num_mrq = a_v4.size();
+ a_v4p = a_v4.data();
+ res = 0;
+ ctl_v4.guard = 'Q';
+ ctl_v4.request_len = a_cdb.size() * max_cdb_sz;
+ ctl_v4.request = (uint64_t)a_cdb.data();
+ ctl_v4.max_response_len = sizeof(rep->sb);
+ ctl_v4.response = (uint64_t)rep->sb;
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_SHARE;
+ if (! (iflagsp->coe || oflagsp->coe))
+ ctl_v4.flags |= SGV4_FLAG_STOP_IF;
+ if ((! clp->verify) && clp->out_flags.order_wr)
+ ctl_v4.flags |= SGV4_FLAG_ORDERED_WR;
+ if (clp->mrq_polled)
+ ctl_v4.flags |= SGV4_FLAG_POLLED;
+ if (clp->in_flags.mout_if || clp->out_flags.mout_if) {
+ ctl_v4.flags |= SGV4_FLAG_META_OUT_IF;
+ if (num_mrq > 0)
+ a_v4[0].info = UINT32_MAX;
+ }
+ ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */
+ ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */
+ ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ if (false /* allow_mrq_abort */) {
+ ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ clp->most_recent_pack_id.store(ctl_v4.request_extra);
+ }
+
+ if (vb && vb_first_time.load()) {
+ pr2serr_lk("First controlling object output by ioctl(%s), flags: "
+ "%s\n", iosub_str, sg_flags_str(ctl_v4.flags, b_len, b));
+ vb_first_time.store(false);
+ } else if (vb > 4)
+ pr2serr_lk("%s: >> Control object _before_ ioctl(%s):\n", __func__,
+ iosub_str);
+ if (vb > 4) {
+ if (vb > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk(">> Control object before", &ctl_v4, id, false);
+ }
+
+try_again:
+ if (!after1 && (vb > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_svb_s);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4);
+ if (res < 0) {
+ err = errno;
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* allow another thread to progress */
+ goto try_again;
+ }
+ pr2serr_lk("%s: ioctl(%s, %s)-->%d, errno=%d: %s\n", __func__,
+ iosub_str, sg_flags_str(ctl_v4.flags, b_len, b), res, err,
+ strerror(err));
+ res = -err;
+ goto fini;
+ }
+ if (vb > 4) {
+ pr2serr_lk("%s: >> Control object after ioctl(%s) seg_blks=%d:\n",
+ __func__, iosub_str, o_seg_blks);
+ if (vb > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk(">> Control object after", &ctl_v4, id, false);
+ if (vb > 5) {
+ for (k = 0; k < num_mrq; ++k) {
+ if ((vb > 6) || a_v4p[k].info) {
+ snprintf(b, b_len, "a_v4[%d/%d]", k, num_mrq);
+ v4hdr_out_lk(b, (a_v4p + k), id, true);
+ }
+ }
+ }
+ }
+ num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
+ out_fin_blks, err_on_in);
+ if (vb > 2)
+ pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; "
+ "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good,
+ in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks);
+
+ if (clp->ese) {
+ int sres = ctl_v4.spare_out;
+
+ if (sres != 0) {
+ clp->reason_res.store(sg_convert_errno(sres));
+ pr2serr_lk("Exit due to secondary error [%d]\n", sres);
+ return -sres;
+ }
+ }
+ if (num_good < 0)
+ res = -ENODATA;
+ else {
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if (num_good < num_mrq) { /* reduced number completed */
+ int resid_blks = in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0) {
+ rep->in_rem_count += resid_blks;
+ rep->stop_after_write = ! (err_on_in && clp->in_flags.coe);
+ }
+
+ resid_blks = out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0) {
+ rep->out_rem_count += resid_blks;
+ rep->stop_after_write = ! ((! err_on_in) &&
+ clp->out_flags.coe);
+ }
+ }
+ }
+fini:
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+#if 0
+/* Returns number found and (partially) processed. 'num' is the number of
+ * completions to wait for when > 0. When 'num' is zero check all inflight
+ * request on 'fd' and return quickly if none completed (i.e. don't wait)
+ * If error return negative errno and if no request inflight or waiting
+ * then return -9999 . */
+static int
+sg_blk_poll(int fd, int num)
+{
+ int res;
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_rd_mask |= SG_SEIM_BLK_POLL;
+ seip->sei_wr_mask |= SG_SEIM_BLK_POLL;
+ seip->num = (num < 0) ? 0 : num;
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0) {
+ pr2serr_lk("%s: SG_SET_GET_EXTENDED(BLK_POLL) error: %s\n",
+ __func__, strerror(errno));
+ return res;
+ }
+ return (seip->num == -1) ? -9999 : seip->num;
+}
+#endif
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+/* Returns the number of times either 'ch1' or 'ch2' is found in
+ * string 's' given the string's length. */
+int
+num_either_ch_in_str(const char * s, int slen, int ch1, int ch2)
+{
+ int k;
+ int res = 0;
+
+ while (--slen >= 0) {
+ k = s[slen];
+ if ((ch1 == k) || (ch2 == k))
+ ++res;
+ }
+ return res;
+}
+
+/* Allocates and then populates a scatter gether list (array) and returns
+ * it via *sgl_pp. Return of 0 is okay, else error number (in which case
+ * NULL is written to *sgl_pp) . */
+static int
+skip_seek(struct global_collection *clp, const char * key, const char * buf,
+ bool is_skip, bool ignore_verbose)
+{
+ bool def_hex = false;
+ int len;
+ int vb = clp->verbose; /* needs to appear before skip/seek= on cl */
+ int64_t ll;
+ const char * cp;
+ class scat_gath_list & either_list = is_skip ? clp->i_sgl : clp->o_sgl;
+
+ if (ignore_verbose)
+ vb = 0;
+ len = (int)strlen(buf);
+ if ((('-' == buf[0]) && (1 == len)) || ((len > 1) && ('@' == buf[0])) ||
+ ((len > 2) && ('H' == toupper(buf[0])) && ('@' == buf[1]))) {
+ if ('H' == toupper(buf[0])) {
+ cp = buf + 2;
+ def_hex = true;
+ } else if ('-' == buf[0])
+ cp = buf;
+ else
+ cp = buf + 1;
+ if (! either_list.load_from_file(cp, def_hex, clp->flexible, true)) {
+ pr2serr("bad argument to '%s=' [err=%d]\n", key,
+ either_list.m_errno);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (num_either_ch_in_str(buf, len, ',', ' ') > 0) {
+ if (! either_list.load_from_cli(buf, vb > 0)) {
+ pr2serr("bad command line argument to '%s='\n", key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else { /* single number on command line (e.g. skip=1234) */
+ ll = sg_get_llnum(buf);
+ if ((ll < 0) || (ll > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("bad argument to '%s='\n", key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ either_list.append_1or(0, ll);
+ if (vb > 1)
+ pr2serr("%s: singleton, half a degenerate sgl element\n", key);
+ }
+
+ either_list.sum_scan(key, vb > 3 /* bool show_sgl */, vb > 1);
+ return 0;
+}
+
+static bool
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return false;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "00"))
+ fp->zero = true;
+ else if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "coe"))
+ fp->coe = true;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "ff"))
+ fp->ff = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "hipri"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "masync"))
+ fp->masync = true;
+ else if (0 == strcmp(cp, "mmap"))
+ ++fp->mmap; /* mmap > 1 stops munmap() being called */
+ else if (0 == strcmp(cp, "nocreat"))
+ fp->nocreat = true;
+ else if (0 == strcmp(cp, "nodur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "no_dur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "no-dur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "nothresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "no_thresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "no-thresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "noxfer"))
+ ; /* accept but ignore */
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "ordered"))
+ fp->order_wr = true;
+ else if (0 == strcmp(cp, "order"))
+ fp->order_wr = true;
+ else if (0 == strcmp(cp, "polled"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "qhead"))
+ fp->qhead = true;
+ else if (0 == strcmp(cp, "qtail"))
+ fp->qtail = true;
+ else if (0 == strcmp(cp, "random"))
+ fp->random = true;
+ else if ((0 == strcmp(cp, "mout_if")) || (0 == strcmp(cp, "mout-if")))
+ fp->mout_if = true;
+ else if ((0 == strcmp(cp, "same_fds")) ||
+ (0 == strcmp(cp, "same-fds")))
+ fp->same_fds = true;
+ else if (0 == strcmp(cp, "serial"))
+ fp->serial = true;
+ else if (0 == strcmp(cp, "swait"))
+ ; /* accept but ignore */
+ else if (0 == strcmp(cp, "wq_excl"))
+ fp->wq_excl = true;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return false;
+ }
+ cp = np;
+ } while (cp);
+ return true;
+}
+
+/* Process arguments given to 'conv=" option. Returns 0 on success,
+ * 1 on error. */
+static int
+process_conv(const char * arg, struct flags_t * ifp, struct flags_t * ofp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no conversions found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "nocreat"))
+ ofp->nocreat = true;
+ else if (0 == strcmp(cp, "noerror"))
+ ifp->coe = true; /* will still fail on write error */
+ else if (0 == strcmp(cp, "notrunc"))
+ ; /* this is the default action of sg_dd so ignore */
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "sync"))
+ ; /* dd(susv4): pad errored block(s) with zeros but sg_dd does
+ * that by default. Typical dd use: 'conv=noerror,sync' */
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return 1;
+ }
+ cp = np;
+ } while (cp);
+ return 0;
+}
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+
+static int
+parse_cmdline_sanity(int argc, char * argv[], struct global_collection * clp,
+ char * outregf)
+{
+ bool contra = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool verify_given = false;
+ bool bpt_given = false;
+ int ibs = 0;
+ int obs = 0;
+ int ret = 0;
+ int k, keylen, n, res;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ char * skip_buf = NULL;
+ char * seek_buf = NULL;
+ const char * cp;
+ const char * ccp;
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ } else
+ continue;
+
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key, "bpt")) {
+ clp->bpt = sg_get_num(buf);
+ if ((clp->bpt < 0) || (clp->bpt > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bpt='\n", my_name);
+ goto syn_err;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key, "bs")) {
+ clp->bs = sg_get_num(buf);
+ if ((clp->bs < 0) || (clp->bs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "cdbsz")) {
+ ccp = strchr(buf, ',');
+ n = sg_get_num(buf);
+ if ((n < 0) || (n > 32)) {
+ pr2serr("%s: bad argument to 'cdbsz=', expect 6, 10, 12 or "
+ "16\n", my_name);
+ goto syn_err;
+ }
+ clp->cdbsz_in = n;
+ if (ccp) {
+ n = sg_get_num(ccp + 1);
+ if ((n < 0) || (n > 32)) {
+ pr2serr("%s: bad second argument to 'cdbsz=', expect 6, "
+ "10, 12 or 16\n", my_name);
+ goto syn_err;
+ }
+ }
+ clp->cdbsz_out = n;
+ clp->cdbsz_given = true;
+ } else if (0 == strcmp(key, "cdl")) {
+ ccp = strchr(buf, ',');
+ n = sg_get_num(buf);
+ if ((n < 0) || (n > 7)) {
+ pr2serr("%s: bad argument to 'cdl=', expect 0 to 7\n",
+ my_name);
+ goto syn_err;
+ }
+ clp->in_flags.cdl = n;
+ if (ccp) {
+ n = sg_get_num(ccp + 1);
+ if ((n < 0) || (n > 7)) {
+ pr2serr("%s: bad second argument to 'cdl=', expect 0 "
+ "to 7\n", my_name);
+ goto syn_err;
+ }
+ }
+ clp->out_flags.cdl = n;
+ clp->cdl_given = true;
+ } else if (0 == strcmp(key, "coe")) {
+ /* not documented, for compat with sgh_dd */
+ clp->in_flags.coe = !! sg_get_num(buf);
+ clp->out_flags.coe = clp->in_flags.coe;
+ } else if (0 == strcmp(key, "conv")) {
+ if (process_conv(buf, &clp->in_flags, &clp->out_flags)) {
+ pr2serr("%s: bad argument to 'conv='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "count")) {
+ if (clp->count_given) {
+ pr2serr("second 'count=' argument detected, only one "
+ "please\n");
+ contra = true;
+ goto syn_err;
+ }
+ if (0 != strcmp("-1", buf)) {
+ clp->dd_count = sg_get_llnum(buf);
+ if ((clp->dd_count < 0) ||
+ (clp->dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("%sbad argument to 'count='\n", my_name);
+ goto syn_err;
+ }
+ } /* treat 'count=-1' as calculate count (same as not given) */
+ clp->count_given = true;
+ } else if (0 == strcmp(key, "dio")) {
+ clp->in_flags.dio = !! sg_get_num(buf);
+ clp->out_flags.dio = clp->in_flags.dio;
+ } else if (0 == strcmp(key, "elemsz_kb")) {
+ n = sg_get_num(buf);
+ if ((n < 1) || (n > (MAX_BPT_VALUE / 1024))) {
+ pr2serr("elemsz_kb=EKB wants an integer > 0\n");
+ goto syn_err;
+ }
+ if (n & (n - 1)) {
+ pr2serr("elemsz_kb=EKB wants EKB to be power of 2\n");
+ goto syn_err;
+ }
+ clp->elem_sz = n * 1024;
+ } else if (0 == strcmp(key, "ese")) {
+ n = sg_get_num(buf);
+ if (n < 0) {
+ pr2serr("ese= wants 0 (default) or 1\n");
+ goto syn_err;
+ }
+ clp->ese = !!n;
+ } else if (0 == strcmp(key, "fua")) {
+ n = sg_get_num(buf);
+ if (n & 1)
+ clp->out_flags.fua = true;
+ if (n & 2)
+ clp->in_flags.fua = true;
+ } else if (0 == strcmp(key, "ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'ibs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "if")) {
+ if (clp->inf_v.size() > 0) {
+ pr2serr("Second 'if=' argument??\n");
+ goto syn_err;
+ } else {
+ cp = buf;
+ while ((ccp = strchr(cp, ','))) {
+ clp->inf_v.push_back(string(cp , ccp - cp));
+ cp = ccp + 1;
+ }
+ clp->inf_v.push_back(string(cp , strlen(cp)));
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (! process_flags(buf, &clp->in_flags)) {
+ pr2serr("%sbad argument to 'iflag='\n", my_name);
+ goto syn_err;
+ }
+ } else if ((0 == strcmp(key, "hipri")) ||
+ (0 == strcmp(key, "mrq")) ||
+ (0 == strcmp(key, "polled"))) {
+ if (isdigit(buf[0]))
+ cp = buf;
+ else {
+ pr2serr("%sonly mrq=NRQS or polled=NRQS which is a number "
+ "allowed here\n", my_name);
+ goto syn_err;
+ }
+ clp->mrq_num = sg_get_num(cp);
+ if (clp->mrq_num < 0) {
+ pr2serr("%sbad argument to 'mrq='\n", my_name);
+ goto syn_err;
+ }
+ if (0 == clp->mrq_num) {
+ clp->mrq_eq_0 = true;
+ clp->mrq_num = 1;
+ pr2serr("note: send single, non-mrq commands\n");
+ }
+ if ('m' != key[0])
+ clp->mrq_polled = true;
+ } else if ((0 == strcmp(key, "no_waitq")) ||
+ (0 == strcmp(key, "no-waitq"))) {
+ n = sg_get_num(buf);
+ if (-1 == n) {
+ pr2serr("%sbad argument to 'no_waitq=', expect 0 or 1\n",
+ my_name);
+ goto syn_err;
+ }
+ clp->in_flags.no_waitq = true;
+ clp->out_flags.no_waitq = true;
+ } else if (0 == strcmp(key, "obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'obs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (strcmp(key, "ofreg") == 0) {
+ if ('\0' != outregf[0]) {
+ pr2serr("Second OFREG argument??\n");
+ contra = true;
+ goto syn_err;
+ } else {
+ memcpy(outregf, buf, INOUTF_SZ);
+ outregf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (strcmp(key, "of") == 0) {
+ if (clp->outf_v.size() > 0) {
+ pr2serr("Second 'of=' argument??\n");
+ goto syn_err;
+ } else {
+ cp = buf;
+ while ((ccp = strchr(cp, ','))) {
+ clp->outf_v.push_back(string(cp , ccp - cp));
+ cp = ccp + 1;
+ }
+ clp->outf_v.push_back(string(cp , strlen(cp)));
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (! process_flags(buf, &clp->out_flags)) {
+ pr2serr("%sbad argument to 'oflag='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "sdt")) {
+ ccp = strchr(buf, ',');
+ n = sg_get_num(buf);
+ if (n < 0) {
+ pr2serr("%sbad argument to 'sdt=CRT[,ICT]'\n", my_name);
+ goto syn_err;
+ }
+ clp->sdt_crt = n;
+ if (ccp) {
+ n = sg_get_num(ccp + 1);
+ if (n < 0) {
+ pr2serr("%sbad 2nd argument to 'sdt=CRT,ICT'\n",
+ my_name);
+ goto syn_err;
+ }
+ clp->sdt_ict = n;
+ }
+ } else if (0 == strcmp(key, "seek")) {
+ n = strlen(buf);
+ if (n < 1) {
+ pr2serr("%sneed argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ seek_buf = (char *)calloc(n + 16, 1);
+ if (NULL == seek_buf)
+ goto syn_err;
+ memcpy(seek_buf, buf, n + 1);
+ } else if (0 == strcmp(key, "skip")) {
+ n = strlen(buf);
+ if (n < 1) {
+ pr2serr("%sneed argument to 'skip='\n", my_name);
+ goto syn_err;
+ }
+ skip_buf = (char *)calloc(n + 16, 1);
+ if (NULL == skip_buf)
+ goto syn_err;
+ memcpy(skip_buf, buf, n + 1);
+ } else if (0 == strcmp(key, "sync"))
+ do_sync = !! sg_get_num(buf);
+ else if (0 == strcmp(key, "thr")) {
+ num_threads = sg_get_num(buf);
+ if ((num_threads < 0) || (num_threads > MAX_BPT_VALUE)) {
+ pr2serr("%sneed argument to 'skip='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "time")) {
+ ccp = strchr(buf, ',');
+ do_time = sg_get_num(buf);
+ if (do_time < 0) {
+ pr2serr("%sbad argument to 'time=0|1|2'\n", my_name);
+ goto syn_err;
+ }
+ if (ccp) {
+ n = sg_get_num(ccp + 1);
+ if ((n < 0) || (n > (MAX_BPT_VALUE / 1000))) {
+ pr2serr("%sbad argument to 'time=0|1|2,TO'\n", my_name);
+ goto syn_err;
+ }
+ clp->cmd_timeout = n ? (n * 1000) : DEF_TIMEOUT;
+ }
+ } else if (0 == strncmp(key, "verb", 4))
+ clp->verbose = sg_get_num(buf);
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'd');
+ clp->dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ clp->help += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ if (n > 0)
+ clp->prefetch = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ clp->verbose += n; /* -v ---> --verbose */
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'x');
+ if (n > 0)
+ verify_given = true;
+ res += n;
+
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ goto syn_err;
+ }
+ } else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++clp->dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?")))
+ ++clp->help;
+ else if ((0 == strncmp(key, "--prefetch", 10)) ||
+ (0 == strncmp(key, "--pre-fetch", 11)))
+ clp->prefetch = true;
+ else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++clp->verbose; /* --verbose */
+ } else if (0 == strncmp(key, "--veri", 6))
+ verify_given = true;
+ else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help'\n");
+ goto syn_err;
+ }
+ } /* end of parsing for loop */
+
+ if (skip_buf) {
+ res = skip_seek(clp, "skip", skip_buf, true /* skip */, false);
+ free(skip_buf);
+ skip_buf = NULL;
+ if (res) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ }
+ if (seek_buf) {
+ res = skip_seek(clp, "seek", seek_buf, false /* skip */, false);
+ free(seek_buf);
+ seek_buf = NULL;
+ if (res) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ }
+ /* heap usage should be all freed up now */
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ clp->verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ clp->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", clp->verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("%s%s\n", my_name, version_str);
+ ret = SG_LIB_OK_FALSE;
+ goto oth_err;
+ }
+ if (clp->help > 0) {
+ usage(clp->help);
+ ret = SG_LIB_OK_FALSE;
+ goto oth_err;
+ }
+ if (clp->bs <= 0) {
+ clp->bs = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ clp->bs);
+ }
+ if (verify_given) {
+ pr2serr("Doing verify/cmp rather than copy\n");
+ clp->verify = true;
+ }
+ if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage(0);
+ goto syn_err;
+ }
+ if (clp->out_flags.append) {
+ if ((clp->o_sgl.lowest_lba > 0) ||
+ (clp->o_sgl.linearity != SGL_LINEAR)) {
+ pr2serr("Can't use both append and seek switches\n");
+ goto syn_err;
+ }
+ if (verify_given) {
+ pr2serr("Can't use both append and verify switches\n");
+ goto syn_err;
+ }
+ }
+ if (clp->bpt < 1) {
+ pr2serr("bpt must be greater than 0\n");
+ goto syn_err;
+ }
+ if (clp->in_flags.mmap && clp->out_flags.mmap) {
+ pr2serr("mmap flag on both IFILE and OFILE doesn't work\n");
+ goto syn_err;
+ }
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ * for the block layer in lk 2.6 and results in an EIO on the
+ * SG_IO ioctl. So reduce it in that case. */
+ if ((clp->bs >= 2048) && (! bpt_given))
+ clp->bpt = DEF_BLOCKS_PER_2048TRANSFER;
+ if (clp->in_flags.order_wr && (! clp->out_flags.order_wr))
+ pr2serr("Warning iflag=order is ignored, use with oflag=\n");
+ if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) {
+ pr2serr("too few or too many threads requested\n");
+ usage(1);
+ goto syn_err;
+ }
+ clp->unit_nanosec = (do_time > 1) || !!getenv("SG3_UTILS_LINUX_NANO");
+ return 0;
+
+syn_err:
+ if (seek_buf)
+ free(seek_buf);
+ if (skip_buf)
+ free(skip_buf);
+ return contra ? SG_LIB_CONTRADICT : SG_LIB_SYNTAX_ERROR;
+oth_err:
+ if (seek_buf)
+ free(seek_buf);
+ if (skip_buf)
+ free(skip_buf);
+ return ret;
+}
+
+static int
+calc_count(struct global_collection * clp, const char * inf,
+ int64_t & in_num_sect, const char * outf, int64_t & out_num_sect)
+{
+ int in_sect_sz, out_sect_sz, res;
+
+ if (clp->dd_count < 0) {
+ in_num_sect = -1;
+ out_num_sect = -1;
+ }
+ if (FT_SG == clp->in_type) {
+ res = scsi_read_capacity(clp->in0fd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(in), continuing\n");
+ res = scsi_read_capacity(clp->in0fd, &in_num_sect,
+ &in_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", inf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", inf);
+ else
+ pr2serr("Unable to read capacity on %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != in_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->infp, clp->bs,
+ in_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (FT_SG == clp->out_type) {
+ res = scsi_read_capacity(clp->out0fd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(out), continuing\n");
+ res = scsi_read_capacity(clp->out0fd, &out_num_sect,
+ &out_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", outf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", outf);
+ else
+ pr2serr("Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != out_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->outfp, clp->bs,
+ out_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if (clp->dd_count < 0) {
+ if (FT_SG == clp->in_type)
+ ;
+ else if (FT_BLOCK == clp->in_type) {
+ if (0 != read_blkdev_capacity(clp->in0fd, &in_num_sect,
+ &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ if (clp->bs != in_sect_sz) {
+ pr2serr("logical block size on %s confusion; bs=%d, from "
+ "device=%d\n", inf, clp->bs, in_sect_sz);
+ in_num_sect = -1;
+ }
+ }
+
+ if (FT_SG == clp->out_type)
+ ;
+ else if (FT_BLOCK == clp->out_type) {
+ if (0 != read_blkdev_capacity(clp->out0fd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outf);
+ out_num_sect = -1;
+ }
+ if (clp->bs != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, from "
+ "device=%d\n", outf, clp->bs, out_sect_sz);
+ out_num_sect = -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+do_count_work(struct global_collection * clp, const char * inf,
+ int64_t & in_num_sect, const char * outf,
+ int64_t & out_num_sect)
+{
+ int res;
+ class scat_gath_list * isglp = &clp->i_sgl;
+ class scat_gath_list * osglp = &clp->o_sgl;
+
+ res = calc_count(clp, inf, in_num_sect, outf, out_num_sect);
+ if (res)
+ return res;
+
+ if ((-1 == in_num_sect) && (FT_OTHER == clp->in_type)) {
+ in_num_sect = clp->in_st_size / clp->bs;
+ if (clp->in_st_size % clp->bs) {
+ ++in_num_sect;
+ pr2serr("Warning: the file size of %s is not a multiple of BS "
+ "[%d]\n", inf, clp->bs);
+ }
+ }
+ if ((in_num_sect > 0) && (isglp->high_lba_p1 > in_num_sect)) {
+ pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds input length: %"
+ PRIx64 " blocks\n", my_name, isglp->high_lba_p1 - 1,
+ in_num_sect);
+ return SG_LIB_CAT_OTHER;
+ }
+ if ((out_num_sect > 0) && (osglp->high_lba_p1 > out_num_sect)) {
+ pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds output length: %"
+ PRIx64 " blocks\n", my_name, osglp->high_lba_p1 - 1,
+ out_num_sect);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (isglp->sum_hard || osglp->sum_hard) {
+ int64_t ccount;
+
+ if (isglp->sum_hard && osglp->sum_hard) {
+ if (isglp->sum != osglp->sum) {
+ pr2serr("%stwo hard sgl_s, sum of blocks differ: in=%" PRId64
+ ", out=%" PRId64 "\n", my_name , isglp->sum,
+ osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = isglp->sum;
+ } else if (isglp->sum_hard) {
+ if (osglp->sum > isglp->sum) {
+ pr2serr("%soutput sgl already too many blocks [%" PRId64
+ "]\n", my_name, osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->linearity != SGL_NON_MONOTONIC)
+ osglp->append_1or(isglp->sum - osglp->sum);
+ else {
+ pr2serr("%soutput sgl non-montonic: can't extend\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = isglp->sum;
+ } else { /* only osglp hard */
+ if (isglp->sum > osglp->sum) {
+ pr2serr("%sinput sgl already too many blocks [%" PRId64
+ "]\n", my_name, isglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (isglp->linearity != SGL_NON_MONOTONIC)
+ isglp->append_1or(osglp->sum - isglp->sum);
+ else {
+ pr2serr("%sinput sgl non-monotonic: can't extend\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = osglp->sum;
+ }
+ if (SG_COUNT_INDEFINITE == clp->dd_count)
+ clp->dd_count = ccount;
+ else if (ccount != clp->dd_count) {
+ pr2serr("%scount=COUNT disagrees with scatter gather list "
+ "length [%" PRId64 "]\n", my_name, ccount);
+ return SG_LIB_CAT_OTHER;
+ }
+ } else if (clp->dd_count != 0) { /* and both input and output are soft */
+ int64_t iposs = INT64_MAX;
+ int64_t oposs = INT64_MAX;
+
+ if (clp->dd_count > 0) {
+ if (isglp->sum > clp->dd_count) {
+ pr2serr("%sskip sgl sum [%" PRId64 "] exceeds COUNT\n",
+ my_name, isglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->sum > clp->dd_count) {
+ pr2serr("%sseek sgl sum [%" PRId64 "] exceeds COUNT\n",
+ my_name, osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ goto fini;
+ }
+
+ /* clp->dd_count == SG_COUNT_INDEFINITE */
+ if (in_num_sect > 0)
+ iposs = in_num_sect + isglp->sum - isglp->high_lba_p1;
+ if (out_num_sect > 0)
+ oposs = out_num_sect + osglp->sum - osglp->high_lba_p1;
+ clp->dd_count = iposs < oposs ? iposs : oposs;
+ if (INT64_MAX == clp->dd_count) {
+ pr2serr("%scan't deduce count=COUNT, please supply one\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (isglp->sum > clp->dd_count) {
+ pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds skip sgl sum\n",
+ my_name, clp->dd_count);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->sum > clp->dd_count) {
+ pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds seek sgl sum\n",
+ my_name, clp->dd_count);
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+ if (clp->dd_count == 0)
+ return 0;
+fini:
+ if (clp->dd_count > isglp->sum)
+ isglp->append_1or(clp->dd_count - isglp->sum);
+ if (clp->dd_count > osglp->sum)
+ osglp->append_1or(clp->dd_count - osglp->sum);
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool fail_after_cli = false;
+ bool ifile_given = true;
+ // char inf[INOUTF_SZ];
+ // char outf[INOUTF_SZ];
+ char outregf[INOUTF_SZ];
+ int res, k, err;
+ size_t num_ifiles, num_ofiles, num_slices, inf0_sz;
+ int64_t in_num_sect = -1;
+ int64_t out_num_sect = -1;
+ const char * ccp = NULL;
+ const char * cc2p;
+ struct global_collection * clp = &gcoll;
+ thread sig_listen_thr;
+ vector<thread> work_thr_v;
+ vector<thread> listen_thr_v;
+ char ebuff[EBUFF_SZ];
+#if 0 /* SG_LIB_ANDROID */
+ struct sigaction actions;
+
+ memset(&actions, 0, sizeof(actions));
+ sigemptyset(&actions.sa_mask);
+ actions.sa_flags = 0;
+ actions.sa_handler = thread_exit_handler;
+ sigaction(SIGUSR1, &actions, NULL);
+ sigaction(SIGUSR2, &actions, NULL);
+#endif
+ /* memset(clp, 0, sizeof(*clp)); */
+ clp->dd_count = SG_COUNT_INDEFINITE;
+ clp->bpt = DEF_BLOCKS_PER_TRANSFER;
+ clp->cmd_timeout = DEF_TIMEOUT;
+ clp->sdt_ict = DEF_SDT_ICT_MS;
+ clp->sdt_crt = DEF_SDT_CRT_SEC;
+ clp->in_type = FT_FIFO;
+ /* change dd's default: if of=OFILE not given, assume /dev/null */
+ clp->out_type = FT_DEV_NULL;
+ clp->cdbsz_in = DEF_SCSI_CDB_SZ;
+ clp->cdbsz_out = DEF_SCSI_CDB_SZ;
+ clp->mrq_num = DEF_MRQ_NUM;
+ // inf[0] = '\0';
+ // outf[0] = '\0';
+ outregf[0] = '\0';
+ fetch_sg_version();
+ if (sg_version >= 40045)
+ sg_version_ge_40045 = true;
+ else {
+ pr2serr(">>> %srequires an sg driver version of 4.0.45 or later\n\n",
+ my_name);
+ fail_after_cli = true;
+ }
+
+ res = parse_cmdline_sanity(argc, argv, clp, outregf);
+ if (SG_LIB_OK_FALSE == res)
+ return 0;
+ if (res)
+ return res;
+ if (fail_after_cli) {
+ pr2serr("%scommand line parsing was okay but sg driver is too old\n",
+ my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ install_handler(SIGINT, interrupt_handler);
+ install_handler(SIGQUIT, interrupt_handler);
+ install_handler(SIGPIPE, interrupt_handler);
+ install_handler(SIGUSR1, siginfo_handler);
+ install_handler(SIGUSR2, siginfo2_handler);
+
+ num_ifiles = clp->inf_v.size();
+ num_ofiles = clp->outf_v.size();
+ if (num_ifiles > MAX_SLICES) {
+ pr2serr("%sonly support %d slices but given %zd IFILEs\n", my_name,
+ MAX_SLICES, num_ifiles);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (num_ofiles > MAX_SLICES) {
+ pr2serr("%sonly support %d slices but given %zd OFILEs\n", my_name,
+ MAX_SLICES, num_ifiles);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (0 == num_ofiles) {
+ if (0 == num_ifiles) {
+ pr2serr("%sexpect either if= or of= to be given\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ for (k = 0; k < (int)num_ifiles; ++k)
+ clp->outf_v.push_back("."); /* same as /dev/null */
+ }
+ if (0 == num_ifiles) {
+ ifile_given = false;
+ for (k = 0; k < (int)num_ofiles; ++k)
+ clp->inf_v.push_back("");
+ }
+ if ((num_ifiles > 1) && (num_ofiles > 1) && (num_ifiles != num_ofiles)) {
+ pr2serr("%snumber of IFILEs [%zd] and number of OFILEs [%zd] > 1 "
+ "and unequal\n", my_name, num_ifiles, num_ofiles);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((num_ifiles > 1) && (1 == num_ofiles)) {
+ /* if many IFILEs and one OFILE, replicate OFILE till same size */
+ for (k = 1; k < (int)num_ifiles; ++k)
+ clp->outf_v.push_back(clp->outf_v[0]);
+ num_ofiles = clp->outf_v.size();
+ } else if ((num_ofiles > 1) && (1 == num_ifiles)) {
+ /* if many OFILEs and one IFILE, replicate IFILE till same size */
+ for (k = 1; k < (int)num_ofiles; ++k)
+ clp->inf_v.push_back(clp->inf_v[0]);
+ num_ifiles = clp->inf_v.size();
+ }
+ num_slices = (num_ifiles > num_ofiles) ? num_ifiles : num_ofiles;
+ if ((int)num_slices > num_threads) {
+ pr2serr("%sNumber of slices [%zd] exceeds number of threads [%d].\n",
+ my_name, num_slices, num_threads);
+ pr2serr("Number of threads needs to be increased.\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ k = 0;
+ for (auto && cvp : clp->cp_ver_arr) {
+ if (k >= (int)num_slices)
+ break;
+ cvp.my_index = k++;
+ cvp.state = cp_ver_pair_t::my_state::init;
+ }
+ clp->in0fd = STDIN_FILENO;
+ clp->out0fd = STDOUT_FILENO;
+ if (clp->in_flags.ff && clp->in_flags.zero) {
+ ccp = "<addr_as_data>";
+ cc2p = "addr_as_data";
+ } else if (clp->in_flags.ff) {
+ ccp = "<0xff bytes>";
+ cc2p = "ff";
+ } else if (clp->in_flags.random) {
+ ccp = "<random>";
+ cc2p = "random";
+ } else if (clp->in_flags.zero) {
+ ccp = "<zero bytes>";
+ cc2p = "00";
+ }
+ inf0_sz = clp->inf_v.size() ? clp->inf_v[0].size() : 0;
+ if (ccp) {
+ if (ifile_given) {
+ pr2serr("%siflag=%s and if=%s contradict\n", my_name, cc2p,
+ clp->inf_v[0].c_str());
+ return SG_LIB_CONTRADICT;
+ }
+ for (auto && cvp : clp->cp_ver_arr) {
+ if (cvp.state == cp_ver_pair_t::my_state::empty)
+ break;
+ cvp.in_type = FT_RANDOM_0_FF;
+ }
+ clp->in_type = FT_RANDOM_0_FF;
+ clp->infp = ccp;
+ clp->in0fd = -1;
+ } else if (inf0_sz && ('-' != clp->inf_v[0].c_str()[0])) {
+ const string & inf_s = clp->inf_v[0];
+ const char * infp = inf_s.c_str();
+
+ clp->in_type = dd_filetype(infp, clp->in_st_size);
+ if (FT_ERROR == clp->in_type) {
+ pr2serr("%sunable to access %s\n", my_name, infp);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == clp->in_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, infp);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_CHAR == clp->in_type) {
+ pr2serr("%sunable to use unknown char device %s\n", my_name, infp);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->in_type) {
+ clp->in0fd = sg_in_open(clp, inf_s, NULL, NULL);
+ if (clp->in0fd < 0)
+ return -clp->in0fd;
+ } else {
+ clp->in0fd = reg_file_open(clp, infp, false /* read */);
+ if (clp->in0fd < 0)
+ return sg_convert_errno(-clp->in0fd);
+ }
+ clp->infp = infp;
+ }
+ if (clp->cdl_given && (! clp->cdbsz_given)) {
+ bool changed = false;
+
+ if ((clp->cdbsz_in < 16) && (clp->in_flags.cdl > 0)) {
+ clp->cdbsz_in = 16;
+ changed = true;
+ }
+ if ((clp->cdbsz_out < 16) && (! clp->verify) &&
+ (clp->out_flags.cdl > 0)) {
+ clp->cdbsz_out = 16;
+ changed = true;
+ }
+ if (changed)
+ pr2serr(">> increasing cdbsz to 16 due to cdl > 0\n");
+ }
+ if ((clp->verbose > 0) &&
+ (clp->in_flags.no_waitq || clp->out_flags.no_waitq))
+ pr2serr("no_waitq=<n> operand is now ignored\n");
+ if (clp->outf_v.size()) {
+ const string & outf_s = clp->outf_v[0].c_str();
+ const char * outfp = outf_s.c_str();
+
+ clp->ofile_given = true;
+ if ('-' == outfp[0])
+ clp->out_type = FT_FIFO;
+ else
+ clp->out_type = dd_filetype(outfp, clp->out_st_size);
+
+ if ((FT_SG != clp->out_type) && clp->verify) {
+ pr2serr("%s --verify only supported by sg OFILEs\n", my_name);
+ return SG_LIB_FILE_ERROR;
+ }
+ if (FT_FIFO == clp->out_type)
+ ;
+ else if (FT_ST == clp->out_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, outfp);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_CHAR == clp->out_type) {
+ pr2serr("%sunable to use unknown char device %s\n", my_name,
+ outfp);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->out_type) {
+ clp->out0fd = sg_out_open(clp, outf_s, NULL, NULL);
+ if (clp->out0fd < 0)
+ return -clp->out0fd;
+ } else if (FT_DEV_NULL == clp->out_type)
+ clp->out0fd = -1; /* don't bother opening */
+ else {
+ clp->out0fd = reg_file_open(clp, outfp, true /* write */);
+ if (clp->out0fd < 0)
+ return sg_convert_errno(-clp->out0fd);
+ }
+ clp->outfp = outfp;
+ }
+ if (clp->verify && (clp->out_type == FT_DEV_NULL)) {
+ pr2serr("Can't do verify when OFILE not given\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if ((FT_SG == clp->in_type) && (FT_SG == clp->out_type)) {
+ if (clp->in_flags.serial || clp->out_flags.serial)
+ pr2serr("serial flag ignored when both IFILE and OFILE are sg "
+ "devices\n");
+ if (clp->in_flags.order_wr && (num_threads > 1))
+ pr2serr("Warning: write ordering only guaranteed for single "
+ "thread\n");
+ } else if (clp->in_flags.order_wr)
+ pr2serr("Warning: oflag=order only active on sg->sg copies\n");
+
+ if (outregf[0]) {
+ int ftyp = dd_filetype(outregf, clp->outreg_st_size);
+
+ clp->outreg_type = ftyp;
+ if (! ((FT_OTHER == ftyp) || (FT_ERROR == ftyp) ||
+ (FT_DEV_NULL == ftyp))) {
+ pr2serr("File: %s can only be regular file or pipe (or "
+ "/dev/null)\n", outregf);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->outregfd = open(outregf, O_WRONLY | O_CREAT, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "could not open %s for writing",
+ outregf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (clp->verbose > 1)
+ pr2serr("ofreg=%s opened okay, fd=%d\n", outregf, clp->outregfd);
+ if (FT_ERROR == ftyp)
+ clp->outreg_type = FT_OTHER; /* regular file created */
+ } else
+ clp->outregfd = -1;
+
+ if ((STDIN_FILENO == clp->in0fd) && (STDOUT_FILENO == clp->out0fd)) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to "
+ "/dev/null\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->in_type == FT_FIFO) && (! clp->i_sgl.is_pipe_suitable())) {
+ pr2serr("The skip= argument is not suitable for a pipe\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->out_type == FT_FIFO) && (! clp->o_sgl.is_pipe_suitable())) {
+ pr2serr("The seek= argument is not suitable for a pipe\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ res = do_count_work(clp, clp->inf_v[0].c_str(), in_num_sect,
+ clp->outf_v[0].c_str(), out_num_sect);
+ if (res)
+ return res;
+
+ if (clp->verbose > 2)
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64
+ ", out_num_sect=%" PRId64 "\n", clp->dd_count, in_num_sect,
+ out_num_sect);
+ if (clp->dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (! clp->cdbsz_given) {
+ if ((FT_SG == clp->in_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_in) &&
+ ((clp->i_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ clp->cdbsz_in = MAX_SCSI_CDB_SZ;
+ }
+ if ((FT_SG == clp->out_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_out) &&
+ ((clp->o_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ clp->cdbsz_out = MAX_SCSI_CDB_SZ;
+ }
+ }
+
+ for (auto && cvp : clp->cp_ver_arr) {
+ cvp.in_type = clp->in_type;
+ cvp.out_type = clp->out_type;
+ cvp.dd_count = clp->dd_count;
+ cvp.in_rem_count = clp->dd_count;
+ cvp.out_rem_count = clp->dd_count;
+ }
+
+ if (clp->dry_run > 0) {
+ pr2serr("Due to --dry-run option, bypass copy/read\n");
+ goto fini;
+ }
+ if (! clp->ofile_given)
+ pr2serr("of=OFILE not given so only read from IFILE, to output to "
+ "stdout use 'of=-'\n");
+
+ sigemptyset(&signal_set);
+ sigaddset(&signal_set, SIGINT);
+ sigaddset(&signal_set, SIGUSR2);
+
+ res = sigprocmask(SIG_BLOCK, &signal_set, &orig_signal_set);
+ if (res < 0) {
+ pr2serr("sigprocmask failed: %s\n", safe_strerror(errno));
+ goto fini;
+ }
+
+ listen_thr_v.emplace_back(sig_listen_thread, clp);
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+
+/* vvvvvvvvvvv Start worker threads vvvvvvvvvvvvvvvvvvvvvvvv */
+ if (num_threads > 0) {
+ auto & cvp = clp->cp_ver_arr[0];
+
+ cvp.in_fd = clp->in0fd;
+ cvp.out_fd = clp->out0fd;
+
+ /* launch "infant" thread to catch early mortality, if any */
+ work_thr_v.emplace_back(read_write_thread, clp, 0, 0, true);
+ {
+ unique_lock<mutex> lk(clp->infant_mut);
+ clp->infant_cv.wait(lk, []{ return gcoll.processed; });
+ }
+ if (clp->cp_ver_arr[0].next_count_pos.load() < 0) {
+ /* infant thread error-ed out, join with it */
+ for (auto & t : work_thr_v) {
+ if (t.joinable())
+ t.join();
+ }
+ goto jump;
+ }
+
+ /* now start the rest of the threads */
+ for (k = 1; k < num_threads; ++k)
+ work_thr_v.emplace_back(read_write_thread, clp, k,
+ k % (int)num_slices, false);
+
+ /* now wait for worker threads to finish */
+ for (auto & t : work_thr_v) {
+ if (t.joinable())
+ t.join();
+ }
+ } /* worker threads hereafter have all exited */
+jump:
+ if (do_time && (start_tm.tv_sec || start_tm.tv_usec))
+ calc_duration_throughput(0);
+
+ if (do_sync) {
+ if (FT_SG == clp->out_type) {
+ pr2serr_lk(">> Synchronizing cache on %s\n",
+ (clp->outf_v.size() ? clp->outf_v[0].c_str() : "" ));
+ res = sg_ll_sync_cache_10(clp->out0fd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr_lk("Unit attention(out), continuing\n");
+ res = sg_ll_sync_cache_10(clp->out0fd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr_lk("Unable to synchronize cache\n");
+ }
+ }
+
+ shutting_down = true;
+ for (auto & t : listen_thr_v) {
+ if (t.joinable()) {
+ t.detach();
+ if (listen_t_tid > 0)
+ kill(listen_t_tid, SIGUSR2);
+ // t.~thread(); /* kill listening thread; doesn't work */
+ }
+ std::this_thread::yield(); // not enough it seems
+ { /* allow time for SIGUSR2 signal to get through */
+ struct timespec tspec = {0, 1000000}; /* 1 msec */
+ struct timespec rem;
+
+ while ((nanosleep(&tspec, &rem) < 0) && (EINTR == errno))
+ tspec = rem;
+ }
+ }
+
+fini:
+ if ((STDIN_FILENO != clp->in0fd) && (clp->in0fd >= 0))
+ close(clp->in0fd);
+ if ((STDOUT_FILENO != clp->out0fd) && (FT_DEV_NULL != clp->out_type) &&
+ (clp->out0fd >= 0))
+ close(clp->out0fd);
+ if ((clp->outregfd >= 0) && (STDOUT_FILENO != clp->outregfd) &&
+ (FT_DEV_NULL != clp->outreg_type))
+ close(clp->outregfd);
+ print_stats("");
+ if (clp->dio_incomplete_count.load()) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ clp->dio_incomplete_count.load());
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+
+ k = 0;
+ for (auto && cvp : gcoll.cp_ver_arr) {
+ if (cvp.state == cp_ver_pair_t::my_state::empty)
+ break;
+ ++k;
+ if (cvp.sum_of_resids.load())
+ pr2serr(">> slice: %d, Non-zero sum of residual counts=%d\n",
+ k, cvp.sum_of_resids.load());
+ }
+ if (clp->verbose && (num_start_eagain > 0))
+ pr2serr("Number of start EAGAINs: %d\n", num_start_eagain.load());
+ if (clp->verbose && (num_fin_eagain > 0))
+ pr2serr("Number of finish EAGAINs: %d\n", num_fin_eagain.load());
+ if (clp->verbose && (num_ebusy > 0))
+ pr2serr("Number of EBUSYs: %d\n", num_ebusy.load());
+ if (clp->verbose && (num_miscompare > 0))
+ pr2serr("Number of miscompare%s: %d\n",
+ (num_miscompare > 1) ? "s" : "", num_miscompare.load());
+ if (clp->verify && (SG_LIB_CAT_MISCOMPARE == res))
+ pr2serr("Verify/compare failed due to miscompare\n");
+ if (0 == res)
+ res = clp->reason_res.load();
+ sigprocmask(SIG_SETMASK, &orig_signal_set, NULL);
+ if (clp->verbose) {
+ int num_sigusr2 = num_fallthru_sigusr2.load();
+ if (num_sigusr2 > 0)
+ pr2serr("Number of fall-through SIGUSR2 signals caught: %d\n",
+ num_sigusr2);
+ }
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+}
diff --git a/testing/sg_queue_tst.c b/testing/sg_queue_tst.c
new file mode 100644
index 00000000..9f83fd3f
--- /dev/null
+++ b/testing/sg_queue_tst.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2010-2019 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *
+ * This program was used to test SCSI mid level queue ordering.
+ * The default behaviour is to "queue at head" which is useful for
+ * error processing but not for streaming READ and WRITE commands.
+ *
+ * Invocation: sg_queue_tst [-l=Q_LEN] [-t] <sg_device>
+ * -t queue at tail
+ *
+ * Version 0.96 (20190128)
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_linux_inc.h"
+
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+#define SDIAG_CMD_LEN 6
+#define SENSE_BUFFER_LEN 96
+
+#define EBUFF_SZ 256
+
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+#define DEF_Q_LEN 16 /* max in sg v3 and earlier */
+#define MAX_Q_LEN 256
+
+static void
+set_nanosecs(int sg_fd)
+{
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS; /* this or previous optional */
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ fprintf(stderr, "ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool q_at_tail = false;
+ bool dur_in_nanosecs = false;
+ int sg_fd, k, ok;
+ uint8_t inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t sdiag_cdb[SDIAG_CMD_LEN] =
+ {0x1d, 0x10 /* PF */, 0, 0, 0, 0};
+ uint8_t inqBuff[MAX_Q_LEN][INQ_REPLY_LEN];
+ sg_io_hdr_t io_hdr[MAX_Q_LEN];
+ sg_io_hdr_t rio_hdr;
+ char * file_name = 0;
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[MAX_Q_LEN][SENSE_BUFFER_LEN] SG_C_CPP_ZERO_INIT;
+ int q_len = DEF_Q_LEN;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-n", argv[k], 2))
+ dur_in_nanosecs = true;
+ else if (0 == memcmp("-t", argv[k], 2))
+ q_at_tail = true;
+ else if (0 == memcmp("-l=", argv[k], 3)) {
+ q_len = atoi(argv[k] + 3);
+ if ((q_len > 511) || (q_len < 1)) {
+ printf("Expect -l= to take a number (q length) between 1 "
+ "and 511\n");
+ file_name = 0;
+ break;
+ }
+
+ } else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("Usage: 'sg_queue_tst [-l=Q_LEN] [-n] [-t] <sg_device>'\n"
+ "where:\n"
+ " -l=Q_LEN queue length, between 1 and 511 "
+ "(def: 16)\n"
+ " -n duration in nanosecs (def: milliseconds)\n"
+ " -t queue_at_tail (def: q_at_head)\n");
+ return 1;
+ }
+
+ /* An access mode of O_RDWR is required for write()/read() interface */
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "sg_queue_tst: error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ if (dur_in_nanosecs)
+ set_nanosecs(sg_fd);
+
+ for (k = 0; k < q_len; ++k) {
+ /* Prepare INQUIRY command */
+ memset(&io_hdr[k], 0, sizeof(sg_io_hdr_t));
+ io_hdr[k].interface_id = 'S';
+ /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */
+ io_hdr[k].mx_sb_len = (uint8_t)sizeof(sense_buffer);
+ if (0 == (k % 3)) {
+ io_hdr[k].cmd_len = sizeof(sdiag_cdb);
+ io_hdr[k].cmdp = sdiag_cdb;
+ io_hdr[k].dxfer_direction = SG_DXFER_NONE;
+ } else {
+ io_hdr[k].cmd_len = sizeof(inq_cdb);
+ io_hdr[k].cmdp = inq_cdb;
+ io_hdr[k].dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr[k].dxfer_len = INQ_REPLY_LEN;
+ io_hdr[k].dxferp = inqBuff[k];
+ }
+ io_hdr[k].sbp = sense_buffer[k];
+ io_hdr[k].mx_sb_len = SENSE_BUFFER_LEN;
+ io_hdr[k].timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr[k].pack_id = k;
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ io_hdr[k].flags |= SG_FLAG_Q_AT_TAIL;
+ else
+ io_hdr[k].flags |= SG_FLAG_Q_AT_HEAD;
+ /* io_hdr[k].usr_ptr = NULL; */
+
+ if (write(sg_fd, &io_hdr[k], sizeof(sg_io_hdr_t)) < 0) {
+ perror("sg_queue_tst: sg write error");
+ close(sg_fd);
+ return 1;
+ }
+ }
+ /* sleep(3); */
+ for (k = 0; k < q_len; ++k) {
+ memset(&rio_hdr, 0, sizeof(sg_io_hdr_t));
+ rio_hdr.interface_id = 'S';
+ if (read(sg_fd, &rio_hdr, sizeof(sg_io_hdr_t)) < 0) {
+ perror("sg_queue_tst: sg read error");
+ close(sg_fd);
+ return 1;
+ }
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&rio_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("command error", &rio_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ /* if (0 == rio_hdr.pack_id) */
+ if (0 == (rio_hdr.pack_id % 3))
+ printf("SEND DIAGNOSTIC %d duration=%u %s\n", rio_hdr.pack_id,
+ rio_hdr.duration, (dur_in_nanosecs ? "ns" : "ms"));
+ else
+ printf("INQUIRY %d duration=%u %s\n", rio_hdr.pack_id,
+ rio_hdr.duration, (dur_in_nanosecs ? "ns" : "ms"));
+ }
+ }
+
+ close(sg_fd);
+ return 0;
+}
diff --git a/testing/sg_scat_gath.cpp b/testing/sg_scat_gath.cpp
new file mode 100644
index 00000000..56752718
--- /dev/null
+++ b/testing/sg_scat_gath.cpp
@@ -0,0 +1,1049 @@
+/*
+ * Copyright (c) 2014-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Version 1.02 [20201124]
+ */
+
+// C headers
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+#include <ctype.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+// C++ headers
+#include <array>
+
+#include "sg_scat_gath.h"
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+using namespace std;
+
+#define MAX_SGL_NUM_VAL (INT32_MAX - 1) /* should reduce for testing */
+// #define MAX_SGL_NUM_VAL 7 /* should reduce for testing */
+#if MAX_SGL_NUM_VAL > INT32_MAX
+#error "MAX_SGL_NUM_VAL cannot exceed 2^31 - 1"
+#endif
+
+bool
+scat_gath_list::empty() const
+{
+ return sgl.empty();
+}
+
+bool
+scat_gath_list::empty_or_00() const
+{
+ if (sgl.empty())
+ return true;
+ return ((sgl.size() == 1) && (sgl[0].lba == 0) && (sgl[0].num == 0));
+}
+
+int
+scat_gath_list::num_elems() const
+{
+ return sgl.size();
+}
+
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space **) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. Assumed to be LBA (64 bit) and
+ * number_of_block (32 bit) pairs. ** Space on command line needs to
+ * be escaped, otherwise it is an operand/option separator. */
+bool
+scat_gath_list::load_from_cli(const char * cl_p, bool b_vb)
+{
+ bool split, full_pair;
+ int in_len, k, j;
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int64_t ll, large_num;
+ uint64_t prev_lba;
+ char * cp;
+ char * c2p;
+ const char * lcp;
+ class scat_gath_elem sge;
+
+ if (NULL == cl_p) {
+ pr2serr("%s: bad arguments\n", __func__);
+ goto err_out;
+ }
+ lcp = cl_p;
+ in_len = strlen(cl_p);
+ if ('-' == cl_p[0]) { /* read from stdin */
+ pr2serr("%s: logic error: no stdin here\n", __func__);
+ goto err_out;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(cl_p, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ if (b_vb)
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ goto err_out;
+ }
+ j = 0;
+ full_pair = true;
+ for (k = 0, split = false; ; ++k) {
+ if (split) {
+ /* splitting given elem with large number_of_blocks into
+ * multiple elems within array being built */
+ ++j;
+ sge.lba = prev_lba + (uint64_t)max_nbs;
+ if (large_num > max_nbs) {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ large_num -= max_nbs;
+ sgl.push_back(sge);
+ } else {
+ sge.num = (uint32_t)large_num;
+ split = false;
+ if (b_vb)
+ pr2serr("%s: split large sg elem into %d element%s\n",
+ __func__, j, (j == 1 ? "" : "s"));
+ sgl.push_back(sge);
+ goto check_for_next;
+ }
+ continue;
+ }
+ full_pair = false;
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ sge.lba = (uint64_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp) {
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ }
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ if (b_vb)
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - cl_p + 1));
+ goto err_out;
+ }
+ ll = sg_get_llnum(lcp);
+ if (ll >= 0) {
+ full_pair = true;
+ if (ll > max_nbs) {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ large_num = ll - max_nbs;
+ split = true;
+ j = 1;
+ continue;
+ }
+ sge.num = (uint32_t)ll;
+ } else { /* bad or negative number as number_of_blocks */
+ if (b_vb)
+ pr2serr("%s: bad number at pos %d\n", __func__,
+ (int)(lcp - cl_p + 1));
+ goto err_out;
+ }
+ sgl.push_back(sge);
+check_for_next:
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp) {
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ }
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } /* end of for loop over items in operand */
+ /* other than first pair, expect even number of items */
+ if ((k > 0) && (! full_pair)) {
+ if (b_vb)
+ pr2serr("%s: expected even number of items: "
+ "LBA0,NUM0,LBA1,NUM1...\n", __func__);
+ goto err_out;
+ }
+ }
+ return true;
+err_out:
+ if (0 == m_errno)
+ m_errno = SG_LIB_SYNTAX_ERROR;
+ return false;
+}
+
+bool
+scat_gath_list::file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+ bool flexible, bool b_vb)
+{
+ bool bit0;
+ bool pre_addr1 = true;
+ bool pre_hex_seen = false;
+ int in_len, k, j, m, ind;
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int off = 0;
+ int64_t ll;
+ uint64_t ull, prev_lba;
+ char * lcp;
+ class scat_gath_elem sge;
+ char line[1024];
+
+ for (j = 0 ; ; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ // could improve with carry_over logic if sizeof(line) too small
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ } else {
+ m_errno = SG_LIB_SYNTAX_ERROR;
+ if (b_vb)
+ pr2serr("%s: %s: line too long, max %d bytes\n",
+ __func__, fnp, (int)(sizeof(line) - 1));
+ goto err_out;
+ }
+ }
+ if (in_len < 1)
+ continue;
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ if (pre_addr1 || pre_hex_seen) {
+ /* Accept lines with leading 'HEX' and ignore as long as there
+ * is one _before_ any LBA,NUM lines in the file. This allows
+ * HEX marked sgls to be concaternated together. */
+ if (('H' == toupper(lcp[0])) && ('E' == toupper(lcp[1])) &&
+ ('X' == toupper(lcp[2]))) {
+ pre_hex_seen = true;
+ if (def_hex)
+ continue; /* bypass 'HEX' marker line if expecting hex */
+ else {
+ if (flexible) {
+ def_hex = true; /* okay, switch to hex parse */
+ continue;
+ } else {
+ pr2serr("%s: %s: 'hex' string detected on line %d, "
+ "expecting decimal\n", __func__, fnp, j + 1);
+ m_errno = EINVAL;
+ goto err_out;
+ }
+ }
+ }
+ }
+ k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXbBdDiIkKmMgGtTpP, \t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: syntax error at line %d, pos %d\n",
+ __func__, fnp, j + 1, m + k + 1);
+ goto err_out;
+ }
+ for (k = 0; k < 256; ++k) {
+ /* limit parseable items on one line to 256 */
+ if (def_hex) { /* don't accept negatives or multipliers */
+ if (1 == sscanf(lcp, "%" SCNx64, &ull))
+ ll = (int64_t)ull;
+ else
+ ll = -1; /* use (2**64 - 1) as error flag */
+ } else
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ ind = ((off + k) >> 1);
+ bit0 = !! (0x1 & (off + k));
+ if (ind >= SG_SGL_MAX_ELEMENTS) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: array length exceeded\n", __func__,
+ fnp);
+ goto err_out;
+ }
+ if (bit0) { /* bit0 set when decoding a NUM */
+ if (ll < 0) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: bad number in line %d, at pos "
+ "%d\n", __func__, fnp, j + 1,
+ (int)(lcp - line + 1));
+ goto err_out;
+ }
+ if (ll > max_nbs) {
+ int h = 1;
+
+ /* split up this elem into multiple, smaller elems */
+ do {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ sgl.push_back(sge);
+ sge.lba = prev_lba + (uint64_t)max_nbs;
+ ++h;
+ off += 2;
+ ll -= max_nbs;
+ } while (ll > max_nbs);
+ if (b_vb)
+ pr2serr("%s: split large sg elem into %d "
+ "elements\n", __func__, h);
+ }
+ sge.num = (uint32_t)ll;
+ sgl.push_back(sge);
+ } else { /* bit0 clear when decoding a LBA */
+ if (pre_addr1)
+ pre_addr1 = false;
+ sge.lba = (uint64_t)ll;
+ }
+ } else { /* failed to decode number on line */
+ if ('#' == *lcp) { /* numbers before #, rest of line comment */
+ --k;
+ break; /* goes to next line */
+ }
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: error in line %d, at pos %d\n",
+ __func__, fnp, j + 1, (int)(lcp - line + 1));
+ goto err_out;
+ }
+ lcp = strpbrk(lcp, " ,\t#");
+ if ((NULL == lcp) || ('#' == *lcp))
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } /* <<< end of for(k < 256) loop */
+ off += (k + 1);
+ } /* <<< end of for loop, one iteration per line */
+ /* allow one items, but not higher odd number of items */
+ if ((off > 1) && (0x1 & off)) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: expect even number of items: "
+ "LBA0,NUM0,LBA1,NUM1...\n", __func__, fnp);
+ goto err_out;
+ }
+ clearerr(fp); /* even EOF on first pass needs this before rescan */
+ return true;
+err_out:
+ clearerr(fp);
+ return false;
+}
+
+/* Read numbers from filename (or stdin), line by line (comma (or (single)
+ * space) separated list); places starting_LBA,number_of_block pairs in an
+ * array of scat_gath_elem elements pointed to by the returned value. If
+ * this fails NULL is returned and an error number is written to errp (if it
+ * is non-NULL). Assumed decimal (and may have suffix multipliers) when
+ * def_hex==false; if a number is prefixed by '0x', '0X' or contains trailing
+ * 'h' or 'H' that denotes a hex number. When def_hex==true all numbers are
+ * assumed to be hex (ignored '0x' prefixes and 'h' suffixes) and multipliers
+ * are not permitted. Heap allocates an array just big enough to hold all
+ * elements if the file is countable. Pipes and stdin are not considered
+ * countable. In the non-countable case an array of MAX_FIXED_SGL_ELEMS
+ * elements is pre-allocated; if it is exceeded sg_convert_errno(EDOM) is
+ * placed in *errp (if it is non-NULL). One of the first actions is to write
+ * 0 to *errp (if it is non-NULL) so the caller does not need to zero it
+ * before calling. */
+bool
+scat_gath_list::load_from_file(const char * file_name, bool def_hex,
+ bool flexible, bool b_vb)
+{
+ bool have_stdin;
+ bool have_err = false;
+ FILE * fp;
+ const char * fnp;
+
+ have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0]));
+ if (have_stdin) {
+ fp = stdin;
+ fnp = "<stdin>";
+ } else {
+ fnp = file_name;
+ fp = fopen(fnp, "r");
+ if (NULL == fp) {
+ m_errno = errno;
+ if (b_vb)
+ pr2serr("%s: opening %s: %s\n", __func__, fnp,
+ safe_strerror(m_errno));
+ return false;
+ }
+ }
+ if (! file2sgl_helper(fp, fnp, def_hex, flexible, b_vb))
+ have_err = true;
+ if (! have_stdin)
+ fclose(fp);
+ return have_err ? false : true;
+}
+
+const char *
+scat_gath_list::linearity_as_str() const
+{
+ switch (linearity) {
+ case SGL_LINEAR:
+ return "linear";
+ case SGL_MONOTONIC:
+ return "monotonic";
+ case SGL_MONO_OVERLAP:
+ return "monotonic, overlapping";
+ case SGL_NON_MONOTONIC:
+ return "non-monotonic";
+ default:
+ return "unknown";
+ }
+}
+
+void
+scat_gath_list::set_weaker_linearity(enum sgl_linearity_e lin)
+{
+ int i_lin = (int)lin;
+
+ if (i_lin > (int)linearity)
+ linearity = lin;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_list::dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+ bool show_sgl) const
+{
+ int num = sgl.size();
+ const char * caller = id_str ? id_str : "unknown";
+ FILE * fp = to_stdout ? stdout : stderr;
+
+ if (! skip_meta) {
+ fprintf(fp, "%s: elems=%d, sgl %spresent, linearity=%s\n",
+ caller, num, (sgl.empty() ? "not " : ""),
+ linearity_as_str());
+ fprintf(fp, " sum=%" PRId64 ", sum_hard=%s lowest=0x%" PRIx64
+ ", high_lba_p1=", sum, (sum_hard ? "true" : "false"),
+ lowest_lba);
+ fprintf(fp, "0x%" PRIx64 "\n", high_lba_p1);
+ }
+ fprintf(fp, " >> %s scatter gather list (%d element%s):\n", caller, num,
+ (num == 1 ? "" : "s"));
+ if (show_sgl) {
+ int k;
+
+ for (k = 0; k < num; ++k) {
+ const class scat_gath_elem & sge = sgl[k];
+
+ fprintf(fp, " lba: 0x%" PRIx64 ", number: 0x%" PRIx32,
+ sge.lba, sge.num);
+ if (sge.lba > 0)
+ fprintf(fp, " [next lba: 0x%" PRIx64 "]", sge.lba + sge.num);
+ fprintf(fp, "\n");
+ }
+ }
+}
+
+/* Assumes sgl array (vector) is setup. The other fields in this object are
+ * set by analyzing sgl in a single pass. The fields that are set are:
+ * fragmented, lowest_lba, high_lba_p1, monotonic, overlapping, sum and
+ * sum_hard. Degenerate elements (i.e. those with 0 blocks) are ignored apart
+ * from when one is last which makes sum_hard false and its LBA becomes
+ * high_lba_p1 if it is the highest in the list. An empty sgl is equivalent
+ * to a 1 element list with [0, 0], so sum_hard==false, monit==true,
+ * fragmented==false and overlapping==false . id_str may be NULL, present
+ * to enhance verbose output. */
+void
+scat_gath_list::sum_scan(const char * id_str, bool show_sgl, bool b_vb)
+{
+ bool degen = false;
+ bool first = true;
+ bool regular = true; /* no overlapping segments detected */
+ int k;
+ int elems = sgl.size();
+ uint32_t prev_num, t_num;
+ uint64_t prev_lba, t_lba, low, high, end;
+
+ sum = 0;
+ for (k = 0, low = 0, high = 0; k < elems; ++k) {
+ const class scat_gath_elem & sge = sgl[k];
+
+ degen = false;
+ t_num = sge.num;
+ if (0 == t_num) {
+ degen = true;
+ if (! first)
+ continue; /* ignore degen element that not first */
+ }
+ if (first) {
+ low = sge.lba;
+ sum = t_num;
+ high = sge.lba + sge.num;
+ first = false;
+ } else {
+ t_lba = sge.lba;
+ if ((prev_lba + prev_num) != t_lba)
+ set_weaker_linearity(SGL_MONOTONIC);
+ sum += t_num;
+ end = t_lba + t_num;
+ if (end > high)
+ high = end; /* high is one plus highest LBA */
+ if (prev_lba < t_lba)
+ ;
+ else if (prev_lba == t_lba) {
+ if (prev_num > 0) {
+ set_weaker_linearity(SGL_MONO_OVERLAP);
+ break;
+ }
+ } else {
+ low = t_lba;
+ set_weaker_linearity(SGL_NON_MONOTONIC);
+ break;
+ }
+ if (regular) {
+ if ((prev_lba + prev_num) > t_lba)
+ regular = false;
+ }
+ }
+ prev_lba = sge.lba;
+ prev_num = sge.num;
+ } /* end of for loop while still elements and monot true */
+
+ if (k < elems) { /* only here if above breaks are taken */
+ prev_lba = t_lba;
+ ++k;
+ for ( ; k < elems; ++k) {
+ const class scat_gath_elem & sge = sgl[k];
+
+ degen = false;
+ t_lba = sge.lba;
+ t_num = sge.num;
+ if (0 == t_num) {
+ degen = true;
+ continue;
+ }
+ sum += t_num;
+ end = t_lba + t_num;
+ if (end > high)
+ high = end;
+ if (prev_lba > t_lba) {
+ if (t_lba < low)
+ low = t_lba;
+ }
+ prev_lba = t_lba;
+ }
+ } else
+ if (! regular)
+ set_weaker_linearity(SGL_MONO_OVERLAP);
+
+ lowest_lba = low;
+ if (degen && (elems > 0)) { /* last element always impacts high_lba_p1 */
+ t_lba = sgl[elems - 1].lba;
+ high_lba_p1 = (t_lba > high) ? t_lba : high;
+ } else
+ high_lba_p1 = high;
+ sum_hard = (elems > 0) ? ! degen : false;
+ if (b_vb)
+ dbg_print(false, id_str, false, show_sgl);
+}
+
+/* Usually will append (or add to start if empty) sge unless 'extra_blks'
+ * exceeds MAX_SGL_NUM_VAL. In that case multiple sge_s are added with
+ * sge.num = MAX_SGL_NUM_VAL or less (for final sge) until extra_blks is
+ * exhausted. Returns new size of scatter gather list. */
+int
+scat_gath_list::append_1or(int64_t extra_blks, int64_t start_lba)
+{
+ int o_num = sgl.size();
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int64_t cnt = 0;
+ class scat_gath_elem sge;
+
+ if ((extra_blks <= 0) && (start_lba < 0))
+ return o_num; /* nothing to do */
+ if ((o_num > 0) && (! sum_hard)) {
+ sge = sgl[o_num - 1]; /* assume sge.num==0 */
+ if (sge.lba == (uint64_t)start_lba) {
+ if (extra_blks <= max_nbs)
+ sge.num = extra_blks;
+ else
+ sge.num = max_nbs;
+ sgl[o_num - 1] = sge;
+ cnt = sge.num;
+ sum += cnt;
+ sum_hard = true;
+ if (cnt <= extra_blks) {
+ high_lba_p1 = sge.lba + cnt;
+ return o_num;
+ }
+ }
+ } else if (0 == o_num) {
+ lowest_lba = start_lba;
+ if (0 == extra_blks) {
+ sge.lba = start_lba;
+ sge.num = 0;
+ sgl.push_back(sge);
+ high_lba_p1 = sge.lba;
+ return sgl.size();
+ }
+ }
+ for ( ; cnt < extra_blks; cnt += max_nbs) {
+ sge.lba = start_lba + cnt;
+ if ((extra_blks - cnt) <= max_nbs)
+ sge.num = extra_blks - cnt;
+ else
+ sge.num = max_nbs;
+ sgl.push_back(sge);
+ sum += sge.num;
+ } /* always loops at least once */
+ sum_hard = true;
+ high_lba_p1 = sge.lba + sge.num;
+ return sgl.size();
+}
+
+int
+scat_gath_list::append_1or(int64_t extra_blks)
+{
+ int o_num = sgl.size();
+
+ if (o_num < 1)
+ return append_1or(extra_blks, 0);
+
+ class scat_gath_elem sge = sgl[o_num - 1];
+
+ return append_1or(extra_blks, sge.lba + sge.num);
+}
+
+bool
+sgls_eq_off(const scat_gath_list & left, int l_e_ind, int l_blk_off,
+ const scat_gath_list & right, int r_e_ind, int r_blk_off,
+ bool allow_partial)
+{
+ int lelems = left.sgl.size();
+ int relems = right.sgl.size();
+
+ while ((l_e_ind < lelems) && (r_e_ind < relems)) {
+ if ((left.sgl[l_e_ind].lba + l_blk_off) !=
+ (right.sgl[r_e_ind].lba + r_blk_off))
+ return false;
+
+ int lrem = left.sgl[l_e_ind].num - l_blk_off;
+ int rrem = right.sgl[r_e_ind].num - r_blk_off;
+
+ if (lrem == rrem) {
+ ++l_e_ind;
+ l_blk_off = 0;
+ ++r_e_ind;
+ r_blk_off = 0;
+ } else if (lrem < rrem) {
+ ++l_e_ind;
+ l_blk_off = 0;
+ r_blk_off += lrem;
+ } else {
+ ++r_e_ind;
+ r_blk_off = 0;
+ l_blk_off += rrem;
+ }
+ }
+ if ((l_e_ind >= lelems) && (r_e_ind >= relems))
+ return true;
+ return allow_partial;
+}
+
+/* If bad arguments returns -1, otherwise returns the lowest LBA in *sglp .
+ * If no elements considered returns 0. If ignore_degen is true than
+ * ignores all elements with sge.num zero unless always_last is also
+ * true in which case the last element is always considered. */
+int64_t
+scat_gath_list::get_lowest_lba(bool ignore_degen, bool always_last) const
+{
+ int k;
+ const int num_elems = sgl.size();
+ bool some = (num_elems > 0);
+ int64_t res = INT64_MAX;
+
+ for (k = 0; k < num_elems; ++k) {
+ if ((0 == sgl[k].num) && ignore_degen)
+ continue;
+ if ((int64_t)sgl[k].lba < res)
+ res = sgl[k].lba;
+ }
+ if (always_last && some) {
+ if ((int64_t)sgl[k - 1].lba < res)
+ res = sgl[k - 1].lba;
+ }
+ return (INT64_MAX == res) ? 0 : res;
+}
+
+/* Returns >= 0 if sgl can be simplified to a single LBA. So an empty sgl
+ * will return 0; a one element sgl will return its LBA. A multiple element
+ * sgl only returns the first element's LBA (that is not degenerate) if the
+ * sgl is monotonic and not fragmented. In the extreme case takes last
+ * element's LBA if all prior elements are degenerate. Else returns -1 .
+ * Assumes sgl_sum_scan() has been called. */
+int64_t
+scat_gath_list::get_low_lba_from_linear() const
+{
+ const int num_elems = sgl.size();
+ int k;
+
+ if (num_elems <= 1)
+ return (1 == num_elems) ? sgl[0].lba : 0;
+ else {
+ if (linearity == SGL_LINEAR) {
+ for (k = 0; k < (num_elems - 1); ++k) {
+ if (sgl[k].num > 0)
+ return sgl[k].lba;
+ }
+ /* take last element's LBA if all earlier are degenerate */
+ return sgl[k].lba;
+ } else
+ return -1;
+ }
+}
+
+bool
+scat_gath_list::is_pipe_suitable() const
+{
+ return (lowest_lba == 0) && (linearity == SGL_LINEAR);
+}
+
+scat_gath_iter::scat_gath_iter(const scat_gath_list & parent)
+ : sglist(parent), it_el_ind(0), it_blk_off(0), blk_idx(0)
+{
+ int elems = sglist.num_elems();
+
+ if (elems > 0)
+ extend_last = (0 == sglist.sgl[elems - 1].num);
+}
+
+bool
+scat_gath_iter::set_by_blk_idx(int64_t _blk_idx)
+{
+ bool first;
+ int k;
+ const int elems = sglist.sgl.size();
+ const int last_ind = elems - 1;
+ int64_t bc = _blk_idx;
+
+ if (bc < 0)
+ return false;
+
+ if (bc == blk_idx)
+ return true;
+ else if (bc > blk_idx) {
+ k = it_el_ind;
+ bc -= blk_idx;
+ } else
+ k = 0;
+ for (first = true; k < elems; ++k, first = false) {
+ uint32_t num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+ sglist.sgl[k].num;
+ if (first) {
+ if ((int64_t)(num - it_blk_off) < bc)
+ bc -= (num - it_blk_off);
+ else {
+ it_blk_off = bc + it_blk_off;
+ break;
+ }
+ } else {
+ if ((int64_t)num < bc)
+ bc -= num;
+ else {
+ it_blk_off = (uint32_t)bc;
+ break;
+ }
+ }
+ }
+ it_el_ind = k;
+ blk_idx = _blk_idx;
+
+ if (k < elems)
+ return true;
+ else if ((k == elems) && (0 == it_blk_off))
+ return true; /* EOL */
+ else
+ return false;
+}
+
+/* Given a blk_count, the iterator (*iter_p) is moved toward the EOL.
+ * Returns true unless blk_count takes iterator two or more past the last
+ * element. So if blk_count takes the iterator to the EOL, this function
+ * returns true. Takes into account iterator's extend_last flag. */
+bool
+scat_gath_iter::add_blks(uint64_t blk_count)
+{
+ bool first;
+ int k;
+ const int elems = sglist.sgl.size();
+ const int last_ind = elems - 1;
+ uint64_t bc = blk_count;
+
+ if (0 == bc)
+ return true;
+ for (first = true, k = it_el_ind; k < elems; ++k) {
+ uint32_t num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+ sglist.sgl[k].num;
+ if (first) {
+ first = false;
+ if ((uint64_t)(num - it_blk_off) <= bc)
+ bc -= (num - it_blk_off);
+ else {
+ it_blk_off = bc + it_blk_off;
+ break;
+ }
+ } else {
+ if ((uint64_t)num <= bc)
+ bc -= num;
+ else {
+ it_blk_off = (uint32_t)bc;
+ break;
+ }
+ }
+ }
+ it_el_ind = k;
+ blk_idx += blk_count;
+
+ if (k < elems)
+ return true;
+ else if ((k == elems) && (0 == it_blk_off))
+ return true; /* EOL */
+ else
+ return false;
+}
+
+/* Move the iterator from its current position (which may be to EOL) towards
+ * the start of the sgl (i.e. backwards) for blk_count blocks. Returns true
+ * if iterator is valid after the move, else returns false. N.B. if false is
+ * returned, then the iterator is invalid and may need to set it to a valid
+ * value. */
+bool
+scat_gath_iter::sub_blks(uint64_t blk_count)
+{
+ bool first;
+ int k = it_el_ind;
+ uint64_t bc = 0;
+ const uint64_t orig_blk_count = blk_count;
+
+ if (0 == blk_count)
+ return true;
+ for (first = true; k >= 0; --k) {
+ if (first) {
+ if (blk_count > (uint64_t)it_blk_off)
+ blk_count -= it_blk_off;
+ else {
+ it_blk_off -= blk_count;
+ break;
+ }
+ first = false;
+ } else {
+ uint32_t off = sglist.sgl[k].num;
+
+ bc = blk_count;
+ if (bc > (uint64_t)off)
+ blk_count -= off;
+ else {
+ bc = off - bc;
+ break;
+ }
+ }
+ }
+ if (k < 0) {
+ blk_idx = 0;
+ it_blk_off = 0;
+ return false; /* bad situation */
+ }
+ if ((int64_t)orig_blk_count <= blk_idx)
+ blk_idx -= orig_blk_count;
+ else
+ blk_idx = 0;
+ it_el_ind = k;
+ if (! first)
+ it_blk_off = (uint32_t)bc;
+ return true;
+}
+
+/* Returns LBA referred to by iterator if valid or returns SG_LBA_INVALID
+ * (-1) if at end or invalid. */
+int64_t
+scat_gath_iter::current_lba() const
+{
+ const int elems = sglist.sgl.size();
+ int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+ if (it_el_ind < elems) {
+ class scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+ if ((uint32_t)it_blk_off < sge.num)
+ return sge.lba + it_blk_off;
+ else if (((uint32_t)it_blk_off == sge.num) &&
+ ((it_el_ind + 1) < elems)) {
+ class scat_gath_iter iter(*this);
+
+ ++iter.it_el_ind;
+ iter.it_blk_off = 0;
+ /* worst case recursion will stop at end of sgl */
+ return iter.current_lba();
+ }
+ }
+ return res;
+}
+
+int64_t
+scat_gath_iter::current_lba_rem_num(int & rem_num) const
+{
+ const int elems = sglist.sgl.size();
+ int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+ if (it_el_ind < elems) {
+ class scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+ if ((uint32_t)it_blk_off < sge.num) {
+ rem_num = sge.num - it_blk_off;
+ return sge.lba + it_blk_off;
+ } else if (((uint32_t)it_blk_off == sge.num) &&
+ ((it_el_ind + 1) < elems)) {
+ class scat_gath_iter iter(*this);
+
+ ++iter.it_el_ind;
+ iter.it_blk_off = 0;
+ /* worst case recursion will stop at end of sgl */
+ return iter.current_lba_rem_num(rem_num);
+ }
+ }
+ rem_num = -1;
+ return res;
+}
+
+class scat_gath_elem
+scat_gath_iter::current_elem() const
+{
+ const int elems = sglist.sgl.size();
+ class scat_gath_elem sge;
+
+ sge.make_bad();
+ if (it_el_ind < elems)
+ return sglist.sgl[it_el_ind];
+ return sge;
+}
+
+/* Returns true of no sgl or sgl is at the end [elems, 0], otherwise it
+ * returns false. */
+bool
+scat_gath_iter::at_end() const
+{
+ const int elems = sglist.sgl.size();
+
+ return ((0 == elems) || ((it_el_ind == elems) && (0 == it_blk_off)));
+}
+
+/* Returns true if associated iterator is monotonic (increasing) and not
+ * fragmented. Empty sgl and single element degenerate considered linear.
+ * Assumes sgl_sum_scan() has been called on sgl. */
+bool
+scat_gath_iter::is_sgl_linear() const
+{
+ return sglist.linearity == SGL_LINEAR;
+}
+
+/* Should return 1 or more unless max_n<=0 or at_end() */
+int
+scat_gath_iter::linear_for_n_blks(int max_n) const
+{
+ int k, rem;
+ const int elems = sglist.sgl.size();
+ uint64_t prev_lba;
+ class scat_gath_elem sge;
+
+ if (at_end() || (max_n <= 0))
+ return 0;
+ sge = sglist.sgl[it_el_ind];
+ rem = (int)sge.num - it_blk_off;
+ if (rem <= 0) {
+ sge = sglist.sgl[it_el_ind + 1];
+ rem = (int)sge.num;
+ }
+ if (max_n <= rem)
+ return max_n;
+ prev_lba = sge.lba + sge.num;
+ for (k = it_el_ind + 1; k < elems; ++k) {
+ sge = sglist.sgl[k];
+ if (sge.lba != prev_lba)
+ return rem;
+ rem += sge.num;
+ if (max_n <= rem)
+ return max_n;
+ prev_lba = sge.lba + sge.num;
+ }
+ return rem;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_iter::dbg_print(const char * id_str, bool to_stdout,
+ int verbose) const
+{
+ const char * caller = id_str ? id_str : "unknown";
+ FILE * fp = to_stdout ? stdout : stderr;
+
+ fprintf(fp, "%s: it_el_ind=%d, it_blk_off=%d, blk_idx=%" PRId64 "\n",
+ caller, it_el_ind, it_blk_off, blk_idx);
+ fprintf(fp, " extend_last=%d\n", extend_last);
+ if (verbose)
+ sglist.dbg_print(false, " iterator's", to_stdout, verbose > 1);
+}
+
+/* Calculates difference between iterators, logically: res <-- lhs - rhs
+ * Checks that lhsp and rhsp have same underlying sgl, if not returns
+ * INT_MIN. Assumes iterators close enough for result to lie in range
+ * from (-INT_MAX) to INT_MAX (inclusive). */
+int
+diff_between_iters(const class scat_gath_iter & left,
+ const class scat_gath_iter & right)
+{
+ int res, k, r_e_ind, l_e_ind;
+
+ if (&left.sglist != &right.sglist) {
+ pr2serr("%s: bad args\n", __func__);
+ return INT_MIN;
+ }
+ r_e_ind = right.it_el_ind;
+ l_e_ind = left.it_el_ind;
+ if (l_e_ind < r_e_ind) { /* so difference will be negative */
+ res = diff_between_iters(right, left); /* cheat */
+ if (INT_MIN == res)
+ return res;
+ return -res;
+ } else if (l_e_ind == r_e_ind)
+ return (int)left.it_blk_off - (int)right.it_blk_off;
+ /* (l_e_ind > r_e_ind) so (lhs > rhs) */
+ res = (int)right.sglist.sgl[r_e_ind].num - right.it_blk_off;
+ for (k = 1; (r_e_ind + k) < l_e_ind; ++k) {
+ // pr2serr("%s: k=%d, res=%d, num=%d\n", __func__, k, res,
+ // (int)right.sglist.sgl[r_e_ind + k].num);
+ res += (int)right.sglist.sgl[r_e_ind + k].num;
+ }
+ res += left.it_blk_off;
+ // pr2serr("%s: at exit res=%d\n", __func__, res);
+ return res;
+}
+
+/* Compares from the current iterator positions of left and left until
+ * the shorter list is exhausted. Returns false on the first inequality.
+ * If no inequality and both remaining lists are same length then returns
+ * true. If no inequality but remaining lists differ in length then returns
+ * allow_partial. */
+bool
+sgls_eq_from_iters(const class scat_gath_iter & left,
+ const class scat_gath_iter & right,
+ bool allow_partial)
+{
+ return sgls_eq_off(left.sglist, left.it_el_ind, left.it_blk_off,
+ right.sglist, right.it_el_ind, right.it_blk_off,
+ allow_partial);
+}
diff --git a/testing/sg_scat_gath.h b/testing/sg_scat_gath.h
new file mode 100644
index 00000000..d316a7b1
--- /dev/null
+++ b/testing/sg_scat_gath.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014-2020 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// C standard headers
+#include <stdio.h>
+#include <stdint.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+// C++ standard headers
+#include <vector>
+
+// This file is a C++ header file
+
+
+#define SG_SGL_MAX_ELEMENTS 16384
+
+#define SG_COUNT_INDEFINITE (-1)
+#define SG_LBA_INVALID SG_COUNT_INDEFINITE
+
+// Sizing matches largest SCSI READ and WRITE commands plus those of Unix
+// read(2)s and write(2)s. User can give larger than 31 bit 'num's but they
+// are split into several consecutive elements.
+class scat_gath_elem {
+public:
+ uint64_t lba; // of start block
+ uint32_t num; // number of blocks from and including start block
+
+ void make_bad() { lba = UINT64_MAX; num = UINT32_MAX; }
+ bool is_bad() const { return (lba == UINT64_MAX && num == UINT32_MAX); }
+};
+
+// Consider "linearity" as a scatter gather list property. Elements of this
+// of from the strongest form to the weakest.
+enum sgl_linearity_e {
+ SGL_LINEAR = 0, // empty list and 0,0 considered linear
+ SGL_MONOTONIC, // since not linear, implies holes
+ SGL_MONO_OVERLAP, // monotonic but same LBA in two or more elements
+ SGL_NON_MONOTONIC // weakest
+};
+
+
+// Holds one scatter gather list and its associated metadata
+class scat_gath_list {
+public:
+ scat_gath_list() : linearity(SGL_LINEAR), sum_hard(false), m_errno(0),
+ high_lba_p1(0), lowest_lba(0), sum(0) { }
+
+ scat_gath_list(const scat_gath_list &) = default;
+ scat_gath_list & operator=(const scat_gath_list &) = default;
+ ~scat_gath_list() = default;
+
+ bool empty() const;
+ bool empty_or_00() const;
+ int num_elems() const;
+ int64_t get_lowest_lba(bool ignore_degen, bool always_last) const;
+ int64_t get_low_lba_from_linear() const;
+ bool is_pipe_suitable() const;
+
+ friend bool sgls_eq_off(const scat_gath_list &left, int l_e_ind,
+ int l_blk_off,
+ const scat_gath_list &right, int r_e_ind,
+ int r_blk_off, bool allow_partial);
+
+ bool load_from_cli(const char * cl_p, bool b_vb);
+ bool load_from_file(const char * file_name, bool def_hex, bool flexible,
+ bool b_vb);
+ int append_1or(int64_t extra_blks, int64_t start_lba);
+ int append_1or(int64_t extra_blks);
+
+ void dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+ bool show_sgl) const;
+
+ // calculates and sets following bool-s and int64_t-s
+ void sum_scan(const char * id_str, bool show_sgl, bool b_verbose);
+
+ void set_weaker_linearity(enum sgl_linearity_e lin);
+ enum sgl_linearity_e linearity;
+ const char * linearity_as_str() const;
+
+ bool sum_hard; // 'num' in last element of 'sgl' is > 0
+ int m_errno; // OS failure errno
+ int64_t high_lba_p1; // highest LBA plus 1, next write from and above
+ int64_t lowest_lba; // initialized to 0
+ int64_t sum; // of all 'num' elements in 'sgl'
+
+ friend int diff_between_iters(const class scat_gath_iter & left,
+ const class scat_gath_iter & right);
+
+private:
+ friend class scat_gath_iter;
+
+ bool file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+ bool flexible, bool b_vb);
+
+ std::vector<scat_gath_elem> sgl; // an array on heap [0..num_elems())
+};
+
+
+class scat_gath_iter {
+public:
+ explicit scat_gath_iter(const scat_gath_list & my_scat_gath_list);
+ scat_gath_iter(const scat_gath_iter & src) = default;
+ scat_gath_iter& operator=(const scat_gath_iter&) = delete;
+ ~scat_gath_iter() = default;
+
+ int64_t current_lba() const;
+ int64_t current_lba_rem_num(int & rem_num) const;
+ class scat_gath_elem current_elem() const;
+ bool at_end() const;
+ bool is_sgl_linear() const; // the whole list
+ // Should return 1 or more unless max_n<=0 or at_end()
+ int linear_for_n_blks(int max_n) const;
+
+ bool set_by_blk_idx(int64_t _blk_idx);
+ // add/sub blocks return true if they reach EOL/start, else false
+ bool add_blks(uint64_t blk_count);
+ bool sub_blks(uint64_t blk_count);
+
+ void dbg_print(const char * id_str, bool to_stdout, int verbose) const;
+
+ friend int diff_between_iters(const class scat_gath_iter & left,
+ const class scat_gath_iter & right);
+
+ friend bool sgls_eq_from_iters(const class scat_gath_iter & left,
+ const class scat_gath_iter & right,
+ bool allow_partial);
+
+private:
+ const scat_gath_list &sglist;
+
+ // dual representation: either it_el_ind,it_blk_off or blk_idx
+ int it_el_ind; // refers to sge==sglist[it_el_ind]
+ int it_blk_off; // refers to LBA==(sge.lba + it_blk_off)
+ int64_t blk_idx; // in range: [0 .. sglist.sum)
+ bool extend_last;
+};
diff --git a/testing/sg_sense_test.c b/testing/sg_sense_test.c
new file mode 100644
index 00000000..ce66cf3e
--- /dev/null
+++ b/testing/sg_sense_test.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2004-2018 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/* This is a simple program that tests the sense data descriptor format
+ * printout function in sg_lib.c . */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "sg_lib.h"
+
+
+#define EBUFF_SZ 256
+
+#define ME "sg_sense_test: "
+
+static const char * version_str = "2.04 20181207";
+
+static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"leadin", required_argument, 0, 'l'},
+ {"stdout", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}, /* sentinel */
+};
+
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "Usage: %s [--help] [--leadin=STR] [--stdout] [--verbose] "
+ "[--version]\n"
+ " where: --help|-h print out usage message\n"
+ " --leadin=STR|-l STR every line output by --sense "
+ "should\n"
+ " be prefixed by STR\n"
+ " --stdout|-s send output to stdout (def: "
+ "stderr)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Test sense data handling of sg_lib. Overlaps somewhat with "
+ "tst_sg_lib\n", ME
+ );
+
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool to_stdout = false;
+ int c, k, prev_len;
+ int verbose = 0;
+ const char * leadin = NULL;
+ FILE * outfp = stderr;
+ uint8_t err1[] = {0x72, 0x5, 0x24, 0x0, 0, 0, 0, 32,
+ 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0,
+ 0, 0xa, 0x80, 0, 1, 2, 3, 4,
+ 0xaa, 0xbb, 0xcc, 0xdd,
+ 1, 0xa, 0, 0, 1, 2, 3, 4,
+ 0xaa, 0xbb, 0xee, 0xff};
+ uint8_t err2[] = {0x72, SPC_SK_MEDIUM_ERROR, 0x11, 0xb, 0x80, 0, 0,
+ 32,
+ 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0,
+ 0, 0xa, 0x80, 0, 1, 2, 3, 4,
+ 0xaa, 0xbb, 0xcc, 0xdd,
+ 1, 0xa, 0, 0, 1, 2, 3, 4,
+ 0xaa, 0xbb, 0xee, 0xff};
+ /* Set SDAT_OVFL */
+ uint8_t err3[] = {0x72, SPC_SK_NO_SENSE, 0x4, 0x4, 0, 0, 0, 8,
+ 0x2, 0x6, 0, 0, 0xc8, 0x12, 0x34, 0};
+ uint8_t err4[] = {0x73, SPC_SK_COPY_ABORTED, 0x8, 0x4, 0, 0, 0, 22,
+ 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0,
+ 0x3, 0x2, 0, 0x55,
+ 0x5, 0x2, 0, 0x20,
+ 0x85, 0x4, 0, 0x20, 0x33, 0x44};
+ /* Set Filemark, EOM, ILI and SDAT_OVFL */
+ uint8_t err5[] = {0xf1, 0, (0xf0 | SPC_SK_ILLEGAL_REQUEST), 0x11,
+ 0x22, 0x33, 0x44, 0xa,
+ 0x0, 0x0, 0, 0, 0x4, 0x1, 0, 0xcf, 0, 5,};
+ uint8_t err6[] = {0x72, SPC_SK_NO_SENSE, 0x4, 0x1, 0, 0, 0, 14,
+ 0x9, 0xc, 1, 0, 0x11, 0x22, 0x66, 0x33,
+ 0x77, 0x44, 0x88, 0x55, 0x1, 0x2};
+ uint8_t err7[] = {0xf1, 0, 0xe5, 0x11, 0x22, 0x33, 0x44, 0xa,
+ 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0xbb,
+ 0xc9, 0x0, 0x2};
+ /* Vendor specific, with "valid" bit set */
+ uint8_t err8[] = {0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc,
+ 0xd, 0xe, 0xf, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99,
+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0};
+ char b[2048];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hl:svV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ leadin = optarg;
+ break;
+ case 's':
+ to_stdout = true;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ fprintf(stderr, "version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised switch code 0x%x ??\n", c);
+ usage();
+ return 1;
+ }
+ }
+ if (optind < argc) {
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return 1;
+ }
+ }
+ if (to_stdout) {
+ outfp = stdout;
+ sg_set_warnings_strm(outfp);
+ }
+
+ fprintf(outfp, "err1 test:\n");
+ sg_print_sense(leadin, err1, sizeof(err1), verbose /* raw_info */);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err2 test:\n");
+ sg_print_sense(leadin, err2, sizeof(err2), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err3 test:\n");
+ sg_print_sense(leadin, err3, sizeof(err3), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err4 test:\n");
+ sg_print_sense(leadin, err4, sizeof(err4), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err5 test: Set Filemark, EOM, ILI and SDAT_OVFL\n");
+ sg_print_sense(leadin, err5, sizeof(err5), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err6 test:\n");
+ sg_print_sense(leadin, err6, sizeof(err6), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err7 test:\n");
+ sg_print_sense(leadin, err7, sizeof(err7), verbose);
+ fprintf(outfp, "\n");
+ fprintf(outfp, "err8 test (vendor specific):\n");
+ sg_print_sense(leadin, err8, sizeof(err8), verbose);
+ fprintf(outfp, "\n");
+
+ if (verbose > 1) {
+ fprintf(outfp, "\n\nTry different output string sizes with "
+ "sg_get_sense_str(err2):\n");
+ for (k = 1, prev_len = -1; k < 512; ++k) {
+ /* snprintf(leadin, sizeof(leadin), "blen=%d", k); */
+ sg_get_sense_str(NULL, err2, sizeof(err2), 0, k, b);
+ fprintf(outfp, "%s\n", b);
+ if (prev_len == (int)strlen(b))
+ break;
+ else
+ prev_len = strlen(b);
+ }
+ }
+
+ if (verbose > 2) {
+ fprintf(outfp, "\n\nTry different output string sizes with "
+ "sg_get_sense_str(err4):\n");
+ for (k = 1, prev_len = -1; k < 512; ++k) {
+ /* snprintf(leadin, sizeof(leadin), "blen=%d", k); */
+ sg_get_sense_str(NULL, err4, sizeof(err4), 0, k, b);
+ fprintf(outfp, "%s\n", b);
+ if (prev_len == (int)strlen(b))
+ break;
+ else
+ prev_len = strlen(b);
+ }
+ }
+ return 0;
+}
diff --git a/testing/sg_take_snap.c b/testing/sg_take_snap.c
new file mode 100644
index 00000000..c714a409
--- /dev/null
+++ b/testing/sg_take_snap.c
@@ -0,0 +1,225 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 2021 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is experimental. It allows the SG_CTL_FLAGM_SNAP_DEV
+ * variant of ioctl(SG_SET_GET_EXTENDED) to be called. This assumes
+ * a Linux sg driver whose version number > 4.00.30 .
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+
+#define ME "sg_take_snap: "
+
+static const char * version_str = "1.01 20210403";
+
+#define SG_TAKE_MAX_DEVS 16
+
+static const char *dev_arr[SG_TAKE_MAX_DEVS];
+static int next_vacant_dev_idx = 0;
+
+static struct option long_options[] = {
+ {"clear", no_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+static void
+usage(void)
+{
+ pr2serr("Usage: sg_take_snap [--clear] [--help] [--verbose] [--version] "
+ "DEVICE*\n"
+ " where:\n"
+ " --clear|-c set 'clear_first' flag; otherwise appends\n"
+ " --help|-h print usage information then exit\n"
+ " --verbose|-v increase the level of verbosity\n"
+ " --version|-V print version number then exit\n\n"
+ "Use ioctl(SG_SET_GET_EXTENDED(SG_CTL_FLAGM_SNAP_DEV)) to take "
+ "snap .\nThe output is placed in /sys/kernel/debug/scsi_generic/"
+ "snapped and needs\nroot permissions to read. Requires a Linux "
+ "sg driver version > 4.00.30 .\nOne or more DEVICEs can be "
+ "given. Note: sending the ioctl to do this\ncreates some "
+ "'noise' in the output\n"
+ );
+}
+
+
+int main(int argc, char * argv[])
+{
+ bool clear_first = false;
+ int c, k, sg_fd, res;
+ int ret = 0;
+ int verbose = 0;
+ const char * device_name = NULL;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "chvV", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ clear_first = true;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+ if (optind < argc) {
+ for (; optind < argc; ++optind) {
+ if (next_vacant_dev_idx < SG_TAKE_MAX_DEVS) {
+ dev_arr[next_vacant_dev_idx] = argv[optind];
+ ++next_vacant_dev_idx;
+ } else if (next_vacant_dev_idx == SG_TAKE_MAX_DEVS) {
+ pr2serr("Maximum of %d DEVICEs on command line\n",
+ next_vacant_dev_idx);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ pr2serr("something is wrong ...\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+ if (NULL == dev_arr[0]) {
+ pr2serr("Need at least one DEVICE name. Use '--help' to see "
+ "usage.\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ for (k = 0; k < next_vacant_dev_idx; ++k) {
+ device_name = dev_arr[k];
+ sg_fd = open(device_name, O_RDWR | O_NONBLOCK);
+ if (sg_fd < 0) {
+ int err = errno;
+
+ ret = sg_convert_errno(err);
+ pr2serr(ME "open error: %s: ", device_name);
+ perror("");
+ sg_fd = -1;
+ goto fini;
+ }
+ if (0 == k) {
+ int t;
+
+ res = ioctl(sg_fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ pr2serr("sg driver prior to 3.0.00\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ if (verbose) {
+ pr2serr("sg driver version: %d.%02d.%02d\n",
+ t / 10000, (t % 10000) / 100, t % 100);
+ }
+ if (t < 40000) {
+ pr2serr("Warning: sg driver prior to 4.0.00\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ } else if (t < 40045) {
+ pr2serr("Warning: sg driver prior to 4.0.45\n");
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ }
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_SNAP_DEV;
+ if (clear_first) /* ... else 0 (due to memset) --> append */
+ seip->ctl_flags |= SG_CTL_FLAGM_SNAP_DEV;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED(SG_CTL_FLAGM_SNAP_DEV)), %s "
+ "failed errno=%d %s\n", device_name, errno,
+ strerror(errno));
+ ret = SG_LIB_FILE_ERROR;
+ goto fini;
+ }
+ if (verbose)
+ pr2serr("ioctl(%s, SG_SET_GET_EXTENDED(SG_CTL_FLAGM_SNAP_DEV)) "
+ "ok\n", device_name);
+ res = close(sg_fd);
+ sg_fd = -1;
+ if (res < 0) {
+ pr2serr("close errno=%d on %s\n", errno, device_name);
+ ret = res;
+ goto fini;
+ }
+ }
+
+fini:
+ if (sg_fd >= 0) {
+ res = close(sg_fd);
+ if (res < 0) {
+ res = sg_convert_errno(errno);
+ perror(ME "close error");
+ if (0 == ret)
+ ret = res;
+ }
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/testing/sg_tst_async.cpp b/testing/sg_tst_async.cpp
new file mode 100644
index 00000000..8904e379
--- /dev/null
+++ b/testing/sg_tst_async.cpp
@@ -0,0 +1,2227 @@
+/*
+ * Copyright (c) 2014-2022 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <iostream>
+#include <vector>
+#include <map>
+#include <list>
+#include <system_error>
+#include <thread>
+#include <mutex>
+#include <chrono>
+#include <atomic>
+#include <random>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <poll.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+#include <limits.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/resource.h> /* getrusage */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pt.h"
+#include "sg_cmds.h"
+
+static const char * version_str = "1.42 20220425";
+static const char * util_name = "sg_tst_async";
+
+/* This is a test program for checking the async usage of the Linux sg
+ * driver. Each thread opens 1 file descriptor to the next sg device (1
+ * or more can be given on the command line) and then starts up to
+ * num_per_thread commands or more while checking with the poll command (or
+ * ioctl(SG_GET_NUM_WAITING) ) for the completion of those commands. Each
+ * command has a unique "pack_id" which is a sequence starting at 1.
+ * Either TEST UNIT UNIT, READ(16) or WRITE(16) commands are issued.
+ *
+ * This is C++ code with some things from C++11 (e.g. threads) and was
+ * only just able to compile (when some things were reverted) with gcc/g++
+ * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
+ * was not available until g++ version 4.8.1 . It should build okay on
+ * recent distributions.
+ *
+ * The build uses various object files from the <sg3_utils>/lib directory
+ * which is assumed to be a sibling of this examples directory. Those
+ * object files in the lib directory can be built with:
+ * cd <sg3_utils_package_root> ; ./configure ; cd lib; make
+ * cd ../testing
+ * make sg_tst_async
+ *
+ * Currently this utility is Linux only and uses the sg driver. The bsg
+ * driver is known to be broken (it doesn't match responses to the
+ * correct file descriptor that requested them). Around Linux kernel 4.15
+ * the async capability of the bsg driver was removed. So this test code
+ * no longer appiles to the bsg driver.
+ *
+ * BEWARE: >>> This utility will modify a logical block (default LBA 1000)
+ * on the given device _when_ the '-W' option is given.
+ *
+ */
+
+using namespace std;
+using namespace std::chrono;
+
+#define DEF_NUM_PER_THREAD 1000
+#define DEF_NUM_THREADS 4
+#define DEF_WAIT_MS 10 /* 0: yield or no wait */
+#define DEF_NANOSEC_WAIT 25000 /* 25 microsecs */
+#define DEF_TIMEOUT_MS 20000 /* 20 seconds */
+#define DEF_LB_SZ 512
+#define DEF_BLOCKING 0
+#define DEF_DIRECT false /* true: direct_io */
+#define DEF_MMAP_IO false /* true: mmap-ed IO with sg */
+#define DEF_NO_XFER 0
+#define DEF_LBA 1000U
+
+#define MAX_Q_PER_FD 16383 /* sg driver per file descriptor limit */
+#define MAX_CONSEC_NOMEMS 4 /* was 16 */
+#define URANDOM_DEV "/dev/urandom"
+
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+#define EBUFF_SZ 256
+
+static mutex console_mutex;
+static mutex rand_lba_mutex;
+static atomic<int> async_starts(0);
+static atomic<int> sync_starts(0);
+static atomic<int> async_finishes(0);
+static atomic<int> start_ebusy_count(0);
+static atomic<int> start_e2big_count(0);
+static atomic<int> start_eagain_count(0);
+static atomic<int> fin_eagain_count(0);
+static atomic<int> fin_ebusy_count(0);
+static atomic<int> start_edom_count(0);
+static atomic<int> enomem_count(0);
+static atomic<int> uniq_pack_id(1);
+// static atomic<int> generic_errs(0);
+
+static int page_size = 4096; /* rough guess, will ask sysconf() */
+
+enum command2execute {SCSI_TUR, SCSI_READ16, SCSI_WRITE16};
+/* Linux Block layer queue disciplines: */
+enum blkLQDiscipline {BLQ_DEFAULT, BLQ_AT_HEAD, BLQ_AT_TAIL};
+/* Queue disciplines of this utility. When both completions and
+ * queuing a new command are both possible: */
+enum myQDiscipline {MYQD_LOW, /* favour completions over new cmds */
+ MYQD_MEDIUM,
+ MYQD_HIGH}; /* favour new cmds over completions */
+
+struct opts_t {
+ vector<const char *> dev_names;
+ vector<int> blk_szs;
+ bool block;
+ bool cmd_time;
+ bool direct;
+ bool excl;
+ bool generic_sync;
+ bool masync;
+ bool mmap_io;
+ bool no_xfer;
+ bool pack_id_force;
+ bool sg_vn_ge_40000;
+ bool sg_vn_ge_40030;
+ bool submit;
+ bool verbose_given;
+ bool v3;
+ bool v3_given;
+ bool v4;
+ bool v4_given;
+ bool version_given;
+ int maxq_per_thread;
+ int num_per_thread;
+ uint64_t lba;
+ unsigned int hi_lba; /* last one, inclusive range */
+ vector<unsigned int> hi_lbas; /* only used when hi_lba=-1 */
+ int lb_sz;
+ int num_lbs;
+ int ovn; /* override number for submission */
+ int stats;
+ int verbose;
+ int wait_ms;
+ command2execute c2e;
+ blkLQDiscipline blqd; /* --qat= 0|1 -> at_head|at_tail */
+ myQDiscipline myqd; /* --qfav= value (def: 2 --> MYQD_HIGH) */
+};
+
+static struct opts_t a_opts; /* Expect zero fill on simple types */
+
+static int pr_rusage(int id);
+
+#if 0
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi) : p{lo, hi} {}
+ unsigned int operator()() const { return r(); }
+private:
+ uniform_int_distribution<unsigned int>::param_type p;
+ auto r = bind(uniform_int_distribution<unsigned int>{p},
+ default_random_engine());
+ /* compiler thinks auto should be a static, bs again? */
+};
+#endif
+
+#if 0
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi, unsigned int my_seed)
+ : r(bind(uniform_int_distribution<unsigned int>{lo, hi},
+ default_random_engine())) { r.seed(myseed); }
+ unsigned int operator()() const { return r(); }
+private:
+ function<unsigned int()> r;
+};
+#endif
+
+/* Use this class to wrap C++11 <random> features to produce uniform random
+ * unsigned ints in the range [lo, hi] (inclusive) given a_seed */
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed)
+ : uid(lo, hi), dre(a_seed) { }
+ /* uid ctor takes inclusive range when integral type */
+
+ unsigned int get() { return uid(dre); }
+
+private:
+ uniform_int_distribution<unsigned int> uid;
+ default_random_engine dre;
+};
+
+static struct option long_options[] = {
+ {"v3", no_argument, 0, '3'},
+ {"v4", no_argument, 0, '4'},
+ {"more-async", no_argument, 0, 'a'},
+ {"more_async", no_argument, 0, 'a'},
+ {"masync", no_argument, 0, 'a'},
+ {"cmd-time", no_argument, 0, 'c'},
+ {"cmd_time", no_argument, 0, 'c'},
+ {"direct", no_argument, 0, 'd'},
+ {"excl", no_argument, 0, 'e'},
+ {"force", no_argument, 0, 'f'},
+ {"generic-sync", no_argument, 0, 'g'},
+ {"generic_sync", no_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"lba", required_argument, 0, 'l'},
+ {"lbsz", required_argument, 0, 'L'},
+ {"maxqpt", required_argument, 0, 'M'},
+ {"mmap-io", no_argument, 0, 'm'},
+ {"mmap_io", no_argument, 0, 'm'},
+ {"numpt", required_argument, 0, 'n'},
+ {"num-pt", required_argument, 0, 'n'},
+ {"num_pt", required_argument, 0, 'n'},
+ {"noxfer", no_argument, 0, 'N'},
+ {"override", required_argument, 0, 'O'},
+ {"pack-id", no_argument, 0, 'p'},
+ {"pack_id", no_argument, 0, 'p'},
+ {"qat", required_argument, 0, 'q'},
+ {"qfav", required_argument, 0, 'Q'},
+ {"read", no_argument, 0, 'R'},
+ {"stats", no_argument, 0, 'S'},
+ {"submit", no_argument, 0, 'u'},
+ {"szlb", required_argument, 0, 's'},
+ {"tnum", required_argument, 0, 't'},
+ {"tur", no_argument, 0, 'T'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wait", required_argument, 0, 'w'},
+ {"write", no_argument, 0, 'W'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage(void)
+{
+ printf("Usage: %s [--cmd-time] [--direct] [--excl] [--force]\n"
+ " [--generic-sync] [--help] [--lba=LBA+] "
+ "[--lbsz=LBSZ]\n"
+ " [--masync] [--maxqpt=QPT] [--mmap-io] "
+ "[--no-waitq]\n"
+ " [--noxfer] [--numpt=NPT] [--override=OVN] "
+ "[--pack-id]\n"
+ " [--qat=AT] [-qfav=FAV] [--read] [--stats] "
+ "[--submit]\n"
+ " [--szlb=LB[,NLBS]] [--tnum=NT] [--tur] "
+ "[--v3] [--v4]\n"
+ " [--verbose] [--version] [--wait=MS] "
+ "[--write]\n"
+ " <sg_disk_device>*\n",
+ util_name);
+ printf(" where\n");
+ printf(" --cmd-time|-c calculate per command average time (ns)\n");
+ printf(" --direct|-d do direct_io (def: indirect)\n");
+ printf(" --excl|-e do wait_exclusive calls\n");
+ printf(" --force|-f force: any sg device (def: only scsi_debug "
+ "owned)\n");
+ printf(" WARNING: <lba> written to if '-W' given\n");
+ printf(" --generic-sync|-g use generic synchronous SG_IO ioctl "
+ "instead\n");
+ printf(" of Linux sg driver assuming /dev/sg* "
+ "(def)\n");
+ printf(" --help|-h print this usage message then exit\n");
+ printf(" --lba=LBA|-l LBA logical block to access (def: %u)\n",
+ DEF_LBA);
+ printf(" --lba=LBA,HI_LBA|-l LBA,HI_LBA logical block range "
+ "(inclusive)\n"
+ " if hi_lba=-1 assume last block on "
+ "device\n");
+ printf(" --lbsz=LBSZ|-L LBSZ logical block size in bytes (def: "
+ "512)\n"
+ " should be power of 2 (0 --> 512)\n");
+ printf(" --masync|-a set 'more async' flag on devices\n");
+ printf(" --maxqpt=QPT|-M QPT maximum commands queued per thread "
+ "(def:%d)\n", MAX_Q_PER_FD);
+ printf(" --mmap-io|-m mmap-ed IO (1 cmd outstanding per thread)\n");
+ printf(" --noxfer|-N no data xfer (def: xfer on READ and "
+ "WRITE)\n");
+ printf(" --numpt=NPT|-n NPT number of commands per thread "
+ "(def: %d)\n", DEF_NUM_PER_THREAD);
+ printf(" --override OVN|-O OVN override FAV=2 when OVN queue "
+ "depth\n"
+ " reached (def: 0 -> no override)\n");
+ printf(" --pack-id|-p set FORCE_PACK_ID, pack-id input to "
+ "read/finish\n");
+ printf(" --qat=AT|-q AT AT=0: q_at_head; AT=1: q_at_tail (def: "
+ "(drv): head)\n");
+ printf(" --qfav=FAV|-Q FAV FAV=0: favour completions (smaller q),\n"
+ " FAV=1: medium,\n"
+ " FAV=2: favour submissions (larger q, "
+ "default)\n");
+ printf(" --read|-R do READs (def: TUR)\n");
+ printf(" --stats|-S show more statistics on completion\n");
+ printf(" --submit|-u use SG_IOSUBMIT+SG_IORECEIVE instead of "
+ "write+read\n");
+ printf(" --szlb=LB[,NLBS]| LB is logical block size (def: 512)\n");
+ printf(" -s LB[,NLBS] NLBS is number of logical blocks (def: "
+ "1)\n");
+ printf(" --tnum=NT|-t NT number of threads (def: %d)\n",
+ DEF_NUM_THREADS);
+ printf(" --tur|-T do TEST UNIT READYs (default is TURs)\n");
+ printf(" --v3|-3 use sg v3 interface (def: v3 if driver < "
+ "3.9)\n");
+ printf(" --v4|-4 use sg v4 interface (def if v4 driver). Sets "
+ "--submit\n");
+ printf(" --verbose|-v increase verbosity\n");
+ printf(" --version|-V print version number then exit\n");
+ printf(" --wait=MS|-w MS >0: poll(<wait_ms>); =0: poll(0); (def: "
+ "%d)\n", DEF_WAIT_MS);
+ printf(" --write|-W do WRITEs (def: TUR)\n\n");
+ printf("Multiple threads send READ(16), WRITE(16) or TEST UNIT READY "
+ "(TUR) SCSI\ncommands. There can be 1 or more <sg_disk_device>s "
+ "and each thread takes\nthe next in a round robin fashion. "
+ "Each thread queues up to NT commands.\nOne block is transferred "
+ "by each READ and WRITE; zeros are written. If a\nlogical block "
+ "range is given, a uniform distribution generates a pseudo\n"
+ "random sequence of LBAs. Set environment variable\n"
+ "SG3_UTILS_LINUX_NANO to get command timings in nanoseconds\n");
+}
+
+#ifdef __GNUC__
+static int pr2serr_lk(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+static void pr_errno_lk(int e_no, const char * fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+#else
+static int pr2serr_lk(const char * fmt, ...);
+static void pr_errno_lk(int e_no, const char * fmt, ...);
+#endif
+
+
+static int
+pr2serr_lk(const char * fmt, ...)
+{
+ int n;
+ va_list args;
+ lock_guard<mutex> lg(console_mutex);
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+static void
+pr_errno_lk(int e_no, const char * fmt, ...)
+{
+ char b[160];
+ va_list args;
+ lock_guard<mutex> lg(console_mutex);
+
+ va_start(args, fmt);
+ vsnprintf(b, sizeof(b), fmt, args);
+ fprintf(stderr, "%s: %s\n", b, strerror(e_no));
+ va_end(args);
+}
+
+static unsigned int
+get_urandom_uint(void)
+{
+ unsigned int res = 0;
+ lock_guard<mutex> lg(rand_lba_mutex);
+
+ int fd = open(URANDOM_DEV, O_RDONLY);
+ if (fd >= 0) {
+ uint8_t b[sizeof(unsigned int)];
+ int n = read(fd, b, sizeof(unsigned int));
+
+ if (sizeof(unsigned int) == n)
+ memcpy(&res, b, sizeof(unsigned int));
+ close(fd);
+ }
+ return res;
+}
+
+#define TUR_CMD_LEN 6
+#define READ16_CMD_LEN 16
+#define READ16_REPLY_LEN 4096
+#define WRITE16_REPLY_LEN 4096
+#define WRITE16_CMD_LEN 16
+
+/* Returns 0 if command injected okay, return -1 for error and 2 for
+ * not done due to queue data size limit struck. */
+static int
+start_sg3_cmd(int sg_fd, command2execute cmd2exe, int pack_id, uint64_t lba,
+ uint8_t * lbp, int xfer_bytes, int flags, bool submit,
+ unsigned int & enomem, unsigned int & eagains,
+ unsigned int & ebusy, unsigned int & e2big, unsigned int & edom)
+{
+ struct sg_io_hdr pt;
+ struct sg_io_v4 p4t;
+ uint8_t turCmdBlk[TUR_CMD_LEN] = {0, 0, 0, 0, 0, 0};
+ uint8_t r16CmdBlk[READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ uint8_t w16CmdBlk[WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ const char * np = NULL;
+ struct sg_io_hdr * ptp;
+
+ if (submit) { /* nest a v3 interface inside a store for v4 */
+ memset(&p4t, 0, sizeof(p4t));
+ ptp = (struct sg_io_hdr *)&p4t; /* p4t is larger than pt */
+ } else {
+ ptp = &pt;
+ memset(ptp, 0, sizeof(*ptp));
+ }
+ switch (cmd2exe) {
+ case SCSI_TUR:
+ np = "TEST UNIT READY";
+ ptp->cmdp = turCmdBlk;
+ ptp->cmd_len = sizeof(turCmdBlk);
+ ptp->dxfer_direction = SG_DXFER_NONE;
+ break;
+ case SCSI_READ16:
+ np = "READ(16)";
+ if (lba > 0xffffffff)
+ sg_put_unaligned_be32(lba >> 32, &r16CmdBlk[2]);
+ sg_put_unaligned_be32(lba & 0xffffffff, &r16CmdBlk[6]);
+ ptp->cmdp = r16CmdBlk;
+ ptp->cmd_len = sizeof(r16CmdBlk);
+ ptp->dxfer_direction = SG_DXFER_FROM_DEV;
+ ptp->dxferp = lbp;
+ ptp->dxfer_len = xfer_bytes;
+ break;
+ case SCSI_WRITE16:
+ np = "WRITE(16)";
+ if (lba > 0xffffffff)
+ sg_put_unaligned_be32(lba >> 32, &w16CmdBlk[2]);
+ sg_put_unaligned_be32(lba & 0xffffffff, &w16CmdBlk[6]);
+ ptp->cmdp = w16CmdBlk;
+ ptp->cmd_len = sizeof(w16CmdBlk);
+ ptp->dxfer_direction = SG_DXFER_TO_DEV;
+ ptp->dxferp = lbp;
+ ptp->dxfer_len = xfer_bytes;
+ break;
+ }
+ ptp->interface_id = 'S';
+ ptp->mx_sb_len = sizeof(sense_buffer);
+ ptp->sbp = sense_buffer; /* ignored .... */
+ ptp->timeout = DEF_TIMEOUT_MS;
+ ptp->pack_id = pack_id;
+ ptp->flags = flags;
+
+ for (int k = 0;
+ (submit ? ioctl(sg_fd, SG_IOSUBMIT_V3, ptp) :
+ write(sg_fd, ptp, sizeof(*ptp)) < 0);
+ ++k) {
+ if ((ENOMEM == errno) && (k < MAX_CONSEC_NOMEMS)) {
+ ++enomem;
+ this_thread::yield();
+ continue;
+ } else if (EAGAIN == errno) {
+ ++eagains;
+ this_thread::yield();
+ continue;
+ } else if (EBUSY == errno) {
+ ++ebusy;
+ this_thread::yield();
+ continue;
+ } else if (E2BIG == errno) {
+ ++e2big;
+ return 2;
+ } else if (EDOM == errno)
+ ++edom;
+ else if (ENOMEM == errno)
+ pr_rusage(-1);
+ pr_errno_lk(errno, "%s: %s, pack_id=%d", __func__, np, pack_id);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+finish_sg3_cmd(int sg_fd, command2execute cmd2exe, int & pack_id,
+ bool receive, int wait_ms, unsigned int & enomem,
+ unsigned int & eagains, unsigned int & ebusys,
+ unsigned int & nanosecs)
+{
+ bool ok;
+ int res, k;
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ const char * np = NULL;
+ struct sg_io_hdr pt;
+ struct sg_io_hdr * ptp;
+ struct sg_io_v4 p4t;
+
+ if (receive) { /* nest a v3 interface inside a store for v4 */
+ memset(&p4t, 0, sizeof(p4t));
+ ptp = (struct sg_io_hdr *)&p4t; /* p4t is larger than pt */
+ } else {
+ ptp = &pt;
+ memset(ptp, 0, sizeof(*ptp));
+ }
+ switch (cmd2exe) {
+ case SCSI_TUR:
+ np = "TEST UNIT READY";
+ ptp->dxfer_direction = SG_DXFER_NONE;
+ break;
+ case SCSI_READ16:
+ np = "READ(16)";
+ ptp->dxfer_direction = SG_DXFER_FROM_DEV;
+ break;
+ case SCSI_WRITE16:
+ np = "WRITE(16)";
+ ptp->dxfer_direction = SG_DXFER_TO_DEV;
+ break;
+ }
+ ptp->interface_id = 'S';
+ ptp->mx_sb_len = sizeof(sense_buffer);
+ ptp->sbp = sense_buffer;
+ ptp->timeout = DEF_TIMEOUT_MS;
+ /* if SG_SET_FORCE_PACK_ID, then need to set ptp->dxfer_direction */
+ ptp->pack_id = pack_id;
+
+ k = 0;
+ while ((((res = receive ? ioctl(sg_fd, SG_IORECEIVE_V3, ptp) :
+ read(sg_fd, ptp, sizeof(*ptp)))) < 0) &&
+ ((EAGAIN == errno) || (EBUSY == errno) || (ENOMEM == errno))) {
+ if (ENOMEM == errno)
+ ++enomem;
+ else if (EAGAIN == errno)
+ ++eagains;
+ else
+ ++ebusys;
+ ++k;
+ if (k > 10000) {
+ pr2serr_lk("%s: sg_fd=%d: after %d EAGAINs, unable to find "
+ "pack_id=%d\n", __func__, sg_fd, k, pack_id);
+ return -1; /* crash out */
+ }
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ if (ENOMEM == errno)
+ pr_rusage(-1);
+ pr_errno_lk(errno, "%s: %s", __func__, np);
+ return -1;
+ }
+ /* now for the error processing */
+ pack_id = ptp->pack_id;
+ ok = false;
+ switch (sg_err_category3(ptp)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ pr2serr_lk("%s: Recovered error on %s, continuing\n", __func__, np);
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+ sg_chk_n_print3(np, ptp, 1);
+ }
+ break;
+ }
+ if (ok)
+ nanosecs = ptp->duration;
+ return ok ? 0 : -1;
+}
+
+/* Returns 0 if command injected okay, return -1 for error and 2 for
+ * not done due to queue data size limit struck. */
+static int
+start_sg4_cmd(int sg_fd, command2execute cmd2exe, int pack_id, uint64_t lba,
+ uint8_t * lbp, int xfer_bytes, int flags, bool submit,
+ unsigned int & enomem, unsigned int & eagains,
+ unsigned int & ebusy, unsigned int & e2big, unsigned int & edom)
+{
+ struct sg_io_v4 p4t;
+ uint8_t turCmdBlk[TUR_CMD_LEN] = {0, 0, 0, 0, 0, 0};
+ uint8_t r16CmdBlk[READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ uint8_t w16CmdBlk[WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ const char * np = NULL;
+ struct sg_io_v4 * ptp;
+
+ if (! submit) {
+ pr2serr_lk("%s: logic error, submit must be true, isn't\n", __func__);
+ return -1;
+ }
+ ptp = &p4t;
+ memset(ptp, 0, sizeof(*ptp));
+ switch (cmd2exe) {
+ case SCSI_TUR:
+ np = "TEST UNIT READY";
+ ptp->request = (uint64_t)turCmdBlk;
+ ptp->request_len = sizeof(turCmdBlk);
+ break;
+ case SCSI_READ16:
+ np = "READ(16)";
+ if (lba > 0xffffffff)
+ sg_put_unaligned_be32(lba >> 32, &r16CmdBlk[2]);
+ sg_put_unaligned_be32(lba & 0xffffffff, &r16CmdBlk[6]);
+ ptp->request = (uint64_t)r16CmdBlk;
+ ptp->request_len = sizeof(r16CmdBlk);
+ ptp->din_xferp = (uint64_t)lbp;
+ ptp->din_xfer_len = xfer_bytes;
+ break;
+ case SCSI_WRITE16:
+ np = "WRITE(16)";
+ if (lba > 0xffffffff)
+ sg_put_unaligned_be32(lba >> 32, &w16CmdBlk[2]);
+ sg_put_unaligned_be32(lba & 0xffffffff, &w16CmdBlk[6]);
+ ptp->request = (uint64_t)w16CmdBlk;
+ ptp->request_len = sizeof(w16CmdBlk);
+ ptp->dout_xferp = (uint64_t)lbp;
+ ptp->dout_xfer_len = xfer_bytes;
+ break;
+ }
+ ptp->guard = 'Q';
+ ptp->max_response_len = sizeof(sense_buffer);
+ ptp->response = (uint64_t)sense_buffer; /* ignored .... */
+ ptp->timeout = DEF_TIMEOUT_MS;
+ ptp->request_extra = pack_id;
+ ptp->flags = flags;
+
+ for (int k = 0; ioctl(sg_fd, SG_IOSUBMIT, ptp) < 0; ++k) {
+ if ((ENOMEM == errno) && (k < MAX_CONSEC_NOMEMS)) {
+ ++enomem;
+ this_thread::yield();
+ continue;
+ } else if (EAGAIN == errno) {
+ ++eagains;
+ this_thread::yield();
+ continue;
+ } else if (EBUSY == errno) {
+ ++ebusy;
+ this_thread::yield();
+ continue;
+ } else if (E2BIG == errno) {
+ ++e2big;
+ return 2;
+ } else if (EDOM == errno)
+ ++edom;
+ else if (ENOMEM == errno)
+ pr_rusage(-1);
+ pr_errno_lk(errno, "%s: %s, pack_id=%d", __func__, np, pack_id);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+finish_sg4_cmd(int sg_fd, command2execute cmd2exe, int & pack_id,
+ bool receive, int wait_ms, unsigned int & enomem,
+ unsigned int & eagains, unsigned int & ebusys,
+ unsigned int & nanosecs)
+{
+ bool ok;
+ int res, k;
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ const char * np = NULL;
+ struct sg_io_v4 * ptp;
+ struct sg_io_v4 p4t;
+
+ if (! receive) {
+ pr2serr_lk("%s: logic error, receive must be true, isn't\n",
+ __func__);
+ return -1;
+ }
+ ptp = &p4t;
+ memset(ptp, 0, sizeof(*ptp));
+ switch (cmd2exe) {
+ case SCSI_TUR:
+ np = "TEST UNIT READY";
+ break;
+ case SCSI_READ16:
+ np = "READ(16)";
+ break;
+ case SCSI_WRITE16:
+ np = "WRITE(16)";
+ break;
+ }
+ ptp->guard = 'Q';
+ ptp->max_response_len = sizeof(sense_buffer);
+ ptp->response = (uint64_t)sense_buffer;
+ ptp->timeout = DEF_TIMEOUT_MS;
+ /* if SG_SET_FORCE_PACK_ID, then need to set ptp->dxfer_direction */
+ ptp->request_extra = pack_id;
+
+ k = 0;
+ while ((((res = ioctl(sg_fd, SG_IORECEIVE, ptp))) < 0) &&
+ ((EAGAIN == errno) || (EBUSY == errno))) {
+ if (EAGAIN == errno)
+ ++eagains;
+ else
+ ++ebusys;
+ ++k;
+ if (k > 10000) {
+ pr2serr_lk("%s: sg_fd=%d: after %d EAGAINs, unable to find "
+ "pack_id=%d\n", __func__, sg_fd, k, pack_id);
+ return -1; /* crash out */
+ }
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ if (ENOMEM == errno) {
+ ++enomem;
+ pr_rusage(-1);
+ }
+ pr_errno_lk(errno, "%s: %s", __func__, np);
+ return -1;
+ }
+ /* now for the error processing */
+ pack_id = ptp->request_extra;
+ ok = false;
+ res = sg_err_category_new(ptp->device_status, ptp->transport_status,
+ ptp->driver_status,
+ (const uint8_t *)ptp->response,
+ ptp->response_len);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ pr2serr_lk("%s: Recovered error on %s, continuing\n", __func__, np);
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_linux_sense_print(np, ptp->device_status,
+ ptp->transport_status,
+ ptp->driver_status,
+ (const uint8_t *)ptp->response,
+ ptp->response_len, true);
+ }
+ break;
+ }
+ if (ok)
+ nanosecs = ptp->duration;
+ return ok ? 0 : -1;
+}
+
+static int
+num_submitted(int sg_fd)
+{
+ uint32_t num_subm_wait = 0;
+ struct sg_extended_info sei;
+ struct sg_extended_info *seip = &sei;
+ const char * err = NULL;
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_SUBMITTED;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0)
+ err = "ioctl(SG_SET_GET_EXTENDED) failed\n";
+ else
+ num_subm_wait = seip->read_value;
+ if (err)
+ pr2serr_lk("%s: %s, errno=%d\n", __func__, err, errno);
+ return err ? -1 : (int)num_subm_wait;
+}
+
+static int
+pr_rusage(int id)
+{
+ int res;
+ struct rusage ru;
+
+ res = getrusage(RUSAGE_SELF /* RUSAGE_THREAD */, &ru);
+ if (res < 0) {
+ pr2serr_lk("%d->id: %s: getrusage() failed, errno=%d\n", id,
+ __func__, errno);
+ return res;
+ }
+ pr2serr_lk("%d->id: maxrss=%ldKB nvcsw=%ld nivcsw=%ld majflt=%ld\n", id,
+ ru.ru_maxrss, ru.ru_nvcsw, ru.ru_nivcsw, ru.ru_majflt);
+ return 0;
+}
+
+static void
+work_sync_thread(int id, const char * dev_name, unsigned int /* hi_lba */,
+ struct opts_t * op)
+{
+ bool is_rw = (SCSI_TUR != op->c2e);
+ int k, sg_fd, err, rs, n, sense_cat, ret;
+ int vb = op->verbose;
+ int num_errs = 0;
+ int thr_sync_starts = 0;
+ struct sg_pt_base * ptp = NULL;
+ uint8_t cdb[6];
+ uint8_t sense_b[32] SG_C_CPP_ZERO_INIT;
+ char b[120];
+
+ if (is_rw) {
+ pr2serr_lk("id=%d: only support TUR here for now\n", id);
+ goto err_out;
+ }
+ if (op->verbose)
+ pr2serr_lk("id=%d: using libsgutils generic sync passthrough\n", id);
+
+ if ((sg_fd = sg_cmds_open_device(dev_name, false /* ro */, vb)) < 0) {
+ pr2serr_lk("id=%d: error opening file: %s: %s\n", id, dev_name,
+ safe_strerror(-sg_fd));
+ if (ENOMEM == -sg_fd)
+ pr_rusage(id);
+ goto err_out;
+ }
+ if (vb > 2)
+ pr2serr_lk(">>>> id=%d: open(%s) --> fd=%d\n", id, dev_name, sg_fd);
+
+ ptp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ err = 0;
+ if ((NULL == ptp) || ((err = get_scsi_pt_os_err(ptp)))) {
+ ret = sg_convert_errno(err ? err : ENOMEM);
+ sg_exit2str(ret, true, sizeof(b), b);
+ pr2serr_lk("id=%d: construct_scsi_pt_obj_with_fd: %s\n", id, b);
+ goto err_out;
+ }
+ for (k = 0; k < op->num_per_thread; ++k) {
+ /* Might get Unit Attention on first invocation */
+ memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */
+ set_scsi_pt_cdb(ptp, cdb, sizeof(cdb));
+ set_scsi_pt_sense(ptp, sense_b, sizeof(sense_b));
+ set_scsi_pt_packet_id(ptp, uniq_pack_id.fetch_add(1));
+ ++thr_sync_starts;
+ rs = do_scsi_pt(ptp, -1, DEF_PT_TIMEOUT, vb);
+ n = sg_cmds_process_resp(ptp, "Test unit ready", rs,
+ (0 == k), vb, &sense_cat);
+ if (-1 == n) {
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptp));
+ sg_exit2str(ret, true, sizeof(b), b);
+ pr2serr_lk("id=%d: do_scsi_pt: %s\n", id, b);
+ goto err_out;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++num_errs;
+ if (1 == op->num_per_thread) {
+ pr2serr_lk("id=%d: device not ready\n", id);
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++num_errs;
+ if (vb)
+ pr2serr_lk("Ignoring Unit attention (sense key)\n");
+ break;
+ default:
+ ++num_errs;
+ if (1 == op->num_per_thread) {
+ sg_get_category_sense_str(sense_cat, sizeof(b), b, vb);
+ pr2serr_lk("%s\n", b);
+ goto err_out;
+ }
+ break;
+ }
+ }
+ clear_scsi_pt_obj(ptp);
+ }
+err_out:
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ if (num_errs > 0)
+ pr2serr_lk("id=%d: number of errors: %d\n", id, num_errs);
+ sync_starts += thr_sync_starts;
+}
+
+static void
+work_thread(int id, struct opts_t * op)
+{
+ bool is_rw = (SCSI_TUR != op->c2e);
+ bool need_finish, repeat;
+ bool once = false;
+ bool once1000 = false;
+ bool once_2000 = false;
+ bool once_4000 = false;
+ bool once5000 = false;
+ bool once_6000 = false;
+ bool once_7000 = false;
+ bool once10_000 = false;
+ bool once20_000 = false;
+ int open_flags = O_RDWR;
+ int thr_async_starts = 0;
+ int thr_async_finishes = 0;
+ int vb = op->verbose;
+ int k, n, res, sg_fd, num_outstanding, do_inc, npt, pack_id, sg_flags;
+ int num_waiting_read, sz, encore_pack_id, ask, j, m, o;
+ int prev_pack_id, blk_sz;
+ unsigned int thr_enomem_count = 0;
+ unsigned int thr_start_eagain_count = 0;
+ unsigned int thr_start_ebusy_count = 0;
+ unsigned int thr_start_e2big_count = 0;
+ unsigned int thr_fin_eagain_count = 0;
+ unsigned int thr_fin_ebusy_count = 0;
+ unsigned int thr_start_edom_count = 0;
+ int needed_sz = op->lb_sz * op->num_lbs;
+ unsigned int nanosecs;
+ unsigned int hi_lba;
+ uint64_t lba;
+ uint64_t sum_nanosecs = 0;
+ uint8_t * lbp;
+ uint8_t * free_lbp = NULL;
+ uint8_t * wrkMmap = NULL;
+ const char * dev_name;
+ const char * err = NULL;
+ Rand_uint * ruip = NULL;
+ char ebuff[EBUFF_SZ];
+ struct pollfd pfd[1];
+ list<pair<uint8_t *, uint8_t *> > free_lst; /* of aligned lb buffers */
+ map<int, pair<uint8_t *, uint8_t *> > pi2buff;/* pack_id -> lb buffer */
+ map<int, uint64_t> pi_2_lba; /* pack_id -> LBA */
+ pair<uint8_t *, uint8_t *> encore_lbps;
+
+ /* device name and hi_lba may depend on id */
+ n = op->dev_names.size();
+ dev_name = op->dev_names[id % n];
+ if (op->blk_szs.size() >= (unsigned)n)
+ blk_sz = op->blk_szs[id % n];
+ else
+ blk_sz = DEF_LB_SZ;
+ if ((UINT_MAX == op->hi_lba) && (n == (int)op->hi_lbas.size()))
+ hi_lba = op->hi_lbas[id % n];
+ else
+ hi_lba = op->hi_lba;
+
+ if (vb) {
+ if ((vb > 1) && hi_lba)
+ pr2serr_lk("Enter work_t_id=%d using %s\n"
+ " LBA range: 0x%x to 0x%x (inclusive)\n",
+ id, dev_name, (unsigned int)op->lba, hi_lba);
+ else
+ pr2serr_lk("Enter work_t_id=%d using %s\n", id, dev_name);
+ }
+ if (op->generic_sync) {
+ work_sync_thread(id, dev_name, hi_lba, op);
+ return;
+ }
+ if (! op->block)
+ open_flags |= O_NONBLOCK;
+
+ sg_fd = open(dev_name, open_flags);
+ if (sg_fd < 0) {
+ pr_errno_lk(errno, "%s: id=%d, error opening file: %s", __func__, id,
+ dev_name);
+ if (ENOMEM == -sg_fd)
+ pr_rusage(id);
+ return;
+ }
+ if (vb > 2)
+ pr2serr_lk(">>>> id=%d: open(%s) --> fd=%d\n", id, dev_name, sg_fd);
+ if (op->pack_id_force) {
+ k = 1;
+ if (ioctl(sg_fd, SG_SET_FORCE_PACK_ID, &k) < 0)
+ pr2serr_lk("ioctl(SG_SET_FORCE_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+ if (op->sg_vn_ge_40000) {
+ if (ioctl(sg_fd, SG_GET_RESERVED_SIZE, &k) >= 0) {
+ if (needed_sz > k)
+ ioctl(sg_fd, SG_SET_RESERVED_SIZE, &needed_sz);
+ }
+ if (op->sg_vn_ge_40030 && (op->cmd_time || op->masync)) {
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ if (op->cmd_time) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ }
+ if (op->masync) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC;
+ seip->ctl_flags |= SG_CTL_FLAGM_MORE_ASYNC;
+ }
+ if (op->excl) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_EXCL_WAITQ;
+ seip->ctl_flags |= SG_CTL_FLAGM_EXCL_WAITQ;
+ }
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+ if (op->cmd_time &&
+ (! (SG_CTL_FLAGM_TIME_IN_NS & seip->ctl_flags))) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0)
+ pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, "
+ "errno=%d %s\n", errno, strerror(errno));
+ else if (vb > 1)
+ pr2serr_lk("t_id: %d: set TIME_IN_NS flag\n", id);
+ }
+ }
+ }
+ if (is_rw && op->mmap_io) {
+
+ if (ioctl(sg_fd, SG_GET_RESERVED_SIZE, &sz) < 0) {
+ pr2serr_lk("t_id=%d: ioctl(SG_GET_RESERVED_SIZE) errno=%d\n",
+ id, errno);
+ return;
+ }
+ if (sz < needed_sz) {
+ sz = needed_sz;
+ if (ioctl(sg_fd, SG_SET_RESERVED_SIZE, &sz) < 0) {
+ pr2serr_lk("t_id=%d: ioctl(SG_SET_RESERVED_SIZE) errno=%d\n",
+ id, errno);
+ return;
+ }
+ if (ioctl(sg_fd, SG_GET_RESERVED_SIZE, &sz) < 0) {
+ pr2serr_lk("t_id=%d: ioctl(SG_GET_RESERVED_SIZE) errno=%d\n",
+ id, errno);
+ return;
+ }
+ if (sz < needed_sz) {
+ pr2serr_lk("t_id=%d: unable to grow reserve buffer to %d "
+ "bytes\n", id, needed_sz);
+ return;
+ }
+ }
+ wrkMmap = (uint8_t *)mmap(NULL, needed_sz, PROT_READ | PROT_WRITE,
+ MAP_SHARED, sg_fd, 0);
+ if (MAP_FAILED == wrkMmap) {
+ int ern = errno;
+
+ pr2serr_lk("t_id=%d: mmap() failed, errno=%d\n", id, ern);
+ return;
+ }
+ }
+ pfd[0].fd = sg_fd;
+ pfd[0].events = POLLIN;
+ if (is_rw && hi_lba) {
+ unsigned int seed = get_urandom_uint();
+
+ if (vb > 1)
+ pr2serr_lk(" id=%d, /dev/urandom seed=0x%x\n", id, seed);
+ ruip = new Rand_uint((unsigned int)op->lba, hi_lba, seed);
+ }
+
+ sg_flags = 0;
+ if (BLQ_AT_TAIL == op->blqd)
+ sg_flags |= SG_FLAG_Q_AT_TAIL;
+ else if (BLQ_AT_HEAD == op->blqd)
+ sg_flags |= SG_FLAG_Q_AT_HEAD;
+ if (op->direct)
+ sg_flags |= SG_FLAG_DIRECT_IO;
+ if (op->mmap_io)
+ sg_flags |= SG_FLAG_MMAP_IO;
+ if (op->no_xfer)
+ sg_flags |= SG_FLAG_NO_DXFER;
+ if (vb > 1)
+ pr2serr_lk(" id=%d, sg_flags=0x%x, %s cmds\n", id, sg_flags,
+ ((SCSI_TUR == op->c2e) ? "TUR":
+ ((SCSI_READ16 == op->c2e) ? "READ" : "WRITE")));
+
+ npt = op->num_per_thread;
+ need_finish = false;
+ lba = 0;
+ pack_id = 0;
+ prev_pack_id = 0;
+ encore_pack_id = 0;
+ do_inc = 0;
+ /* main loop, continues until num_per_thread exhausted and there are
+ * no more outstanding responses */
+ for (k = 0, m = 0, o=0, num_outstanding = 0; (k < npt) || num_outstanding;
+ k = do_inc ? k + 1 : k, ++o) {
+ int num_to_read = 0;
+
+ if (do_inc)
+ m = 0;
+ else {
+ ++m;
+ if (m > 100) {
+ if (vb)
+ pr2serr_lk("%d->id: no main loop inc =%d times\n", id, m);
+ m = 0;
+ }
+ }
+ if (vb && (! once1000) && (num_outstanding >= 1000)) {
+ int num_waiting;
+ int num_subm = (op->sg_vn_ge_40030) ? num_submitted(sg_fd) :
+ pi2buff.size();
+
+ once1000 = true;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ pr2serr_lk("%d->id: once 1000: k=%d, submitted=%d waiting=%d; "
+ "pi2buff.sz=%u\n", id, k, num_subm, num_waiting,
+ (uint32_t)pi2buff.size());
+ pr_rusage(id);
+ }
+ if (vb && ! once5000 && num_outstanding >= 5000) {
+ int num_waiting;
+ int num_subm = (op->sg_vn_ge_40030) ? num_submitted(sg_fd) :
+ pi2buff.size();
+
+ once5000 = true;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ pr2serr_lk("%d->id: once 5000: k=%d, submitted=%d waiting=%d\n",
+ id, k, num_subm, num_waiting);
+ pr_rusage(id);
+ }
+ if (vb && ! once_7000 && num_outstanding >= 7000) {
+ int num_waiting;
+ int num_subm = (op->sg_vn_ge_40030) ? num_submitted(sg_fd) :
+ pi2buff.size();
+
+ once_7000 = true;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ pr2serr_lk("%d->id: once 7000: k=%d, submitted=%d waiting=%d\n",
+ id, k, num_subm, num_waiting);
+ pr_rusage(id);
+ }
+ if (vb && ! once10_000 && num_outstanding >= 10000) {
+ int num_waiting;
+ int num_subm = (op->sg_vn_ge_40030) ? num_submitted(sg_fd) :
+ pi2buff.size();
+
+ once10_000 = true;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ pr2serr_lk("%d->id: once 10^4: k=%d, submitted=%d waiting=%d\n",
+ id, k, num_subm, num_waiting);
+ pr_rusage(id);
+ }
+ if (vb && ! once20_000 && num_outstanding >= 20000) {
+ int num_waiting;
+ int num_subm = (op->sg_vn_ge_40030) ? num_submitted(sg_fd) :
+ pi2buff.size();
+
+ once20_000 = true;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ pr2serr_lk("%d->id: once 20000: k=%d, submitted=%d waiting=%d\n",
+ id, k, num_subm, num_waiting);
+ pr_rusage(id);
+ }
+ do_inc = 0;
+ if ((num_outstanding < op->maxq_per_thread) && (k < npt)) {
+ do_inc = 1;
+ if (need_finish) {
+ pack_id = encore_pack_id;
+ need_finish = false;
+ repeat = true;
+ } else {
+ prev_pack_id = pack_id;
+ pack_id = uniq_pack_id.fetch_add(1);
+ repeat = false;
+ }
+ if (is_rw) { /* get new lb buffer or one from free list */
+ if (free_lst.empty()) {
+ lbp = sg_memalign(op->lb_sz * op->num_lbs, 0, &free_lbp,
+ false);
+ if (NULL == lbp) {
+ err = "out of memory";
+ break;
+ }
+ } else if (! repeat) {
+ lbp = free_lst.back().first;
+ free_lbp = free_lst.back().second;
+ free_lst.pop_back();
+ } else {
+ lbp = encore_lbps.first;
+ free_lbp = encore_lbps.second;
+ if (vb && !once && free_lst.size() > 1000) {
+ once = true;
+ pr2serr_lk("%d->id: free_lst.size() over 1000\n", id);
+ }
+ if (vb && !once_2000 && free_lst.size() > 2000) {
+ once_2000 = true;
+ pr2serr_lk("%d->id: free_lst.size() over 2000\n", id);
+ }
+ if (vb && !once_6000 && free_lst.size() > 6000) {
+ once_2000 = true;
+ pr2serr_lk("%d->id: free_lst.size() over 6000\n", id);
+ }
+ }
+ } else
+ lbp = NULL;
+ if (is_rw) {
+ if (ruip) {
+ if (! repeat) {
+ lba = ruip->get(); /* fetch a random LBA */
+ if (vb > 3)
+ pr2serr_lk(" id=%d: start IO at lba=0x%" PRIx64
+ "\n", id, lba);
+ }
+ } else
+ lba = op->lba;
+ } else
+ lba = 0;
+ if (vb > 4)
+ pr2serr_lk("t_id=%d: starting pack_id=%d\n", id, pack_id);
+ res = (op->v4) ?
+ start_sg4_cmd(sg_fd, op->c2e, pack_id, lba, lbp,
+ blk_sz * op->num_lbs, sg_flags, op->submit,
+ thr_enomem_count, thr_start_eagain_count,
+ thr_start_ebusy_count, thr_start_e2big_count,
+ thr_start_edom_count) :
+ start_sg3_cmd(sg_fd, op->c2e, pack_id, lba, lbp,
+ blk_sz * op->num_lbs, sg_flags, op->submit,
+ thr_enomem_count, thr_start_eagain_count,
+ thr_start_ebusy_count, thr_start_e2big_count,
+ thr_start_edom_count);
+ if (res) {
+ if (res > 1) { /* here if E2BIG, start not done, try finish */
+ do_inc = 0;
+ need_finish = true;
+ encore_pack_id = pack_id;
+ pack_id = prev_pack_id;
+ encore_lbps = make_pair(lbp, free_lbp);
+ if (vb > 2)
+ pr2serr_lk("t_id=%d: E2BIG hit, prev_pack_id=%d, "
+ "encore_pack_id=%d\n", id, prev_pack_id,
+ encore_pack_id);
+ } else {
+ err = "start_sg3_cmd()";
+ break;
+ }
+ } else { /* no error */
+ ++thr_async_starts;
+ ++num_outstanding;
+ pi2buff[pack_id] = make_pair(lbp, free_lbp);
+ if (ruip)
+ pi_2_lba[pack_id] = lba;
+ }
+ if (vb && !once && (pi2buff.size() > 1000)) {
+ once = true;
+ pr2serr_lk("%d->id: pi2buff.size() over 1000 (b)\n", id);
+ }
+ if (vb && !once_2000 && free_lst.size() > 2000) {
+ once_2000 = true;
+ pr2serr_lk("%d->id: free_lst.size() over 2000 (b)\n", id);
+ }
+ if (vb && !once_6000 && free_lst.size() > 6000) {
+ once_2000 = true;
+ pr2serr_lk("%d->id: free_lst.size() over 6000 (b)\n", id);
+ }
+ }
+ if (need_finish) {
+ num_waiting_read = 0;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting_read) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ } else if (vb > 3)
+ pr2serr_lk("t_id=%d: num_waiting_read=%d\n", id,
+ num_waiting_read);
+ if (num_waiting_read > 0)
+ num_to_read = num_waiting_read;
+ else {
+ struct timespec tspec = {0, 100000 /* 100 usecs */};
+
+ nanosleep(&tspec, NULL);
+ if (vb > 3)
+ pr2serr_lk("t_id=%d: E2BIG, 100 usecs sleep\n", id);
+ // err = "strange, E2BIG but nothing to read";
+ // break;
+ }
+ } else if ((num_outstanding >= op->maxq_per_thread) || (k >= npt)) {
+ /* full queue or finished injecting */
+ num_waiting_read = 0;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting_read) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ if (1 == num_waiting_read)
+ num_to_read = num_waiting_read;
+ else if (num_waiting_read > 0) {
+ if (k >= npt)
+ num_to_read = num_waiting_read;
+ else {
+ switch (op->myqd) {
+ case MYQD_LOW:
+ num_to_read = num_waiting_read;
+ break;
+ case MYQD_MEDIUM:
+ num_to_read = num_waiting_read / 2;
+ break;
+ case MYQD_HIGH:
+ default:
+ if (op->ovn > 0) {
+ if (op->sg_vn_ge_40030) {
+ int num_subm = num_submitted(sg_fd);
+
+ if (num_subm > op->ovn) {
+ num_to_read = num_waiting_read > 0 ?
+ num_waiting_read : 1;
+ break;
+ }
+ } else {
+ if (num_waiting_read > (op->ovn / 2)) {
+ num_to_read = num_waiting_read / 2;
+ break;
+ }
+ }
+ }
+ num_to_read = 1;
+ break;
+ }
+ }
+ } else { /* nothing waiting to be read */
+ if (op->sg_vn_ge_40030) {
+ int val = num_submitted(sg_fd);
+
+ if (0 == val) {
+ err = "nothing submitted now ??";
+ break;
+ } else if (val < 0) {
+ err = "num_submitted failed";
+ break;
+ }
+ }
+ n = (op->wait_ms > 0) ? op->wait_ms : 0;
+ if (n > 0) {
+ for (j = 0; (j < 1000000) &&
+ (0 == (res = poll(pfd, 1, n)));
+ ++j)
+ ;
+ if (j >= 1000000) {
+ err = "poll() looped 1 million times";
+ break;
+ }
+ if (res < 0) {
+ err = "poll(wait_ms) failed";
+ break;
+ }
+ } else {
+ struct timespec ts;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = DEF_NANOSEC_WAIT;
+ if (nanosleep(&ts, NULL) < 0) {
+ err = "nanosleep() failed";
+ break;
+ }
+ }
+ }
+ } else { /* not full, not finished injecting */
+ if (MYQD_HIGH == op->myqd) {
+ num_to_read = 0;
+ if (op->ovn) {
+ if (op->sg_vn_ge_40030) {
+ int num_subm = num_submitted(sg_fd);
+
+ if (num_subm > op->ovn)
+ num_to_read = num_waiting_read > 0 ?
+ num_waiting_read : 1;
+ } else {
+ num_waiting_read = 0;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING,
+ &num_waiting_read) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ if (num_waiting_read > (op->ovn / 2))
+ num_to_read = num_waiting_read / 2;
+ }
+ }
+ } else {
+ num_waiting_read = 0;
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting_read) < 0) {
+ err = "ioctl(SG_GET_NUM_WAITING) failed";
+ break;
+ }
+ if (num_waiting_read > 0)
+ num_to_read = num_waiting_read /
+ ((MYQD_LOW == op->myqd) ? 1 : 2);
+ else
+ num_to_read = 0;
+ }
+ }
+
+ if (vb && !once_4000 && (num_to_read > 4000)) {
+ once_4000 = true;
+ pr2serr_lk("%d->id: num_to_read=%d\n", id, num_to_read);
+ }
+ while (num_to_read > 0) {
+ --num_to_read;
+ if (op->pack_id_force) {
+ j = pi2buff.size();
+ if (j > 0)
+ pack_id = pi2buff.begin()->first;
+ else
+ pack_id = -1;
+ } else
+ pack_id = -1;
+ ask = pack_id;
+ res = (op->v4) ?
+ finish_sg4_cmd(sg_fd, op->c2e, pack_id, op->submit,
+ op->wait_ms, thr_enomem_count,
+ thr_fin_eagain_count, thr_fin_ebusy_count,
+ nanosecs) :
+ finish_sg3_cmd(sg_fd, op->c2e, pack_id, op->submit,
+ op->wait_ms, thr_enomem_count,
+ thr_fin_eagain_count, thr_fin_ebusy_count,
+ nanosecs);
+ if (res) {
+ err = "finish_sg3_cmd()";
+ if (ruip && (pack_id > 0)) {
+ auto q = pi_2_lba.find(pack_id);
+
+ if (q != pi_2_lba.end()) {
+ snprintf(ebuff, sizeof(ebuff), "%s: lba=0x%" PRIx64 ,
+ err, q->second);
+ err = ebuff;
+ }
+ }
+ break;
+ }
+ if (op->cmd_time && op->sg_vn_ge_40030)
+ sum_nanosecs += nanosecs;
+ ++thr_async_finishes;
+ --num_outstanding;
+ if (vb > 4)
+ pr2serr_lk("t_id=%d: finishing pack_id ask=%d, got=%d, "
+ "outstanding=%d\n", id, ask, pack_id,
+ num_outstanding);
+ auto p = pi2buff.find(pack_id);
+
+ if (p == pi2buff.end()) {
+ snprintf(ebuff, sizeof(ebuff), "pack_id=%d from "
+ "finish_sg3_cmd() not found\n", pack_id);
+ if (! err)
+ err = ebuff;
+ } else {
+ lbp = p->second.first;
+ free_lbp = p->second.second;
+ pi2buff.erase(p);
+ if (lbp)
+ free_lst.push_front(make_pair(lbp, free_lbp));
+ }
+ if (ruip && (pack_id > 0)) {
+ auto q = pi_2_lba.find(pack_id);
+
+ if (q != pi_2_lba.end()) {
+ if (vb > 3)
+ pr2serr_lk(" id=%d: finish IO at lba=0x%" PRIx64
+ "\n", id, q->second);
+ pi_2_lba.erase(q);
+ }
+ }
+ if (err)
+ break;
+ } /* end of while loop counting down num_to_read */
+ if (err)
+ break;
+ } /* end of for loop over npt (number per thread) */
+ if (vb)
+ pr2serr_lk("%d->id: leaving main thread loop; k=%d, o=%d\n", id, k,
+ o);
+ close(sg_fd); // sg driver will handle any commands "in flight"
+ if (ruip)
+ delete ruip;
+
+ if (err || (k < npt)) {
+ if (k < npt)
+ pr2serr_lk("t_id=%d FAILed at iteration %d%s%s\n", id, k,
+ (err ? ", Reason: " : ""), (err ? err : ""));
+ else
+ pr2serr_lk("t_id=%d FAILed on last%s%s\n", id,
+ (err ? ", Reason: " : ""), (err ? err : ""));
+ }
+ n = pi2buff.size();
+ if (n > 0)
+ pr2serr_lk("t_id=%d Still %d elements in pi2buff map on "
+ "exit\n", id, n);
+ for (k = 0; ! free_lst.empty(); ++k) {
+ lbp = free_lst.back().first;
+ free_lbp = free_lst.back().second;
+ free_lst.back().second = NULL;
+ free_lst.pop_back();
+ if (vb > 6)
+ pr2serr_lk("t_id=%d freeing %p (free_ %p)\n", id, lbp, free_lbp);
+ if (free_lbp) {
+ free(free_lbp);
+ free_lbp = NULL;
+ }
+ }
+ if ((vb > 2) && (k > 0))
+ pr2serr_lk("%d->id: Maximum number of READ/WRITEs queued: %d\n",
+ id, k);
+ async_starts += thr_async_starts;
+ async_finishes += thr_async_finishes;
+ start_eagain_count += thr_start_eagain_count;
+ start_ebusy_count += thr_start_ebusy_count;
+ start_e2big_count += thr_start_e2big_count;
+ fin_eagain_count += thr_fin_eagain_count;
+ fin_ebusy_count += thr_fin_ebusy_count;
+ enomem_count += thr_enomem_count;
+ start_edom_count += thr_start_edom_count;
+ if (op->cmd_time && op->sg_vn_ge_40030 && (npt > 0)) {
+ pr2serr_lk("t_id=%d average nanosecs per cmd: %" PRId64
+ "\n", id, sum_nanosecs / npt);
+ }
+}
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+
+/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field
+ * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success,
+ * else -1 . */
+static int
+do_inquiry_prod_id(const char * dev_name, int block, int & sg_ver_num,
+ char * b, int b_mlen)
+{
+ int sg_fd, ok, ret;
+ struct sg_io_hdr pt;
+ uint8_t inqCmdBlk [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */
+
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ sg_fd = open(dev_name, open_flags);
+ if (sg_fd < 0) {
+ pr_errno_lk(errno, "%s: error opening file: %s", __func__, dev_name);
+ return -1;
+ }
+ if (ioctl(sg_fd, SG_GET_VERSION_NUM, &sg_ver_num) < 0)
+ sg_ver_num = 0;
+ /* Prepare INQUIRY command */
+ memset(&pt, 0, sizeof(pt));
+ pt.interface_id = 'S';
+ pt.cmd_len = sizeof(inqCmdBlk);
+ /* pt.iovec_count = 0; */ /* memset takes care of this */
+ pt.mx_sb_len = sizeof(sense_buffer);
+ pt.dxfer_direction = SG_DXFER_FROM_DEV;
+ pt.dxfer_len = INQ_REPLY_LEN;
+ pt.dxferp = inqBuff;
+ pt.cmdp = inqCmdBlk;
+ pt.sbp = sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* pt.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* pt.pack_id = 0; */
+ /* pt.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &pt) < 0) {
+ pr_errno_lk(errno, "%s: Inquiry SG_IO ioctl error", __func__);
+ close(sg_fd);
+ return -1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&pt)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ pr2serr_lk("Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+ sg_chk_n_print3("INQUIRY command error", &pt, 1);
+ }
+ break;
+ }
+ if (ok) {
+ /* Good, so fetch Product ID from response, copy to 'b' */
+ if (b_mlen > 0) {
+ if (b_mlen > 16) {
+ memcpy(b, inqBuff + 16, 16);
+ b[16] = '\0';
+ } else {
+ memcpy(b, inqBuff + 16, b_mlen - 1);
+ b[b_mlen - 1] = '\0';
+ }
+ }
+ ret = 0;
+ } else
+ ret = -1;
+
+ close(sg_fd);
+ return ret;
+}
+
+/* Only allow ranges up to 2**32-1 upper limit, so READ CAPACITY(10)
+ * sufficient. Return of 0 -> success, -1 -> failure, 2 -> try again */
+static int
+do_read_capacity(const char * dev_name, int block, unsigned int * last_lba,
+ unsigned int * blk_sz)
+{
+ int res, sg_fd;
+ uint8_t rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t rcBuff[64];
+ uint8_t sense_b[64] SG_C_CPP_ZERO_INIT;
+ sg_io_hdr_t io_hdr SG_C_CPP_ZERO_INIT;
+ int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */
+
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ sg_fd = open(dev_name, open_flags);
+ if (sg_fd < 0) {
+ pr_errno_lk(errno, "%s: error opening file: %s", __func__, dev_name);
+ return -1;
+ }
+ /* Prepare READ CAPACITY(10) command */
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rcCmdBlk);
+ io_hdr.mx_sb_len = sizeof(sense_b);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = sizeof(rcBuff);
+ io_hdr.dxferp = rcBuff;
+ io_hdr.cmdp = rcCmdBlk;
+ io_hdr.sbp = sense_b;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */;
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ pr_errno_lk(errno, "%s (SG_IO) error", __func__);
+ close(sg_fd);
+ return -1;
+ }
+ res = sg_err_category3(&io_hdr);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ lock_guard<mutex> lg(console_mutex);
+ sg_chk_n_print3("read capacity", &io_hdr, 1);
+ close(sg_fd);
+ return 2; /* probably have another go ... */
+ } else if (SG_LIB_CAT_CLEAN != res) {
+ lock_guard<mutex> lg(console_mutex);
+ sg_chk_n_print3("read capacity", &io_hdr, 1);
+ close(sg_fd);
+ return -1;
+ }
+ *last_lba = sg_get_unaligned_be32(&rcBuff[0]);
+ *blk_sz = sg_get_unaligned_be32(&rcBuff[4]);
+ close(sg_fd);
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool maxq_per_thread_given = false;
+ int n;
+ int force = 0;
+ int64_t ll;
+ int num_threads = DEF_NUM_THREADS;
+ struct timespec start_tm, end_tm;
+ struct opts_t * op;
+ const char * cp;
+
+ op = &a_opts;
+#if 0
+ memset(op, 0, sizeof(*op)); // C++ doesn't like this
+#endif
+ op->direct = DEF_DIRECT;
+ op->lba = DEF_LBA;
+ op->hi_lba = 0;
+ op->lb_sz = DEF_LB_SZ;
+ op->maxq_per_thread = MAX_Q_PER_FD;
+ op->mmap_io = DEF_MMAP_IO;
+ op->num_per_thread = DEF_NUM_PER_THREAD;
+ op->num_lbs = 1;
+ op->no_xfer = !! DEF_NO_XFER;
+ op->verbose = 0;
+ op->wait_ms = DEF_WAIT_MS;
+ op->c2e = SCSI_TUR;
+ op->blqd = BLQ_DEFAULT;
+ op->block = !! DEF_BLOCKING;
+ op->myqd = MYQD_HIGH;
+ page_size = sysconf(_SC_PAGESIZE);
+
+ while (1) {
+ int option_index = 0;
+ int c;
+
+ c = getopt_long(argc, argv,
+ "34acdefghl:L:mM:n:NO:pq:Q:Rs:St:TuvVw:W",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '3':
+ op->v3 = true;
+ op->v3_given = true;
+ op->v4 = false; /* if '-4 -3' take latter */
+ op->v4_given = false;
+ break;
+ case '4':
+ op->v4 = true;
+ op->v4_given = true;
+ op->v3 = false;
+ op->v3_given = false;
+ break;
+ case 'a':
+ op->masync = true;
+ break;
+ case 'c':
+ op->cmd_time = true;
+ break;
+ case 'd':
+ op->direct = true;
+ break;
+ case 'e':
+ op->excl = true;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'g':
+ op->generic_sync = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ if (isdigit(*optarg)) {
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr_lk("could not decode lba\n");
+ return 1;
+ } else
+ op->lba = (uint64_t)ll;
+ cp = strchr(optarg, ',');
+ if (cp) {
+ if (0 == strcmp("-1", cp + 1))
+ op->hi_lba = UINT_MAX;
+ else {
+ ll = sg_get_llnum(cp + 1);
+ if ((-1 == ll) || (ll > UINT_MAX)) {
+ pr2serr_lk("could not decode hi_lba, or > "
+ "UINT_MAX\n");
+ return 1;
+ } else
+ op->hi_lba = (unsigned int)ll;
+ }
+ }
+ } else {
+ pr2serr_lk("--lba= expects a number\n");
+ return 1;
+ }
+ break;
+ case 'L':
+ op->lb_sz = sg_get_num(optarg);
+ if (op->lb_sz < 0) {
+ pr2serr_lk("--lbsz= expects power of 2\n");
+ return 1;
+ }
+ if (0 == op->lb_sz)
+ op->lb_sz = DEF_LB_SZ;
+ break;
+ case 'm':
+ op->mmap_io = true;
+ break;
+ case 'M':
+ if (isdigit(*optarg)) {
+ n = atoi(optarg);
+ if ((n < 1) || (n > MAX_Q_PER_FD)) {
+ pr2serr_lk("-M expects a value from 1 to %d\n",
+ MAX_Q_PER_FD);
+ return 1;
+ }
+ maxq_per_thread_given = true;
+ op->maxq_per_thread = n;
+ } else {
+ pr2serr_lk("--maxqpt= expects a number\n");
+ return 1;
+ }
+ break;
+ case 'n':
+ if (isdigit(*optarg))
+ op->num_per_thread = sg_get_num(optarg);
+ else {
+ pr2serr_lk("--numpt= expects a number\n");
+ return 1;
+ }
+ break;
+ case 'N':
+ op->no_xfer = true;
+ break;
+ case 'O':
+ if (isdigit(*optarg))
+ op->ovn = sg_get_num(optarg);
+ else {
+ pr2serr_lk("--override= expects a number\n");
+ return 1;
+ }
+ if (op->ovn < 0) {
+ pr2serr_lk("--override= bad number\n");
+ return 1;
+ }
+ break;
+ case 'p':
+ op->pack_id_force = true;
+ break;
+ case 'q':
+ if (isdigit(*optarg)) {
+ n = atoi(optarg);
+ if (0 == n)
+ op->blqd = BLQ_AT_HEAD;
+ else if (1 == n)
+ op->blqd = BLQ_AT_TAIL;
+ } else {
+ pr2serr_lk("--qat= expects a number: 0 or 1\n");
+ return 1;
+ }
+ break;
+ case 'Q':
+ if (isdigit(*optarg)) {
+ n = atoi(optarg);
+ if (0 == n)
+ op->myqd = MYQD_LOW;
+ else if (1 == n)
+ op->myqd = MYQD_MEDIUM;
+ else if (2 == n)
+ op->myqd = MYQD_HIGH;
+ } else {
+ pr2serr_lk("--qfav= expects a number: 0, 1 or 2\n");
+ return 1;
+ }
+ break;
+ case 'R':
+ op->c2e = SCSI_READ16;
+ break;
+ case 's':
+ if (isdigit(*optarg)) {
+ op->lb_sz = atoi(optarg);
+ if (op->lb_sz < 256) {
+ cerr << "Strange lb_sz, using 256" << endl;
+ op->lb_sz = 256;
+ }
+ } else {
+ pr2serr_lk("--szlb= expects a number\n");
+ return 1;
+ }
+ if ((cp = strchr(optarg, ','))) {
+ n = sg_get_num(cp + 1);
+ if (n < 1) {
+ pr2serr_lk("could not decode 2nd part of "
+ "--szlb=LBS,NLBS\n");
+ return 1;
+ }
+ op->num_lbs = n;
+ }
+ break;
+ case 'S':
+ ++op->stats;
+ break;
+ case 't':
+ if (isdigit(*optarg))
+ num_threads = atoi(optarg);
+ else {
+ pr2serr_lk("--tnum= expects a number\n");
+ return 1;
+ }
+ break;
+ case 'T':
+ op->c2e = SCSI_TUR;
+ break;
+ case 'u':
+ op->submit = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'w':
+ if ((isdigit(*optarg) || ('-' == *optarg))) {
+ if ('-' == *optarg)
+ op->wait_ms = - atoi(optarg + 1);
+ else
+ op->wait_ms = atoi(optarg);
+ } else {
+ pr2serr_lk("--wait= expects a number\n");
+ return 1;
+ }
+ break;
+ case 'W':
+ op->c2e = SCSI_WRITE16;
+ break;
+ default:
+ pr2serr_lk("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return 1;
+ }
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ op->dev_names.push_back(argv[optind]);
+ }
+#ifdef DEBUG
+ pr2serr_lk("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr_lk("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr_lk("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr_lk("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr_lk("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr_lk("version: %s\n", version_str);
+ return 0;
+ }
+ if (op->mmap_io) {
+ if (maxq_per_thread_given && (op->maxq_per_thread > 1)) {
+ pr2serr_lk("With mmap_io selected, QPT cannot exceed 1\n");
+ return 1;
+ } else if (op->direct) {
+ pr2serr_lk("direct IO and mmap-ed IO cannot both be selected\n");
+ return 1;
+ } else if (op->generic_sync) {
+ pr2serr_lk("--generic-sync and and mmap-ed IO are compatible\n");
+ return 1;
+ } else
+ op->maxq_per_thread = 1;
+ }
+ if (! op->cmd_time && getenv("SG3_UTILS_LINUX_NANO")) {
+ op->cmd_time = true;
+ if (op->verbose)
+ fprintf(stderr, "setting nanosecond timing due to environment "
+ "variable: SG3_UTILS_LINUX_NANO\n");
+ }
+ if (0 == op->dev_names.size()) {
+ fprintf(stderr, "No sg_disk_device-s given\n\n");
+ usage();
+ return 1;
+ }
+ if (op->hi_lba && (op->lba > op->hi_lba)) {
+ cerr << "lba,hi_lba range is illegal" << endl;
+ return 1;
+ }
+ if (op->v4) {
+ if (! op->submit) {
+ op->submit = true;
+ if (op->verbose > 1)
+ cerr << "when --v4 is given, --submit will be set" << endl;
+ }
+ }
+
+ try {
+ int k, sg_ver_num;
+ unsigned int last_lba;
+ unsigned int blk_sz;
+ struct stat a_stat;
+
+ for (k = 0; k < (int)op->dev_names.size(); ++k) {
+ int res;
+ const char * dev_name;
+ char b[128];
+
+ dev_name = op->dev_names[k];
+ if (stat(dev_name, &a_stat) < 0) {
+ snprintf(b, sizeof(b), "could not stat() %s", dev_name);
+ perror(b);
+ return 1;
+ }
+ if (! S_ISCHR(a_stat.st_mode)) {
+ pr2serr_lk("%s should be a sg device which is a char "
+ "device. %s\n", dev_name, dev_name);
+ pr2serr_lk("is not a char device and damage could be done "
+ "if it is a BLOCK\ndevice, exiting ...\n");
+ return 1;
+ }
+ res = do_inquiry_prod_id(dev_name, op->block, sg_ver_num,
+ b, sizeof(b));
+ if (! force) {
+ if (res) {
+ pr2serr_lk("INQUIRY failed on %s\n", dev_name);
+ return 1;
+ }
+ // For safety, since <lba> written to, only permit scsi_debug
+ // devices. Bypass this with '-f' option.
+ if (0 != memcmp("scsi_debug", b, 10)) {
+ pr2serr_lk("Since this utility may write to LBAs, "
+ "only devices with the\n"
+ "product ID 'scsi_debug' accepted. Use '-f' "
+ "to override.\n");
+ return 2;
+ }
+ }
+ if (sg_ver_num < 30000) {
+ pr2serr_lk("%s either not sg device or too old\n", dev_name);
+ return 2;
+ } else if (sg_ver_num >= 40030) {
+ op->sg_vn_ge_40030 = true;
+ op->sg_vn_ge_40000 = true;
+ if (! (op->v3_given || op->v4_given)) {
+ op->v4 = true;
+ op->v3 = false;
+ op->submit = true;
+ }
+ } else if (sg_ver_num >= 40000) {
+ op->sg_vn_ge_40030 = false;
+ op->sg_vn_ge_40000 = true;
+ if (! (op->v3_given || op->v4_given)) {
+ op->v4 = true;
+ op->v3 = false;
+ op->submit = true;
+ }
+ } else {
+ if (! (op->v3_given || op->v4_given)) {
+ op->v4 = false;
+ op->v3 = true;
+ op->submit = false;
+ }
+ }
+
+ if ((SCSI_WRITE16 == op->c2e) || (SCSI_READ16 == op->c2e)) {
+ res = do_read_capacity(dev_name, op->block, &last_lba,
+ &blk_sz);
+ if (2 == res)
+ res = do_read_capacity(dev_name, op->block, &last_lba,
+ &blk_sz);
+ if (res) {
+ pr2serr_lk("READ CAPACITY(10) failed on %s\n", dev_name);
+ return 1;
+ }
+ if (blk_sz != (unsigned int)op->lb_sz) {
+ pr2serr_lk(">>> Logical block size (%d) of %s\n"
+ " differs from command line option (or "
+ "default)\n", blk_sz, dev_name);
+ pr2serr_lk("... continue anyway\n");
+ }
+ op->blk_szs.push_back(blk_sz);
+ if (UINT_MAX == op->hi_lba)
+ op->hi_lbas.push_back(last_lba);
+ }
+ }
+
+ start_tm.tv_sec = 0;
+ start_tm.tv_nsec = 0;
+ if (clock_gettime(CLOCK_MONOTONIC, &start_tm) < 0)
+ perror("clock_gettime failed");
+
+ vector<thread *> vt;
+
+ /* start multi-threaded section */
+ for (k = 0; k < num_threads; ++k) {
+ thread * tp = new thread {work_thread, k, op};
+ vt.push_back(tp);
+ }
+
+ // g++ 4.7.3 didn't like range-for loop here
+ for (k = 0; k < (int)vt.size(); ++k)
+ vt[k]->join();
+ /* end multi-threaded section, just this main thread left */
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ delete vt[k];
+
+ n = uniq_pack_id.load() - 1;
+ if (((n > 0) || op->generic_sync) &&
+ (0 == clock_gettime(CLOCK_MONOTONIC, &end_tm))) {
+ struct timespec res_tm;
+ double a, b;
+
+ if (op->generic_sync)
+ n = op->num_per_thread * num_threads;
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_nsec = end_tm.tv_nsec - start_tm.tv_nsec;
+ if (res_tm.tv_nsec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_nsec += 1000000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * (res_tm.tv_nsec / 1000));
+ b = (double)n;
+ if (a > 0.000001) {
+ printf("Time to complete %d commands was %d.%06d seconds\n",
+ n, (int)res_tm.tv_sec, (int)(res_tm.tv_nsec / 1000));
+ printf("Implies %.0f IOPS\n", (b / a));
+ }
+ }
+
+ if (op->verbose || op->stats) {
+ cout << "Number of sync_starts: " << sync_starts.load() << endl;
+ cout << "Number of async_starts: " << async_starts.load() << endl;
+ cout << "Number of async_finishes: " << async_finishes.load() <<
+ endl;
+ cout << "Last pack_id: " << n << endl;
+ }
+ n = start_ebusy_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of start EBUSYs: " << n << endl;
+ n = fin_ebusy_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of finish EBUSYs: " << n << endl;
+ n = start_eagain_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of start EAGAINs: " << n << endl;
+ n = fin_eagain_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of finish EAGAINs: " << n << endl;
+ n = start_e2big_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of E2BIGs: " << n << endl;
+ n = start_edom_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of EDOMs: " << n << endl;
+ n = enomem_count.load();
+ if (op->verbose || op->stats || (n > 0))
+ cout << "Number of ENOMEMs: " << n << endl;
+ }
+ catch(system_error& e) {
+ cerr << "got a system_error exception: " << e.what() << '\n';
+ auto ec = e.code();
+ cerr << "category: " << ec.category().name() << '\n';
+ cerr << "value: " << ec.value() << '\n';
+ cerr << "message: " << ec.message() << '\n';
+ cerr << "\nNote: if g++ may need '-pthread' or similar in "
+ "compile/link line" << '\n';
+ }
+ catch(...) {
+ cerr << "got another exception: " << '\n';
+ }
+ return 0;
+}
diff --git a/testing/sg_tst_bidi.c b/testing/sg_tst_bidi.c
new file mode 100644
index 00000000..0b81e144
--- /dev/null
+++ b/testing/sg_tst_bidi.c
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2019 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Invocation: See usage() function below.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_linux_inc.h"
+#include "sg_pr2serr.h"
+#include "sg_unaligned.h"
+
+/* This program tests bidirectional (bidi) SCSI command support in version 4.0
+ * and later of the Linux sg driver. The SBC-3 command XDWRITEREAD(10) that
+ is implemented by the scsi_debug driver is used. */
+
+
+static const char * version_str = "Version: 1.06 20191021";
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_OP 0x12
+#define INQ_CMD_LEN 6
+#define SENSE_BUFFER_LEN 96
+#define XDWRITEREAD_10_OP 0x53
+#define XDWRITEREAD_10_LEN 10
+
+#define EBUFF_SZ 256
+
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+#define DEF_Q_LEN 16 /* max in sg v3 and earlier */
+#define MAX_Q_LEN 256
+
+#define DEF_RESERVE_BUFF_SZ (256 * 1024)
+
+
+static bool q_at_tail = false;
+static int q_len = DEF_Q_LEN;
+static int sleep_secs = 0;
+static int reserve_buff_sz = DEF_RESERVE_BUFF_SZ;
+static int verbose = 0;
+
+
+static void
+usage(void)
+{
+ printf("Usage: sg_tst_bidi [-b=LB_SZ] [-d=DIO_BLKS] [-D] [-h] -l=LBA [-N] "
+ "[-q=Q_LEN]\n"
+ " [-Q] [-r=SZ] [-R=RC] [-s=SEC] [-t] [-v] [-V] "
+ "[-w]\n"
+ " <sg_or_bsg_device>\n"
+ " where:\n"
+ " -b=LB_SZ logical block size (def: 512 bytes)\n"
+ " -d=DIO_BLKS data in and out length (unit: logical "
+ "blocks; def: 1)\n"
+ " -D do direct IO (def: indirect which is also "
+ "fallback)\n"
+ " -h help: print usage message then exit\n"
+ " -l=LBA logical block address (LDA) of first modded "
+ "block\n"
+ " -N durations in nanoseconds (def: milliseconds)\n"
+ " -q=Q_LEN queue length, between 1 and 511 (def: 16). "
+ " Calls\n"
+ " ioctl(SG_IO) when -q=1 else SG_IOSUBMIT "
+ "(async)\n"
+ " -Q quiet, suppress usual output\n"
+ " -r=SZ reserve buffer size in KB (def: 256 --> 256 "
+ "KB)\n"
+ " -R=RC repetition count (def: 0)\n"
+ " -s=SEC sleep between writes and reads (def: 0)\n"
+ " -t queue_at_tail (def: q_at_head)\n"
+ " -v increase verbosity of output\n"
+ " -V print version string then exit\n"
+ " -w sets DISABLE WRITE bit on cdb to 0 (def: 1)\n\n"
+ "Warning: this test utility writes to location LBA and Q_LEN "
+ "following\nblocks using the XDWRITEREAD(10) SBC-3 command. "
+ "When -q=1 does\nioctl(SG_IO) and that is only case when a "
+ "bsg device can be given.\n");
+}
+
+static int
+ext_ioctl(int sg_fd, bool nanosecs)
+{
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_RESERVED_SIZE;
+ seip->reserved_sz = reserve_buff_sz;
+ seip->sei_rd_mask |= SG_SEIM_RESERVED_SIZE;
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS; /* this or previous optional */
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (nanosecs)
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ else
+ seip->ctl_flags &= ~SG_CTL_FLAGM_TIME_IN_NS;
+
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool done;
+ bool direct_io = false;
+ bool lba_given = false;
+ bool nanosecs = false;
+ bool quiet = false;
+ bool disable_write = true;
+ int k, j, ok, ver_num, pack_id, num_waiting, din_len;
+ int dout_len, cat;
+ int ret = 0;
+ int rep_count = 0;
+ int sg_fd = -1;
+ int lb_sz = 512;
+ int dio_blks = 1;
+ int dirio_count = 0;
+ int64_t lba = 0;
+ uint8_t inq_cdb[INQ_CMD_LEN] = {INQ_CMD_OP, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t xdwrrd10_cdb[XDWRITEREAD_10_LEN] =
+ {XDWRITEREAD_10_OP, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+ uint8_t inqBuff[MAX_Q_LEN][INQ_REPLY_LEN];
+ struct sg_io_v4 io_v4[MAX_Q_LEN];
+ struct sg_io_v4 rio_v4;
+ struct sg_io_v4 * io_v4p;
+ char * file_name = 0;
+ uint8_t * dinp;
+ uint8_t * free_dinp = NULL;
+ uint8_t * doutp;
+ uint8_t * free_doutp = NULL;
+ char ebuff[EBUFF_SZ];
+ uint8_t sense_buffer[MAX_Q_LEN][SENSE_BUFFER_LEN] SG_C_CPP_ZERO_INIT;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-b=", argv[k], 3)) {
+ lb_sz = atoi(argv[k] + 3);
+ if (lb_sz < 512 || (0 != (lb_sz % 512))) {
+ printf("Expect -b=LB_SZ be 512 or higher and a power of 2\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-d=", argv[k], 3)) {
+ dio_blks = atoi(argv[k] + 3);
+ if ((dio_blks < 1) || (dio_blks > 0xffff)) {
+ fprintf(stderr, "Expect -d=DIO_BLKS to be 1 or greater and "
+ "less than 65536\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-D", argv[k], 2))
+ direct_io = true;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ file_name = 0;
+ break;
+ } else if (0 == memcmp("-l=", argv[k], 3)) {
+ if (lba_given) {
+ pr2serr("can only give -l=LBA option once\n");
+ file_name = 0;
+ break;
+ }
+ lba = sg_get_llnum(argv[k] + 3);
+ if ((lba < 0) || (lba > 0xffffffff)) {
+ pr2serr("Expect -l= argument (LBA) to be non-negative and "
+ "fit in 32 bits\n");
+ return -1;
+ }
+ lba_given = true;
+ } else if (0 == memcmp("-N", argv[k], 2))
+ nanosecs = true;
+ else if (0 == memcmp("-q=", argv[k], 3)) {
+ q_len = atoi(argv[k] + 3);
+ if ((q_len > 511) || (q_len < 1)) {
+ printf("Expect -q= to take a number (q length) between 1 "
+ "and 511\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-Q", argv[k], 2))
+ quiet = true;
+ else if (0 == memcmp("-r=", argv[k], 3)) {
+ reserve_buff_sz = atoi(argv[k] + 3);
+ if (reserve_buff_sz < 0) {
+ printf("Expect -r= to take a number 0 or higher\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-R=", argv[k], 3)) {
+ rep_count = atoi(argv[k] + 3);
+ if (rep_count < 0) {
+ printf("Expect -R= to take a number 0 or higher\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-s=", argv[k], 3)) {
+ sleep_secs = atoi(argv[k] + 3);
+ if (sleep_secs < 0) {
+ printf("Expect -s= to take a number 0 or higher\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-t", argv[k], 2))
+ q_at_tail = true;
+ else if (0 == memcmp("-vvvvv", argv[k], 5))
+ verbose += 5;
+ else if (0 == memcmp("-vvvv", argv[k], 5))
+ verbose += 4;
+ else if (0 == memcmp("-vvv", argv[k], 4))
+ verbose += 3;
+ else if (0 == memcmp("-vv", argv[k], 3))
+ verbose += 2;
+ else if (0 == memcmp("-v", argv[k], 2))
+ verbose += 1;
+ else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s\n", version_str);
+ return 0;
+ } else if (0 == memcmp("-w", argv[k], 2))
+ disable_write = false;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if (0 == file_name) {
+ printf("No filename (sg device) given\n\n");
+ usage();
+ return 1;
+ }
+ if (! lba_given) {
+ pr2serr("Needs the -l=LBA 'option' to be given, hex numbers "
+ "prefixed by '0x';\nor with a trailing 'h'\n");
+ ret = 1;
+ goto out;
+ }
+ din_len = lb_sz * dio_blks;
+ dout_len = lb_sz * dio_blks;
+ dinp = sg_memalign(din_len * q_len, 0, &free_dinp, false);
+ if (NULL == dinp) {
+ fprintf(stderr, "Unable to allocate %d byte for din buffer\n",
+ din_len * q_len);
+ ret = 1;
+ goto out;
+ }
+ doutp = sg_memalign(dout_len * q_len, 0, &free_doutp, false);
+ if (NULL == doutp) {
+ fprintf(stderr, "Unable to allocate %d byte for dout buffer\n",
+ dout_len * q_len);
+ ret = 1;
+ goto out;
+ }
+
+ /* An access mode of O_RDWR is required for write()/read() interface */
+ if ((sg_fd = open(file_name, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "error opening file: %s", file_name);
+ perror(ebuff);
+ return 1;
+ }
+ if (verbose)
+ fprintf(stderr, "opened given file: %s successfully, fd=%d\n",
+ file_name, sg_fd);
+
+ if (ioctl(sg_fd, SG_GET_VERSION_NUM, &ver_num) < 0) {
+ pr2serr("ioctl(SG_GET_VERSION_NUM) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ goto out;
+ }
+ if (! quiet)
+ printf("Linux sg driver version: %d\n", ver_num);
+ if ((q_len > 1) && ext_ioctl(sg_fd, nanosecs))
+ goto out;
+
+
+ if (1 == q_len) { /* do sync ioct(SG_IO) */
+ io_v4p = &io_v4[k];
+rep_sg_io:
+ memset(io_v4p, 0, sizeof(*io_v4p));
+ io_v4p->guard = 'Q';
+ if (direct_io)
+ io_v4p->flags |= SG_FLAG_DIRECT_IO;
+ if (disable_write)
+ xdwrrd10_cdb[2] |= 0x4;
+ sg_put_unaligned_be16(dio_blks, xdwrrd10_cdb + 7);
+ sg_put_unaligned_be32(lba, xdwrrd10_cdb + 2);
+ if (verbose > 2) {
+ pr2serr(" %s cdb: ", "XDWRITE(10)");
+ for (j = 0; j < XDWRITEREAD_10_LEN; ++j)
+ pr2serr("%02x ", xdwrrd10_cdb[j]);
+ pr2serr("\n");
+ }
+ io_v4p->request_len = XDWRITEREAD_10_LEN;
+ io_v4p->request = (uint64_t)(uintptr_t)xdwrrd10_cdb;
+ io_v4p->din_xfer_len = din_len;
+ io_v4p->din_xferp = (uint64_t)(uintptr_t)(dinp + (k * din_len));
+ io_v4p->dout_xfer_len = dout_len;
+ io_v4p->dout_xferp = (uint64_t)(uintptr_t)(doutp + (k * dout_len));
+ io_v4p->response = (uint64_t)(uintptr_t)sense_buffer[k];
+ io_v4p->max_response_len = SENSE_BUFFER_LEN;
+ io_v4p->timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_v4p->request_extra = 99; /* so pack_id doesn't start at 0 */
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ io_v4p->flags |= SG_FLAG_Q_AT_TAIL;
+ else
+ io_v4p->flags |= SG_FLAG_Q_AT_HEAD;
+ /* io_v4p->usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, io_v4p) < 0) {
+ pr2serr("sg ioctl(SG_IO) errno=%d [%s]\n", errno,
+ strerror(errno));
+ close(sg_fd);
+ return 1;
+ }
+ /* now for the error processing */
+ ok = 0;
+ rio_v4 = *io_v4p;
+ cat = sg_err_category_new(rio_v4.device_status,
+ rio_v4.transport_status,
+ rio_v4.driver_status,
+ (const uint8_t *)(unsigned long)rio_v4.response,
+ rio_v4.response_len);
+ switch (cat) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_linux_sense_print(NULL, rio_v4.device_status,
+ rio_v4.transport_status,
+ rio_v4.driver_status,
+ (const uint8_t *)(unsigned long)rio_v4.response,
+ rio_v4.response_len, true);
+ break;
+ }
+ if ((rio_v4.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO)
+ ++dirio_count;
+ if (verbose > 3) {
+ pr2serr(">> din_resid=%d, dout_resid=%d, info=0x%x\n",
+ rio_v4.din_resid, rio_v4.dout_resid, rio_v4.info);
+ if (rio_v4.response_len > 0) {
+ pr2serr("sense buffer: ");
+ hex2stderr(sense_buffer[k], rio_v4.response_len, -1);
+ }
+ }
+ if ((! quiet) && ok) /* output result if it is available */
+ printf("XDWRITEREAD(10) using ioctl(SG_IO) duration=%u\n",
+ rio_v4.duration);
+ if (rep_count-- > 0)
+ goto rep_sg_io;
+ goto out;
+ }
+
+rep_async:
+ if (! quiet)
+ printf("start write() calls\n");
+ for (k = 0; k < q_len; ++k) {
+ io_v4p = &io_v4[k];
+ memset(io_v4p, 0, sizeof(*io_v4p));
+ io_v4p->guard = 'Q';
+ if (direct_io)
+ io_v4p->flags |= SG_FLAG_DIRECT_IO;
+ /* io_v4p->iovec_count = 0; */ /* memset takes care of this */
+ if (0 != (k % 64)) {
+ if (disable_write)
+ xdwrrd10_cdb[2] |= 0x4;
+ sg_put_unaligned_be16(dio_blks, xdwrrd10_cdb + 7);
+ sg_put_unaligned_be32(lba, xdwrrd10_cdb + 2);
+ if (verbose > 2) {
+ pr2serr(" %s cdb: ", "XDWRITE(10)");
+ for (j = 0; j < XDWRITEREAD_10_LEN; ++j)
+ pr2serr("%02x ", xdwrrd10_cdb[j]);
+ pr2serr("\n");
+ }
+ io_v4p->request_len = XDWRITEREAD_10_LEN;
+ io_v4p->request = (uint64_t)(uintptr_t)xdwrrd10_cdb;
+ io_v4p->din_xfer_len = din_len;
+ io_v4p->din_xferp = (uint64_t)(uintptr_t)(dinp + (k * din_len));
+ io_v4p->dout_xfer_len = dout_len;
+ io_v4p->dout_xferp = (uint64_t)(uintptr_t)(doutp + (k * dout_len));
+ } else {
+ if (verbose > 2) {
+ pr2serr(" %s cdb: ", "INQUIRY");
+ for (j = 0; j < INQ_CMD_LEN; ++j)
+ pr2serr("%02x ", inq_cdb[j]);
+ pr2serr("\n");
+ }
+ io_v4p->request_len = sizeof(inq_cdb);
+ io_v4p->request = (uint64_t)(uintptr_t)inq_cdb;
+ io_v4p->din_xfer_len = INQ_REPLY_LEN;
+ io_v4p->din_xferp = (uint64_t)(uintptr_t)inqBuff[k];
+ }
+ io_v4p->response = (uint64_t)(uintptr_t)sense_buffer[k];
+ io_v4p->max_response_len = SENSE_BUFFER_LEN;
+ io_v4p->timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_v4p->request_extra = k + 3; /* so pack_id doesn't start at 0 */
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ io_v4p->flags |= SG_FLAG_Q_AT_TAIL;
+ else
+ io_v4p->flags |= SG_FLAG_Q_AT_HEAD;
+ /* io_v4p->usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IOSUBMIT, io_v4p) < 0) {
+ pr2serr("sg ioctl(SG_IOSUBMIT) errno=%d [%s]\n", errno,
+ strerror(errno));
+ close(sg_fd);
+ return 1;
+ }
+ }
+
+#if 0
+ {
+ struct sg_scsi_id ssi;
+
+ memset(&ssi, 0, sizeof(ssi));
+ if (ioctl(sg_fd, SG_GET_SCSI_ID, &ssi) < 0)
+ pr2serr("ioctl(SG_GET_SCSI_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else {
+ printf("host_no: %d\n", ssi.host_no);
+ printf(" channel: %d\n", ssi.channel);
+ printf(" scsi_id: %d\n", ssi.scsi_id);
+ printf(" lun: %d\n", ssi.lun);
+ printf(" pdt: %d\n", ssi.scsi_type);
+ printf(" h_cmd_per_lun: %d\n", ssi.h_cmd_per_lun);
+ printf(" d_queue_depth: %d\n", ssi.d_queue_depth);
+ }
+ }
+#endif
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("num_waiting: %d\n", num_waiting);
+
+ if (sleep_secs > 0)
+ sleep(sleep_secs);
+
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("num_waiting: %d\n", num_waiting);
+
+ if (! quiet)
+ printf("\nstart read() calls\n");
+ for (k = 0, done = false; k < q_len; ++k) {
+ if ((! done) && (k == q_len / 2)) {
+ done = true;
+ if (! quiet)
+ printf("\n>>> half way through read\n");
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else if (! quiet)
+ printf("num_waiting: %d\n", num_waiting);
+ }
+ memset(&rio_v4, 0, sizeof(struct sg_io_v4));
+ rio_v4.guard = 'Q';
+ if (ioctl(sg_fd, SG_IORECEIVE, &rio_v4) < 0) {
+ perror("sg ioctl(SG_IORECEIVE) error");
+ close(sg_fd);
+ return 1;
+ }
+ /* now for the error processing */
+ ok = 0;
+ cat = sg_err_category_new(rio_v4.device_status,
+ rio_v4.transport_status,
+ rio_v4.driver_status,
+ (const uint8_t *)(unsigned long)rio_v4.response,
+ rio_v4.response_len);
+ switch (cat) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_linux_sense_print(NULL, rio_v4.device_status,
+ rio_v4.transport_status,
+ rio_v4.driver_status,
+ (const uint8_t *)(unsigned long)rio_v4.response,
+ rio_v4.response_len, true);
+ break;
+ }
+ if ((rio_v4.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO)
+ ++dirio_count;
+ if (verbose > 3) {
+ pr2serr(">> din_resid=%d, dout_resid=%d, info=0x%x\n",
+ rio_v4.din_resid, rio_v4.dout_resid, rio_v4.info);
+ if (rio_v4.response_len > 0) {
+ pr2serr("sense buffer: ");
+ hex2stderr(sense_buffer[k], rio_v4.response_len, -1);
+ }
+ }
+ if ((! quiet) && ok) { /* output result if it is available */
+ if (0 != ((rio_v4.request_extra - 3) % 64))
+ printf("XDWRITEREAD(10) %d duration=%u\n",
+ rio_v4.request_extra, rio_v4.duration);
+ else
+ printf("INQUIRY %d duration=%u\n",
+ rio_v4.request_extra,
+ rio_v4.duration);
+ }
+ }
+ if (direct_io && (dirio_count < q_len)) {
+ pr2serr("Direct IO requested %d times, done %d times\nMaybe need "
+ "'echo 1 > /sys/module/sg/parameters/allow_dio'\n", q_len, dirio_count);
+ }
+ if (rep_count-- > 0)
+ goto rep_async;
+ ret = 0;
+
+out:
+ if (sg_fd >= 0)
+ close(sg_fd);
+ if (free_dinp)
+ free(free_dinp);
+ if (free_doutp)
+ free(free_doutp);
+ return ret;
+}
diff --git a/testing/sg_tst_context.cpp b/testing/sg_tst_context.cpp
new file mode 100644
index 00000000..f7cff2fb
--- /dev/null
+++ b/testing/sg_tst_context.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 2013-2022 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <iostream>
+#include <vector>
+#include <system_error>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_pt.h"
+
+static const char * version_str = "1.06 20220425";
+static const char * util_name = "sg_tst_context";
+
+/* This is a test program for checking that file handles keep their
+ * context properly when sent (synchronous) SCSI pass-through commands.
+ * A disk device is assumed and even-numbered threads send TEST UNIT
+ * READY commands while odd-numbered threads send alternating START STOP
+ * UNIT commands (i.e. start then stop then start, etc). The point is to
+ * check the results to make sure that they don't get the other command's
+ * response. For example a START STOP UNIT command should not see a "not
+ * ready" sense key.
+ *
+ * This is C++ code with some things from C++11 (e.g. threads) and was
+ * only just able to compile (when some things were reverted) with gcc/g++
+ * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
+ * was not available until g++ version 4.8.1 and that is found in Fedora
+ * 19 and Ubuntu 13.10 .
+ *
+ * The build uses various object files from the <sg3_utils>/lib directory
+ * which is assumed to be a sibling of this examples directory. Those
+ * object files in the lib directory can be built with:
+ * cd <sg3_utils> ; ./configure ; cd lib; make
+ * Then:
+ * cd ../testing
+ * make sg_tst_context
+ *
+ */
+
+using namespace std;
+using namespace std::chrono;
+
+#define DEF_NUM_PER_THREAD 200
+#define DEF_NUM_THREADS 2
+
+#define EBUFF_SZ 256
+
+
+static mutex count_mutex;
+static mutex console_mutex;
+static unsigned int even_notreadys;
+static unsigned int odd_notreadys;
+static unsigned int ebusy_count;
+static int verbose;
+
+
+static void
+usage(void)
+{
+ printf("Usage: %s [-e] [-h] [-n <n_per_thr>] [-N] [-R] [-s]\n"
+ " [-t <num_thrs>] [-v] [-V] <disk_device>\n",
+ util_name);
+ printf(" where\n");
+ printf(" -e use O_EXCL on open (def: don't)\n");
+ printf(" -h print this usage message then exit\n");
+ printf(" -n <n_per_thr> number of loops per thread "
+ "(def: %d)\n", DEF_NUM_PER_THREAD);
+ printf(" -N use O_NONBLOCK on open (def: don't)\n");
+ printf(" -R make sure device in ready (started) "
+ "state after\n"
+ " test (do extra iteration if "
+ "necessary)\n");
+ printf(" -s share an open file handle (def: one "
+ "per thread)\n");
+ printf(" -t <num_thrs> number of threads (def: %d)\n",
+ DEF_NUM_THREADS);
+ printf(" -v increase verbosity\n");
+ printf(" -V print version number then exit\n\n");
+ printf("Test if file handles keep context through to their responses. "
+ "Sends\nTEST UNIT READY commands on even threads (origin 0) and "
+ "START STOP\nUNIT commands on odd threads. Expect NOT READY "
+ "sense keys only\nfrom the even threads (i.e from TUR)\n");
+}
+
+static int
+pt_err(int res)
+{
+ if (res < 0)
+ fprintf(stderr, " pass through OS error: %s\n", safe_strerror(-res));
+ else if (SCSI_PT_DO_BAD_PARAMS == res)
+ fprintf(stderr, " bad pass through setup\n");
+ else if (SCSI_PT_DO_TIMEOUT == res)
+ fprintf(stderr, " pass through timeout\n");
+ else
+ fprintf(stderr, " do_scsi_pt error=%d\n", res);
+ return (res < 0) ? res : -EPERM /* -1 */;
+}
+
+static int
+pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp)
+{
+ int slen;
+ char b[256];
+ const int bl = (int)sizeof(b);
+ const char * cp = NULL;
+
+ b[0] = '\0';
+ switch (cat) {
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b);
+ cp = " scsi status: %s\n";
+ break;
+ case SCSI_PT_RESULT_SENSE:
+ slen = get_scsi_pt_sense_len(ptp);
+ sg_get_sense_str("", sbp, slen, 1, bl, b);
+ cp = "%s\n";
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ get_scsi_pt_transport_err_str(ptp, bl, b);
+ cp = " transport: %s\n";
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ get_scsi_pt_os_err_str(ptp, bl, b);
+ cp = " os: %s\n";
+ break;
+ default:
+ cp = " unknown pt result category (%d)\n";
+ break;
+ }
+ if (cp) {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, cp, b);
+ }
+ return -EIO /* -5 */;
+}
+
+#define TUR_CMD_LEN 6
+#define SSU_CMD_LEN 6
+#define NOT_READY SG_LIB_CAT_NOT_READY
+
+/* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative
+ * errno */
+static int
+do_tur(struct sg_pt_base * ptp, int id)
+{
+ int slen, res, cat;
+ unsigned char turCmdBlk [TUR_CMD_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, turCmdBlk, sizeof(turCmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ res = do_scsi_pt(ptp, -1, 20 /* secs timeout */, verbose);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "TEST UNIT READY do_scsi_pt() submission error, "
+ "id=%d\n", id);
+ }
+ res = pt_err(res);
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ slen = get_scsi_pt_sense_len(ptp);
+ if ((SCSI_PT_RESULT_SENSE == cat) &&
+ (NOT_READY == sg_err_category_sense(sense_buffer, slen))) {
+ res = 1024;
+ goto err;
+ }
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "TEST UNIT READY do_scsi_pt() category problem, "
+ "id=%d\n", id);
+ }
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ goto err;
+ }
+ res = 0;
+err:
+ return res;
+}
+
+/* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative
+ * errno */
+static int
+do_ssu(struct sg_pt_base * ptp, int id, bool start)
+{
+ int slen, res, cat;
+ unsigned char ssuCmdBlk [SSU_CMD_LEN] = {0x1b, 0x0, 0x0, 0x0, 0x0, 0x0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+
+ if (start)
+ ssuCmdBlk[4] |= 0x1;
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, ssuCmdBlk, sizeof(ssuCmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ res = do_scsi_pt(ptp, -1, 40 /* secs timeout */, verbose);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "START STOP UNIT do_scsi_pt() submission error, "
+ "id=%d\n", id);
+ }
+ res = pt_err(res);
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ slen = get_scsi_pt_sense_len(ptp);
+ if ((SCSI_PT_RESULT_SENSE == cat) &&
+ (NOT_READY == sg_err_category_sense(sense_buffer, slen))) {
+ res = 1024;
+ goto err;
+ }
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "START STOP UNIT do_scsi_pt() category problem, "
+ "id=%d\n", id);
+ }
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ goto err;
+ }
+ res = 0;
+err:
+ return res;
+}
+
+static void
+work_thread(const char * dev_name, int id, int num, bool share,
+ int pt_fd, int nonblock, int oexcl, bool ready_after)
+{
+ bool started = true;
+ int k;
+ int res = 0;
+ unsigned int thr_even_notreadys = 0;
+ unsigned int thr_odd_notreadys = 0;
+ struct sg_pt_base * ptp = NULL;
+
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ cerr << "Enter work_thread id=" << id << " num=" << num << " share="
+ << share << endl;
+ }
+ if (! share) { /* ignore passed ptp, make this thread's own */
+ int oflags = O_RDWR;
+ unsigned int thr_ebusy_count = 0;
+
+ if (nonblock)
+ oflags |= O_NONBLOCK;
+ if (oexcl)
+ oflags |= O_EXCL;
+ while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose)) < 0)
+ && (-EBUSY == pt_fd)) {
+ ++thr_ebusy_count;
+ this_thread::yield(); // give other threads a chance
+ }
+ if (pt_fd < 0) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "work_thread id=%d: error opening: %s",
+ id, dev_name);
+ perror(ebuff);
+ return;
+ }
+ if (thr_ebusy_count) {
+ lock_guard<mutex> lg(count_mutex);
+
+ ebusy_count += thr_ebusy_count;
+ }
+ }
+ /* The instance of 'struct sg_pt_base' is local to this thread but the
+ * pt_fd it contains may be shared, depending on the 'share' boolean. */
+ ptp = construct_scsi_pt_obj_with_fd(pt_fd, verbose);
+ if (NULL == ptp) {
+ fprintf(stderr, "work_thread id=%d: "
+ "construct_scsi_pt_obj_with_fd() failed, memory?\n", id);
+ return;
+ }
+ for (k = 0; k < num; ++k) {
+ if (0 == (id % 2)) {
+ /* Even thread ids do TEST UNIT READYs */
+ res = do_tur(ptp, id);
+ if (1024 == res) {
+ ++thr_even_notreadys;
+ res = 0;
+ }
+ } else {
+ /* Odd thread ids do START STOP UNITs, alternating between
+ * starts and stops */
+ started = (0 == (k % 2));
+ res = do_ssu(ptp, id, started);
+ if (1024 == res) {
+ ++thr_odd_notreadys;
+ res = 0;
+ }
+ }
+ if (res)
+ break;
+ if (ready_after && (! started))
+ do_ssu(ptp, id, true);
+ }
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ if ((! share) && (pt_fd >= 0))
+ close(pt_fd);
+
+ {
+ lock_guard<mutex> lg(count_mutex);
+
+ even_notreadys += thr_even_notreadys;
+ odd_notreadys += thr_odd_notreadys;
+ }
+
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ if (k < num)
+ cerr << "thread id=" << id << " FAILed at iteration: " << k
+ << " [negated errno: " << res << " <"
+ << safe_strerror(-res) << ">]" << endl;
+ else
+ cerr << "thread id=" << id << " normal exit" << '\n';
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int k;
+ int pt_fd = -1;
+ int oexcl = 0;
+ int nonblock = 0;
+ int num_per_thread = DEF_NUM_PER_THREAD;
+ bool ready_after = false;
+ bool share = false;
+ int num_threads = DEF_NUM_THREADS;
+ char * dev_name = NULL;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-e", argv[k], 2))
+ ++oexcl;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ usage();
+ return 0;
+ } else if (0 == memcmp("-n", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k])) {
+ num_per_thread = sg_get_num(argv[k]);
+ if (num_per_thread<= 0) {
+ fprintf(stderr, "want positive integer for number "
+ "per thread\n");
+ return 1;
+ }
+ } else
+ break;
+ } else if (0 == memcmp("-N", argv[k], 2))
+ ++nonblock;
+ else if (0 == memcmp("-R", argv[k], 2))
+ ready_after = true;
+ else if (0 == memcmp("-s", argv[k], 2))
+ share = true;
+ else if (0 == memcmp("-t", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_threads = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-v", argv[k], 2))
+ ++verbose;
+ else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s version: %s\n", util_name, version_str);
+ return 0;
+ } else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ dev_name = NULL;
+ break;
+ }
+ else if (! dev_name)
+ dev_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ dev_name = 0;
+ break;
+ }
+ }
+ if (0 == dev_name) {
+ usage();
+ return 1;
+ }
+ try {
+ if (share) {
+ int oflags = O_RDWR;
+
+ if (nonblock)
+ oflags |= O_NONBLOCK;
+ if (oexcl)
+ oflags |= O_EXCL;
+ while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose))
+ < 0) && (-EBUSY == pt_fd)) {
+ ++ebusy_count;
+ sleep(0); // process yield ??
+ }
+ if (pt_fd < 0) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "main: error opening: %s",
+ dev_name);
+ perror(ebuff);
+ return 1;
+ }
+ /* Tried calling construct_scsi_pt_obj_with_fd() here but that
+ * doesn't work since 'struct sg_pt_base' objects aren't
+ * thread-safe without user space intervention (e.g. mutexes). */
+ }
+
+ vector<thread *> vt;
+
+ for (k = 0; k < num_threads; ++k) {
+ thread * tp = new thread {work_thread, dev_name, k,
+ num_per_thread, share, pt_fd, nonblock,
+ oexcl, ready_after};
+ vt.push_back(tp);
+ }
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ vt[k]->join();
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ delete vt[k];
+
+ if (share)
+ scsi_pt_close_device(pt_fd);
+
+ cout << "Expected not_readys on TEST UNIT READY: " << even_notreadys
+ << endl;
+ cout << "UNEXPECTED not_readys on START STOP UNIT: "
+ << odd_notreadys << endl;
+ if (ebusy_count)
+ cout << "Number of EBUSYs (on open): " << ebusy_count << endl;
+
+ }
+ catch(system_error& e) {
+ cerr << "got a system_error exception: " << e.what() << '\n';
+ auto ec = e.code();
+ cerr << "category: " << ec.category().name() << '\n';
+ cerr << "value: " << ec.value() << '\n';
+ cerr << "message: " << ec.message() << '\n';
+ cerr << "\nNote: if g++ may need '-pthread' or similar in "
+ "compile/link line" << '\n';
+ }
+ catch(...) {
+ cerr << "got another exception: " << '\n';
+ }
+ if (pt_fd >= 0)
+ close(pt_fd);
+ return 0;
+}
diff --git a/testing/sg_tst_excl.cpp b/testing/sg_tst_excl.cpp
new file mode 100644
index 00000000..0354de60
--- /dev/null
+++ b/testing/sg_tst_excl.cpp
@@ -0,0 +1,984 @@
+/*
+ * Copyright (c) 2013-2022 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <iostream>
+#include <vector>
+#include <system_error>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+
+static const char * version_str = "1.14 20220425";
+static const char * util_name = "sg_tst_excl";
+
+/* This is a test program for checking O_EXCL on open() works. It uses
+ * multiple threads and can be run as multiple processes and attempts
+ * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK
+ * and do a double increment on a LB then close it. Prior to the first
+ * increment, the value is checked for even or odd. Assuming the count
+ * starts as an even (typically 0) then it should remain even. Odd instances
+ * are counted and reported at the end of the program, after all threads
+ * have completed.
+ *
+ * This is C++ code with some things from C++11 (e.g. threads) and was
+ * only just able to compile (when some things were reverted) with gcc/g++
+ * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
+ * was not available until g++ version 4.8.1 and that is only currently
+ * found in Fedora 19 .
+ *
+ * The build uses various object files from the <sg3_utils>/lib directory
+ * which is assumed to be a sibling of this examples directory. Those
+ * object files in the lib directory can be built with:
+ * cd <sg3_utils> ; ./configure ; cd lib; make
+ * Then:
+ * cd ../testing
+ * make sg_tst_excl
+ *
+ * Currently this utility is Linux only and assumes the SG_IO v3 interface
+ * which is supported by sg and block devices (but not bsg devices which
+ * require the SG_IO v4 interface). This restriction is relaxed in the
+ * sg_tst_excl2 variant of this utility.
+ *
+ * BEWARE: this utility modifies a logical block (default LBA 1000) on the
+ * given device.
+ *
+ */
+
+using namespace std;
+using namespace std::chrono;
+
+#define DEF_NUM_PER_THREAD 200
+#define DEF_NUM_THREADS 4
+#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */
+
+
+#define DEF_LBA 1000U
+
+#define EBUFF_SZ 256
+
+static mutex odd_count_mutex;
+static mutex console_mutex;
+static unsigned int odd_count;
+static unsigned int ebusy_count;
+static unsigned int eagain_count;
+static int sg_ifc_ver = 3;
+
+
+static void
+usage(void)
+{
+ printf("Usage: %s [-b] [-f] [-h] [-i <sg_ver>] [-l <lba>] "
+ "[-n <n_per_thr>]\n"
+ " [-t <num_thrs>] [-V] [-w <wait_ms>] "
+ "[-x] [-xx]\n"
+ " <sg_disk_device>\n", util_name);
+ printf(" where\n");
+ printf(" -b block on open (def: O_NONBLOCK)\n");
+ printf(" -f force: any SCSI disk (def: only "
+ "scsi_debug)\n");
+ printf(" WARNING: <lba> written to\n");
+ printf(" -h print this usage message then exit\n");
+ printf(" -i <sg_ver> sg driver interface version (default: "
+ "3)\n");
+ printf(" -l <lba> logical block to increment (def: %u)\n",
+ DEF_LBA);
+ printf(" -n <n_per_thr> number of loops per thread "
+ "(def: %d)\n", DEF_NUM_PER_THREAD);
+ printf(" -t <num_thrs> number of threads (def: %d)\n",
+ DEF_NUM_THREADS);
+ printf(" -V print version number then exit\n");
+ printf(" -w <wait_ms> >0: sleep_for(<wait_ms>); =0: "
+ "yield(); -1: no\n"
+ " wait; -2: sleep(0) (def: %d)\n",
+ DEF_WAIT_MS);
+ printf(" -x don't use O_EXCL on first thread "
+ "(def: use\n"
+ " O_EXCL on all threads)\n"
+ " -xx don't use O_EXCL on any thread\n\n");
+ printf("Test O_EXCL open flag with Linux sg driver. Each open/close "
+ "cycle with the\nO_EXCL flag does a double increment on "
+ "lba (using its first 4 bytes).\nEach increment uses a READ_16, "
+ "READ_16, increment, WRITE_16 cycle. The two\nREAD_16s are "
+ "launched asynchronously. Note that '-xx' will run test\n"
+ "without any O_EXCL flags.\n");
+}
+
+
+#define READ16_REPLY_LEN 512
+#define READ16_CMD_LEN 16
+#define WRITE16_REPLY_LEN 512
+#define WRITE16_CMD_LEN 16
+
+/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for
+ * wait_ms milliseconds if wait_ms is positive.
+ * Reads lba (twice) and treats the first 4 bytes as an int (SCSI endian),
+ * increments it and writes it back. Repeats so that happens twice. Then
+ * closes dev_name. If an error occurs returns -1 else returns 0 if
+ * first int read from lba is even otherwise returns 1. */
+static int
+do_rd_inc_wr_twice_v3(const char * dev_name, unsigned int lba, int block,
+ int excl, int wait_ms, int id, unsigned int & ebusy,
+ unsigned int & eagains)
+{
+ bool odd = false;
+ int k, sg_fd;
+ struct sg_io_hdr pt, pt2;
+ unsigned char r16CmdBlk [READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char w16CmdBlk [WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ unsigned char lb[READ16_REPLY_LEN];
+ int open_flags = O_RDWR;
+
+ sg_put_unaligned_be64(lba, r16CmdBlk + 2);
+ sg_put_unaligned_be64(lba, w16CmdBlk + 2);
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ if (excl)
+ open_flags |= O_EXCL;
+
+ while (((sg_fd = open(dev_name, open_flags)) < 0) &&
+ (EBUSY == errno)) {
+ ++ebusy;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s: error opening file: %s", __func__,
+ dev_name);
+ perror(ebuff);
+ return -1;
+ }
+
+ for (k = 0; k < 2; ++k) {
+ bool ok = false;
+ int res;
+ unsigned int u = 0;
+
+ /* Prepare READ_16 command */
+ memset(&pt, 0, sizeof(pt));
+ pt.interface_id = 'S';
+ pt.cmd_len = sizeof(r16CmdBlk);
+ pt.mx_sb_len = sizeof(sense_buffer);
+ pt.dxfer_direction = SG_DXFER_FROM_DEV;
+ pt.dxfer_len = READ16_REPLY_LEN;
+ pt.dxferp = lb;
+ pt.cmdp = r16CmdBlk;
+ pt.sbp = sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ pt.pack_id = id;
+
+ // queue up two READ_16s to same LBA
+ if (write(sg_fd, &pt, sizeof(pt)) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" write(sg, READ_16)");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ pt2 = pt;
+ if (write(sg_fd, &pt2, sizeof(pt2)) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" write(sg, READ_16) 2");
+ }
+ close(sg_fd);
+ return -1;
+ }
+
+ while (((res = read(sg_fd, &pt, sizeof(pt))) < 0) &&
+ (EAGAIN == errno)) {
+ ++eagains;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" read(sg, READ_16)");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ /* now for the error processing */
+ switch (sg_err_category3(&pt)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "Recovered error on READ_16, continuing\n");
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_chk_n_print3("READ_16 command error", &pt, 1);
+ }
+ break;
+ }
+ if (ok) {
+ while (((res = read(sg_fd, &pt2, sizeof(pt2))) < 0) &&
+ (EAGAIN == errno)) {
+ ++eagains;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" read(sg, READ_16) 2");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ pt = pt2;
+ /* now for the error processing */
+ ok = false;
+ switch (sg_err_category3(&pt)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "%s: Recovered error on READ_16, "
+ "continuing 2\n", __func__);
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_chk_n_print3("READ_16 command error 2", &pt, 1);
+ }
+ break;
+ }
+ }
+ if (! ok) {
+ close(sg_fd);
+ return -1;
+ }
+
+ u = sg_get_unaligned_be32(lb);
+ // Assuming u starts test as even (probably 0), expect it to stay even
+ if (0 == k)
+ odd = (1 == (u % 2));
+ ++u;
+ sg_put_unaligned_be32(u, lb);
+
+ if (wait_ms > 0) /* allow daylight for bad things ... */
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+
+ /* Prepare WRITE_16 command */
+ memset(&pt, 0, sizeof(pt));
+ pt.interface_id = 'S';
+ pt.cmd_len = sizeof(w16CmdBlk);
+ pt.mx_sb_len = sizeof(sense_buffer);
+ pt.dxfer_direction = SG_DXFER_TO_DEV;
+ pt.dxfer_len = WRITE16_REPLY_LEN;
+ pt.dxferp = lb;
+ pt.cmdp = w16CmdBlk;
+ pt.sbp = sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ pt.pack_id = id;
+
+ if (ioctl(sg_fd, SG_IO, &pt) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" WRITE_16 SG_IO ioctl error");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ /* now for the error processing */
+ ok = false;
+ switch (sg_err_category3(&pt)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "%s: Recovered error on WRITE_16, "
+ "continuing\n", __func__);
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_chk_n_print3("WRITE_16 command error", &pt, 1);
+ }
+ break;
+ }
+ if (! ok) {
+ close(sg_fd);
+ return -1;
+ }
+ }
+ close(sg_fd);
+ return (int)odd;
+}
+
+/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for
+ * wait_ms milliseconds if wait_ms is positive.
+ * Reads lba (twice) and treats the first 4 bytes as an int (SCSI endian),
+ * increments it and writes it back. Repeats so that happens twice. Then
+ * closes dev_name. If an error occurs returns -1 else returns 0 if
+ * first int read from lba is even otherwise returns 1. */
+static int
+do_rd_inc_wr_twice_v4(const char * dev_name, unsigned int lba, int block,
+ int excl, int wait_ms, int id, unsigned int & ebusy,
+ unsigned int & eagains)
+{
+ bool odd = false;
+ int k, sg_fd;
+ struct sg_io_v4 pt, pt2;
+ unsigned char r16CmdBlk [READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char w16CmdBlk [WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ unsigned char lb[READ16_REPLY_LEN];
+ int open_flags = O_RDWR;
+
+ sg_put_unaligned_be64(lba, r16CmdBlk + 2);
+ sg_put_unaligned_be64(lba, w16CmdBlk + 2);
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ if (excl)
+ open_flags |= O_EXCL;
+
+ while (((sg_fd = open(dev_name, open_flags)) < 0) &&
+ (EBUSY == errno)) {
+ ++ebusy;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s: error opening file: %s", __func__,
+ dev_name);
+ perror(ebuff);
+ return -1;
+ }
+
+ for (k = 0; k < 2; ++k) {
+ bool ok = false;
+ int res;
+ unsigned int u = 0;
+
+ /* Prepare READ_16 command */
+ memset(&pt, 0, sizeof(pt));
+ pt.guard = 'Q';
+ pt.request_len = sizeof(r16CmdBlk);
+ pt.max_response_len = sizeof(sense_buffer);
+ // pt.dxfer_direction = SG_DXFER_FROM_DEV;
+ pt.din_xfer_len = READ16_REPLY_LEN;
+ pt.din_xferp = (uint64_t)(sg_uintptr_t)lb;
+ pt.request = (uint64_t)(sg_uintptr_t)r16CmdBlk;
+ pt.response = (uint64_t)(sg_uintptr_t)sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ pt.request_extra = id; /* pack_id field */
+
+ // queue up two READ_16s to same LBA
+ if (ioctl(sg_fd, SG_IOSUBMIT, &pt) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" write(sg, READ_16)");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ pt2 = pt;
+ if (ioctl(sg_fd, SG_IOSUBMIT, &pt2) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" write(sg, READ_16) 2");
+ }
+ close(sg_fd);
+ return -1;
+ }
+
+ while (((res = ioctl(sg_fd, SG_IORECEIVE, &pt)) < 0) &&
+ (EAGAIN == errno)) {
+ ++eagains;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" read(sg, READ_16)");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ /* now for the error processing */
+ switch (sg_err_category_new(pt.device_status, pt.transport_status,
+ pt.driver_status, sense_buffer, pt.response_len)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "Recovered error on READ_16, continuing\n");
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_linux_sense_print("READ_16 command error",
+ pt.device_status, pt.transport_status,
+ pt.driver_status, sense_buffer,
+ pt.response_len, true);
+ // sg_chk_n_print3("READ_16 command error", &pt, 1);
+ }
+ break;
+ }
+ if (ok) {
+ while (((res = ioctl(sg_fd, SG_IORECEIVE, &pt2)) < 0) &&
+ (EAGAIN == errno)) {
+ ++eagains;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (res < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" read(sg, READ_16) 2");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ pt = pt2;
+ /* now for the error processing */
+ ok = false;
+ switch (sg_err_category_new(pt.device_status, pt.transport_status,
+ pt.driver_status, sense_buffer, pt.response_len)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "%s: Recovered error on READ_16, "
+ "continuing 2\n", __func__);
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_linux_sense_print("READ_16 command error 2",
+ pt.device_status,
+ pt.transport_status,
+ pt.driver_status, sense_buffer,
+ pt.response_len, true);
+ // sg_chk_n_print3("READ_16 command error 2", &pt, 1);
+ }
+ break;
+ }
+ }
+ if (! ok) {
+ close(sg_fd);
+ return -1;
+ }
+
+ u = sg_get_unaligned_be32(lb);
+ // Assuming u starts test as even (probably 0), expect it to stay even
+ if (0 == k)
+ odd = (1 == (u % 2));
+ ++u;
+ sg_put_unaligned_be32(u, lb);
+
+ if (wait_ms > 0) /* allow daylight for bad things ... */
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+
+ /* Prepare WRITE_16 command */
+ memset(&pt, 0, sizeof(pt));
+ pt.guard = 'Q';
+ pt.request_len = sizeof(w16CmdBlk);
+ pt.max_response_len = sizeof(sense_buffer);
+ // pt.dxfer_direction = SG_DXFER_TO_DEV;
+ pt.dout_xfer_len = WRITE16_REPLY_LEN;
+ pt.dout_xferp = (uint64_t)(sg_uintptr_t)lb;
+ pt.request = (uint64_t)(sg_uintptr_t)w16CmdBlk;
+ pt.response = (uint64_t)(sg_uintptr_t)sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ pt.request_extra = id; /* pack_id field */
+
+ if (ioctl(sg_fd, SG_IO, &pt) < 0) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(" WRITE_16 SG_IO ioctl error");
+ }
+ close(sg_fd);
+ return -1;
+ }
+ /* now for the error processing */
+ ok = false;
+ switch (sg_err_category_new(pt.device_status, pt.transport_status,
+ pt.driver_status, sense_buffer, pt.response_len)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = true;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "%s: Recovered error on WRITE_16, "
+ "continuing\n", __func__);
+ }
+ ok = true;
+ break;
+ default: /* won't bother decoding other categories */
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ sg_linux_sense_print("WRITE_16 command error",
+ pt.device_status, pt.transport_status,
+ pt.driver_status, sense_buffer,
+ pt.response_len, true);
+ }
+ break;
+ }
+ if (! ok) {
+ close(sg_fd);
+ return -1;
+ }
+ }
+ close(sg_fd);
+ return odd;
+}
+
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+
+/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field
+ * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success,
+ * else -1 . */
+static int
+do_inquiry_prod_id(const char * dev_name, int block, int wait_ms,
+ unsigned int & ebusys, char * b, int b_mlen)
+{
+ int sg_fd, ok, ret;
+ struct sg_io_hdr pt;
+ unsigned char inqCmdBlk [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char inqBuff[INQ_REPLY_LEN];
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */
+
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ while (((sg_fd = open(dev_name, open_flags)) < 0) &&
+ (EBUSY == errno)) {
+ ++ebusys;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield();
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ,
+ "do_inquiry_prod_id: error opening file: %s", dev_name);
+ perror(ebuff);
+ return -1;
+ }
+ /* Prepare INQUIRY command */
+ memset(&pt, 0, sizeof(pt));
+ pt.interface_id = 'S';
+ pt.cmd_len = sizeof(inqCmdBlk);
+ /* pt.iovec_count = 0; */ /* memset takes care of this */
+ pt.mx_sb_len = sizeof(sense_buffer);
+ pt.dxfer_direction = SG_DXFER_FROM_DEV;
+ pt.dxfer_len = INQ_REPLY_LEN;
+ pt.dxferp = inqBuff;
+ pt.cmdp = inqCmdBlk;
+ pt.sbp = sense_buffer;
+ pt.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ /* pt.flags = 0; */ /* take defaults: indirect IO, etc */
+ /* pt.pack_id = 0; */
+ /* pt.usr_ptr = NULL; */
+
+ if (ioctl(sg_fd, SG_IO, &pt) < 0) {
+ perror("do_inquiry_prod_id: Inquiry SG_IO ioctl error");
+ close(sg_fd);
+ return -1;
+ }
+
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&pt)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error on INQUIRY, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("INQUIRY command error", &pt, 1);
+ break;
+ }
+ if (ok) {
+ /* Good, so fetch Product ID from response, copy to 'b' */
+ if (b_mlen > 0) {
+ if (b_mlen > 16) {
+ memcpy(b, inqBuff + 16, 16);
+ b[16] = '\0';
+ } else {
+ memcpy(b, inqBuff + 16, b_mlen - 1);
+ b[b_mlen - 1] = '\0';
+ }
+ }
+ ret = 0;
+ } else
+ ret = -1;
+ close(sg_fd);
+ return ret;
+}
+
+static void
+work_thread(const char * dev_name, unsigned int lba, int id, int block,
+ int excl, int num, int wait_ms)
+{
+ unsigned int thr_odd_count = 0;
+ unsigned int thr_ebusy_count = 0;
+ unsigned int thr_eagain_count = 0;
+ int k, res;
+
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ cerr << "Enter work_thread id=" << id << " excl=" << excl << " block="
+ << block << endl;
+ }
+ for (k = 0; k < num; ++k) {
+ if (sg_ifc_ver == 3)
+ res = do_rd_inc_wr_twice_v3(dev_name, lba, block, excl, wait_ms,
+ k, thr_ebusy_count, thr_eagain_count);
+ else if (sg_ifc_ver == 4)
+ res = do_rd_inc_wr_twice_v4(dev_name, lba, block, excl, wait_ms,
+ k, thr_ebusy_count, thr_eagain_count);
+ else {
+ lock_guard<mutex> lg(console_mutex);
+
+ cerr << "sg_ifc_ver=" << sg_ifc_ver << " not supported" << endl;
+ res = -1;
+ }
+ if (res < 0)
+ break;
+ if (res)
+ ++thr_odd_count;
+ }
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ if (k < num)
+ cerr << "thread id=" << id << " FAILed at iteration: " << k <<
+ '\n';
+ else
+ cerr << "thread id=" << id << " normal exit" << '\n';
+ }
+ {
+ lock_guard<mutex> lg(odd_count_mutex);
+
+ odd_count += thr_odd_count;
+ ebusy_count += thr_ebusy_count;
+ eagain_count += thr_eagain_count;
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int k;
+ int block = 0;
+ int force = 0;
+ unsigned int lba = DEF_LBA;
+ int num_per_thread = DEF_NUM_PER_THREAD;
+ int num_threads = DEF_NUM_THREADS;
+ int wait_ms = DEF_WAIT_MS;
+ int no_o_excl = 0;
+ char * dev_name = NULL;
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-b", argv[k], 2))
+ ++block;
+ else if (0 == memcmp("-f", argv[k], 2))
+ ++force;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ usage();
+ return 0;
+ } else if (0 == memcmp("-i", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ sg_ifc_ver = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-l", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ lba = (unsigned int)atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-n", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_per_thread = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-t", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_threads = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s version: %s\n", util_name, version_str);
+ return 0;
+ } else if (0 == memcmp("-w", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) {
+ if ('-' == *argv[k])
+ wait_ms = - atoi(argv[k] + 1);
+ else
+ wait_ms = atoi(argv[k]);
+ } else
+ break;
+ } else if (0 == memcmp("-xxx", argv[k], 4))
+ no_o_excl += 3;
+ else if (0 == memcmp("-xx", argv[k], 3))
+ no_o_excl += 2;
+ else if (0 == memcmp("-x", argv[k], 2))
+ ++no_o_excl;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ dev_name = NULL;
+ break;
+ }
+ else if (! dev_name)
+ dev_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ dev_name = 0;
+ break;
+ }
+ }
+ if (0 == dev_name) {
+ usage();
+ return 1;
+ }
+ try {
+ struct stat a_stat;
+
+ if (stat(dev_name, &a_stat) < 0) {
+ perror("stat() on dev_name failed");
+ return 1;
+ }
+ if (! S_ISCHR(a_stat.st_mode)) {
+ fprintf(stderr, "%s should be a sg device which is a char "
+ "device. %s\n", dev_name, dev_name);
+ fprintf(stderr, "is not a char device and damage could be done "
+ "if it is a BLOCK\ndevice, exiting ...\n");
+ return 1;
+ }
+ if (! force) {
+ char b[64];
+ int res = do_inquiry_prod_id(dev_name, block, wait_ms,
+ ebusy_count, b, sizeof(b));
+
+ if (res) {
+ fprintf(stderr, "INQUIRY failed on %s\n", dev_name);
+ return 1;
+ }
+ // For safety, since <lba> written to, only permit scsi_debug
+ // devices. Bypass this with '-f' option.
+ if (0 != memcmp("scsi_debug", b, 10)) {
+ fprintf(stderr, "Since this utility writes to LBA %u, only "
+ "devices with scsi_debug\nproduct ID accepted.\n",
+ lba);
+ return 2;
+ }
+ }
+
+ vector<thread *> vt;
+
+ for (k = 0; k < num_threads; ++k) {
+ int excl = 1;
+
+ if (no_o_excl > 1)
+ excl = 0;
+ else if ((0 == k) && (1 == no_o_excl))
+ excl = 0;
+
+ thread * tp = new thread {work_thread, dev_name, lba, k, block,
+ excl, num_per_thread, wait_ms};
+ vt.push_back(tp);
+ }
+
+ // g++ 4.7.3 didn't like range-for loop here
+ for (k = 0; k < (int)vt.size(); ++k)
+ vt[k]->join();
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ delete vt[k];
+
+ if (no_o_excl)
+ cout << "Odd count: " << odd_count << endl;
+ else
+ cout << "Expecting odd count of 0, got " << odd_count << endl;
+ cout << "Number of EBUSYs: " << ebusy_count << endl;
+ cout << "Number of EAGAINs: " << eagain_count << endl;
+
+ }
+ catch(system_error& e) {
+ cerr << "got a system_error exception: " << e.what() << '\n';
+ auto ec = e.code();
+ cerr << "category: " << ec.category().name() << '\n';
+ cerr << "value: " << ec.value() << '\n';
+ cerr << "message: " << ec.message() << '\n';
+ cerr << "\nNote: if g++ may need '-pthread' or similar in "
+ "compile/link line" << '\n';
+ }
+ catch(...) {
+ cerr << "got another exception: " << '\n';
+ }
+ return 0;
+}
diff --git a/testing/sg_tst_excl2.cpp b/testing/sg_tst_excl2.cpp
new file mode 100644
index 00000000..d198d713
--- /dev/null
+++ b/testing/sg_tst_excl2.cpp
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) 2013-2022 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <iostream>
+#include <vector>
+#include <system_error>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+static const char * version_str = "1.11 20220425";
+static const char * util_name = "sg_tst_excl2";
+
+/* This is a test program for checking O_EXCL on open() works. It uses
+ * multiple threads and can be run as multiple processes and attempts
+ * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK
+ * and do a double increment on a LB then close it. Prior to the first
+ * increment, the value is checked for even or odd. Assuming the count
+ * starts as an even (typically 0) then it should remain even. Odd instances
+ * are counted and reported at the end of the program, after all threads
+ * have completed.
+ *
+ * This is C++ code with some things from C++11 (e.g. threads) and was
+ * only just able to compile (when some things were reverted) with gcc/g++
+ * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
+ * was not available until g++ version 4.8.1 and that is only currently
+ * found in Fedora 19 .
+ *
+ * The build uses various object files from the <sg3_utils>/lib directory
+ * which is assumed to be a sibling of this examples directory. Those
+ * object files in the lib directory can be built with:
+ * cd <sg3_utils> ; ./configure ; cd lib; make
+ * Then:
+ * cd ../testing
+ * make sg_tst_excl2
+ *
+ * BEWARE: this utility modifies a logical block (default LBA 1000) on the
+ * given device.
+ *
+ */
+
+using namespace std;
+using namespace std::chrono;
+
+#define DEF_NUM_PER_THREAD 200
+#define DEF_NUM_THREADS 4
+#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */
+
+#define DEF_LBA 1000
+
+#define EBUFF_SZ 256
+
+
+static mutex odd_count_mutex;
+static mutex console_mutex;
+static unsigned int odd_count;
+static unsigned int ebusy_count;
+
+
+static void
+usage(void)
+{
+ printf("Usage: %s [-b] [-f] [-h] [-l <lba>] [-n <n_per_thr>] "
+ "[-t <num_thrs>]\n"
+ " [-V] [-w <wait_ms>] [-x] "
+ "<disk_device>\n", util_name);
+ printf(" where\n");
+ printf(" -b block on open (def: O_NONBLOCK)\n");
+ printf(" -f force: any SCSI disk (def: only "
+ "scsi_debug)\n");
+ printf(" WARNING: <lba> written to\n");
+ printf(" -h print this usage message then exit\n");
+ printf(" -l <lba> logical block to increment (def: %u)\n",
+ DEF_LBA);
+ printf(" -n <n_per_thr> number of loops per thread "
+ "(def: %d)\n", DEF_NUM_PER_THREAD);
+ printf(" -t <num_thrs> number of threads (def: %d)\n",
+ DEF_NUM_THREADS);
+ printf(" -V print version number then exit\n");
+ printf(" -w <wait_ms> >0: sleep_for(<wait_ms>); =0: "
+ "yield(); -1: no\n"
+ " wait; -2: sleep(0) (def: %d)\n",
+ DEF_WAIT_MS);
+ printf(" -x don't use O_EXCL on first thread "
+ "(def: use\n"
+ " O_EXCL on all threads)\n\n");
+ printf("Test O_EXCL open flag with pass-through drivers. Each "
+ "open/close cycle with\nthe O_EXCL flag does a double increment "
+ "on lba (using its first 4 bytes).\n");
+}
+
+/* Assumed a lock (mutex) held when pt_err() is called */
+static int
+pt_err(int res)
+{
+ if (res < 0)
+ fprintf(stderr, " pass through os error: %s\n", safe_strerror(-res));
+ else if (SCSI_PT_DO_BAD_PARAMS == res)
+ fprintf(stderr, " bad pass through setup\n");
+ else if (SCSI_PT_DO_TIMEOUT == res)
+ fprintf(stderr, " pass through timeout\n");
+ else
+ fprintf(stderr, " do_scsi_pt error=%d\n", res);
+ return -1;
+}
+
+/* Assumed a lock (mutex) held when pt_cat_no_good() is called */
+static int
+pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp)
+{
+ int slen;
+ char b[256];
+ const int bl = (int)sizeof(b);
+
+ switch (cat) {
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b);
+ fprintf(stderr, " scsi status: %s\n", b);
+ break;
+ case SCSI_PT_RESULT_SENSE:
+ slen = get_scsi_pt_sense_len(ptp);
+ sg_get_sense_str("", sbp, slen, 1, bl, b);
+ fprintf(stderr, "%s", b);
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ get_scsi_pt_transport_err_str(ptp, bl, b);
+ fprintf(stderr, " transport: %s", b);
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ get_scsi_pt_os_err_str(ptp, bl, b);
+ fprintf(stderr, " os: %s", b);
+ break;
+ default:
+ fprintf(stderr, " unknown pt result category (%d)\n", cat);
+ break;
+ }
+ return -1;
+}
+
+#define READ16_REPLY_LEN 512
+#define READ16_CMD_LEN 16
+#define WRITE16_REPLY_LEN 512
+#define WRITE16_CMD_LEN 16
+
+/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for
+ * wait_ms milliseconds if wait_ms is positive. Reads lba and treats the
+ * first 4 bytes as an int (SCSI endian), increments it and writes it back.
+ * Repeats so that happens twice. Then closes dev_name. If an error occurs
+ * returns -1 else returns 0 if first int read is even otherwise returns 1. */
+static int
+do_rd_inc_wr_twice(const char * dev_name, unsigned int lba, int block,
+ int excl, int wait_ms, unsigned int & ebusys)
+{
+ int k, sg_fd, res, cat;
+ int odd = 0;
+ unsigned int u = 0;
+ struct sg_pt_base * ptp = NULL;
+ unsigned char r16CmdBlk [READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char w16CmdBlk [WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ unsigned char lb[READ16_REPLY_LEN];
+ char ebuff[EBUFF_SZ];
+ int open_flags = O_RDWR;
+
+ sg_put_unaligned_be64(lba, r16CmdBlk + 2);
+ sg_put_unaligned_be64(lba, w16CmdBlk + 2);
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ if (excl)
+ open_flags |= O_EXCL;
+
+ while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) &&
+ (-EBUSY == sg_fd)) {
+ ++ebusys;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "do_rd_inc_wr_twice: error opening file: %s", dev_name);
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(ebuff);
+ }
+ return -1;
+ }
+
+ ptp = construct_scsi_pt_obj();
+ for (k = 0; k < 2; ++k) {
+ /* Prepare READ_16 command */
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, r16CmdBlk, sizeof(r16CmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_in(ptp, lb, READ16_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "READ_16 do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ }
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "READ_16 do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ }
+ goto err;
+ }
+
+ u = sg_get_unaligned_be32(lb);
+ // Assuming u starts test as even (probably 0), expect it to stay even
+ if (0 == k)
+ odd = (1 == (u % 2));
+ ++u;
+ sg_put_unaligned_be32(u, lb);
+
+ if (wait_ms > 0) /* allow daylight for bad things ... */
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+
+ /* Prepare WRITE_16 command */
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, w16CmdBlk, sizeof(w16CmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_out(ptp, lb, WRITE16_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "WRITE_16 do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ }
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "WRITE_16 do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ }
+ goto err;
+ }
+ }
+err:
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ scsi_pt_close_device(sg_fd);
+ return odd;
+}
+
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+
+/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field
+ * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success,
+ * else -1 . */
+static int
+do_inquiry_prod_id(const char * dev_name, int block, int wait_ms,
+ unsigned int & ebusys, char * b, int b_mlen)
+{
+ int sg_fd, res, cat;
+ struct sg_pt_base * ptp = NULL;
+ unsigned char inqCmdBlk [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char inqBuff[INQ_REPLY_LEN];
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ char ebuff[EBUFF_SZ];
+ int open_flags = O_RDWR; /* since O_EXCL | O_RDONLY gives EPERM */
+
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) &&
+ (-EBUSY == sg_fd)) {
+ ++ebusys;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "do_inquiry_prod_id: error opening file: %s", dev_name);
+ perror(ebuff);
+ return -1;
+ }
+ /* Prepare INQUIRY command */
+ ptp = construct_scsi_pt_obj();
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, inqCmdBlk, sizeof(inqCmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_in(ptp, inqBuff, INQ_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "INQUIRY do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ }
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "INQUIRY do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ }
+ goto err;
+ }
+
+ /* Good, so fetch Product ID from response, copy to 'b' */
+ if (b_mlen > 0) {
+ if (b_mlen > 16) {
+ memcpy(b, inqBuff + 16, 16);
+ b[16] = '\0';
+ } else {
+ memcpy(b, inqBuff + 16, b_mlen - 1);
+ b[b_mlen - 1] = '\0';
+ }
+ }
+err:
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ close(sg_fd);
+ return 0;
+}
+
+static void
+work_thread(const char * dev_name, unsigned int lba, int id, int block,
+ int excl, int num, int wait_ms)
+{
+ unsigned int thr_odd_count = 0;
+ unsigned int thr_ebusy_count = 0;
+ int k, res;
+
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ cerr << "Enter work_thread id=" << id << " excl=" << excl << " block="
+ << block << endl;
+ }
+ for (k = 0; k < num; ++k) {
+ res = do_rd_inc_wr_twice(dev_name, lba, block, excl, wait_ms,
+ thr_ebusy_count);
+ if (res < 0)
+ break;
+ if (res)
+ ++thr_odd_count;
+ }
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ if (k < num)
+ cerr << "thread id=" << id << " FAILed at iteration: " << k <<
+ '\n';
+ else
+ cerr << "thread id=" << id << " normal exit" << '\n';
+ }
+
+ {
+ lock_guard<mutex> lg(odd_count_mutex);
+
+ odd_count += thr_odd_count;
+ ebusy_count += thr_ebusy_count;
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int k, res;
+ int block = 0;
+ int force = 0;
+ unsigned int lba = DEF_LBA;
+ int num_per_thread = DEF_NUM_PER_THREAD;
+ int num_threads = DEF_NUM_THREADS;
+ int wait_ms = DEF_WAIT_MS;
+ int exclude_o_excl = 0;
+ char * dev_name = NULL;
+ char b[64];
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-b", argv[k], 2))
+ ++block;
+ else if (0 == memcmp("-f", argv[k], 2))
+ ++force;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ usage();
+ return 0;
+ } else if (0 == memcmp("-l", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ lba = (unsigned int)atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-n", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_per_thread = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-t", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_threads = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s version: %s\n", util_name, version_str);
+ return 0;
+ } else if (0 == memcmp("-w", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) {
+ if ('-' == *argv[k])
+ wait_ms = - atoi(argv[k] + 1);
+ else
+ wait_ms = atoi(argv[k]);
+ } else
+ break;
+ } else if (0 == memcmp("-x", argv[k], 2))
+ ++exclude_o_excl;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ dev_name = NULL;
+ break;
+ }
+ else if (! dev_name)
+ dev_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ dev_name = 0;
+ break;
+ }
+ }
+ if (0 == dev_name) {
+ usage();
+ return 1;
+ }
+ try {
+
+ if (! force) {
+ res = do_inquiry_prod_id(dev_name, block, wait_ms, ebusy_count,
+ b, sizeof(b));
+ if (res) {
+ fprintf(stderr, "INQUIRY failed on %s\n", dev_name);
+ return 1;
+ }
+ // For safety, since <lba> written to, only permit scsi_debug
+ // devices. Bypass this with '-f' option.
+ if (0 != memcmp("scsi_debug", b, 10)) {
+ fprintf(stderr, "Since this utility writes to LBA %d, only "
+ "devices with scsi_debug\nproduct ID accepted.\n",
+ lba);
+ return 2;
+ }
+ }
+
+ vector<thread *> vt;
+
+ for (k = 0; k < num_threads; ++k) {
+ int excl = ((0 == k) && exclude_o_excl) ? 0 : 1;
+
+ thread * tp = new thread {work_thread, dev_name, lba, k, block,
+ excl, num_per_thread, wait_ms};
+ vt.push_back(tp);
+ }
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ vt[k]->join();
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ delete vt[k];
+
+ cout << "Expecting odd count of 0, got " << odd_count << endl;
+ cout << "Number of EBUSYs: " << ebusy_count << endl;
+
+ }
+ catch(system_error& e) {
+ cerr << "got a system_error exception: " << e.what() << '\n';
+ auto ec = e.code();
+ cerr << "category: " << ec.category().name() << '\n';
+ cerr << "value: " << ec.value() << '\n';
+ cerr << "message: " << ec.message() << '\n';
+ cerr << "\nNote: if g++ may need '-pthread' or similar in "
+ "compile/link line" << '\n';
+ }
+ catch(...) {
+ cerr << "got another exception: " << '\n';
+ }
+ return 0;
+}
diff --git a/testing/sg_tst_excl3.cpp b/testing/sg_tst_excl3.cpp
new file mode 100644
index 00000000..b1cbf130
--- /dev/null
+++ b/testing/sg_tst_excl3.cpp
@@ -0,0 +1,561 @@
+/*
+ * Copyright (c) 2013-2022 Douglas Gilbert.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <iostream>
+#include <vector>
+#include <system_error>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+
+static const char * version_str = "1.11 20220425";
+static const char * util_name = "sg_tst_excl3";
+
+/* This is a test program for checking O_EXCL on open() works. It uses
+ * multiple threads and can be run as multiple processes and attempts
+ * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK
+ * and do a double increment on a LB then close it from a single thread.
+ * the remaining threads open that device O_NONBLOCK and do a read and
+ * note if the number is odd. Assuming the count starts as an even
+ * (typically 0) then it should remain even. Odd instances
+ * are counted and reported at the end of the program, after all threads
+ * have completed.
+ *
+ * This is C++ code with some things from C++11 (e.g. threads) and was
+ * only just able to compile (when some things were reverted) with gcc/g++
+ * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support
+ * was not available until g++ version 4.8.1 and that is found in Fedora
+ * 19 and Ubuntu 13.10 .
+ *
+ * The build uses various object files from the <sg3_utils>/lib directory
+ * which is assumed to be a sibling of this examples directory. Those
+ * object files in the lib directory can be built with:
+ * cd <sg3_utils> ; ./configure ; cd lib; make
+ * Then:
+ * cd ../testing
+ * make sg_tst_excl3
+ *
+ * BEWARE: this utility modifies a logical block (default LBA 1000) on the
+ * given device.
+ *
+ */
+
+using namespace std;
+using namespace std::chrono;
+
+#define DEF_NUM_PER_THREAD 200
+#define DEF_NUM_THREADS 4
+#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */
+
+#define DEF_LBA 1000
+
+#define EBUFF_SZ 256
+
+
+static mutex odd_count_mutex;
+static mutex console_mutex;
+static unsigned int odd_count;
+static unsigned int ebusy_count;
+
+
+static void
+usage(void)
+{
+ printf("Usage: %s [-b] [-f] [-h] [-l <lba>] [-n <n_per_thr>]\n"
+ " [-R] [-t <num_thrs>] [-V] [-w <wait_ms>] "
+ "[-x]\n"
+ " <disk_device>\n", util_name);
+ printf(" where\n");
+ printf(" -b block on open (def: O_NONBLOCK)\n");
+ printf(" -f force: any SCSI disk (def: only "
+ "scsi_debug)\n");
+ printf(" WARNING: <lba> written to\n");
+ printf(" -h print this usage message then exit\n");
+ printf(" -l <lba> logical block to increment (def: %u)\n",
+ DEF_LBA);
+ printf(" -n <n_per_thr> number of loops per thread "
+ "(def: %d)\n", DEF_NUM_PER_THREAD);
+ printf(" -R all readers; so first thread (id=0) "
+ "just reads\n");
+ printf(" -t <num_thrs> number of threads (def: %d)\n",
+ DEF_NUM_THREADS);
+ printf(" -V print version number then exit\n");
+ printf(" -w <wait_ms> >0: sleep_for(<wait_ms>); =0: "
+ "yield(); -1: no\n"
+ " wait; -2: sleep(0) (def: %d)\n",
+ DEF_WAIT_MS);
+ printf(" -x don't use O_EXCL on first thread "
+ "(def: use\n"
+ " O_EXCL on first thread)\n\n");
+ printf("Test O_EXCL open flag with pass-through drivers. First thread "
+ "(id=0) does\nopen/close cycle with the O_EXCL flag then does a "
+ "double increment on\nlba (using its first 4 bytes). Remaining "
+ "theads read (without\nO_EXCL flag on open) and check the "
+ "value is even.\n");
+}
+
+/* Assumed a lock (mutex) held when pt_err() is called */
+static int
+pt_err(int res)
+{
+ if (res < 0)
+ fprintf(stderr, " pass through os error: %s\n", safe_strerror(-res));
+ else if (SCSI_PT_DO_BAD_PARAMS == res)
+ fprintf(stderr, " bad pass through setup\n");
+ else if (SCSI_PT_DO_TIMEOUT == res)
+ fprintf(stderr, " pass through timeout\n");
+ else
+ fprintf(stderr, " do_scsi_pt error=%d\n", res);
+ return -1;
+}
+
+/* Assumed a lock (mutex) held when pt_cat_no_good() is called */
+static int
+pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp)
+{
+ int slen;
+ char b[256];
+ const int bl = (int)sizeof(b);
+
+ switch (cat) {
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b);
+ fprintf(stderr, " scsi status: %s\n", b);
+ break;
+ case SCSI_PT_RESULT_SENSE:
+ slen = get_scsi_pt_sense_len(ptp);
+ sg_get_sense_str("", sbp, slen, 1, bl, b);
+ fprintf(stderr, "%s", b);
+ break;
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ get_scsi_pt_transport_err_str(ptp, bl, b);
+ fprintf(stderr, " transport: %s", b);
+ break;
+ case SCSI_PT_RESULT_OS_ERR:
+ get_scsi_pt_os_err_str(ptp, bl, b);
+ fprintf(stderr, " os: %s", b);
+ break;
+ default:
+ fprintf(stderr, " unknown pt result category (%d)\n", cat);
+ break;
+ }
+ return -1;
+}
+
+#define READ16_REPLY_LEN 512
+#define READ16_CMD_LEN 16
+#define WRITE16_REPLY_LEN 512
+#define WRITE16_CMD_LEN 16
+
+/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for
+ * wait_ms milliseconds if wait_ms is positive. Reads lba and treats the
+ * first 4 bytes as an int (SCSI endian), increments it and writes it back.
+ * Repeats so that happens twice. Then closes dev_name. If an error occurs
+ * returns -1 else returns 0 if first int read is even otherwise returns 1. */
+static int
+do_rd_inc_wr_twice(const char * dev_name, int read_only, unsigned int lba,
+ int block, int excl, int wait_ms, unsigned int & ebusys)
+{
+ int k, sg_fd, res, cat;
+ int odd = 0;
+ unsigned int u = 0;
+ struct sg_pt_base * ptp = NULL;
+ unsigned char r16CmdBlk [READ16_CMD_LEN] =
+ {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char w16CmdBlk [WRITE16_CMD_LEN] =
+ {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ unsigned char lb[READ16_REPLY_LEN];
+ char ebuff[EBUFF_SZ];
+ int open_flags = O_RDWR;
+
+ sg_put_unaligned_be64(lba, r16CmdBlk + 2);
+ sg_put_unaligned_be64(lba, w16CmdBlk + 2);
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ if (excl)
+ open_flags |= O_EXCL;
+
+ while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) &&
+ (-EBUSY == sg_fd)) {
+ ++ebusys;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "do_rd_inc_wr_twice: error opening file: %s", dev_name);
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ perror(ebuff);
+ }
+ return -1;
+ }
+
+ ptp = construct_scsi_pt_obj();
+ for (k = 0; k < 2; ++k) {
+ /* Prepare READ_16 command */
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, r16CmdBlk, sizeof(r16CmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_in(ptp, lb, READ16_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "READ_16 do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ }
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "READ_16 do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ }
+ goto err;
+ }
+
+ u = sg_get_unaligned_be32(lb);
+ // Assuming u starts test as even (probably 0), expect it to stay even
+ if (0 == k)
+ odd = (1 == (u % 2));
+
+ if (wait_ms > 0) /* allow daylight for bad things ... */
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+
+ if (read_only)
+ break;
+ ++u;
+ sg_put_unaligned_be32(u, lb);
+
+ /* Prepare WRITE_16 command */
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, w16CmdBlk, sizeof(w16CmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_out(ptp, lb, WRITE16_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "WRITE_16 do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ }
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ fprintf(stderr, "WRITE_16 do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ }
+ goto err;
+ }
+ }
+err:
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ scsi_pt_close_device(sg_fd);
+ return odd;
+}
+
+
+#define INQ_REPLY_LEN 96
+#define INQ_CMD_LEN 6
+
+/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field
+ * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success,
+ * else -1 . */
+static int
+do_inquiry_prod_id(const char * dev_name, int block, int wait_ms,
+ unsigned int & ebusys, char * b, int b_mlen)
+{
+ int sg_fd, res, cat;
+ struct sg_pt_base * ptp = NULL;
+ unsigned char inqCmdBlk [INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ unsigned char inqBuff[INQ_REPLY_LEN];
+ unsigned char sense_buffer[64] SG_C_CPP_ZERO_INIT;
+ char ebuff[EBUFF_SZ];
+ int open_flags = O_RDWR; /* since O_EXCL | O_RDONLY gives EPERM */
+
+ if (! block)
+ open_flags |= O_NONBLOCK;
+ while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) &&
+ (-EBUSY == sg_fd)) {
+ ++ebusys;
+ if (wait_ms > 0)
+ this_thread::sleep_for(milliseconds{wait_ms});
+ else if (0 == wait_ms)
+ this_thread::yield(); // thread yield
+ else if (-2 == wait_ms)
+ sleep(0); // process yield ??
+ }
+ if (sg_fd < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "do_inquiry_prod_id: error opening file: %s", dev_name);
+ perror(ebuff);
+ return -1;
+ }
+ /* Prepare INQUIRY command */
+ ptp = construct_scsi_pt_obj();
+ clear_scsi_pt_obj(ptp);
+ set_scsi_pt_cdb(ptp, inqCmdBlk, sizeof(inqCmdBlk));
+ set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer));
+ set_scsi_pt_data_in(ptp, inqBuff, INQ_REPLY_LEN);
+ res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1);
+ if (res) {
+ fprintf(stderr, "INQUIRY do_scsi_pt() submission error\n");
+ res = pt_err(res);
+ goto err;
+ }
+ cat = get_scsi_pt_result_category(ptp);
+ if (SCSI_PT_RESULT_GOOD != cat) {
+ fprintf(stderr, "INQUIRY do_scsi_pt() category problem\n");
+ res = pt_cat_no_good(cat, ptp, sense_buffer);
+ goto err;
+ }
+
+ /* Good, so fetch Product ID from response, copy to 'b' */
+ if (b_mlen > 0) {
+ if (b_mlen > 16) {
+ memcpy(b, inqBuff + 16, 16);
+ b[16] = '\0';
+ } else {
+ memcpy(b, inqBuff + 16, b_mlen - 1);
+ b[b_mlen - 1] = '\0';
+ }
+ }
+err:
+ if (ptp)
+ destruct_scsi_pt_obj(ptp);
+ close(sg_fd);
+ return res;
+}
+
+static void
+work_thread(const char * dev_name, unsigned int lba, int id, int block,
+ int excl, bool all_readers, int num, int wait_ms)
+{
+ unsigned int thr_odd_count = 0;
+ unsigned int thr_ebusy_count = 0;
+ int k, res;
+ int reader = ((id > 0) || (all_readers));
+
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ cerr << "Enter work_thread id=" << id << " excl=" << excl << " block="
+ << block << " reader=" << reader << endl;
+ }
+ for (k = 0; k < num; ++k) {
+ res = do_rd_inc_wr_twice(dev_name, reader, lba, block, excl,
+ wait_ms, thr_ebusy_count);
+ if (res < 0)
+ break;
+ if (res)
+ ++thr_odd_count;
+ }
+ {
+ lock_guard<mutex> lg(console_mutex);
+
+ if (k < num)
+ cerr << "thread id=" << id << " FAILed at iteration: " << k
+ << '\n';
+ else
+ cerr << "thread id=" << id << " normal exit" << '\n';
+ }
+
+ {
+ lock_guard<mutex> lg(odd_count_mutex);
+
+ odd_count += thr_odd_count;
+ ebusy_count += thr_ebusy_count;
+ }
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ int k, res;
+ int block = 0;
+ int force = 0;
+ unsigned int lba = DEF_LBA;
+ int num_per_thread = DEF_NUM_PER_THREAD;
+ bool all_readers = false;
+ int num_threads = DEF_NUM_THREADS;
+ int wait_ms = DEF_WAIT_MS;
+ int exclude_o_excl = 0;
+ char * dev_name = NULL;
+ char b[64];
+
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-b", argv[k], 2))
+ ++block;
+ else if (0 == memcmp("-f", argv[k], 2))
+ ++force;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ usage();
+ return 0;
+ } else if (0 == memcmp("-l", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ lba = (unsigned int)atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-n", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_per_thread = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-t", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && isdigit(*argv[k]))
+ num_threads = atoi(argv[k]);
+ else
+ break;
+ } else if (0 == memcmp("-R", argv[k], 2))
+ all_readers = true;
+ else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s version: %s\n", util_name, version_str);
+ return 0;
+ } else if (0 == memcmp("-w", argv[k], 2)) {
+ ++k;
+ if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) {
+ if ('-' == *argv[k])
+ wait_ms = - atoi(argv[k] + 1);
+ else
+ wait_ms = atoi(argv[k]);
+ } else
+ break;
+ } else if (0 == memcmp("-x", argv[k], 2))
+ ++exclude_o_excl;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ dev_name = NULL;
+ break;
+ }
+ else if (! dev_name)
+ dev_name = argv[k];
+ else {
+ printf("too many arguments\n");
+ dev_name = 0;
+ break;
+ }
+ }
+ if (0 == dev_name) {
+ usage();
+ return 1;
+ }
+ try {
+
+ if (! force) {
+ res = do_inquiry_prod_id(dev_name, block, wait_ms, ebusy_count,
+ b, sizeof(b));
+ if (res) {
+ fprintf(stderr, "INQUIRY failed on %s\n", dev_name);
+ return 1;
+ }
+ // For safety, since <lba> written to, only permit scsi_debug
+ // devices. Bypass this with '-f' option.
+ if (0 != memcmp("scsi_debug", b, 10)) {
+ fprintf(stderr, "Since this utility writes to LBA %d, only "
+ "devices with scsi_debug\nproduct ID accepted.\n",
+ lba);
+ return 2;
+ }
+ }
+
+ vector<thread *> vt;
+
+ for (k = 0; k < num_threads; ++k) {
+ int excl = ((0 == k) && (! exclude_o_excl)) ? 1 : 0;
+
+ thread * tp = new thread {work_thread, dev_name, lba, k, block,
+ excl, all_readers, num_per_thread,
+ wait_ms};
+ vt.push_back(tp);
+ }
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ vt[k]->join();
+
+ for (k = 0; k < (int)vt.size(); ++k)
+ delete vt[k];
+
+ cout << "Expecting odd count of 0, got " << odd_count << endl;
+ cout << "Number of EBUSYs: " << ebusy_count << endl;
+
+ }
+ catch(system_error& e) {
+ cerr << "got a system_error exception: " << e.what() << '\n';
+ auto ec = e.code();
+ cerr << "category: " << ec.category().name() << '\n';
+ cerr << "value: " << ec.value() << '\n';
+ cerr << "message: " << ec.message() << '\n';
+ cerr << "\nNote: if g++ may need '-pthread' or similar in "
+ "compile/link line" << '\n';
+ }
+ catch(...) {
+ cerr << "got another exception: " << '\n';
+ }
+ return 0;
+}
diff --git a/testing/sg_tst_ioctl.c b/testing/sg_tst_ioctl.c
new file mode 100644
index 00000000..ade56a1d
--- /dev/null
+++ b/testing/sg_tst_ioctl.c
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (C) 2018-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Invocation: See usage() function below.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#include <sys/socket.h> /* For passing fd_s via Unix sockets */
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_linux_inc.h"
+#include "sg_pr2serr.h"
+
+/* This program tests ioctl() calls added and modified in version 4.0 and
+ * later of the Linux sg driver. */
+
+
+static const char * version_str = "Version: 1.21 20220202";
+
+#define INQ_REPLY_LEN 128
+#define INQ_CMD_LEN 6
+#define SDIAG_CMD_LEN 6
+#define SENSE_BUFFER_LEN 96
+
+#define EBUFF_SZ 512
+
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+#define DEF_Q_LEN 16 /* max in sg v3 and earlier */
+#define MAX_Q_LEN 512
+
+#define DEF_RESERVE_BUFF_SZ (256 * 1024)
+
+static bool create_time = false;
+static bool is_parent = false;
+static bool do_fork = false;
+static bool ioctl_only = false;
+static bool more_async = false;
+static bool no_duration = false;
+static bool q_at_tail = false;
+static bool write_only = false;
+static bool mrq_immed = false; /* if set, also sets mrq_iosubmit */
+static bool mrq_half_immed = false;
+static bool mrq_iosubmit = false;
+static bool show_size_value = false;
+static bool do_v3_only = false;
+
+static int childs_pid = 0;
+static int iterator_test = -1;
+static int object_walk_test = -1;
+static int sg_drv_ver_num = 0;
+static int q_len = DEF_Q_LEN;
+static int sleep_secs = 0;
+static int reserve_buff_sz = DEF_RESERVE_BUFF_SZ;
+static int num_mrqs = 0;
+static int num_sgnw = 0;
+static int dname_current = 0;
+static int dname_last = 0;
+static int dname_pos = 0;
+static int verbose = 0;
+
+static const char * relative_cp = "";
+static char * file_name = NULL;
+
+
+static void
+usage(void)
+{
+ printf("Usage: sg_tst_ioctl [-3] [-c] [-f] [-h] [-I=0|1] [-J=0|1] "
+ "[-l=Q_LEN]\n"
+ " [-m=MRQS[,I|S]] [-M] [-n] [-o] [-r=SZ] "
+ "[-s=SEC]\n"
+ " [-S] [-t] [-T=NUM] [-v] [-V] [-w]\n"
+ " <sg_device>[-<num>] [<sg_device2>]\n"
+ " where:\n"
+ " -3 use sg v3 interface (def: sg v4 if available)\n"
+ " -c timestamp when sg driver created <sg_device>\n"
+ " -f fork and test share between processes\n"
+ " -h help: print usage message then exit\n"
+ " -I=0|1 iterator test of mid-level; 0: unlocked, 1: "
+ "locked\n"
+ " does test -T=NUM times, outputs duration\n"
+ " -J=0|1 object walk up then 2 lookups; 0: no logging; "
+ "1: log\n"
+ " up-scan once per 1000 iterations\n"
+ " -l=Q_LEN queue length, between 1 and 511 (def: 16)\n"
+ " -m=MRQS[,I|S] test multi-req, MRQS number to do; if "
+ "the letter\n"
+ " 'I' is appended after a comma, then do "
+ "IMMED mrq;\n"
+ " 'i' IMMED on submission, non-IMMED on "
+ "receive;\n"
+ " 'S' is appended, then use "
+ "ioctl(SG_IOSUBMIT)\n"
+ " -M set 'more async' flag\n"
+ " -n do not calculate per command duration (def: do)\n"
+ " -o ioctls only, then exit\n"
+ " -r=SZ reserve buffer size in KB (def: 256 --> 256 "
+ "KB)\n"
+ " -s=SEC sleep between writes and reads (def: 0)\n"
+ " -S size of interface structures plus ioctl "
+ "values\n"
+ " -t queue_at_tail (def: q_at_head)\n"
+ " -T=NUM time overhead of NUM invocations of\n"
+ " ioctl(SG_GET_NUM_WAITING); then exit\n"
+ " -v increase verbosity of output\n"
+ " -V print version string then exit\n"
+ " -w write (submit) only then exit\n\n");
+ printf("There are various groups of options for different tests. The "
+ "get_num_waiting\ngroup needs '-T=NUM' given. When '-I=0|1' is "
+ "also given then an object tree\niterator test is done NUM "
+ "times. If instead '-J=0|1' is given then an\nobject tree "
+ "traversal (up/down) is done 10,000 times (and NUM is\n"
+ "ignored).\n"
+ );
+}
+
+static void
+timespec_add(const struct timespec *lhs_p, const struct timespec *rhs_p,
+ struct timespec *res_p)
+{
+ if ((lhs_p->tv_nsec + rhs_p->tv_nsec) > 1000000000L) {
+ res_p->tv_sec = lhs_p->tv_sec + rhs_p->tv_sec + 1;
+ res_p->tv_nsec = lhs_p->tv_nsec + rhs_p->tv_nsec - 1000000000L;
+ } else {
+ res_p->tv_sec = lhs_p->tv_sec + rhs_p->tv_sec;
+ res_p->tv_nsec = lhs_p->tv_nsec + rhs_p->tv_nsec;
+ }
+}
+
+static void
+timespec_diff(const struct timespec *lhs_p, const struct timespec *rhs_p,
+ struct timespec *res_p)
+{
+ if ((lhs_p->tv_nsec - rhs_p->tv_nsec) < 0) {
+ res_p->tv_sec = lhs_p->tv_sec - rhs_p->tv_sec - 1;
+ res_p->tv_nsec = lhs_p->tv_nsec - rhs_p->tv_nsec + 1000000000L;
+ } else {
+ res_p->tv_sec = lhs_p->tv_sec - rhs_p->tv_sec;
+ res_p->tv_nsec = lhs_p->tv_nsec - rhs_p->tv_nsec;
+ }
+}
+
+/* Returns 0 on success. */
+int timespec2str(char *buf, unsigned int len, struct timespec *ts)
+{
+ int ret;
+ struct tm t;
+
+ tzset();
+ if (localtime_r(&(ts->tv_sec), &t) == NULL)
+ return 1;
+
+ ret = strftime(buf, len, "%F %T", &t);
+ if (ret == 0)
+ return 2;
+ len -= ret - 1;
+
+ ret = snprintf(&buf[strlen(buf)], len, ".%09ld", ts->tv_nsec);
+ if (ret >= (int)len)
+ return 3;
+ return 0;
+}
+
+/* This function taken from Keith Parkard's blog dated 20121005 */
+static ssize_t
+sock_fd_write(int sock, const void *buf, ssize_t buflen, int fd)
+{
+ ssize_t size;
+ struct msghdr msg;
+ struct iovec iov;
+ union {
+ struct cmsghdr cmsghdr;
+ char control[CMSG_SPACE(sizeof (int))];
+ } cmsgu;
+ struct cmsghdr *cmsg;
+
+ iov.iov_base = (void *)buf; /* OS shouldn't write back in this */
+ iov.iov_len = buflen;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (fd != -1) {
+ msg.msg_control = cmsgu.control;
+ msg.msg_controllen = sizeof(cmsgu.control);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof (int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+
+ printf ("passing fd %d\n", fd);
+ *((int *) CMSG_DATA(cmsg)) = fd;
+ } else {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ printf ("not passing fd\n");
+ }
+
+ size = sendmsg(sock, &msg, 0);
+
+ if (size < 0)
+ perror ("sendmsg");
+ return size;
+}
+
+/* This function taken from Keith Parkard's blog dated 2101205 */
+static ssize_t
+sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd)
+{
+ ssize_t size;
+
+ if (fd) {
+ struct msghdr msg;
+ struct iovec iov;
+ union {
+ struct cmsghdr cmsghdr;
+ char control[CMSG_SPACE(sizeof (int))];
+ } cmsgu;
+ struct cmsghdr *cmsg;
+
+ iov.iov_base = buf;
+ iov.iov_len = bufsize;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgu.control;
+ msg.msg_controllen = sizeof(cmsgu.control);
+ size = recvmsg (sock, &msg, 0);
+ if (size < 0) {
+ perror ("recvmsg");
+ exit(1);
+ }
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+ if (cmsg->cmsg_level != SOL_SOCKET) {
+ fprintf (stderr, "invalid cmsg_level %d\n",
+ cmsg->cmsg_level);
+ exit(1);
+ }
+ if (cmsg->cmsg_type != SCM_RIGHTS) {
+ fprintf (stderr, "invalid cmsg_type %d\n",
+ cmsg->cmsg_type);
+ exit(1);
+ }
+
+ *fd = *((int *) CMSG_DATA(cmsg));
+ printf ("received fd %d\n", *fd);
+ } else
+ *fd = -1;
+ } else {
+ size = read (sock, buf, bufsize);
+ if (size < 0) {
+ perror("read");
+ exit(1);
+ }
+ }
+ return size;
+}
+
+static void
+set_more_async(int fd, bool more_asy, bool no_dur)
+{
+ if (sg_drv_ver_num > 40030) {
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ if (more_asy) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC;
+ seip->ctl_flags = SG_CTL_FLAGM_MORE_ASYNC;
+ }
+ if (no_dur) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags = SG_CTL_FLAGM_NO_DURATION;
+ }
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED, MORE_ASYNC(|NO_DUR)) failed, "
+ "errno=%d %s\n", errno, strerror(errno));
+ return;
+ }
+ } else
+ pr2serr("sg driver too old for ioctl(SG_SET_GET_EXTENDED)\n");
+}
+
+static void
+pr_create_dev_time(int sg_fd, const char * dev_name)
+{
+ uint32_t u;
+ uint64_t l;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+ struct timespec time_up, realtime, boottime, createtime, tmp;
+ char b[64];
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_DEV_TS_LOWER;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("%s: ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n",
+ __func__, errno, strerror(errno));
+ return;
+ }
+ u = seip->read_value;
+ seip->read_value = SG_SEIRV_DEV_TS_UPPER;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("%s: ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n",
+ __func__, errno, strerror(errno));
+ return;
+ }
+ l = seip->read_value;
+ l <<= 32;
+ l |= u;
+ time_up.tv_sec = l / 1000000000UL;
+ time_up.tv_nsec = l % 1000000000UL;
+ /* printf("create time nanoseconds=%" PRIu64 "\n", l); */
+ if (clock_gettime(CLOCK_REALTIME, &realtime) < 0) {
+ pr2serr("%s: clock_gettime(CLOCK_REALTIME) failed, errno=%d %s\n",
+ __func__, errno, strerror(errno));
+ return;
+ }
+ if (clock_gettime(CLOCK_BOOTTIME, &boottime) < 0) {
+ pr2serr("%s: clock_gettime(CLOCK_REALTIME) failed, errno=%d %s\n",
+ __func__, errno, strerror(errno));
+ return;
+ }
+ timespec_diff(&realtime, &boottime, &tmp);
+ timespec_add(&tmp, &time_up, &createtime);
+#if 0
+ printf("real time: %ld,%ld\n", realtime.tv_sec, realtime.tv_nsec);
+ printf("boot time: %ld,%ld\n", boottime.tv_sec, boottime.tv_nsec);
+ printf("time up: %ld,%ld\n", time_up.tv_sec, time_up.tv_nsec);
+ printf("create time: %ld,%ld\n", createtime.tv_sec, createtime.tv_nsec);
+#endif
+ timespec2str(b, sizeof(b), &createtime);
+ printf("Create time of %s was %s\n", dev_name, b);
+}
+
+static int
+tst_extended_ioctl(const char * fnp, int sg_fd, const char * fn2p, int sg_fd2,
+ int sock, const char * cp)
+{
+ uint32_t cflags;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_RESERVED_SIZE;
+ seip->reserved_sz = reserve_buff_sz;
+ seip->sgat_elem_sz = 64 * 1024;
+ seip->sei_rd_mask |= SG_SEIM_RESERVED_SIZE;
+ seip->sei_rd_mask |= SG_SEIM_TOT_FD_THRESH;
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS; /* this or previous optional */
+ seip->sei_rd_mask |= SG_SEIM_MINOR_INDEX;
+ seip->sei_wr_mask |= SG_SEIM_SGAT_ELEM_SZ;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_OTHER_OPENS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_ORPHANS;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_Q_TAIL;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_IS_SHARE;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_IS_READ_SIDE;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_UNSHARE;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_READ_SIDE_FINI;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_READ_SIDE_ERR;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags_rd_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_NO_DURATION;
+
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+#if 1
+ printf("%sSG_SET_GET_EXTENDED ioctl ok\n", cp);
+ if (SG_SEIM_RESERVED_SIZE & seip->sei_rd_mask)
+ printf(" %sreserved size: %u\n", cp, seip->reserved_sz);
+ if (SG_SEIM_MINOR_INDEX & seip->sei_rd_mask)
+ printf(" %sminor index: %u\n", cp, seip->minor_index);
+ if (SG_SEIM_TOT_FD_THRESH & seip->sei_rd_mask)
+ printf(" %stot_fd_thresh: %u\n", cp, seip->tot_fd_thresh);
+ if ((SG_SEIM_CTL_FLAGS & seip->sei_rd_mask) ||
+ (SG_SEIM_CTL_FLAGS & seip->sei_wr_mask)) {
+ cflags = seip->ctl_flags;
+ if (SG_CTL_FLAGM_TIME_IN_NS & seip->ctl_flags_rd_mask)
+ printf(" %sTIME_IN_NS: %s\n", cp,
+ (SG_CTL_FLAGM_TIME_IN_NS & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_OTHER_OPENS & seip->ctl_flags_rd_mask)
+ printf(" %sOTHER_OPENS: %s\n", cp,
+ (SG_CTL_FLAGM_OTHER_OPENS & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_ORPHANS & seip->ctl_flags_rd_mask)
+ printf(" %sORPHANS: %s\n", cp,
+ (SG_CTL_FLAGM_ORPHANS & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_Q_TAIL & seip->ctl_flags_rd_mask)
+ printf(" %sQ_TAIL: %s\n", cp,
+ (SG_CTL_FLAGM_Q_TAIL & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_IS_SHARE & seip->ctl_flags_rd_mask)
+ printf(" %sIS_SHARE: %s\n", cp,
+ (SG_CTL_FLAGM_IS_SHARE & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_IS_READ_SIDE & seip->ctl_flags_rd_mask)
+ printf(" %sIS_READ_SIDE: %s\n", cp,
+ (SG_CTL_FLAGM_IS_READ_SIDE & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_UNSHARE & seip->ctl_flags_rd_mask)
+ printf(" %sUNSHARE: %s\n", cp,
+ (SG_CTL_FLAGM_UNSHARE & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_READ_SIDE_FINI & seip->ctl_flags_rd_mask)
+ printf(" %sREAD_SIDE_FINI: %s\n", cp,
+ (SG_CTL_FLAGM_READ_SIDE_FINI & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_READ_SIDE_ERR & seip->ctl_flags_rd_mask)
+ printf(" %sREAD_SIDE_ERR: %s\n", cp,
+ (SG_CTL_FLAGM_READ_SIDE_ERR & cflags) ? "true" : "false");
+ if (SG_CTL_FLAGM_NO_DURATION & seip->ctl_flags_rd_mask)
+ printf(" %sNO_DURATION: %s\n", cp,
+ (SG_CTL_FLAGM_NO_DURATION & cflags) ? "true" : "false");
+ }
+ if (SG_SEIM_MINOR_INDEX & seip->sei_rd_mask)
+ printf(" %sminor_index: %u\n", cp, seip->minor_index);
+ printf("\n");
+#endif
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_INT_MASK;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_INT_MASK]= %u\n", cp, seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_BOOL_MASK;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_BOOL_MASK]= %u\n", cp, seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_VERS_NUM;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_VERS_NUM]= %u\n", cp, seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_INACT_RQS;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_INACT_RQS]= %u\n", cp, seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_DEV_INACT_RQS;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_DEV_INACT_RQS]= %u\n", cp,
+ seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_SUBMITTED;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_SUBMITTED]= %u\n", cp, seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_READ_VAL;
+ seip->sei_rd_mask |= SG_SEIM_READ_VAL;
+ seip->read_value = SG_SEIRV_DEV_SUBMITTED;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(SG_SET_GET_EXTENDED) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ return 1;
+ }
+ printf(" %sread_value[SG_SEIRV_DEV_SUBMITTED]= %u\n", cp,
+ seip->read_value);
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_SHARE_FD;
+#if 1
+ seip->share_fd = sg_fd2;
+#else
+ seip->share_fd = sg_fd;
+#endif
+ if (do_fork && is_parent)
+ goto bypass_share;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("%sioctl(SG_SET_GET_EXTENDED) shared_fd=%d, failed errno=%d "
+ "%s\n", cp, sg_fd2, errno, strerror(errno));
+ }
+ printf(" %sshare successful, read back previous shared_fd= %d\n", cp,
+ (int)seip->share_fd);
+bypass_share:
+
+ if (ioctl(sg_fd, SG_GET_TRANSFORM, NULL) < 0)
+ pr2serr("ioctl(SG_GET_TRANSFORM) fail expected, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("%sSG_GET_TRANSFORM okay (does nothing)\n", cp);
+ if (ioctl(sg_fd, SG_SET_TRANSFORM, NULL) < 0)
+ pr2serr("ioctl(SG_SET_TRANSFORM) fail expected, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("%sSG_SET_TRANSFORM okay (does nothing)\n", cp);
+ printf("\n");
+
+ /* test sending a sg file descriptor between 2 processes using UNIX
+ * sockets */
+ if (do_fork && is_parent && fnp && (sock >= 0)) { /* master/READ side */
+ int res;
+ int fd_ma = open(fnp, O_RDWR);
+
+ if (fd_ma < 0) {
+ pr2serr("%s: opening %s failed: %s\n", __func__, fnp,
+ strerror(errno));
+ return 1;
+ }
+ res = sock_fd_write(sock, "boo", 4, fd_ma);
+ if (res < 0)
+ pr2serr("%s: sock_fd_write() failed\n", __func__);
+ else
+ printf("%s: sock_fd_write() returned: %d\n", __func__, res);
+ } else if (do_fork && !is_parent && fn2p && (sock >= 0)) {
+ int res, fd_ma;
+ /* int fd_sl = open(fn2p, O_RDWR); not needed */
+ uint8_t b[32];
+
+ fd_ma = -1;
+ res = sock_fd_read(sock, b, sizeof(b), &fd_ma);
+ if (res < 0)
+ pr2serr("%s: sock_fd_read() failed\n", __func__);
+ else
+ printf("%s: sock_fd_read() returned: %d, fd_ma=%d\n", __func__,
+ res, fd_ma);
+ /* yes it works! */
+ }
+ return 0;
+}
+
+static int
+do_mrqs(int sg_fd, int sg_fd2, int mrqs)
+{
+ bool both = (sg_fd2 >= 0);
+ int k, j, arr_v4_sz, good;
+ int res = 0;
+ struct sg_io_v4 * arr_v4;
+ struct sg_io_v4 * h4p;
+ struct sg_io_v4 * mrq_h4p;
+ struct sg_io_v4 mrq_h4;
+ uint8_t sense_buffer[SENSE_BUFFER_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t inq_cdb[INQ_CMD_LEN] = /* Device Id VPD page */
+ {0x12, 0x1, 0x83, 0, INQ_REPLY_LEN, 0};
+ uint8_t sdiag_cdb[SDIAG_CMD_LEN] =
+ {0x1d, 0x10 /* PF */, 0, 0, 0, 0};
+ uint8_t inqBuff[INQ_REPLY_LEN];
+
+ if (both) {
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_SHARE_FD;
+ seip->share_fd = sg_fd; /* master */
+ if (ioctl(sg_fd2, SG_SET_GET_EXTENDED, seip) < 0) {
+ res = errno;
+ pr2serr("ioctl(sg_fd2, SG_SET_GET_EXTENDED) shared_fd, "
+ "failed errno=%d %s\n", res, strerror(res));
+ return res;
+ }
+ }
+ memset(inqBuff, 0, sizeof(inqBuff));
+ mrq_h4p = &mrq_h4;
+ memset(mrq_h4p, 0, sizeof(*mrq_h4p));
+ mrq_h4p->guard = 'Q';
+ mrq_h4p->flags = SGV4_FLAG_MULTIPLE_REQS;
+ if (mrq_immed)
+ mrq_h4p->flags |= SGV4_FLAG_IMMED;
+ arr_v4 = (struct sg_io_v4 *)calloc(mrqs, sizeof(struct sg_io_v4));
+ if (NULL == arr_v4) {
+ res = ENOMEM;
+ goto fini;
+ }
+ arr_v4_sz = mrqs * sizeof(struct sg_io_v4);
+
+ for (k = 0; k < mrqs; ++k) {
+ h4p = arr_v4 + k;
+
+ h4p->guard = 'Q';
+ /* ->protocol and ->subprotocol are already zero */
+ /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */
+ if (0 == (k % 2)) {
+ h4p->request_len = sizeof(sdiag_cdb);
+ h4p->request = (uint64_t)(uintptr_t)sdiag_cdb;
+ /* all din and dout fields are zero */
+ } else {
+ h4p->request_len = sizeof(inq_cdb);
+ h4p->request = (uint64_t)(uintptr_t)inq_cdb;
+ h4p->din_xfer_len = INQ_REPLY_LEN;
+ h4p->din_xferp = (uint64_t)(uintptr_t)inqBuff;
+ if (both)
+ h4p->flags |= SGV4_FLAG_DO_ON_OTHER;
+ }
+ h4p->response = (uint64_t)(uintptr_t)sense_buffer;
+ h4p->max_response_len = sizeof(sense_buffer);
+ h4p->timeout = 20000; /* 20000 millisecs == 20 seconds */
+ h4p->request_extra = k + 3; /* so pack_id doesn't start at 0 */
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ h4p->flags |= SG_FLAG_Q_AT_TAIL;
+ else
+ h4p->flags |= SG_FLAG_Q_AT_HEAD;
+ }
+ mrq_h4p->dout_xferp = (uint64_t)(uintptr_t)arr_v4;
+ mrq_h4p->dout_xfer_len = arr_v4_sz;
+ mrq_h4p->din_xferp = mrq_h4p->dout_xferp;
+ mrq_h4p->din_xfer_len = mrq_h4p->dout_xfer_len;
+ if (ioctl(sg_fd, (mrq_iosubmit ? SG_IOSUBMIT : SG_IO), mrq_h4p) < 0) {
+ res = errno;
+ pr2serr("ioctl(SG_IO%s, mrq) failed, errno=%d %s\n",
+ (mrq_iosubmit ? "SUBMIT" : ""), res, strerror(res));
+ goto fini;
+ }
+ if ((mrq_h4p->dout_resid > 0) || ((int)mrq_h4p->info < mrqs))
+ pr2serr("ioctl(SG_IO%s, mrq) dout_resid=%d, info=%d\n\n",
+ (mrq_iosubmit ? "SUBMIT" : ""), mrq_h4p->dout_resid,
+ mrq_h4p->info);
+
+ good = 0;
+ j = 0;
+ if (mrq_immed) {
+receive_more:
+ if (mrq_half_immed)
+ mrq_h4p->flags = SGV4_FLAG_MULTIPLE_REQS; // zap SGV4_FLAG_IMMED
+ if (ioctl(sg_fd, SG_IORECEIVE, mrq_h4p) < 0) {
+ res = errno;
+ pr2serr("ioctl(SG_IORECEIVE, mrq) failed, errno=%d %s\n",
+ res, strerror(res));
+ goto fini;
+ }
+ if ((mrq_h4p->din_resid > 0) || ((int)mrq_h4p->info < mrqs))
+ pr2serr("ioctl(SG_IORECEIVE, mrq) din_resid=%d, info=%d\n",
+ mrq_h4p->din_resid, mrq_h4p->info);
+ }
+
+ for (k = 0; k < (int)mrq_h4p->info; ++k, ++j) {
+ h4p = arr_v4 + k;
+ if (! (h4p->driver_status || h4p->transport_status ||
+ h4p->device_status)) {
+ if (h4p->info & SG_INFO_MRQ_FINI)
+ ++good;
+ }
+ if ((! (h4p->info & SG_INFO_MRQ_FINI)) && (verbose > 1))
+ pr2serr("%s: k=%d: SG_INFO_MRQ_FINI not set on response\n",
+ __func__, k);
+ }
+ if (mrq_immed && (j < mrqs))
+ goto receive_more;
+
+ if (good > 0) {
+ printf("Final INQUIRY response:\n");
+ hex2stdout(inqBuff, INQ_REPLY_LEN, 0);
+ }
+ printf("Good responses: %d, bad responses: %d\n", good, mrqs - good);
+ if (mrq_h4p->driver_status != 0)
+ printf("Master mrq object: driver_status=%d\n",
+ mrq_h4p->driver_status);
+ h4p = arr_v4 + mrqs - 1;
+ if (h4p->driver_status != 0)
+ printf("Last mrq object: driver_status=%d\n", h4p->driver_status);
+
+fini:
+ if (arr_v4)
+ free(arr_v4);
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool done, is_first;
+ bool nw_given = false;
+ bool has_dname_range = false;
+ int k, ok, pack_id, num_waiting;
+ int res = 0;
+ int sum_nw = 0;
+ int sg_fd = -1;
+ int sg_fd2 = -1;
+ int sock = -1;
+ uint8_t inq_cdb[INQ_CMD_LEN] =
+ {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
+ uint8_t sdiag_cdb[SDIAG_CMD_LEN] =
+ {0x1d, 0x10 /* PF */, 0, 0, 0, 0};
+ uint8_t inqBuff[MAX_Q_LEN][INQ_REPLY_LEN];
+ sg_io_hdr_t io_hdr[MAX_Q_LEN];
+ sg_io_hdr_t rio_hdr;
+ char ebuff[EBUFF_SZ];
+ char dname[256];
+ uint8_t sense_buffer[MAX_Q_LEN][SENSE_BUFFER_LEN] SG_C_CPP_ZERO_INIT;
+ const char * second_fname = NULL;
+ const char * cp;
+ char * chp;
+ struct sg_scsi_id ssi;
+
+
+ if (sizeof(struct sg_extended_info) != 96)
+ pr2serr("Warning <<<< sizeof(struct sg_extended_info)=%zu not 96\n",
+ sizeof(struct sg_extended_info));
+ for (k = 1; k < argc; ++k) {
+ if (0 == memcmp("-3", argv[k], 2))
+ do_v3_only = true;
+ else if (0 == memcmp("-c", argv[k], 2))
+ create_time = true;
+ else if (0 == memcmp("-f", argv[k], 2))
+ do_fork = true;
+ else if (0 == memcmp("-h", argv[k], 2)) {
+ file_name = 0;
+ break;
+ } else if (0 == memcmp("-I=", argv[k], 3)) {
+ iterator_test = atoi(argv[k] + 3);
+ if ((iterator_test > 1) || (iterator_test < -1)) {
+ printf("Expect -I= to take a number, either 0 or 1\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-J=", argv[k], 3)) {
+ object_walk_test = atoi(argv[k] + 3);
+ if ((object_walk_test > 1) || (object_walk_test < -1)) {
+ printf("Expect -J= to take a number, either 0 or 1\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-l=", argv[k], 3)) {
+ q_len = atoi(argv[k] + 3);
+ if ((q_len > 511) || (q_len < 1)) {
+ printf("Expect -l= to take a number (q length) between 1 "
+ "and 511\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-m=", argv[k], 3)) {
+ num_mrqs = sg_get_num(argv[k] + 3);
+ if (num_mrqs < 1) {
+ printf("Expect -m= to take a number greater than 0\n");
+ file_name = 0;
+ break;
+ }
+ if ((cp = strchr(argv[k] + 3, ','))) {
+ mrq_iosubmit = true;
+ if (cp[1] == 'I')
+ mrq_immed = true;
+ else if (cp[1] == 'i') {
+ mrq_immed = true;
+ mrq_half_immed = true;
+ } else if (toupper(cp[1]) == 'S')
+ ;
+ else {
+ printf("-m= option expects 'A' or 'a' as a suffix, "
+ "after comma\n");
+ file_name = 0;
+ break;
+ }
+ }
+ } else if (0 == memcmp("-M", argv[k], 2))
+ more_async = true;
+ else if (0 == memcmp("-n", argv[k], 2))
+ no_duration = true;
+ else if (0 == memcmp("-o", argv[k], 2))
+ ioctl_only = true;
+ else if (0 == memcmp("-r=", argv[k], 3)) {
+ reserve_buff_sz = atoi(argv[k] + 3);
+ if (reserve_buff_sz < 0) {
+ printf("Expect -r= to take a number 0 or higher\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-s=", argv[k], 3)) {
+ sleep_secs = atoi(argv[k] + 3);
+ if (sleep_secs < 0) {
+ printf("Expect -s= to take a number 0 or higher\n");
+ file_name = 0;
+ break;
+ }
+ } else if (0 == memcmp("-S", argv[k], 2))
+ show_size_value = true;
+ else if (0 == memcmp("-t", argv[k], 2))
+ q_at_tail = true;
+ else if (0 == memcmp("-T=", argv[k], 3)) {
+ num_sgnw = sg_get_num(argv[k] + 3);
+ if (num_sgnw < 0) {
+ printf("Expect -T= to take a number >= 0\n");
+ file_name = 0;
+ break;
+ }
+ nw_given = true;
+ } else if (0 == memcmp("-vvvvvvv", argv[k], 8))
+ verbose += 7;
+ else if (0 == memcmp("-vvvvvv", argv[k], 7))
+ verbose += 6;
+ else if (0 == memcmp("-vvvvv", argv[k], 6))
+ verbose += 5;
+ else if (0 == memcmp("-vvvv", argv[k], 5))
+ verbose += 4;
+ else if (0 == memcmp("-vvv", argv[k], 4))
+ verbose += 3;
+ else if (0 == memcmp("-vv", argv[k], 3))
+ verbose += 2;
+ else if (0 == memcmp("-v", argv[k], 2))
+ verbose += 1;
+ else if (0 == memcmp("-V", argv[k], 2)) {
+ printf("%s\n", version_str);
+ return 0;
+ } else if (0 == memcmp("-w", argv[k], 2))
+ write_only = true;
+ else if (*argv[k] == '-') {
+ printf("Unrecognized switch: %s\n", argv[k]);
+ file_name = 0;
+ break;
+ }
+ else if (0 == file_name)
+ file_name = argv[k];
+ else if (NULL == second_fname)
+ second_fname = argv[k];
+ else {
+ printf("too many arguments\n");
+ file_name = 0;
+ break;
+ }
+ }
+ if ((iterator_test >= 0) || (object_walk_test >= 0))
+ nw_given = false;
+
+ if (show_size_value) {
+ struct utsname unam;
+
+ printf("Size in bytes:\n");
+ printf("\t%zu\tsizeof(struct sg_header) Version 2 interface "
+ "structure\n", sizeof(struct sg_header));
+ printf("\t%zu\tsizeof(struct sg_io_hdr) Version 3 interface "
+ "structure\n", sizeof(struct sg_io_hdr));
+ printf("\t%zu\tsizeof(struct sg_io_v4) Version 4 interface "
+ "structure\n", sizeof(struct sg_io_v4));
+ printf("\t%zu\tsizeof(struct sg_iovec) scatter gather element\n",
+ sizeof(struct sg_iovec));
+ printf("\t%zu\tsizeof(struct sg_scsi_id) topological device id\n",
+ sizeof(struct sg_scsi_id));
+ printf("\t%zu\tsizeof(struct sg_req_info) request information\n",
+ sizeof(struct sg_req_info));
+ printf("\t%zu\tsizeof(struct sg_extended_info) for "
+ "SG_SET_GET_EXTENDED\n",
+ sizeof(struct sg_extended_info));
+ printf("\nioctl values (i.e. second argument to ioctl()):\n");
+ printf("\t0x%lx\t\tvalue of SG_GET_NUM_WAITING ioctl\n",
+ (unsigned long)SG_GET_NUM_WAITING);
+ printf("\t0x%lx\t\tvalue of SG_IO ioctl\n",
+ (unsigned long)SG_IO);
+ printf("\t0x%lx\tvalue of SG_IOABORT ioctl\n",
+ (unsigned long)SG_IOABORT);
+ printf("\t0x%lx\tvalue of SG_IORECEIVE ioctl\n",
+ (unsigned long)SG_IORECEIVE);
+ printf("\t0x%lx\tvalue of SG_IORECEIVE_V3 ioctl\n",
+ (unsigned long)SG_IORECEIVE_V3);
+ printf("\t0x%lx\tvalue of SG_IOSUBMIT ioctl\n",
+ (unsigned long)SG_IOSUBMIT);
+ printf("\t0x%lx\tvalue of SG_IOSUBMIT_V3 ioctl\n",
+ (unsigned long)SG_IOSUBMIT_V3);
+ printf("\t0x%lx\tvalue of SG_SET_GET_EXTENDED ioctl\n",
+ (unsigned long)SG_SET_GET_EXTENDED);
+ printf("\n\t0x%x\t\tbase value of most SG_* ioctls\n",
+ SG_IOCTL_MAGIC_NUM);
+ printf("\nsizeof(void *) [a pointer] on this machine: %u bytes\n",
+ (unsigned)sizeof(void *));
+ if (0 == uname(&unam))
+ printf("Machine name: %s\n", unam.machine);
+
+ return 0;
+ }
+ if (0 == file_name) {
+ printf("No filename (sg device) given\n\n");
+ usage();
+ return 1;
+ }
+ memset(dname, 0, sizeof(dname));
+ if (strlen(file_name) > 255) {
+ fprintf(stderr, "file_name too long\n");
+ goto out;
+ }
+ strncpy(dname, file_name, sizeof(dname) - 1);
+ if ((chp = strchr(dname, '-'))) {
+ if (1 != sscanf(chp + 1, "%d", &dname_last)) {
+ fprintf(stderr, "can't code number after '-' in file_name\n");
+ goto out;
+ }
+ *chp = '\0';
+ --chp;
+ while (isdigit(*chp))
+ --chp;
+ ++chp;
+ if (1 != sscanf(chp, "%d", &dname_current)) {
+ fprintf(stderr, "can't code number before '-' in file_name\n");
+ goto out;
+ }
+ *chp = '\0';
+ has_dname_range = true;
+ dname_pos = strlen(dname);
+ }
+ is_first = true;
+
+dname_range_loop:
+ if (has_dname_range)
+ sprintf(dname + dname_pos, "%d", dname_current);
+
+ /* An access mode of O_RDWR is required for write()/read() interface */
+ if ((sg_fd = open(dname, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ, "error opening file: %s", dname);
+ perror(ebuff);
+ return 1;
+ }
+ if (verbose)
+ fprintf(stderr, "opened given file: %s successfully, fd=%d\n",
+ dname, sg_fd);
+
+ if (ioctl(sg_fd, SG_GET_VERSION_NUM, &sg_drv_ver_num) < 0) {
+ pr2serr("ioctl(SG_GET_VERSION_NUM) failed, errno=%d %s\n", errno,
+ strerror(errno));
+ goto out;
+ }
+ if (is_first)
+ printf("Linux sg driver version: %d\n", sg_drv_ver_num);
+
+ if (create_time && (sg_drv_ver_num > 40030)) {
+ pr_create_dev_time(sg_fd, dname);
+ goto out;
+ }
+
+ if (nw_given || (iterator_test >= 0) || (object_walk_test >= 0)) {
+ /* -T=NUM and/or -I=0|1 or -j=0|1 */
+ /* time ioctl(SG_GET_NUM_WAITING) or do iterator_test */
+ int nw;
+ struct timespec start_tm, fin_tm, res_tm;
+
+ if (is_first) {
+ int rang = has_dname_range ? (1 + dname_last - dname_current) : 1;
+
+ is_first = false;
+ if (nw_given)
+ printf("Timing %d x %d calls to ioctl(SG_GET_NUM_WAITING)\n",
+ rang, num_sgnw);
+ else if (iterator_test >= 0) {
+ k = num_sgnw + 1000;
+ printf("Timing %d calls to ioctl(SG_SET_DEBUG, %d)\n",
+ rang, ((0 == iterator_test) ? -k : k));
+ } else
+ printf("Timing %d calls to ioctl(SG_SET_DEBUG, %d)\n",
+ rang, (object_walk_test == 0) ? 999 : -999);
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) {
+ res = errno;
+ perror("start clock_gettime() failed:");
+ goto out;
+ }
+ }
+ if (nw_given) {
+ for (k = 0; k < num_sgnw; ++k, sum_nw += nw) {
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &nw) < 0) {
+ res = errno;
+ fprintf(stderr, "%d: ioctl(SG_GET_NUM_WAITING) failed "
+ "errno=%d\n", k, res);
+ goto out;
+ }
+ }
+ } else if (iterator_test >= 0) {
+ int fd, pid;
+
+ k = num_sgnw + 1000;
+ if (0 == iterator_test)
+ k = -k;
+ if (second_fname) {
+ if ((sg_fd2 = open(second_fname, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ, "%s: error opening file: %s",
+ __func__, second_fname);
+ perror(ebuff);
+ return 1;
+ }
+ printf("About to fork due to second filename\n");
+ pid = fork();
+ if (pid < 0) {
+ perror("fork() failed");
+ goto out;
+ } else if (0 == pid) {
+ relative_cp = "child: ";
+ is_parent = false;
+ fd = sg_fd2;
+ } else {
+ relative_cp = "parent: ";
+ is_parent = true;
+ childs_pid = pid;
+ fd = sg_fd;
+ }
+ } else {
+ fd = sg_fd;
+ relative_cp = "";
+ }
+ if (ioctl(fd, SG_SET_DEBUG, &k) < 0) {
+ res = errno;
+ fprintf(stderr, "%s%d: ioctl(SG_SET_DEBUG) failed errno=%d\n",
+ relative_cp, k, res);
+ goto out;
+ } else if (verbose)
+ fprintf(stderr, "%siterator_test good ioctl(SG_SET_DEBUG, "
+ "%d)\n", relative_cp, k);
+ sum_nw += num_sgnw;
+ } else if (object_walk_test >= 0) {
+ const char * ccp = "object_walk_test";
+
+ relative_cp = "";
+ k = (object_walk_test == 0) ? 999 : -999;
+ if (ioctl(sg_fd, SG_SET_DEBUG, &k) < 0) {
+ res = errno;
+ fprintf(stderr, "%s: ioctl(SG_SET_DEBUG, %d) failed "
+ "errno=%d\n", ccp, k, res);
+ } else if (verbose)
+ fprintf(stderr, "%s: good call to ioctl(SG_SET_DEBUG, %d)\n",
+ ccp, k);
+ sum_nw += 10000; /* (1_up-scan + 2_lookups) * 10,000 times */
+ }
+
+ if (has_dname_range) {
+ ++dname_current;
+ if (dname_current <= dname_last) {
+ if (sg_fd >= 0)
+ close(sg_fd);
+ goto dname_range_loop;
+ }
+ }
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &fin_tm)) {
+ res = errno;
+ perror("finish clock_gettime() failed:");
+ goto out;
+ }
+ res_tm.tv_sec = fin_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_nsec = fin_tm.tv_nsec - start_tm.tv_nsec;
+ if (res_tm.tv_nsec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_nsec += 1000000000;
+ }
+ if (verbose) {
+ if (nw_given && (verbose > 1))
+ printf("sum of num_waiting_s=%d\n", sum_nw);
+ printf("%selapsed time (nanosecond precision): %d.%09d secs\n",
+ relative_cp, (int)res_tm.tv_sec, (int)res_tm.tv_nsec);
+ } else
+ printf("%selapsed time: %d.%06d secs\n", relative_cp,
+ (int)res_tm.tv_sec, (int)(res_tm.tv_nsec / 1000));
+ if (num_sgnw >= 100) {
+ double m = (double)res_tm.tv_sec +
+ ((double)res_tm.tv_nsec / 1000000000.0);
+ double num = num_sgnw;
+
+ if (m > 0.000001)
+ printf("%sCalls per second: %.2f\n", relative_cp, num / m);
+ }
+ res = 0;
+ goto out;
+ }
+ if ((more_async || no_duration) && !do_v3_only)
+ set_more_async(sg_fd, more_async, no_duration);
+
+ if (second_fname) {
+ if ((sg_fd2 = open(second_fname, O_RDWR)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "%s: error opening file: %s", __func__, second_fname);
+ perror(ebuff);
+ return 1;
+ }
+ if (verbose)
+ fprintf(stderr, "opened second file: %s successfully, fd=%d\n",
+ second_fname, sg_fd2);
+ if (more_async && !do_v3_only)
+ set_more_async(sg_fd2, more_async, no_duration);
+ }
+
+ if ((num_mrqs > 0) && !do_v3_only) {
+ res = do_mrqs(sg_fd, sg_fd2, num_mrqs);
+ goto out;
+ }
+
+ if (do_fork) {
+ int pid;
+ int sv[2];
+
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0) {
+ perror("socketpair");
+ exit(1);
+ }
+ printf("socketpair: sv[0]=%d, sv[1]=%d sg_fd=%d\n", sv[0], sv[1],
+ sg_fd);
+ pid = fork();
+ if (pid < 0) {
+ perror("fork() failed");
+ goto out;
+ } else if (0 == pid) {
+ relative_cp = "child ";
+ is_parent = false;
+ close(sv[0]);
+ sock = sv[1];
+ } else {
+ relative_cp = "parent ";
+ is_parent = true;
+ childs_pid = pid;
+ close(sv[1]);
+ sock = sv[0];
+ }
+ }
+
+ cp = do_fork ? relative_cp : "";
+ if (! do_v3_only && (sg_drv_ver_num > 40030)) {
+ if (tst_extended_ioctl(dname, sg_fd, second_fname, sg_fd2, sock,
+ cp))
+ goto out;
+ }
+ if (ioctl_only)
+ goto out;
+
+ if (do_fork && !is_parent)
+ return 0;
+
+ printf("start write() calls [submits]\n");
+ for (k = 0; k < q_len; ++k) {
+ /* Prepare INQUIRY command */
+ memset(&io_hdr[k], 0, sizeof(sg_io_hdr_t));
+ io_hdr[k].interface_id = 'S';
+ /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */
+ io_hdr[k].mx_sb_len = (uint8_t)sizeof(sense_buffer);
+ if (0 == (k % 3)) {
+ io_hdr[k].cmd_len = sizeof(sdiag_cdb);
+ io_hdr[k].cmdp = sdiag_cdb;
+ io_hdr[k].dxfer_direction = SG_DXFER_NONE;
+ } else {
+ io_hdr[k].cmd_len = sizeof(inq_cdb);
+ io_hdr[k].cmdp = inq_cdb;
+ io_hdr[k].dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr[k].dxfer_len = INQ_REPLY_LEN;
+ io_hdr[k].dxferp = inqBuff[k];
+ }
+ io_hdr[k].sbp = sense_buffer[k];
+ io_hdr[k].mx_sb_len = SENSE_BUFFER_LEN;
+ io_hdr[k].timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr[k].pack_id = k + 3; /* so pack_id doesn't start at 0 */
+ /* default is to queue at head (in SCSI mid level) */
+ if (q_at_tail)
+ io_hdr[k].flags |= SG_FLAG_Q_AT_TAIL;
+ else
+ io_hdr[k].flags |= SG_FLAG_Q_AT_HEAD;
+ /* io_hdr[k].usr_ptr = NULL; */
+
+ if (write(sg_fd, &io_hdr[k], sizeof(sg_io_hdr_t)) < 0) {
+ pr2serr("%ssg write errno=%d [%s]\n", cp, errno, strerror(errno));
+ close(sg_fd);
+ return 1;
+ }
+ }
+
+ memset(&ssi, 0, sizeof(ssi));
+ if (ioctl(sg_fd, SG_GET_SCSI_ID, &ssi) < 0)
+ pr2serr("ioctl(SG_GET_SCSI_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else {
+ printf("host_no: %d\n", ssi.host_no);
+ printf(" channel: %d\n", ssi.channel);
+ printf(" scsi_id: %d\n", ssi.scsi_id);
+ printf(" lun: %d\n", ssi.lun);
+ printf(" pdt: %d\n", ssi.scsi_type);
+ printf(" h_cmd_per_lun: %d\n", ssi.h_cmd_per_lun);
+ printf(" d_queue_depth: %d\n", ssi.d_queue_depth);
+ printf(" SCSI 8 byte LUN: ");
+ hex2stdout(ssi.scsi_lun, 8, -1);
+ }
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("num_waiting: %d\n", num_waiting);
+
+ sleep(sleep_secs);
+
+ if (write_only)
+ goto out;
+
+ if (do_fork)
+ printf("\n\nFollowing starting with get_pack_id are all CHILD\n");
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("num_waiting: %d\n", num_waiting);
+
+ printf("\nstart read() calls [io receive]\n");
+ for (k = 0, done = false; k < q_len; ++k) {
+ if ((! done) && (k == q_len / 2)) {
+ done = true;
+ printf("\n>>> half way through read\n");
+ if (ioctl(sg_fd, SG_GET_PACK_ID, &pack_id) < 0)
+ pr2serr("ioctl(SG_GET_PACK_ID) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("first available pack_id: %d\n", pack_id);
+ if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting) < 0)
+ pr2serr("ioctl(SG_GET_NUM_WAITING) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ else
+ printf("num_waiting: %d\n", num_waiting);
+ }
+ memset(&rio_hdr, 0, sizeof(sg_io_hdr_t));
+ rio_hdr.interface_id = 'S';
+ if (read(sg_fd, &rio_hdr, sizeof(sg_io_hdr_t)) < 0) {
+ perror("sg read error");
+ close(sg_fd);
+ return 1;
+ }
+ /* now for the error processing */
+ ok = 0;
+ switch (sg_err_category3(&rio_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ ok = 1;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ printf("Recovered error, continuing\n");
+ ok = 1;
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("command error", &rio_hdr, 1);
+ break;
+ }
+
+ if (ok) { /* output result if it is available */
+ if (0 == (rio_hdr.pack_id % 3))
+ printf("SEND DIAGNOSTIC %d duration=%u\n", rio_hdr.pack_id,
+ rio_hdr.duration);
+ else
+ printf("INQUIRY %d duration=%u\n", rio_hdr.pack_id,
+ rio_hdr.duration);
+ }
+ }
+
+out:
+ if (sg_fd >= 0)
+ close(sg_fd);
+ if (sg_fd2 >= 0)
+ close(sg_fd2);
+ return res;
+}
diff --git a/testing/sg_tst_json_builder.c b/testing/sg_tst_json_builder.c
new file mode 100644
index 00000000..0637e30b
--- /dev/null
+++ b/testing/sg_tst_json_builder.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
+/*
+ * Simple streaming JSON writer
+ *
+ * This takes care of the annoying bits of JSON syntax like the commas
+ * after elements
+ *
+ * Authors: Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Borrowed from Linux kernel [5.17.0]: tools/bpf/bpftool/json_writer.[hc]
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <malloc.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "../lib/sg_json_builder.h"
+#include "sg_pr2serr.h"
+
+#define MY_NAME "sg_tst_json_builder"
+
+
+static json_serialize_opts out_settings = {
+ json_serialize_mode_multiline,
+ 0,
+ 4
+};
+
+int
+main(int argc, char * argv[])
+{
+ size_t len;
+ sgj_state jstate;
+ sgj_state * jstp = &jstate;
+ json_value * jv1p;
+ json_value * jv2p;
+ json_value * jv3p = json_object_new(0);
+ json_value * jvp = NULL;
+ json_value * jv4p;
+ json_value * jv5p;
+ json_value * ja1p = json_array_new(0);
+ json_value * ja2p;
+ json_value * jsp = json_string_new("hello world 1");
+ json_value * js2p = json_string_new("hello world 2");
+ json_value * js3p = json_string_new("hello world 3");
+ json_value * js10 = json_string_new("good-bye world");
+ json_value * js11 = json_string_new("good-bye world 2");
+ json_value * js12 = json_string_new("duplicate name 1");
+ char b[8192];
+
+ sgj_init_state(jstp, NULL);
+ jvp = sgj_start_r(MY_NAME, "0.02 20220503", argc, argv, jstp);
+ jv1p = json_object_push(jvp, "contents", jsp);
+
+ if (jvp == jv1p)
+ printf("jvp == jv1p\n");
+ else
+ printf("jvp != jv1p\n");
+
+#if 1
+ json_array_push(ja1p, js2p);
+ jv2p = json_object_push(jvp, "extra", js3p);
+ if (jv2p)
+ printf("jv2p->type=%d\n", jv2p->type);
+ else
+ printf("jv2p is NULL\n");
+ ja2p = json_array_push(ja1p, json_string_new(
+ "test double quote, etc: \" world \\ 99\t\ttwo tabs"));
+ if (ja2p)
+ printf("ja2p->type=%d\n", ja2p->type);
+ else
+ printf("ja2p is NULL\n");
+ // json_object_push(ja2p, "boo", json_string_new("hello world 88"));
+ json_object_push(jvp, "a_array", ja1p);
+ jv4p = json_object_push(jvp, "a_object", jv3p);
+ if (jv4p)
+ printf("jv4p->type=%d\n", jv4p->type);
+ else
+ printf("jv4p is NULL\n");
+ json_object_push(jv4p, "test", js10);
+ json_object_push(jv4p, "test2", js11);
+ json_object_push(jv4p, "test", js12);
+ // ja3p = json_array_push(ja2p, json_string_new("good-bye"));
+ // jv4p = json_object_push(jvp, "a_array", ja2p);
+ // jv5p = json_object_merge(jvp, ja1p);
+#endif
+ jv5p = jvp;
+
+ len = json_measure_ex(jv5p, out_settings);
+ printf("jvp length: %zu bytes\n", len);
+ if (len < sizeof(b)) {
+ json_serialize_ex(b, jv5p, out_settings);
+ printf("json serialized:\n");
+ printf("%s\n", b);
+ } else
+ printf("since json output length [%zu] > 8192, skip outputting\n",
+ len);
+
+ json_builder_free(jvp);
+ return 0;
+}
+
+
+#if 0
+int main(int argc, char **argv)
+{
+ json_writer_t *wr = jsonw_new(stdout);
+
+ jsonw_start_object(wr);
+ jsonw_pretty(wr, true);
+ jsonw_name(wr, "Vyatta");
+ jsonw_start_object(wr);
+ jsonw_string_field(wr, "url", "http://vyatta.com");
+ jsonw_uint_field(wr, "downloads", 2000000ul);
+ jsonw_float_field(wr, "stock", 8.16);
+
+ jsonw_name(wr, "ARGV");
+ jsonw_start_array(wr);
+ while (--argc)
+ jsonw_string(wr, *++argv);
+ jsonw_end_array(wr);
+
+ jsonw_name(wr, "empty");
+ jsonw_start_array(wr);
+ jsonw_end_array(wr);
+
+ jsonw_name(wr, "NIL");
+ jsonw_start_object(wr);
+ jsonw_end_object(wr);
+
+ jsonw_null_field(wr, "my_null");
+
+ jsonw_name(wr, "special chars");
+ jsonw_start_array(wr);
+ jsonw_string_field(wr, "slash", "/");
+ jsonw_string_field(wr, "newline", "\n");
+ jsonw_string_field(wr, "tab", "\t");
+ jsonw_string_field(wr, "ff", "\f");
+ jsonw_string_field(wr, "quote", "\"");
+ jsonw_string_field(wr, "tick", "\'");
+ jsonw_string_field(wr, "backslash", "\\");
+ jsonw_end_array(wr);
+
+jsonw_name(wr, "ARGV");
+jsonw_start_array(wr);
+jsonw_string(wr, "boo: appended or new entry?");
+jsonw_end_array(wr);
+
+ jsonw_end_object(wr);
+
+ jsonw_end_object(wr);
+ jsonw_destroy(&wr);
+ return 0;
+}
+
+#endif
diff --git a/testing/sg_tst_nvme.c b/testing/sg_tst_nvme.c
new file mode 100644
index 00000000..4cc696aa
--- /dev/null
+++ b/testing/sg_tst_nvme.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (c) 2018-2021 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * This program issues a NVMe Identify command (controller or namespace)
+ * or a Device self-test command via the "SCSI" pass-through interface of
+ * this package's sg_utils library. That interface is primarily shown in
+ * the ../include/sg_pt.h header file.
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_pt.h"
+#include "sg_pt_nvme.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+static const char * version_str = "1.07 20210225";
+
+
+#define ME "sg_tst_nvme: "
+
+#define SENSE_BUFF_LEN 32 /* Arbitrary, only need 16 bytes for NVME
+ * (and SCSI at least 18) currently */
+#define SENSE_BUFF_NVME_LEN 16 /* 4 DWords, little endian, as byte string */
+
+#define INQUIRY_CMD 0x12 /* SCSI command to get VPD page 0x83 */
+#define INQUIRY_CMDLEN 6
+#define INQUIRY_MAX_RESP_LEN 252
+
+#define VPD_DEVICE_ID 0x83
+
+#define NVME_NSID_ALL 0xffffffff
+
+#define DEF_TIMEOUT_SECS 60
+
+
+static struct option long_options[] = {
+ {"ctl", no_argument, 0, 'c'},
+ {"dev-id", no_argument, 0, 'd'},
+ {"dev_id", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"long", no_argument, 0, 'l'},
+ {"maxlen", required_argument, 0, 'm'},
+ {"nsid", required_argument, 0, 'n'},
+ {"self-test", required_argument, 0, 's'},
+ {"self_test", required_argument, 0, 's'},
+ {"to-ms", required_argument, 0, 't'},
+ {"to_ms", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+/* Assume index is less than 16 */
+static const char * sg_ansi_version_arr[16] =
+{
+ "no conformance claimed",
+ "SCSI-1", /* obsolete, ANSI X3.131-1986 */
+ "SCSI-2", /* obsolete, ANSI X3.131-1994 */
+ "SPC", /* withdrawn, ANSI INCITS 301-1997 */
+ "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */
+ "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */
+ "SPC-4", /* ANSI INCITS 513-2015 */
+ "SPC-5",
+ "ecma=1, [8h]",
+ "ecma=1, [9h]",
+ "ecma=1, [Ah]",
+ "ecma=1, [Bh]",
+ "reserved [Ch]",
+ "reserved [Dh]",
+ "reserved [Eh]",
+ "reserved [Fh]",
+};
+
+#define MAX_DEV_NAMES 8
+
+static const char * dev_name_arr[MAX_DEV_NAMES] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+static int next_dev_name_pos = 0;
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_tst_nvme [--ctl] [--dev-id] [--help] [--long] "
+ "[--maxlen=LEN]\n"
+ " [--nsid=ID] [--self-test=ST] [--to-ms=TO] "
+ "[--verbose]\n"
+ " [--version] DEVICE [DEVICE ...]\n"
+ " where:\n"
+ " --ctl|-c only do Identify controller command\n"
+ " --dev-id|-d do SCSI INQUIRY for device "
+ " identification\n"
+ " VPD page (0x83) via own SNTL\n"
+ " --help|-h print out usage message\n"
+ " --long|-l add more detail to decoded output\n"
+ " --maxlen=LEN| -m LEN allocation length for SCSI devices\n"
+ " --nsid=ID| -n ID do Identify namespace with nsid set to "
+ "ID; if ID\n"
+ " is 0 then try to get nsid from "
+ "DEVICE.\n"
+ " Can also be used with self-test (def: "
+ "0)\n"
+ " --self-test=ST|-s ST do (or abort) device self-test, ST "
+ "can be:\n"
+ " 0: do nothing\n"
+ " 1: do short (background) "
+ "self-test\n"
+ " 2: do long self-test\n"
+ " 15: abort self-test in "
+ "progress\n"
+ " if nsid is 0 then test controller "
+ "only\n"
+ " if nsid is 0xffffffff (-1) then test "
+ "controller\n"
+ " and all namespaces\n"
+ " --to-ms=TO|-t TO command timeout in milliseconds (def: "
+ "60,000)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string then exit\n\n"
+ "Performs a NVME Identify or Device self-test Admin command on "
+ "each DEVICE.\nCan also simulate a SCSI device identification VPD "
+ "page [0x83] via\na local SNTL. --nsid= accepts '-1' for "
+ "0xffffffff which means all.\n"
+ );
+}
+
+static void
+show_nvme_id_ctl(const uint8_t *dinp, const char *dev_name, int do_long,
+ uint32_t * max_nsid_p)
+{
+ bool got_fguid;
+ uint8_t ver_min, ver_ter, mtds;
+ uint16_t ver_maj, oacs, oncs;
+ uint32_t k, ver, max_nsid, npss, j, n, m;
+ uint64_t sz1, sz2;
+ const uint8_t * up;
+
+ max_nsid = sg_get_unaligned_le32(dinp + 516); /* NN */
+ if (max_nsid_p)
+ *max_nsid_p = max_nsid;
+ printf("Identify controller for %s:\n", dev_name);
+ printf(" Model number: %.40s\n", (const char *)(dinp + 24));
+ printf(" Serial number: %.20s\n", (const char *)(dinp + 4));
+ printf(" Firmware revision: %.8s\n", (const char *)(dinp + 64));
+ ver = sg_get_unaligned_le32(dinp + 80);
+ ver_maj = (ver >> 16);
+ ver_min = (ver >> 8) & 0xff;
+ ver_ter = (ver & 0xff);
+ printf(" Version: %u.%u", ver_maj, ver_min);
+ if ((ver_maj > 1) || ((1 == ver_maj) && (ver_min > 2)) ||
+ ((1 == ver_maj) && (2 == ver_min) && (ver_ter > 0)))
+ printf(".%u\n", ver_ter);
+ else
+ printf("\n");
+ oacs = sg_get_unaligned_le16(dinp + 256);
+ if (0x1ff & oacs) {
+ printf(" Optional admin command support:\n");
+ if (0x100 & oacs)
+ printf(" Doorbell buffer config\n");
+ if (0x80 & oacs)
+ printf(" Virtualization management\n");
+ if (0x40 & oacs)
+ printf(" NVMe-MI send and NVMe-MI receive\n");
+ if (0x20 & oacs)
+ printf(" Directive send and directive receive\n");
+ if (0x10 & oacs)
+ printf(" Device self-test\n");
+ if (0x8 & oacs)
+ printf(" Namespace management and attachment\n");
+ if (0x4 & oacs)
+ printf(" Firmware download and commit\n");
+ if (0x2 & oacs)
+ printf(" Format NVM\n");
+ if (0x1 & oacs)
+ printf(" Security send and receive\n");
+ } else
+ printf(" No optional admin command support\n");
+ oncs = sg_get_unaligned_le16(dinp + 256);
+ if (0x7f & oncs) {
+ printf(" Optional NVM command support:\n");
+ if (0x40 & oncs)
+ printf(" Timestamp feature\n");
+ if (0x20 & oncs)
+ printf(" Reservations\n");
+ if (0x10 & oncs)
+ printf(" Save and Select fields non-zero\n");
+ if (0x8 & oncs)
+ printf(" Write zeroes\n");
+ if (0x4 & oncs)
+ printf(" Dataset management\n");
+ if (0x2 & oncs)
+ printf(" Write uncorrectable\n");
+ if (0x1 & oncs)
+ printf(" Compare\n");
+ } else
+ printf(" No optional NVM command support\n");
+ printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n",
+ sg_get_unaligned_le16(dinp + 0),
+ sg_get_unaligned_le16(dinp + 2));
+ printf(" IEEE OUI Identifier: 0x%x\n",
+ sg_get_unaligned_le24(dinp + 73));
+ got_fguid = ! sg_all_zeros(dinp + 112, 16);
+ if (got_fguid) {
+ printf(" FGUID: 0x%02x", dinp[112]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[112 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" FGUID: 0x0\n");
+ printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78));
+ if (do_long) {
+ printf(" Management endpoint capabilities, over a PCIe port: %d\n",
+ !! (0x2 & dinp[255]));
+ printf(" Management endpoint capabilities, over a SMBus/I2C port: "
+ "%d\n", !! (0x1 & dinp[255]));
+ }
+ printf(" Number of namespaces: %u\n", max_nsid);
+ sz1 = sg_get_unaligned_le64(dinp + 280); /* lower 64 bits */
+ sz2 = sg_get_unaligned_le64(dinp + 288); /* upper 64 bits */
+ if (sz2)
+ printf(" Total NVM capacity: huge ...\n");
+ else if (sz1)
+ printf(" Total NVM capacity: %" PRIu64 " bytes\n", sz1);
+ mtds = dinp[77];
+ printf(" Maximum data transfer size: ");
+ if (mtds)
+ printf("%u pages\n", 1U << mtds);
+ else
+ printf("<unlimited>\n");
+
+ if (do_long) {
+ const char * const non_op = "does not process I/O";
+ const char * const operat = "processes I/O";
+ const char * cp;
+
+ printf(" Total NVM capacity: 0 bytes\n");
+ npss = dinp[263] + 1;
+ up = dinp + 2048;
+ for (k = 0; k < npss; ++k, up += 32) {
+ n = sg_get_unaligned_le16(up + 0);
+ n *= (0x1 & up[3]) ? 1 : 100; /* unit: 100 microWatts */
+ j = n / 10; /* unit: 1 milliWatts */
+ m = j % 1000;
+ j /= 1000;
+ cp = (0x2 & up[3]) ? non_op : operat;
+ printf(" Power state %u: Max power: ", k);
+ if (0 == j) {
+ m = n % 10;
+ n /= 10;
+ printf("%u.%u milliWatts, %s\n", n, m, cp);
+ } else
+ printf("%u.%03u Watts, %s\n", j, m, cp);
+ n = sg_get_unaligned_le32(up + 4);
+ if (0 == n)
+ printf(" [ENLAT], ");
+ else
+ printf(" ENLAT=%u, ", n);
+ n = sg_get_unaligned_le32(up + 8);
+ if (0 == n)
+ printf("[EXLAT], ");
+ else
+ printf("EXLAT=%u, ", n);
+ n = 0x1f & up[12];
+ printf("RRT=%u, ", n);
+ n = 0x1f & up[13];
+ printf("RRL=%u, ", n);
+ n = 0x1f & up[14];
+ printf("RWT=%u, ", n);
+ n = 0x1f & up[15];
+ printf("RWL=%u\n", n);
+ }
+ }
+}
+
+static const char * rperf[] = {"Best", "Better", "Good", "Degraded"};
+
+static void
+show_nvme_id_ns(const uint8_t * dinp, uint32_t nsid, const char *dev_name,
+ int do_long)
+{
+ bool got_eui_128 = false;
+ uint32_t u, k, off, num_lbaf, flbas, flba_info, md_size, lb_size;
+ uint64_t ns_sz, eui_64;
+
+ printf("Identify namespace %u for %s:\n", nsid, dev_name);
+ num_lbaf = dinp[25] + 1; /* spec says this is "0's based value" */
+ flbas = dinp[26] & 0xf; /* index of active LBA format (for this ns) */
+ ns_sz = sg_get_unaligned_le64(dinp + 0);
+ eui_64 = sg_get_unaligned_be64(dinp + 120); /* N.B. EUI is big endian */
+ if (! sg_all_zeros(dinp + 104, 16))
+ got_eui_128 = true;
+ printf(" Namespace size/capacity: %" PRIu64 "/%" PRIu64
+ " blocks\n", ns_sz, sg_get_unaligned_le64(dinp + 8));
+ printf(" Namespace utilization: %" PRIu64 " blocks\n",
+ sg_get_unaligned_le64(dinp + 16));
+ if (got_eui_128) { /* N.B. big endian */
+ printf(" NGUID: 0x%02x", dinp[104]);
+ for (k = 1; k < 16; ++k)
+ printf("%02x", dinp[104 + k]);
+ printf("\n");
+ } else if (do_long)
+ printf(" NGUID: 0x0\n");
+ if (eui_64)
+ printf(" EUI-64: 0x%" PRIx64 "\n", eui_64); /* N.B. big endian */
+ printf(" Number of LBA formats: %u\n", num_lbaf);
+ printf(" Index LBA size: %u\n", flbas);
+ for (k = 0, off = 128; k < num_lbaf; ++k, off += 4) {
+ printf(" LBA format %u support:", k);
+ if (k == flbas)
+ printf(" <-- active\n");
+ else
+ printf("\n");
+ flba_info = sg_get_unaligned_le32(dinp + off);
+ md_size = flba_info & 0xffff;
+ lb_size = flba_info >> 16 & 0xff;
+ if (lb_size > 31) {
+ pr2serr("%s: logical block size exponent of %u implies a LB "
+ "size larger than 4 billion bytes, ignore\n", __func__,
+ lb_size);
+ continue;
+ }
+ lb_size = 1U << lb_size;
+ ns_sz *= lb_size;
+ ns_sz /= 500*1000*1000;
+ if (ns_sz & 0x1)
+ ns_sz = (ns_sz / 2) + 1;
+ else
+ ns_sz = ns_sz / 2;
+ u = (flba_info >> 24) & 0x3;
+ printf(" Logical block size: %u bytes\n", lb_size);
+ printf(" Approximate namespace size: %" PRIu64 " GB\n", ns_sz);
+ printf(" Metadata size: %u bytes\n", md_size);
+ printf(" Relative performance: %s [0x%x]\n", rperf[u], u);
+ }
+}
+
+/* Invokes a NVMe Admin command via sg_utils library pass-through that will
+ * potentially fetch data from the device (din). Returns 0 -> success,
+ * various SG_LIB_* positive values or negated errno values.
+ * SG_LIB_NVME_STATUS is returned if the NVMe status is non-zero. */
+static int
+nvme_din_admin_cmd(struct sg_pt_base * ptvp, const uint8_t *cmdp,
+ uint32_t cmd_len, const char *cmd_str, uint8_t *dip,
+ int di_len, int timeout_ms, uint16_t *sct_scp, int vb)
+{
+ int res, k;
+ uint16_t sct_sc = 0;
+ uint32_t result, clen;
+ uint8_t sense_b[SENSE_BUFF_NVME_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t ucmd[128];
+ char b[32];
+
+ snprintf(b, sizeof(b), "%s", cmd_str);
+ clen = (cmd_len > sizeof(ucmd)) ? sizeof(ucmd) : cmd_len;
+ memcpy(ucmd, cmdp, clen);
+ if (vb > 1) {
+ pr2serr(" %s cdb:\n", b);
+ hex2stderr(ucmd, clen, -1);
+ }
+ set_scsi_pt_cdb(ptvp, ucmd, clen);
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ if (dip && (di_len > 0))
+ set_scsi_pt_data_in(ptvp, dip, di_len);
+ res = do_scsi_pt(ptvp, -1, -timeout_ms, vb);
+ if (res) {
+ if (res < 0) {
+ res = sg_convert_errno(-res);
+ goto err_out;
+ } else {
+ if (SCSI_PT_DO_BAD_PARAMS == res)
+ pr2serr("%s: bad parameters to do_scsi_pt()\n", __func__);
+ else if (SCSI_PT_DO_TIMEOUT == res)
+ pr2serr("%s: timeout in do_scsi_pt()\n", __func__);
+ else if (SCSI_PT_DO_NVME_STATUS == res) {
+ sct_sc = get_scsi_pt_status_response(ptvp);
+ res = SG_LIB_NVME_STATUS;
+ goto nvme_status_err;
+ } else
+ pr2serr("%s: unknown error (%d) from do_scsi_pt()\n",
+ __func__, res);
+ }
+ res = SG_LIB_FILE_ERROR;
+ goto err_out;
+ }
+
+ if ((vb > 2) && dip && di_len) {
+ k = get_scsi_pt_resid(ptvp);
+ pr2serr(" Data in buffer [%d bytes]:\n", di_len - k);
+ if (di_len > k)
+ hex2stderr(dip, di_len - k, -1);
+ if (vb > 3)
+ pr2serr(" do_scsi_pt(nvme): res=%d resid=%d\n", res, k);
+ }
+ sct_sc = get_scsi_pt_status_response(ptvp);
+ result = get_pt_result(ptvp);
+ k = get_scsi_pt_sense_len(ptvp);
+ if (vb) {
+ pr2serr("Status: 0x%x [SCT<<8 + SC], Result: 0x%x, Completion Q:\n",
+ sct_sc, result);
+ if (k > 0)
+ hex2stderr(sense_b, k, -1);
+ }
+nvme_status_err:
+ if (sct_scp)
+ *sct_scp = sct_sc;
+err_out:
+ return res;
+}
+
+static void
+std_inq_decode(const char * prefix, uint8_t * b, int len, int vb)
+{
+ int pqual, n;
+
+ if (len < 4)
+ return;
+ pqual = (b[0] & 0xe0) >> 5;
+ if (0 == pqual)
+ printf("%s:\n", prefix);
+ else if (1 == pqual)
+ printf("%s: [qualifier indicates no connected LU]\n", prefix);
+ else if (3 == pqual)
+ printf("%s: [qualifier indicates not capable of supporting LU]\n",
+ prefix);
+ else
+ printf("%s: [reserved or vendor specific qualifier [%d]]\n",
+ prefix, pqual);
+ printf(" PQual=%d Device_type=%d RMB=%d LU_CONG=%d "
+ "version=0x%02x ", pqual, b[0] & 0x1f, !!(b[1] & 0x80),
+ !!(b[1] & 0x40), (unsigned int)b[2]);
+ printf(" [%s]\n", sg_ansi_version_arr[b[2] & 0xf]);
+ printf(" [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d "
+ " Resp_data_format=%d\n",
+ !!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20),
+ !!(b[3] & 0x10), b[3] & 0x0f);
+ if (len < 5)
+ return;
+ n = b[4] + 5;
+ if (vb)
+ pr2serr(">> requested %d bytes, %d bytes available\n", len, n);
+ printf(" SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d ",
+ !!(b[5] & 0x80), !!(b[5] & 0x40), ((b[5] & 0x30) >> 4),
+ !!(b[5] & 0x08), !!(b[5] & 0x01));
+ printf(" [BQue=%d]\n EncServ=%d ", !!(b[6] & 0x80),
+ !!(b[6] & 0x40));
+ if (b[6] & 0x10)
+ printf("MultiP=1 (VS=%d) ", !!(b[6] & 0x20));
+ else
+ printf("MultiP=0 ");
+ printf("[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n [RelAdr=%d] ",
+ !!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01),
+ !!(b[7] & 0x80));
+ printf("WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ",
+ !!(b[7] & 0x20), !!(b[7] & 0x10), !!(b[7] & 0x08),
+ !!(b[7] & 0x04));
+ printf("CmdQue=%d\n", !!(b[7] & 0x02));
+ if (len < 36)
+ return;
+ printf(" Vendor_identification: %.8s\n", b + 8);
+ printf(" Product_identification: %.16s\n", b + 16);
+ printf(" Product_revision_level: %.4s\n", b + 32);
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION
+ * CODES command instead). Adds the ability to set the command abort timeout
+ * and the ability to report the residual count. If timeout_secs is zero
+ * the default command abort timeout (60 seconds) is used.
+ * If residp is non-NULL then the residual value is written where residp
+ * points. A residual value of 0 implies mx_resp_len bytes have be written
+ * where resp points. If the residual value equals mx_resp_len then no
+ * bytes have been written. */
+static int
+sg_scsi_inquiry(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int vb)
+{
+ int res, ret, k, sense_cat, resid;
+ uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t * up;
+
+ if (evpd)
+ inq_cdb[1] |= 1;
+ inq_cdb[2] = (uint8_t)pg_op;
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
+ if (vb > 1) {
+ pr2serr(" INQUIRY cdb: ");
+ for (k = 0; k < INQUIRY_CMDLEN; ++k)
+ pr2serr("%02x ", inq_cdb[k]);
+ pr2serr("\n");
+ }
+ if (resp && (mx_resp_len > 0)) {
+ up = (uint8_t *)resp;
+ up[0] = 0x7f; /* defensive prefill */
+ if (mx_resp_len > 4)
+ up[4] = 0;
+ }
+ if (timeout_secs == 0)
+ timeout_secs = DEF_TIMEOUT_SECS;
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, timeout_secs, vb);
+ ret = sg_cmds_process_resp(ptvp, "inquiry", res, noisy, vb, &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret)
+ ;
+ else if (-2 == ret) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ ret = 0;
+ break;
+ default:
+ ret = sense_cat;
+ break;
+ }
+ } else if (ret < 4) {
+ if (vb)
+ pr2serr("%s: got too few bytes (%d)\n", __func__, ret);
+ ret = SG_LIB_CAT_MALFORMED;
+ } else
+ ret = 0;
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2serr("INQUIRY resid (%d) should never exceed requested "
+ "len=%d\n", resid, mx_resp_len);
+ return ret ? ret : SG_LIB_CAT_MALFORMED;
+ }
+ /* zero unfilled section of response buffer */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+ return ret;
+}
+
+int
+main(int argc, char * argv[])
+{
+ bool do_all = false;
+ bool do_dev_id_vpd = false;
+ bool do_id_ctl = false;
+ bool do_id_ns = false;
+ bool do_self_test = false;
+ bool flagged = false;
+ bool is_nvme = false;
+ int res, c, n, resid, off, len, ln, k, q, num;
+ int curr_dev_name_pos = 0;
+ int do_long = 0;
+ int maxlen = INQUIRY_MAX_RESP_LEN;
+ int self_test = 0;
+ int sg_fd = -1;
+ int ret = 0;
+ int timeout_ms = DEF_TIMEOUT_SECS * 1000;
+ int vb = 0;
+ uint32_t nsid = 0;
+ uint32_t dn_nsid, al_size;
+ uint32_t pg_sz = sg_get_page_size();
+ int64_t ll;
+ uint8_t * al_buff = NULL;
+ uint8_t * free_al_buff = NULL;
+ uint8_t * bp;
+ const char * device_name = NULL;
+ const char * cp;
+ struct sg_pt_base * ptvp = NULL;
+ char cmd_name[32];
+ char b[2048];
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "cdhlm:n:s:t:vV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ strcpy(cmd_name, "Identify(ctl)");
+ do_id_ctl = true;
+ break;
+ case 'd':
+ strcpy(cmd_name, "INQUIRY(vpd=0x83)");
+ do_dev_id_vpd = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'l':
+ ++do_long;
+ break;
+ case 'm':
+ maxlen = sg_get_num(optarg);
+ if (maxlen < 0) {
+ pr2serr("bad argument to '--maxlen='\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'n':
+ if ((2 == strlen(optarg)) && (0 == memcmp("-1", optarg, 2))) {
+ nsid = NVME_NSID_ALL; /* treat '-1' as (2**32 - 1) */
+ break;
+ }
+ ll = sg_get_llnum(optarg);
+ if ((ll < 0) || (ll > UINT32_MAX)) {
+ pr2serr("bad argument to '--nsid', accept 0 to 0xffffffff\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ strcpy(cmd_name, "Identify(ns)");
+ nsid = (uint32_t)ll;
+ do_id_ns = true;
+ break;
+ case 's':
+ self_test = sg_get_num(optarg);
+ if (self_test < 0) {
+ pr2serr("bad argument to '--self-test=', expect 0 or "
+ "higher\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ strcpy(cmd_name, "Device self-test");
+ do_self_test = true;
+ break;
+ case 't':
+ timeout_ms = sg_get_num(optarg);
+ if (timeout_ms < 0) {
+ pr2serr("bad argument to '--to-ms=', expect 0 or higher\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+ case 'v':
+ ++vb;
+ break;
+ case 'V':
+ pr2serr(ME "version: %s\n", version_str);
+ return 0;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind) {
+ if (next_dev_name_pos >= MAX_DEV_NAMES) {
+ pr2serr("Only accepts %d DEVICE names\n", MAX_DEV_NAMES);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ dev_name_arr[next_dev_name_pos++] = argv[optind];
+ }
+ }
+
+ if (next_dev_name_pos < 1) {
+ pr2serr("Need at least one DEVICE, can have up to %d\n\n",
+ MAX_DEV_NAMES);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (do_self_test && do_id_ns)
+ do_id_ns = false; /* self-test with DW10 set to nsid */
+ n = (int)do_id_ctl + (int)do_id_ns + (int)do_dev_id_vpd +
+ (int)do_self_test;
+ if (n > 1) {
+ pr2serr("can only have one of --ctl, --dev-id, --nsid= and "
+ "--self-test=\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ } else if (0 == n) {
+ do_id_ns = true;
+ strcpy(cmd_name, "Identify(ns)");
+ }
+
+ al_size = ((uint32_t)maxlen > pg_sz) ? (uint32_t)maxlen : pg_sz;
+ al_buff = sg_memalign(al_size, pg_sz, &free_al_buff, vb > 3);
+ if (NULL == al_buff) {
+ pr2serr("out of memory allocating page sized buffer (of %u bytes)\n",
+ al_size);
+ return SG_LIB_OS_BASE_ERR + ENOMEM;
+ }
+ device_name = dev_name_arr[curr_dev_name_pos++];
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
+ ret = SG_LIB_FILE_ERROR;
+ flagged = true;
+ goto fini;
+ }
+ n = check_pt_file_handle(sg_fd, device_name, vb);
+ if (n < 0) {
+ pr2serr("check_pt_file_handle error: %s: %s\n", device_name,
+ safe_strerror(-n));
+ flagged = true;
+ goto fini;
+ }
+ cp = NULL;
+ switch (n) {
+ case 0:
+ cp = "Unidentified device (SATA disk ?)";
+ break;
+ case 1:
+ cp = "SCSI char device (e.g. in Linux: sg or bsg device)";
+ break;
+ case 2:
+ cp = "SCSI block device (e.g. in FreeBSD: /dev/da0)";
+ break;
+ case 3:
+ cp = "NVMe char device (e.g. in Linux: /dev/nvme0)";
+ break;
+ case 4:
+ cp = "NVMe block device (e.g. in FreeBSD: /dev/nvme0ns1)";
+ break;
+ default:
+ pr2serr("Strange value from check_pt_file_handle() --> %d\n", n);
+ break;
+ }
+ if (cp && (vb || (do_long > 0)))
+ pr2serr("%s\n", cp);
+
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ if (NULL == ptvp) {
+ pr2serr("%s: out of memory\n", b);
+ ret = sg_convert_errno(ENOMEM);
+ goto fini;
+ }
+ k = get_scsi_pt_os_err(ptvp);
+ if (k) {
+ pr2serr("OS error from construct_scsi_pt_obj_with_fd(): %s\n",
+ safe_strerror(k));
+ ret = sg_convert_errno(k);
+ goto fini;
+ }
+
+ /* Loop over all given DEVICEs */
+ for (q = 0; q < MAX_DEV_NAMES; ++q) {
+ is_nvme = pt_device_is_nvme(ptvp);
+ if ((curr_dev_name_pos > 1) && vb)
+ pr2serr("Device %d [%s] seems to be %s\n", q + 1, device_name,
+ is_nvme ? "NVMe" : "SCSI or ATA");
+ resid = 0;
+ if (do_dev_id_vpd || (! is_nvme)) {
+ if (do_dev_id_vpd)
+ ret = sg_scsi_inquiry(ptvp, true /* evpd */, VPD_DEVICE_ID,
+ al_buff, maxlen, timeout_ms / 1000,
+ &resid, true, vb);
+ else /* do a standard INQUIRY */
+ ret = sg_scsi_inquiry(ptvp, false /* evpd */, 0, al_buff,
+ maxlen, timeout_ms / 1000, &resid, true,
+ vb);
+ if (ret) {
+ pr2serr("SCSI INQUIRY(%s) failed\n",
+ do_dev_id_vpd ? "dev_id" : "standard");
+ goto fini;
+ }
+ len = maxlen - resid;
+ if (len < 4) {
+ pr2serr("Something wrong with data-in, len=%d (resid=%d)\n",
+ len, resid);
+ goto fini;
+ }
+ if (do_dev_id_vpd) {
+ printf(" Device %d [%s] identification VPD:\n", q + 1,
+ device_name);
+ for (off = -1, bp = al_buff + 4, ln = len - 4;
+ 0 == sg_vpd_dev_id_iter(bp, ln, &off, -1, -1, -1); ) {
+ n = sg_get_designation_descriptor_str(" ", bp + off,
+ bp[off + 3] + 4, do_long,
+ do_long > 1, sizeof(b), b);
+ if (n > 0)
+ printf("%s", b);
+ }
+ } else {
+ snprintf(b, sizeof(b), " Device %d [%s] Standard INQUIRY:",
+ q + 1, device_name);
+ std_inq_decode(b, al_buff, len, vb);
+ }
+ clear_scsi_pt_obj(ptvp);
+ } else { /* NVME Identify or Device self-test */
+ bool this_ctl = false;
+ uint16_t sct_sc = 0;
+ uint32_t max_nsid;
+ struct sg_nvme_passthru_cmd n_cmd;
+
+ if ((! do_self_test) && (NVME_NSID_ALL == nsid))
+ do_all = true;
+ num = 1; /* preliminary, may alter */
+ for (k = 0; k < num; ++k) {
+ bp = (uint8_t *)&n_cmd;
+ memset(bp, 0, sizeof(n_cmd));
+ if (do_self_test) {
+ n_cmd.opcode = 0x14; /* Device self-test */
+ n_cmd.nsid = nsid;
+ n_cmd.cdw10 = self_test;
+ if (0 == k) {
+ if (0 == nsid)
+ printf("Starting Device self-test for controller "
+ "only\n");
+ else if (do_all)
+ printf("Starting Device self-test for controller "
+ "and all namespaces\n");
+ else
+ printf("Starting Device self-test for controller "
+ "and namespace %u\n", nsid);
+ }
+ } else { /* one or more variants of Identify */
+ n_cmd.opcode = 0x6; /* Identify */
+ dn_nsid = get_pt_nvme_nsid(ptvp);
+ if ((0 == k) && (do_id_ctl || (0 == nsid) || do_all)) {
+ n_cmd.cdw10 = 0x1; /* Controller */
+ this_ctl = true;
+ } else {
+ n_cmd.cdw10 = 0x0; /* Namespace */
+ if (do_all)
+ n_cmd.nsid = k;
+ else if (nsid > 0)
+ n_cmd.nsid = nsid;
+ else if (dn_nsid > 0)
+ n_cmd.nsid = dn_nsid;
+ else
+ break;
+ this_ctl = false;
+ }
+ sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)al_buff,
+ bp + SG_NVME_PT_ADDR);
+ sg_put_unaligned_le32(pg_sz, bp + SG_NVME_PT_DATA_LEN);
+ }
+ ret = nvme_din_admin_cmd(ptvp, (const uint8_t *)&n_cmd,
+ sizeof(n_cmd), cmd_name, al_buff,
+ pg_sz, timeout_ms, &sct_sc, vb);
+ if (sct_sc || (SG_LIB_NVME_STATUS == ret)) {
+ sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b);
+ pr2serr("%s: %s\n", cmd_name, b);
+ flagged = true;
+ goto fini;
+ }
+ if (ret)
+ goto fini;
+ if (0x6 == n_cmd.opcode) {
+ if (this_ctl) {
+ show_nvme_id_ctl(al_buff, device_name, do_long,
+ &max_nsid);
+ num = max_nsid + 1;
+ } else
+ show_nvme_id_ns(al_buff, n_cmd.nsid, device_name,
+ do_long);
+ }
+
+ clear_scsi_pt_obj(ptvp);
+ if (do_self_test)
+ break;
+ if (do_id_ctl)
+ break;
+ } /* end of for loop */
+ }
+ ret = 0;
+
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ ret = sg_convert_errno(-res);
+ break;
+ }
+ sg_fd = -1;
+ }
+ if (ret)
+ break;
+ if (curr_dev_name_pos < next_dev_name_pos)
+ device_name = dev_name_arr[curr_dev_name_pos++];
+ else
+ break;
+ if (NULL == device_name) {
+ pr2serr("Unexpected NULL device name at pos=%d\n",
+ curr_dev_name_pos - 1);
+ ret = sg_convert_errno(EINVAL);
+ flagged = true;
+ break;
+ }
+ sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
+ if (sg_fd < 0) {
+ pr2serr(ME "open error: %s: %s\n", device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ flagged = true;
+ break;
+ }
+ k = set_pt_file_handle(ptvp, sg_fd, vb);
+ if (k) {
+ ret = sg_convert_errno(k);
+ pr2serr("set_pt_file_handle() failed: %s\n", safe_strerror(k));
+ flagged = true;
+ break;
+ }
+ printf("\n");
+ } /* end of "q" outer for loop */
+fini:
+ if (ptvp) {
+ destruct_scsi_pt_obj(ptvp);
+ ptvp = NULL;
+ }
+ if (free_al_buff)
+ free(free_al_buff);
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (ret && (0 == vb) && (! flagged)) {
+ if (! sg_if_can2stderr("", ret))
+ pr2serr("Some error occurred [%d]\n", ret);
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/testing/sgh_dd.cpp b/testing/sgh_dd.cpp
new file mode 100644
index 00000000..b0704ffa
--- /dev/null
+++ b/testing/sgh_dd.cpp
@@ -0,0 +1,5090 @@
+/*
+ * A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 2018-2022 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a specialisation of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device.
+ * A logical block size ('bs') is assumed to be 512 if not given. This
+ * program complains if 'ibs' or 'obs' are given with some other value
+ * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If
+ * 'of' is not given or 'of=-' then stdout assumed.
+ *
+ * A non-standard argument "bpt" (blocks per transfer) is added to control
+ * the maximum number of blocks in each transfer. The default value is 128.
+ * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16 KiB
+ * in this case) are transferred to or from the sg device in a single SCSI
+ * command.
+ *
+ * This version is designed for the linux kernel 2.4, 2.6, 3, 4 and 5 series.
+ *
+ * sgp_dd is a Posix threads specialization of the sg_dd utility. Both
+ * sgp_dd and sg_dd only perform special tasks when one or both of the given
+ * devices belong to the Linux sg driver.
+ *
+ * sgh_dd further extends sgp_dd to use the experimental kernel buffer
+ * sharing feature added in 3.9.02 .
+ * N.B. This utility was previously called sgs_dd but there was already an
+ * archived version of a dd variant called sgs_dd so this utility name was
+ * renamed [20181221]
+ */
+
+static const char * version_str = "2.22 20221020";
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <sys/time.h>
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+#include <sys/mman.h> /* for mmap() system call */
+
+#include <vector>
+#include <array>
+#include <atomic> // C++ header replacing <stdatomic.h> also link
+ // needed '-l atomic' . Not anymore??
+#include <random>
+#include <thread> // needed for std::this_thread::yield()
+#include <mutex>
+#include <chrono>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h> /* for getrandom() system call */
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+using namespace std;
+
+#ifdef __GNUC__
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+#endif
+
+/* comment out following line to stop ioctl(SG_CTL_FLAGM_SNAP_DEV) */
+#define SGH_DD_SNAP_DEV 1
+
+#ifndef SGV4_FLAG_POLLED
+#define SGV4_FLAG_POLLED 0x800
+#endif
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SDT_ICT_MS 300
+#define DEF_SDT_CRT_SEC 3
+#define DEF_SCSI_CDBSZ 10
+#define MAX_SCSI_CDBSZ 16
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#define SGP_READ10 0x28
+#define SGP_PRE_FETCH10 0x34
+#define SGP_PRE_FETCH16 0x90
+#define SGP_VERIFY10 0x2f
+#define SGP_WRITE10 0x2a
+#define DEF_NUM_THREADS 4
+#define MAX_NUM_THREADS 1024 /* was SG_MAX_QUEUE with v3 driver */
+#define DEF_NUM_MRQS 0
+
+#define FT_OTHER 1 /* filetype other than one of the following */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_DEV_NULL 4 /* either /dev/null, /dev/zero or "." */
+#define FT_ST 8 /* filetype is st char device (tape) */
+#define FT_CHAR 16 /* filetype is st char device (tape) */
+#define FT_BLOCK 32 /* filetype is a block device */
+#define FT_FIFO 64 /* fifo (named or unnamed pipe (stdout)) */
+#define FT_RANDOM_0_FF 128 /* iflag=00, iflag=ff and iflag=random
+ override if=IFILE */
+#define FT_ERROR 256 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+#define DEV_ZERO_MINOR_NUM 5
+
+#define EBUFF_SZ 768
+
+#define PROC_SCSI_SG_VERSION "/proc/scsi/sg/version"
+#define SYS_SCSI_SG_VERSION "/sys/module/sg/version"
+
+struct flags_t {
+ bool append;
+ bool coe;
+ bool defres; /* without this res_sz==bs*bpt */
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool ff;
+ bool fua;
+ bool polled; /* formerly called 'hipri' */
+ bool masync; /* more async sg v4 driver flag */
+ bool mrq_immed; /* mrq submit non-blocking */
+ bool mrq_svb; /* mrq shared_variable_block, for sg->sg copy */
+ bool no_dur;
+ bool nocreat;
+ bool noshare;
+ bool no_thresh;
+ bool no_unshare; /* leave it for driver close/release */
+ bool no_waitq; /* dummy, no longer supported, just warn */
+ bool noxfer;
+ bool qhead;
+ bool qtail;
+ bool random;
+ bool mout_if; /* META_OUT_IF flag at mrq level */
+ bool same_fds;
+ bool swait; /* now ignore; kept for backward compatibility */
+ bool v3;
+ bool v4;
+ bool v4_given;
+ bool wq_excl;
+ bool zero;
+ int mmap;
+};
+
+struct global_collection
+{ /* one instance visible to all threads */
+ int infd;
+ int64_t skip;
+ int in_type;
+ int cdbsz_in;
+ int help;
+ int elem_sz;
+ struct flags_t in_flags;
+ // int64_t in_blk; /* -\ next block address to read */
+ // int64_t in_count; /* | blocks remaining for next read */
+ atomic<int64_t> in_rem_count; /* | count of remaining in blocks */
+ atomic<int> in_partial; /* | */
+ atomic<bool> in_stop; /* | */
+ off_t in_st_size; /* Only for FT_OTHER (regular) file */
+ pthread_mutex_t in_mutex; /* -/ */
+ int nmrqs; /* Number of multi-reqs for sg v4 */
+ int outfd;
+ int64_t seek;
+ int out_type;
+ int out2fd;
+ int out2_type;
+ int cdbsz_out;
+ int aen; /* abort every nth command */
+ int m_aen; /* abort mrq every nth command */
+ struct flags_t out_flags;
+ atomic<int64_t> out_blk; /* -\ next block address to write */
+ atomic<int64_t> out_count; /* | blocks remaining for next write */
+ atomic<int64_t> out_rem_count; /* | count of remaining out blocks */
+ atomic<int> out_partial; /* | */
+ atomic<bool> out_stop; /* | */
+ off_t out_st_size; /* Only for FT_OTHER (regular) file */
+ pthread_mutex_t out_mutex; /* | */
+ pthread_cond_t out_sync_cv; /* | hold writes until "in order" */
+ pthread_mutex_t out2_mutex;
+ int bs;
+ int bpt;
+ int cmd_timeout; /* in milliseconds */
+ int outregfd;
+ int outreg_type;
+ int ofsplit;
+ atomic<int> dio_incomplete_count;
+ atomic<int> sum_of_resids;
+ uint32_t sdt_ict; /* stall detection; initial check time (milliseconds) */
+ uint32_t sdt_crt; /* check repetition time (seconds), after first stall */
+ int fail_mask;
+ int verbose;
+ int dry_run;
+ int chkaddr;
+ bool aen_given;
+ bool cdbsz_given;
+ bool is_mrq_i;
+ bool is_mrq_o;
+ bool m_aen_given;
+ bool ofile_given;
+ bool ofile2_given;
+ bool unit_nanosec; /* default duration unit is millisecond */
+ bool mrq_cmds; /* mrq=<NRQS>,C given */
+ bool mrq_async; /* mrq_immed flag given */
+ bool noshare; /* don't use request sharing */
+ bool unbalanced_mrq; /* so _not_ sg->sg request sharing sync mrq */
+ bool verify; /* don't copy, verify like Unix: cmp */
+ bool prefetch; /* for verify: do PF(b),RD(a),V(b)_a_data */
+ bool unshare; /* let close() do file unshare operation */
+ const char * infp;
+ const char * outfp;
+ const char * out2fp;
+};
+
+typedef struct mrq_abort_info
+{
+ int from_tid;
+ int fd;
+ int mrq_id;
+ int debug;
+} Mrq_abort_info;
+
+typedef struct request_element
+{ /* one instance per worker thread */
+ struct global_collection *clp;
+ bool wr;
+ bool has_share;
+ bool both_sg;
+ bool same_sg;
+ bool only_in_sg;
+ bool only_out_sg;
+ // bool mrq_abort_thread_active;
+ int id;
+ int bs;
+ int infd;
+ int outfd;
+ int out2fd;
+ int outregfd;
+ int64_t iblk;
+ int64_t oblk;
+ int num_blks;
+ uint8_t * buffp;
+ uint8_t * alloc_bp;
+ struct sg_io_hdr io_hdr;
+ struct sg_io_v4 io_hdr4[2];
+ uint8_t cmd[MAX_SCSI_CDBSZ];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int dio_incomplete_count;
+ int mmap_active;
+ int resid;
+ int rd_p_id;
+ int rep_count;
+ int rq_id;
+ int mmap_len;
+ int mrq_id;
+ int mrq_index;
+ uint32_t in_mrq_q_blks;
+ uint32_t out_mrq_q_blks;
+ long seed;
+#ifdef HAVE_SRAND48_R /* gcc extension. N.B. non-reentrant version slower */
+ struct drand48_data drand;/* opaque, used by srand48_r and mrand48_r */
+#endif
+ pthread_t mrq_abort_thread_id;
+ Mrq_abort_info mai;
+} Rq_elem;
+
+typedef struct thread_info
+{
+ int id;
+ struct global_collection * gcp;
+ pthread_t a_pthr;
+} Thread_info;
+
+/* Additional parameters for sg_start_io() and sg_finish_io() */
+struct sg_io_extra {
+ bool is_wr2;
+ bool prefetch;
+ bool dout_is_split;
+ int hpv4_ind;
+ int blk_offset;
+ int blks;
+};
+
+#define MONO_MRQ_ID_INIT 0x10000
+
+// typedef vector< pair<int, struct sg_io_v4> > mrq_arr_t;
+typedef array<uint8_t, 32> big_cdb; /* allow up to a 32 byte cdb */
+typedef pair< vector<struct sg_io_v4>, vector<big_cdb> > mrq_arr_t;
+
+
+/* Use this class to wrap C++11 <random> features to produce uniform random
+ * unsigned ints in the range [lo, hi] (inclusive) given a_seed */
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed)
+ : uid(lo, hi), dre(a_seed) { }
+ /* uid ctor takes inclusive range when integral type */
+
+ unsigned int get() { return uid(dre); }
+
+private:
+ uniform_int_distribution<unsigned int> uid;
+ default_random_engine dre;
+};
+
+static atomic<int> mono_pack_id(1);
+static atomic<int> mono_mrq_id(MONO_MRQ_ID_INIT);
+static atomic<long int> pos_index(0);
+
+static atomic<int> num_ebusy(0);
+static atomic<int> num_start_eagain(0);
+static atomic<int> num_fin_eagain(0);
+static atomic<int> num_abort_req(0);
+static atomic<int> num_abort_req_success(0);
+static atomic<int> num_mrq_abort_req(0);
+static atomic<int> num_mrq_abort_req_success(0);
+static atomic<int> num_miscompare(0);
+static atomic<long> num_waiting_calls(0);
+static atomic<bool> vb_first_time(true);
+static atomic<bool> shutting_down(false);
+
+static sigset_t signal_set;
+static sigset_t orig_signal_set;
+static pthread_t sig_listen_thread_id;
+
+static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio";
+
+static void sg_in_rd_cmd(struct global_collection * clp, Rq_elem * rep,
+ mrq_arr_t & def_arr);
+static void sg_out_wr_cmd(Rq_elem * rep, mrq_arr_t & def_arr, bool is_wr2,
+ bool prefetch);
+static bool normal_in_rd(Rq_elem * rep, int blocks);
+static void normal_out_wr(Rq_elem * rep, int blocks);
+static int sg_start_io(Rq_elem * rep, mrq_arr_t & def_arr, int & pack_id,
+ struct sg_io_extra *xtrp);
+static int sg_finish_io(bool wr, Rq_elem * rep, int pack_id,
+ struct sg_io_extra *xtrp);
+static int sg_in_open(struct global_collection *clp, const char *inf,
+ uint8_t **mmpp, int *mmap_len);
+static int sg_out_open(struct global_collection *clp, const char *outf,
+ uint8_t **mmpp, int *mmap_len);
+static int sgh_do_deferred_mrq(Rq_elem * rep, mrq_arr_t & def_arr);
+
+#define STRERR_BUFF_LEN 128
+
+static pthread_mutex_t strerr_mut = PTHREAD_MUTEX_INITIALIZER;
+
+static bool have_sg_version = false;
+static int sg_version = 0;
+static bool sg_version_lt_4 = false;
+static bool sg_version_ge_40045 = false;
+static bool do_sync = false;
+static int do_time = 1;
+static struct global_collection gcoll;
+static struct timeval start_tm;
+static int64_t dd_count = -1;
+static int num_threads = DEF_NUM_THREADS;
+static int exit_status = 0;
+static bool after1 = false;
+
+static const char * my_name = "sgh_dd: ";
+
+static const char * mrq_blk_s = "mrq: ordinary blocking";
+static const char * mrq_vb_s = "mrq: variable blocking";
+static const char * mrq_svb_s = "mrq: shared variable blocking (svb)";
+static const char * mrq_s_nb_s = "mrq: submit of full non-blocking";
+
+
+#ifdef __GNUC__
+static int pr2serr_lk(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#if 0
+static void pr_errno_lk(int e_no, const char * fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+#endif
+#else
+static int pr2serr_lk(const char * fmt, ...);
+#if 0
+static void pr_errno_lk(int e_no, const char * fmt, ...);
+#endif
+#endif
+
+
+static int
+pr2serr_lk(const char * fmt, ...)
+{
+ int n;
+ va_list args;
+
+ pthread_mutex_lock(&strerr_mut);
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ pthread_mutex_unlock(&strerr_mut);
+ return n;
+}
+
+static void
+usage(int pg_num)
+{
+ if (pg_num > 3)
+ goto page4;
+ else if (pg_num > 2)
+ goto page3;
+ else if (pg_num > 1)
+ goto page2;
+
+ pr2serr("Usage: sgh_dd [bs=BS] [conv=CONVS] [count=COUNT] [ibs=BS] "
+ "[if=IFILE]\n"
+ " [iflag=FLAGS] [obs=BS] [of=OFILE] [oflag=FLAGS] "
+ "[seek=SEEK]\n"
+ " [skip=SKIP] [--help] [--version]\n\n");
+ pr2serr(" [ae=AEN[,MAEN]] [bpt=BPT] [cdbsz=6|10|12|16] "
+ "[coe=0|1]\n"
+ " [dio=0|1] [elemsz_kb=EKB] [fail_mask=FM] "
+ "[fua=0|1|2|3]\n"
+ " [mrq=[I|O,]NRQS[,C]] [noshare=0|1] "
+ "[of2=OFILE2]\n"
+ " [ofreg=OFREG] [ofsplit=OSP] [sdt=SDT] "
+ "[sync=0|1]\n"
+ " [thr=THR] [time=0|1|2[,TO]] [unshare=1|0] "
+ "[verbose=VERB]\n"
+ " [--dry-run] [--prefetch] [-v|-vv|-vvv] "
+ "[--verbose]\n"
+ " [--verify] [--version]\n\n"
+ " where the main options (shown in first group above) are:\n"
+ " bs must be device logical block size (default "
+ "512)\n"
+ " conv comma separated list from: [nocreat,noerror,"
+ "notrunc,\n"
+ " null,sync]\n"
+ " count number of blocks to copy (def: device size)\n"
+ " if file or device to read from (def: stdin)\n"
+ " iflag comma separated list from: [00,coe,defres,dio,"
+ "direct,dpo,\n"
+ " dsync,excl,ff,fua,masync,mmap,mout_if,"
+ "mrq_immed,mrq_svb,\n"
+ " nocreat,nodur,noxfer,null,polled,qhead,"
+ "qtail,\n"
+ " random,same_fds,v3,v4,wq_excl]\n"
+ " of file or device to write to (def: /dev/null "
+ "N.B. different\n"
+ " from dd it defaults to stdout). If 'of=.' "
+ "uses /dev/null\n"
+ " of2 second file or device to write to (def: "
+ "/dev/null)\n"
+ " oflag comma separated list from: [append,<<list from "
+ "iflag>>]\n"
+ " seek block position to start writing to OFILE\n"
+ " skip block position to start reading from IFILE\n"
+ " --help|-h output this usage message then exit\n"
+ " --verify|-x do a verify (compare) operation [def: do a "
+ "copy]\n"
+ " --version|-V output version string then exit\n\n"
+ "Copy IFILE to OFILE, similar to dd command. This utility is "
+ "specialized for\nSCSI devices and uses multiple POSIX threads. "
+ "It expects one or both IFILE\nand OFILE to be sg devices. With "
+ "--verify option does a verify/compare\noperation instead of a "
+ "copy. This utility is Linux specific and uses the\nv4 sg "
+ "driver 'share' capability if available. Use '-hh', '-hhh' or "
+ "'-hhhh'\nfor more information.\n"
+ );
+ return;
+page2:
+ pr2serr("Syntax: sgh_dd [operands] [options]\n\n"
+ " where: operands have the form name=value and are pecular to "
+ "'dd'\n"
+ " style commands, and options start with one or "
+ "two hyphens;\n"
+ " the lesser used operands and option are:\n\n"
+ " ae AEN: abort every n commands (def: 0 --> don't "
+ "abort any)\n"
+ " MAEN: abort every n mrq commands (def: 0 --> "
+ "don't)\n"
+ " [requires commands with > 1 ms duration]\n"
+ " bpt is blocks_per_transfer (default is 128)\n"
+ " cdbsz size of SCSI READ, WRITE or VERIFY cdb_s "
+ "(default is 10)\n"
+ " coe continue on error, 0->exit (def), "
+ "1->zero + continue\n"
+ " dio is direct IO, 1->attempt, 0->indirect IO (def)\n"
+ " elemsz_kb scatter gather list element size in kilobytes "
+ "(def: 32[KB])\n"
+ " fail_mask 1: misuse KEEP_SHARE flag; 0: nothing (def)\n"
+ " fua force unit access: 0->don't(def), 1->OFILE, "
+ "2->IFILE,\n"
+ " 3->OFILE+IFILE\n"
+ " mrq number of cmds placed in each sg call "
+ "(def: 0);\n"
+ " may have trailing ',C', to send bulk cdb_s; "
+ "if preceded\n"
+ " by 'I' then mrq only on IFILE, likewise 'O' "
+ "for OFILE\n"
+ " noshare 0->use request sharing(def), 1->don't\n"
+ " ofreg OFREG is regular file or pipe to send what is "
+ "read from\n"
+ " IFILE in the first half of each shared element\n"
+ " ofsplit split ofile write in two at block OSP (def: 0 "
+ "(no split))\n"
+ " sdt stall detection times: CRT[,ICT]. CRT: check "
+ "repetition\n"
+ " time (after first) in seconds; ICT: initial "
+ "check time\n"
+ " in milliseconds. Default: 3,300 . Use CRT=0 "
+ "to disable\n"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE "
+ "after copy\n"
+ " thr is number of threads, must be > 0, default 4, "
+ "max 1024\n"
+ " time 0->no timing, 1->calc throughput(def), "
+ "2->nanosec\n"
+ " precision; TO is command timeout in seconds "
+ "(def: 60)\n"
+ " unshare 0->don't explicitly unshare after share; 1->let "
+ "close do\n"
+ " file unshare (default)\n"
+ " verbose increase verbosity\n"
+ " --chkaddr|-c exits if read block does not contain "
+ "32 bit block\n"
+ " address, used once only checks first "
+ "address in block\n"
+ " --dry-run|-d prepare but bypass copy/read\n"
+ " --prefetch|-p with verify: do pre-fetch first\n"
+ " --verbose|-v increase verbosity of utility\n\n"
+ "Use '-hhh' or '-hhhh' for more information about flags.\n"
+ );
+ return;
+page3:
+ pr2serr("Syntax: sgh_dd [operands] [options]\n\n"
+ " where: 'iflag=<arg>' and 'oflag=<arg>' arguments are listed "
+ "below:\n\n"
+ " 00 use all zeros instead of if=IFILE (only in "
+ "iflags)\n"
+ " 00,ff generates blocks that contain own (32 bit be) "
+ "blk address\n"
+ " append append output to OFILE (assumes OFILE is "
+ "regular file)\n"
+ " coe continue of error (reading, fills with zeros)\n"
+ " defres keep default reserve buffer size (else its "
+ "bs*bpt)\n"
+ " dio sets the SG_FLAG_DIRECT_IO in sg requests\n"
+ " direct sets the O_DIRECT flag on open()\n"
+ " dpo sets the DPO (disable page out) in SCSI READs "
+ "and WRITEs\n"
+ " dsync sets the O_SYNC flag on open()\n"
+ " excl sets the O_EXCL flag on open()\n"
+ " ff use all 0xff bytes instead of if=IFILE (only in "
+ "iflags)\n"
+ " fua sets the FUA (force unit access) in SCSI READs "
+ "and WRITEs\n"
+ " hipri same as 'polled'; 'hipri' name is deprecated\n"
+ " masync set 'more async' flag on this sg device\n"
+ " mmap setup mmap IO on IFILE or OFILE; OFILE only "
+ "with noshare\n"
+ " mmap,mmap when used twice, doesn't call munmap()\n"
+ " mout_if set META_OUT_IF flag on each request\n"
+ " mrq_immed if mrq active, do submit non-blocking (def: "
+ "ordered\n"
+ " blocking)\n"
+ " mrq_svb if mrq and sg->sg copy, do shared_variable_"
+ "blocking\n"
+ " nocreat will fail rather than create OFILE\n"
+ " nodur turns off command duration calculations\n"
+ " noxfer no transfer to/from the user space\n"
+ " no_thresh skip checking per fd max data xfer\n"
+ " null does nothing, placeholder\n"
+ " polled set POLLED flag on command, uses blk_poll() to "
+ "complete\n"
+ " qhead queue new request at head of block queue\n"
+ " qtail queue new request at tail of block queue (def: "
+ "q at head)\n"
+ " random use random data instead of if=IFILE (only in "
+ "iflags)\n"
+ " same_fds each thread uses the same IFILE and OFILE(2) "
+ "file\n"
+ " descriptors (def: each threads has own file "
+ "descriptors)\n"
+ " swait this option is now ignored\n"
+ " v3 use v3 sg interface (def: v3 unless sg driver "
+ "is v4)\n"
+ " v4 use v4 sg interface (def: v3 unless sg driver "
+ "is v4)\n"
+ " wq_excl set SG_CTL_FLAGM_EXCL_WAITQ on this sg fd\n"
+ "\n"
+ "Copies IFILE to OFILE (and to OFILE2 if given). If IFILE and "
+ "OFILE are sg\ndevices 'shared' mode is selected unless "
+ "'noshare' is given to 'iflag=' or\n'oflag='. of2=OFILE2 uses "
+ "'oflag=FLAGS'. When sharing, the data stays in a\nsingle "
+ "in-kernel buffer which is copied (or mmap-ed) to the user "
+ "space\nif the 'ofreg=OFREG' is given. Use '-hhhh' for more "
+ "information.\n"
+ );
+ return;
+page4:
+ pr2serr("pack_id:\n"
+ "These are ascending integers, starting at 1, associated with "
+ "each issued\nSCSI command. When both IFILE and OFILE are sg "
+ "devices, then the READ in\neach read-write pair is issued an "
+ "even pack_id and its WRITE pair is\ngiven the pack_id one "
+ "higher (i.e. an odd number). This enables a\n'dmesg -w' "
+ "user to see that progress is being "
+ "made.\n\n");
+ pr2serr("Debugging:\n"
+ "Apart from using one or more '--verbose' options which gets a "
+ "bit noisy\n'dmesg -w' can give a good overview "
+ "of what is happening.\nThat does a sg driver object tree "
+ "traversal that does minimal locking\nto make sure that each "
+ "traversal is 'safe'. So it is important to note\nthe whole "
+ "tree is not locked. This means for fast devices the overall\n"
+ "tree state may change while the traversal is occurring. For "
+ "example,\nit has been observed that both the read- and write- "
+ "sides of a request\nshare show they are in 'active' state "
+ "which should not be possible.\nIt occurs because the read-"
+ "side probably jumped out of active state and\nthe write-side "
+ "request entered it while some other nodes were being "
+ "printed.\n\n");
+ pr2serr("Busy state:\n"
+ "Busy state (abbreviated to 'bsy' in the dmesg "
+ "output)\nis entered during request setup and completion. It "
+ "is intended to be\na temporary state. It should not block "
+ "but does sometimes (e.g. in\nblock_get_request()). Even so "
+ "that blockage should be short and if not\nthere is a "
+ "problem.\n\n");
+ pr2serr("--verify :\n"
+ "For comparing IFILE with OFILE. Does repeated sequences of: "
+ "READ(ifile)\nand uses data returned to send to VERIFY(ofile, "
+ "BYTCHK=1). So the OFILE\ndevice/disk is doing the actual "
+ "comparison. Stops on first miscompare.\n\n");
+ pr2serr("--prefetch :\n"
+ "Used with --verify option. Prepends a PRE-FETCH(ofile, IMMED) "
+ "to verify\nsequence. This should speed the trailing VERIFY by "
+ "making sure that\nthe data it needs for the comparison is "
+ "already in its cache.\n");
+ return;
+}
+
+static void
+lk_print_command_len(const char *prefix, uint8_t * cmdp, int len, bool lock)
+{
+ if (lock)
+ pthread_mutex_lock(&strerr_mut);
+ if (prefix && *prefix)
+ fputs(prefix, stderr);
+ sg_print_command_len(cmdp, len);
+ if (lock)
+ pthread_mutex_unlock(&strerr_mut);
+}
+
+static void
+lk_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, bool raw_sinfo)
+{
+ pthread_mutex_lock(&strerr_mut);
+ sg_chk_n_print3(leadin, hp, raw_sinfo);
+ pthread_mutex_unlock(&strerr_mut);
+}
+
+static void
+lk_chk_n_print4(const char * leadin, const struct sg_io_v4 * h4p,
+ bool raw_sinfo)
+{
+ pthread_mutex_lock(&strerr_mut);
+ sg_linux_sense_print(leadin, h4p->device_status, h4p->transport_status,
+ h4p->driver_status, (const uint8_t *)h4p->response,
+ h4p->response_len, raw_sinfo);
+ pthread_mutex_unlock(&strerr_mut);
+}
+
+static void
+hex2stderr_lk(const uint8_t * b_str, int len, int no_ascii)
+{
+ pthread_mutex_lock(&strerr_mut);
+ hex2stderr(b_str, len, no_ascii);
+ pthread_mutex_unlock(&strerr_mut);
+}
+
+/* Flags decoded into abbreviations for those that are set, separated by
+ * '|' . */
+static char *
+sg_flags_str(int flags, int b_len, char * b)
+{
+ int n = 0;
+
+ if ((b_len < 1) || (! b))
+ return b;
+ b[0] = '\0';
+ if (SG_FLAG_DIRECT_IO & flags) { /* 0x1 */
+ n += sg_scnpr(b + n, b_len - n, "DIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_MMAP_IO & flags) { /* 0x4 */
+ n += sg_scnpr(b + n, b_len - n, "MMAP|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_YIELD_TAG & flags) { /* 0x8 */
+ n += sg_scnpr(b + n, b_len - n, "YTAG|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_TAIL & flags) { /* 0x10 */
+ n += sg_scnpr(b + n, b_len - n, "QTAI|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_HEAD & flags) { /* 0x20 */
+ n += sg_scnpr(b + n, b_len - n, "QHEA|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DOUT_OFFSET & flags) { /* 0x40 */
+ n += sg_scnpr(b + n, b_len - n, "DOFF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_EVENTFD & flags) { /* 0x80 */
+ n += sg_scnpr(b + n, b_len - n, "EVFD|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_COMPLETE_B4 & flags) { /* 0x100 */
+ n += sg_scnpr(b + n, b_len - n, "CPL_B4|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SIGNAL & flags) { /* 0x200 */
+ n += sg_scnpr(b + n, b_len - n, "SIGNAL|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_IMMED & flags) { /* 0x400 */
+ n += sg_scnpr(b + n, b_len - n, "IMM|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_POLLED & flags) { /* 0x800 */
+ n += sg_scnpr(b + n, b_len - n, "POLLED|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_STOP_IF & flags) { /* 0x1000 */
+ n += sg_scnpr(b + n, b_len - n, "STOPIF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DEV_SCOPE & flags) { /* 0x2000 */
+ n += sg_scnpr(b + n, b_len - n, "DEV_SC|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SHARE & flags) { /* 0x4000 */
+ n += sg_scnpr(b + n, b_len - n, "SHARE|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DO_ON_OTHER & flags) { /* 0x8000 */
+ n += sg_scnpr(b + n, b_len - n, "DO_OTH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_NO_DXFER & flags) { /* 0x10000 */
+ n += sg_scnpr(b + n, b_len - n, "NOXFER|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_KEEP_SHARE & flags) { /* 0x20000 */
+ n += sg_scnpr(b + n, b_len - n, "KEEP_SH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_MULTIPLE_REQS & flags) { /* 0x40000 */
+ n += sg_scnpr(b + n, b_len - n, "MRQS|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_ORDERED_WR & flags) { /* 0x80000 */
+ n += sg_scnpr(b + n, b_len - n, "OWR|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_REC_ORDER & flags) { /* 0x100000 */
+ n += sg_scnpr(b + n, b_len - n, "REC_O|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_META_OUT_IF & flags) { /* 0x200000 */
+ n += sg_scnpr(b + n, b_len - n, "MOUT_IF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (0 == n)
+ n += sg_scnpr(b + n, b_len - n, "<none>");
+fini:
+ if (n < b_len) { /* trim trailing '\' */
+ if ('|' == b[n - 1])
+ b[n - 1] = '\0';
+ } else if ('|' == b[b_len - 1])
+ b[b_len - 1] = '\0';
+ return b;
+}
+
+/* Info field decoded into abbreviations for those bits that are set,
+ * separated by '|' . */
+static char *
+sg_info_str(int info, int b_len, char * b)
+{
+ int n = 0;
+
+ if ((b_len < 1) || (! b))
+ return b;
+ b[0] = '\0';
+ if (SG_INFO_CHECK & info) { /* 0x1 */
+ n += sg_scnpr(b + n, b_len - n, "CHK|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_DIRECT_IO & info) { /* 0x2 */
+ n += sg_scnpr(b + n, b_len - n, "DIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_MIXED_IO & info) { /* 0x4 */
+ n += sg_scnpr(b + n, b_len - n, "MIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_DEVICE_DETACHING & info) { /* 0x8 */
+ n += sg_scnpr(b + n, b_len - n, "DETA|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_ABORTED & info) { /* 0x10 */
+ n += sg_scnpr(b + n, b_len - n, "ABRT|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_INFO_MRQ_FINI & info) { /* 0x20 */
+ n += sg_scnpr(b + n, b_len - n, "MRQF|");
+ if (n >= b_len)
+ goto fini;
+ }
+fini:
+ if (n < b_len) { /* trim trailing '\' */
+ if ('|' == b[n - 1])
+ b[n - 1] = '\0';
+ } else if ('|' == b[b_len - 1])
+ b[b_len - 1] = '\0';
+ return b;
+}
+
+static void
+v4hdr_out_lk(const char * leadin, const sg_io_v4 * h4p, int id)
+{
+ char b[80];
+
+ pthread_mutex_lock(&strerr_mut);
+ if (leadin)
+ pr2serr("%s [id=%d]:\n", leadin, id);
+ if (('Q' != h4p->guard) || (0 != h4p->protocol) ||
+ (0 != h4p->subprotocol))
+ pr2serr(" <<<sg_io_v4 _NOT_ properly set>>>\n");
+ pr2serr(" pointers: cdb=%s sense=%s din=%p dout=%p\n",
+ (h4p->request ? "y" : "NULL"), (h4p->response ? "y" : "NULL"),
+ (void *)h4p->din_xferp, (void *)h4p->dout_xferp);
+ pr2serr(" lengths: cdb=%u sense=%u din=%u dout=%u\n",
+ h4p->request_len, h4p->max_response_len, h4p->din_xfer_len,
+ h4p->dout_xfer_len);
+ pr2serr(" flags=0x%x request_extra{pack_id}=%d\n",
+ h4p->flags, h4p->request_extra);
+ pr2serr(" flags set: %s\n", sg_flags_str(h4p->flags, sizeof(b), b));
+ pr2serr(" %s OUT fields:\n", leadin);
+ pr2serr(" response_len=%d driver/transport/device_status="
+ "0x%x/0x%x/0x%x\n", h4p->response_len, h4p->driver_status,
+ h4p->transport_status, h4p->device_status);
+ pr2serr(" info=0x%x din_resid=%u dout_resid=%u spare_out=%u "
+ "dur=%u\n",
+ h4p->info, h4p->din_resid, h4p->dout_resid, h4p->spare_out,
+ h4p->duration);
+ pthread_mutex_unlock(&strerr_mut);
+}
+
+static void
+fetch_sg_version(void)
+{
+ FILE * fp;
+ char b[96];
+
+ have_sg_version = false;
+ sg_version = 0;
+ fp = fopen(PROC_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (1 == sscanf(b, "%d", &sg_version))
+ have_sg_version = !!sg_version;
+ } else {
+ int j, k, l;
+
+ if (fp)
+ fclose(fp);
+ fp = fopen(SYS_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (3 == sscanf(b, "%d.%d.%d", &j, &k, &l)) {
+ sg_version = (j * 10000) + (k * 100) + l;
+ have_sg_version = !!sg_version;
+ }
+ }
+ }
+ if (fp)
+ fclose(fp);
+}
+
+static void
+calc_duration_throughput(int contin)
+{
+ struct timeval end_tm, res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+ b = (double)gcoll.bs * (dd_count - gcoll.out_rem_count.load());
+ pr2serr("time to %s data %s %d.%06d secs",
+ (gcoll.verify ? "verify" : "copy"), (contin ? "so far" : "was"),
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+}
+
+static void
+print_stats(const char * str)
+{
+ int64_t infull;
+
+ if (0 != gcoll.out_rem_count.load())
+ pr2serr(" remaining block count=%" PRId64 "\n",
+ gcoll.out_rem_count.load());
+ infull = dd_count - gcoll.in_rem_count.load();
+ pr2serr("%s%" PRId64 "+%d records in\n", str,
+ infull - gcoll.in_partial.load(), gcoll.in_partial.load());
+
+ if (gcoll.out_type == FT_DEV_NULL)
+ pr2serr("%s0+0 records out\n", str);
+ else {
+ int64_t outfull = dd_count - gcoll.out_rem_count.load();
+
+ pr2serr("%s%" PRId64 "+%d records %s\n", str,
+ outfull - gcoll.out_partial.load(), gcoll.out_partial.load(),
+ (gcoll.verify ? "verified" : "out"));
+ }
+}
+
+static void
+interrupt_handler(int sig)
+{
+ struct sigaction sigact;
+
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(sig, &sigact, NULL);
+ pr2serr("Interrupted by signal,");
+ if (do_time > 0)
+ calc_duration_throughput(0);
+ print_stats("");
+ kill(getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time > 0)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+static void
+siginfo2_handler(int sig)
+{
+ struct global_collection * clp = &gcoll;
+
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time > 0)
+ calc_duration_throughput(1);
+ print_stats(" ");
+ pr2serr("Send broadcast on out_sync_cv condition variable\n");
+ pthread_cond_broadcast(&clp->out_sync_cv);
+}
+
+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);
+ }
+}
+
+#ifdef SG_LIB_ANDROID
+static void
+thread_exit_handler(int sig)
+{
+ pthread_exit(0);
+}
+#endif
+
+/* Make safe_strerror() thread safe */
+static char *
+tsafe_strerror(int code, char * ebp)
+{
+ char * cp;
+
+ pthread_mutex_lock(&strerr_mut);
+ cp = safe_strerror(code);
+ strncpy(ebp, cp, STRERR_BUFF_LEN);
+ pthread_mutex_unlock(&strerr_mut);
+
+ ebp[strlen(ebp)] = '\0';
+ return ebp;
+}
+
+
+/* Following macro from D.R. Butenhof's POSIX threads book:
+ * ISBN 0-201-63392-2 . [Highly recommended book.] Changed __FILE__
+ * to __func__ */
+#define err_exit(code,text) do { \
+ char strerr_buff[STRERR_BUFF_LEN + 1]; \
+ pr2serr("%s at \"%s\":%d: %s\n", \
+ text, __func__, __LINE__, tsafe_strerror(code, strerr_buff)); \
+ exit(1); \
+ } while (0)
+
+
+static int
+dd_filetype(const char * filename, off_t & st_size)
+{
+ struct stat st;
+ size_t len = strlen(filename);
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if ((MEM_MAJOR == major(st.st_rdev)) &&
+ ((DEV_NULL_MINOR_NUM == minor(st.st_rdev)) ||
+ (DEV_ZERO_MINOR_NUM == minor(st.st_rdev))))
+ return FT_DEV_NULL; /* treat /dev/null + /dev/zero the same */
+ if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+ return FT_ST;
+ return FT_CHAR;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ else if (S_ISFIFO(st.st_mode))
+ return FT_FIFO;
+ st_size = st.st_size;
+ return FT_OTHER;
+}
+
+static inline void
+stop_both(struct global_collection * clp)
+{
+ clp->in_stop = true;
+ clp->out_stop = true;
+}
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rcBuff[RCAP16_REPLY_LEN] = {};
+
+ res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false, 0);
+ if (0 != res)
+ return res;
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+
+ res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false,
+ 0);
+ if (0 != res)
+ return res;
+ *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)sg_get_unaligned_be32(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ return 0;
+}
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ #endif
+ }
+ return 0;
+#else
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+static int
+system_wrapper(const char * cmd)
+{
+ int res;
+
+ res = system(cmd);
+ if (WIFSIGNALED(res) &&
+ (WTERMSIG(res) == SIGINT || WTERMSIG(res) == SIGQUIT))
+ raise(WTERMSIG(res));
+ return WEXITSTATUS(res);
+}
+
+/* Has an infinite loop doing a timed wait for any signals in sig_set. After
+ * each timeout (300 ms) checks if the most_recent_pack_id atomic integer
+ * has changed. If not after another two timeouts announces a stall has
+ * been detected. If shutting down atomic is true breaks out of loop and
+ * shuts down this thread. Other than that, this thread is normally cancelled
+ * by the main thread, after other threads have exited. */
+static void *
+sig_listen_thread(void * v_clp)
+{
+ bool stall_reported = false;
+ int prev_pack_id = 0;
+ struct timespec ts;
+ struct timespec * tsp = &ts;
+ struct global_collection * clp = (struct global_collection *)v_clp;
+ uint32_t ict_ms = (clp->sdt_ict ? clp->sdt_ict : DEF_SDT_ICT_MS);
+
+ tsp->tv_sec = ict_ms / 1000;
+ tsp->tv_nsec = (ict_ms % 1000) * 1000 * 1000; /* DEF_SDT_ICT_MS */
+ while (1) {
+ int sig_number = sigtimedwait(&signal_set, NULL, tsp);
+
+ if (sig_number < 0) {
+ int err = errno;
+
+ /* EAGAIN implies a timeout */
+ if ((EAGAIN == err) && (clp->sdt_crt > 0)) {
+ int pack_id = mono_pack_id.load();
+
+ if ((pack_id > 0) && (pack_id == prev_pack_id)) {
+ if (! stall_reported) {
+ stall_reported = true;
+ tsp->tv_sec = clp->sdt_crt;
+ tsp->tv_nsec = 0;
+ pr2serr_lk("%s: first stall at pack_id=%d detected\n",
+ __func__, pack_id);
+ } else
+ pr2serr_lk("%s: subsequent stall at pack_id=%d\n",
+ __func__, pack_id);
+ // following command assumes linux bash or similar shell
+ system_wrapper("cat /proc/scsi/sg/debug >> /dev/stderr\n");
+ // system_wrapper("/usr/bin/dmesg\n");
+ } else
+ prev_pack_id = pack_id;
+ } else if (EAGAIN != err)
+ pr2serr_lk("%s: sigtimedwait() errno=%d\n", __func__, err);
+ }
+ if (SIGINT == sig_number) {
+ pr2serr_lk("%sinterrupted by SIGINT\n", my_name);
+ stop_both(clp);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ sigprocmask(SIG_SETMASK, &orig_signal_set, NULL);
+ raise(SIGINT);
+ break;
+ }
+ if (SIGUSR2 == sig_number) {
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: interrupted by SIGUSR2\n", __func__);
+ break;
+ }
+ if (shutting_down)
+ break;
+ } /* end of while loop */
+ if (clp->verbose > 3)
+ pr2serr_lk("%s: exiting\n", __func__);
+
+ return NULL;
+}
+
+static void *
+mrq_abort_thread(void * v_maip)
+{
+ int res, err;
+ int n = 0;
+ int seed;
+ unsigned int rn;
+ Mrq_abort_info l_mai = *(Mrq_abort_info *)v_maip;
+ struct sg_io_v4 ctl_v4 {};
+
+#ifdef HAVE_GETRANDOM
+ {
+ ssize_t ssz = getrandom(&seed, sizeof(seed), GRND_NONBLOCK);
+
+ if (ssz < (ssize_t)sizeof(seed)) {
+ pr2serr("getrandom() failed, ret=%d\n", (int)ssz);
+ seed = (int)time(NULL);
+ }
+ }
+#else
+ seed = (int)time(NULL); /* use seconds since epoch as proxy */
+#endif
+ if (l_mai.debug)
+ pr2serr_lk("%s: from_id=%d: to abort mrq_pack_id=%d\n", __func__,
+ l_mai.from_tid, l_mai.mrq_id);
+ res = ioctl(l_mai.fd, SG_GET_NUM_WAITING, &n);
+ ++num_waiting_calls;
+ if (res < 0) {
+ err = errno;
+ pr2serr_lk("%s: ioctl(SG_GET_NUM_WAITING) failed: %s [%d]\n",
+ __func__, safe_strerror(err), err);
+ } else if (l_mai.debug)
+ pr2serr_lk("%s: num_waiting=%d\n", __func__, n);
+
+ Rand_uint * ruip = new Rand_uint(5, 500, seed);
+ struct timespec tspec = {0, 4000 /* 4 usecs */};
+ rn = ruip->get();
+ tspec.tv_nsec = rn * 1000;
+ if (l_mai.debug > 1)
+ pr2serr_lk("%s: /dev/urandom seed=0x%x delay=%u microsecs\n",
+ __func__, seed, rn);
+ if (rn >= 20)
+ nanosleep(&tspec, NULL);
+ else if (l_mai.debug > 1)
+ pr2serr_lk("%s: skipping nanosleep cause delay < 20 usecs\n",
+ __func__);
+
+ ctl_v4.guard = 'Q';
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS;
+ ctl_v4.request_extra = l_mai.mrq_id;
+ ++num_mrq_abort_req;
+ res = ioctl(l_mai.fd, SG_IOABORT, &ctl_v4);
+ if (res < 0) {
+ err = errno;
+ if (ENODATA == err)
+ pr2serr_lk("%s: ioctl(SG_IOABORT) no match on "
+ "MRQ pack_id=%d\n", __func__, l_mai.mrq_id);
+ else
+ pr2serr_lk("%s: MRQ ioctl(SG_IOABORT) failed: %s [%d]\n",
+ __func__, safe_strerror(err), err);
+ } else {
+ ++num_mrq_abort_req_success;
+ if (l_mai.debug > 1)
+ pr2serr_lk("%s: from_id=%d sent ioctl(SG_IOABORT) on MRQ rq_id="
+ "%d, success\n", __func__, l_mai.from_tid,
+ l_mai.mrq_id);
+ }
+ delete ruip;
+ return NULL;
+}
+
+static bool
+sg_share_prepare(int write_side_fd, int read_side_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_wr_mask |= SG_SEIM_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_SHARE_FD;
+ seip->share_fd = read_side_fd;
+ if (ioctl(write_side_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(shared_fd=%d), failed "
+ "errno=%d %s\n", id, read_side_fd, errno,
+ strerror(errno));
+ return false;
+ }
+ if (vb_b)
+ pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(shared_fd)) ok, "
+ "read_side_fd=%d, write_side_fd=%d\n", __func__,
+ id, read_side_fd, write_side_fd);
+ return true;
+}
+
+static void
+sg_unshare(int sg_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_UNSHARE;
+ seip->ctl_flags |= SG_CTL_FLAGM_UNSHARE; /* needs to be set to unshare */
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(UNSHARE), failed errno=%d %s\n",
+ id, errno, strerror(errno));
+ return;
+ }
+ if (vb_b)
+ pr2serr_lk("tid=%d: ioctl(UNSHARE) ok\n", id);
+}
+
+static void
+sg_noshare_enlarge(int sg_fd, bool vb_b)
+{
+ if (sg_version_ge_40045) {
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ sei.sei_wr_mask |= SG_SEIM_TOT_FD_THRESH;
+ seip->tot_fd_thresh = 96 * 1024 * 1024;
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("%s: ioctl(EXTENDED(TOT_FD_THRESH), failed errno=%d "
+ "%s\n", __func__, errno, strerror(errno));
+ return;
+ }
+ if (vb_b)
+ pr2serr_lk("ioctl(TOT_FD_THRESH) ok\n");
+ }
+}
+
+static void
+sg_take_snap(int sg_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_SNAP_DEV;
+ seip->ctl_flags &= ~SG_CTL_FLAGM_SNAP_DEV; /* 0 --> append */
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(SNAP_DEV), failed errno=%d %s\n",
+ id, errno, strerror(errno));
+ return;
+ }
+ if (vb_b)
+ pr2serr_lk("tid=%d: ioctl(SNAP_DEV) ok\n", id);
+}
+
+static void
+cleanup_in(void * v_clp)
+{
+ struct global_collection * clp = (struct global_collection *)v_clp;
+
+ pr2serr("thread cancelled while in mutex held\n");
+ stop_both(clp);
+ pthread_mutex_unlock(&clp->in_mutex);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+}
+
+static void
+cleanup_out(void * v_clp)
+{
+ struct global_collection * clp = (struct global_collection *)v_clp;
+
+ pr2serr("thread cancelled while out_mutex held\n");
+ stop_both(clp);
+ pthread_mutex_unlock(&clp->out_mutex);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+}
+
+static void inline buffp_onto_next(Rq_elem * rep)
+{
+ struct global_collection * clp = rep->clp;
+
+ if ((clp->nmrqs > 0) && clp->unbalanced_mrq) {
+ ++rep->mrq_index;
+ if (rep->mrq_index >= clp->nmrqs)
+ rep->mrq_index = 0; /* wrap */
+ }
+}
+
+static inline uint8_t *
+get_buffp(Rq_elem * rep)
+{
+ struct global_collection * clp = rep->clp;
+
+ if ((clp->nmrqs > 0) && clp->unbalanced_mrq && (rep->mrq_index > 0))
+ return rep->buffp + (rep->mrq_index * clp->bs * clp->bpt);
+ else
+ return rep->buffp;
+}
+
+static void *
+read_write_thread(void * v_tip)
+{
+ Thread_info * tip;
+ struct global_collection * clp;
+ Rq_elem rel {};
+ Rq_elem * rep = &rel;
+ int n, sz, blocks, status, vb, err, res, wr_blks, c_addr;
+ int num_sg = 0;
+ int64_t my_index;
+ volatile bool stop_after_write = false;
+ bool own_infd = false;
+ bool in_is_sg, in_mmap, out_is_sg, out_mmap;
+ bool own_outfd = false;
+ bool own_out2fd = false;
+ bool share_and_ofreg;
+ mrq_arr_t deferred_arr; /* MRQ deferred array (vector) */
+
+ tip = (Thread_info *)v_tip;
+ clp = tip->gcp;
+ vb = clp->verbose;
+ rep->bs = clp->bs;
+ sz = clp->bpt * rep->bs;
+ c_addr = clp->chkaddr;
+ in_is_sg = (FT_SG == clp->in_type);
+ in_mmap = (in_is_sg && (clp->in_flags.mmap > 0));
+ out_is_sg = (FT_SG == clp->out_type);
+ out_mmap = (out_is_sg && (clp->out_flags.mmap > 0));
+ /* Following clp members are constant during lifetime of thread */
+ rep->clp = clp;
+ rep->id = tip->id;
+ if (vb > 2)
+ pr2serr_lk("%d <-- Starting worker thread\n", rep->id);
+ if (! (in_mmap || out_mmap)) {
+ n = sz;
+ if (clp->unbalanced_mrq)
+ n *= clp->nmrqs;
+ rep->buffp = sg_memalign(n, 0 /* page align */, &rep->alloc_bp,
+ false);
+ if (NULL == rep->buffp)
+ err_exit(ENOMEM, "out of memory creating user buffers\n");
+ }
+ rep->infd = clp->infd;
+ rep->outfd = clp->outfd;
+ rep->out2fd = clp->out2fd;
+ rep->outregfd = clp->outregfd;
+ rep->rep_count = 0;
+ if (clp->unbalanced_mrq && (clp->nmrqs > 0))
+ rep->mrq_index = clp->nmrqs - 1;
+
+ if (rep->infd == rep->outfd) {
+ if (in_is_sg)
+ rep->same_sg = true;
+ } else if (in_is_sg && out_is_sg)
+ rep->both_sg = true;
+ else if (in_is_sg)
+ rep->only_in_sg = true;
+ else if (out_is_sg)
+ rep->only_out_sg = true;
+
+ if (clp->in_flags.random) {
+#ifdef HAVE_GETRANDOM
+ ssize_t ssz = getrandom(&rep->seed, sizeof(rep->seed), GRND_NONBLOCK);
+
+ if (ssz < (ssize_t)sizeof(rep->seed)) {
+ pr2serr_lk("thread=%d: getrandom() failed, ret=%d\n",
+ rep->id, (int)ssz);
+ rep->seed = (long)time(NULL);
+ }
+#else
+ rep->seed = (long)time(NULL); /* use seconds since epoch as proxy */
+#endif
+ if (vb > 1)
+ pr2serr_lk("thread=%d: seed=%ld\n", rep->id, rep->seed);
+#ifdef HAVE_SRAND48_R
+ srand48_r(rep->seed, &rep->drand);
+#else
+ srand48(rep->seed);
+#endif
+ }
+ if (clp->in_flags.same_fds || clp->out_flags.same_fds)
+ ;
+ else {
+ int fd;
+
+ if (in_is_sg && clp->infp) {
+ fd = sg_in_open(clp, clp->infp, (in_mmap ? &rep->buffp : NULL),
+ (in_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->infd = fd;
+ rep->mmap_active = in_mmap ? clp->in_flags.mmap : 0;
+ if (in_mmap && (vb > 4))
+ pr2serr_lk("thread=%d: mmap buffp=%p\n", rep->id, rep->buffp);
+ own_infd = true;
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("thread=%d: opened local sg IFILE\n", rep->id);
+ }
+ if (out_is_sg && clp->outfp) {
+ fd = sg_out_open(clp, clp->outfp, (out_mmap ? &rep->buffp : NULL),
+ (out_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->outfd = fd;
+ if (! rep->mmap_active)
+ rep->mmap_active = out_mmap ? clp->out_flags.mmap : 0;
+ if (out_mmap && (vb > 4))
+ pr2serr_lk("thread=%d: mmap buffp=%p\n", rep->id, rep->buffp);
+ own_outfd = true;
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("thread=%d: opened local sg OFILE\n", rep->id);
+ }
+ if ((FT_SG == clp->out2_type) && clp->out2fp) {
+ fd = sg_out_open(clp, clp->out2fp,
+ (out_mmap ? &rep->buffp : NULL),
+ (out_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->out2fd = fd;
+ own_out2fd = true;
+ if (vb > 2)
+ pr2serr_lk("thread=%d: opened local sg OFILE2\n", rep->id);
+ }
+ }
+ if (vb > 2) {
+ if (in_is_sg && (! own_infd))
+ pr2serr_lk("thread=%d: using global sg IFILE, fd=%d\n", rep->id,
+ rep->infd);
+ if (out_is_sg && (! own_outfd))
+ pr2serr_lk("thread=%d: using global sg OFILE, fd=%d\n", rep->id,
+ rep->outfd);
+ if ((FT_SG == clp->out2_type) && (! own_out2fd))
+ pr2serr_lk("thread=%d: using global sg OFILE2, fd=%d\n", rep->id,
+ rep->out2fd);
+ }
+ if (!sg_version_ge_40045) {
+ if (vb > 4)
+ pr2serr_lk("thread=%d: Skipping share because driver too old\n",
+ rep->id);
+ } else if (clp->noshare) {
+ if (vb > 4)
+ pr2serr_lk("thread=%d: Skipping IFILE share with OFILE due to "
+ "noshare=1\n", rep->id);
+ } else if (sg_version_ge_40045 && in_is_sg && out_is_sg)
+ rep->has_share = sg_share_prepare(rep->outfd, rep->infd, rep->id,
+ vb > 9);
+ if (vb > 9)
+ pr2serr_lk("tid=%d, has_share=%s\n", rep->id,
+ (rep->has_share ? "true" : "false"));
+ share_and_ofreg = (rep->has_share && (rep->outregfd >= 0));
+
+ /* vvvvvvvvvvvvvv Main segment copy loop vvvvvvvvvvvvvvvvvvvvvvv */
+ while (1) {
+ rep->wr = false;
+ my_index = atomic_fetch_add(&pos_index, (long int)clp->bpt);
+ /* Start of READ half of a segment */
+ buffp_onto_next(rep);
+ status = pthread_mutex_lock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "lock in_mutex");
+
+ if (dd_count >= 0) {
+ if (my_index >= dd_count) {
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+ if ((clp->nmrqs > 0) && (deferred_arr.first.size() > 0)) {
+ if (vb > 2)
+ pr2serr_lk("thread=%d: tail-end my_index>=dd_count, "
+ "to_do=%u\n", rep->id,
+ (uint32_t)deferred_arr.first.size());
+ res = sgh_do_deferred_mrq(rep, deferred_arr);
+ if (res)
+ pr2serr_lk("%s tid=%d: sgh_do_deferred_mrq failed\n",
+ __func__, rep->id);
+ }
+ break; /* at or beyond end, so leave loop >>>>>>>>>> */
+ } else if ((my_index + clp->bpt) > dd_count)
+ blocks = dd_count - my_index;
+ else
+ blocks = clp->bpt;
+ } else
+ blocks = clp->bpt;
+
+ rep->iblk = clp->skip + my_index;
+ rep->oblk = clp->seek + my_index;
+ rep->num_blks = blocks;
+
+ // clp->in_blk += blocks;
+ // clp->in_count -= blocks;
+
+ pthread_cleanup_push(cleanup_in, (void *)clp);
+ if (in_is_sg)
+ sg_in_rd_cmd(clp, rep, deferred_arr);
+ else {
+ stop_after_write = normal_in_rd(rep, blocks);
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+ }
+ if (c_addr && (rep->bs > 3)) {
+ int k, j, off, num;
+ uint32_t addr = (uint32_t)rep->iblk;
+
+ num = (1 == c_addr) ? 4 : (rep->bs - 3);
+ for (k = 0, off = 0; k < blocks; ++k, ++addr, off += rep->bs) {
+ for (j = 0; j < num; j += 4) {
+ if (addr != sg_get_unaligned_be32(rep->buffp + off + j))
+ break;
+ }
+ if (j < num)
+ break;
+ }
+ if (k < blocks) {
+ pr2serr("%s: chkaddr failure at addr=0x%x\n", __func__, addr);
+ exit_status = SG_LIB_CAT_MISCOMPARE;
+ ++num_miscompare;
+ stop_both(clp);
+ }
+ }
+ pthread_cleanup_pop(0);
+ ++rep->rep_count;
+
+ /* Start of WRITE part of a segment */
+ rep->wr = true;
+ status = pthread_mutex_lock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "lock out_mutex");
+
+ /* Make sure the OFILE (+ OFREG) are in same sequence as IFILE */
+ if (clp->in_flags.random)
+ goto skip_force_out_sequence;
+ if ((rep->outregfd < 0) && in_is_sg && out_is_sg)
+ goto skip_force_out_sequence;
+ if (share_and_ofreg || (FT_DEV_NULL != clp->out_type)) {
+ while ((! clp->out_stop.load()) &&
+ (rep->oblk != clp->out_blk.load())) {
+ /* if write would be out of sequence then wait */
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ status = pthread_cond_wait(&clp->out_sync_cv, &clp->out_mutex);
+ if (0 != status) err_exit(status, "cond out_sync_cv");
+ pthread_cleanup_pop(0);
+ }
+ }
+
+skip_force_out_sequence:
+ if (clp->out_stop.load() || (clp->out_count.load() <= 0)) {
+ if (! clp->out_stop.load())
+ clp->out_stop = true;
+ status = pthread_mutex_unlock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ break; /* stop requested so leave loop >>>>>>>>>> */
+ }
+ if (stop_after_write)
+ clp->out_stop = true;
+
+ clp->out_count -= blocks;
+ clp->out_blk += blocks;
+
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ if (rep->outregfd >= 0) {
+ res = write(rep->outregfd, get_buffp(rep),
+ rep->bs * rep->num_blks);
+ err = errno;
+ if (res < 0)
+ pr2serr_lk("%s: tid=%d: write(outregfd) failed: %s\n",
+ __func__, rep->id, strerror(err));
+ else if (vb > 9)
+ pr2serr_lk("%s: tid=%d: write(outregfd), fd=%d, num_blks=%d"
+ "\n", __func__, rep->id, rep->outregfd,
+ rep->num_blks);
+ }
+ /* Output to OFILE */
+ wr_blks = rep->num_blks;
+ if (out_is_sg) {
+ sg_out_wr_cmd(rep, deferred_arr, false, clp->prefetch);
+ ++rep->rep_count;
+ } else if (FT_DEV_NULL == clp->out_type) {
+ /* skip actual write operation */
+ wr_blks = 0;
+ clp->out_rem_count -= blocks;
+ status = pthread_mutex_unlock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ } else {
+ normal_out_wr(rep, blocks);
+ status = pthread_mutex_unlock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ ++rep->rep_count;
+ }
+ pthread_cleanup_pop(0);
+
+ /* Output to OFILE2 if sg device */
+ if ((clp->out2fd >= 0) && (FT_SG == clp->out2_type)) {
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ status = pthread_mutex_lock(&clp->out2_mutex);
+ if (0 != status) err_exit(status, "lock out2_mutex");
+ /* releases out2_mutex mid operation */
+ sg_out_wr_cmd(rep, deferred_arr, true, false);
+
+ pthread_cleanup_pop(0);
+ }
+ if (0 == rep->num_blks) {
+ if ((clp->nmrqs > 0) && (deferred_arr.first.size() > 0)) {
+ if (wr_blks > 0)
+ rep->out_mrq_q_blks += wr_blks;
+ if (vb > 2)
+ pr2serr_lk("thread=%d: tail-end, to_do=%u\n", rep->id,
+ (uint32_t)deferred_arr.first.size());
+ res = sgh_do_deferred_mrq(rep, deferred_arr);
+ if (res)
+ pr2serr_lk("%s tid=%d: sgh_do_deferred_mrq failed\n",
+ __func__, rep->id);
+ }
+ clp->out_stop = true;
+ stop_after_write = true;
+ break; /* read nothing so leave loop >>>>>>>>>> */
+ }
+ // if ((! rep->has_share) && (FT_DEV_NULL != clp->out_type))
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ if (stop_after_write)
+ break; /* leaving main loop >>>>>>>>> */
+ } /* ^^^^^^^^^^ end of main while loop which copies segments ^^^^^^ */
+
+ status = pthread_mutex_lock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "lock in_mutex");
+ if (! clp->in_stop.load())
+ clp->in_stop = true; /* flag other workers to stop */
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+
+fini:
+ if ((1 == rep->mmap_active) && (rep->mmap_len > 0)) {
+ if (munmap(rep->buffp, rep->mmap_len) < 0) {
+ err = errno;
+ char bb[STRERR_BUFF_LEN + 1];
+
+ pr2serr_lk("thread=%d: munmap() failed: %s\n", rep->id,
+ tsafe_strerror(err, bb));
+ }
+ if (vb > 4)
+ pr2serr_lk("thread=%d: munmap(%p, %d)\n", rep->id, rep->buffp,
+ rep->mmap_len);
+ rep->mmap_active = 0;
+ }
+ if (rep->alloc_bp) {
+ free(rep->alloc_bp);
+ rep->alloc_bp = NULL;
+ rep->buffp = NULL;
+ }
+
+ if (sg_version_ge_40045) {
+ if (clp->noshare) {
+ if ((clp->nmrqs > 0) && clp->unshare)
+ sg_unshare(rep->infd, rep->id, vb > 9);
+ } else if (in_is_sg && out_is_sg)
+ if (clp->unshare)
+ sg_unshare(rep->infd, rep->id, vb > 9);
+ }
+ if (own_infd && (rep->infd >= 0)) {
+ if (vb && in_is_sg) {
+ ++num_waiting_calls;
+ if (ioctl(rep->infd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior close(in)\n",
+ __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->infd);
+ }
+ if (own_outfd && (rep->outfd >= 0)) {
+ if (vb && out_is_sg) {
+ ++num_waiting_calls;
+ if (ioctl(rep->outfd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior "
+ "close(out)\n", __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->outfd);
+ }
+ if (own_out2fd && (rep->out2fd >= 0))
+ close(rep->out2fd);
+ pthread_cond_broadcast(&clp->out_sync_cv);
+ return stop_after_write ? NULL : clp;
+}
+
+/* N.B. A return of true means it wants to stop the copy. So false is the
+ * 'good' reply (i.e. keep going). */
+static bool
+normal_in_rd(Rq_elem * rep, int blocks)
+{
+ struct global_collection * clp = rep->clp;
+ bool stop_after_write = false;
+ bool same_fds = clp->in_flags.same_fds || clp->out_flags.same_fds;
+ int res;
+
+ if (clp->verbose > 4)
+ pr2serr_lk("%s: tid=%d: iblk=%" PRIu64 ", blocks=%d\n", __func__,
+ rep->id, rep->iblk, blocks);
+ if (FT_RANDOM_0_FF == clp->in_type) {
+ int k, j;
+ const int jbump = sizeof(uint32_t);
+ long rn;
+ uint8_t * bp;
+
+ if (clp->in_flags.zero && clp->in_flags.ff && (rep->bs >= 4)) {
+ uint32_t pos = (uint32_t)rep->iblk;
+ uint32_t off;
+
+ for (k = 0, off = 0; k < blocks; ++k, off += rep->bs, ++pos) {
+ for (j = 0; j < (rep->bs - 3); j += 4)
+ sg_put_unaligned_be32(pos, rep->buffp + off + j);
+ }
+ } else if (clp->in_flags.zero)
+ memset(rep->buffp, 0, blocks * rep->bs);
+ else if (clp->in_flags.ff)
+ memset(rep->buffp, 0xff, blocks * rep->bs);
+ else {
+ for (k = 0, bp = rep->buffp; k < blocks; ++k, bp += rep->bs) {
+ for (j = 0; j < rep->bs; j += jbump) {
+ /* mrand48 takes uniformly from [-2^31, 2^31) */
+#ifdef HAVE_SRAND48_R
+ mrand48_r(&rep->drand, &rn);
+#else
+ rn = mrand48();
+#endif
+ *((uint32_t *)(bp + j)) = (uint32_t)rn;
+ }
+ }
+ }
+ clp->in_rem_count -= blocks;
+ return stop_after_write;
+ }
+ if (! same_fds) { /* each has own file pointer, so we need to move it */
+ int64_t pos = rep->iblk * rep->bs;
+
+ if (lseek64(rep->infd, pos, SEEK_SET) < 0) { /* problem if pipe! */
+ pr2serr_lk("%s: tid=%d: >> lseek64(%" PRId64 "): %s\n", __func__,
+ rep->id, pos, safe_strerror(errno));
+ stop_both(clp);
+ return true;
+ }
+ }
+ /* enters holding in_mutex */
+ while (((res = read(clp->infd, rep->buffp, blocks * rep->bs)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ char strerr_buff[STRERR_BUFF_LEN + 1];
+
+ if (clp->in_flags.coe) {
+ memset(rep->buffp, 0, rep->num_blks * rep->bs);
+ pr2serr_lk("tid=%d: >> substituted zeros for in blk=%" PRId64
+ " for %d bytes, %s\n", rep->id, rep->iblk,
+ rep->num_blks * rep->bs,
+ tsafe_strerror(errno, strerr_buff));
+ res = rep->num_blks * rep->bs;
+ }
+ else {
+ pr2serr_lk("tid=%d: error in normal read, %s\n", rep->id,
+ tsafe_strerror(errno, strerr_buff));
+ stop_both(clp);
+ return true;
+ }
+ }
+ if (res < blocks * rep->bs) {
+ // int o_blocks = blocks;
+
+ stop_after_write = true;
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->in_partial++;
+ }
+ /* Reverse out + re-apply blocks on clp */
+ // clp->in_blk -= o_blocks;
+ // clp->in_count += o_blocks;
+ rep->num_blks = blocks;
+ // clp->in_blk += blocks;
+ // clp->in_count -= blocks;
+ }
+ clp->in_rem_count -= blocks;
+ return stop_after_write;
+}
+
+static void
+normal_out_wr(Rq_elem * rep, int blocks)
+{
+ int res;
+ struct global_collection * clp = rep->clp;
+
+ /* enters holding out_mutex */
+ if (clp->verbose > 4)
+ pr2serr_lk("%s: tid=%d: oblk=%" PRIu64 ", blocks=%d\n", __func__,
+ rep->id, rep->oblk, blocks);
+ while (((res = write(clp->outfd, rep->buffp, blocks * rep->bs))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ char strerr_buff[STRERR_BUFF_LEN + 1];
+
+ if (clp->out_flags.coe) {
+ pr2serr_lk("tid=%d: >> ignored error for out blk=%" PRId64
+ " for %d bytes, %s\n", rep->id, rep->oblk,
+ rep->num_blks * rep->bs,
+ tsafe_strerror(errno, strerr_buff));
+ res = rep->num_blks * rep->bs;
+ }
+ else {
+ pr2serr_lk("tid=%d: error normal write, %s\n", rep->id,
+ tsafe_strerror(errno, strerr_buff));
+ stop_both(clp);
+ return;
+ }
+ }
+ if (res < blocks * rep->bs) {
+ blocks = res / rep->bs;
+ if ((res % rep->bs) > 0) {
+ blocks++;
+ clp->out_partial++;
+ }
+ rep->num_blks = blocks;
+ }
+ clp->out_rem_count -= blocks;
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool ver_true, bool write_true,
+ bool fua, bool dpo)
+{
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int ve_opcode[] = {0xff /* no VER(6) */, 0x2f, 0xaf, 0x8f};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+ int sz_ind;
+
+ memset(cdbp, 0, cdb_sz);
+ if (ver_true) { /* only support VERIFY(10) */
+ if (cdb_sz < 10) {
+ pr2serr_lk("%sonly support VERIFY(10)\n", my_name);
+ return 1;
+ }
+ cdb_sz = 10;
+ fua = false;
+ cdbp[1] |= 0x2; /* BYTCHK=1 --> sending dout for comparison */
+ cdbp[0] = ve_opcode[1];
+ }
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr_lk("%sfor 6 byte commands, maximum number of blocks is "
+ "256\n", my_name);
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr_lk("%sfor 6 byte commands, can't address blocks beyond "
+ "%d\n", my_name, 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr_lk("%sfor 6 byte commands, neither dpo nor fua bits "
+ "supported\n", my_name);
+ return 1;
+ }
+ break;
+ case 10:
+ if (! ver_true) {
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ }
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr_lk("%sfor 10 byte commands, maximum number of blocks is "
+ "%d\n", my_name, 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr_lk("%sexpected cdb size of 6, 10, 12, or 16 but got %d\n",
+ my_name, cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+/* Enters this function holding in_mutex */
+static void
+sg_in_rd_cmd(struct global_collection * clp, Rq_elem * rep,
+ mrq_arr_t & def_arr)
+{
+ int status, pack_id;
+
+ while (1) {
+ int res = sg_start_io(rep, def_arr, pack_id, NULL);
+
+ if (1 == res)
+ err_exit(ENOMEM, "sg starting in command");
+ else if (res < 0) {
+ pr2serr_lk("tid=%d: inputting to sg failed, blk=%" PRId64 "\n",
+ rep->id, rep->iblk);
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+ stop_both(clp);
+ return;
+ }
+ /* Now release in mutex to let other reads run in parallel */
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+
+ res = sg_finish_io(rep->wr, rep, pack_id, NULL);
+ switch (res) {
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ /* try again with same addr, count info */
+ /* now re-acquire in mutex for balance */
+ /* N.B. This re-read could now be out of read sequence */
+ status = pthread_mutex_lock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "lock in_mutex");
+ break; /* will loop again */
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (0 == clp->in_flags.coe) {
+ pr2serr_lk("error finishing sg in command (medium)\n");
+ if (exit_status <= 0)
+ exit_status = res;
+ stop_both(clp);
+ return;
+ } else {
+ memset(get_buffp(rep), 0, rep->num_blks * rep->bs);
+ pr2serr_lk("tid=%d: >> substituted zeros for in blk=%" PRId64
+ " for %d bytes\n", rep->id, rep->iblk,
+ rep->num_blks * rep->bs);
+ }
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case 0:
+ status = pthread_mutex_lock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "lock in_mutex");
+ if (rep->dio_incomplete_count || rep->resid) {
+ clp->dio_incomplete_count += rep->dio_incomplete_count;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->in_rem_count -= rep->num_blks;
+ status = pthread_mutex_unlock(&clp->in_mutex);
+ if (0 != status) err_exit(status, "unlock in_mutex");
+ return;
+ default:
+ pr2serr_lk("tid=%d: error finishing sg in command (%d)\n",
+ rep->id, res);
+ if (exit_status <= 0)
+ exit_status = res;
+ stop_both(clp);
+ return;
+ }
+ } /* end of while (1) loop */
+}
+
+static bool
+sg_wr_swap_share(Rq_elem * rep, int to_fd, bool before)
+{
+ bool not_first = false;
+ int err = 0;
+ int k;
+ int read_side_fd = rep->infd;
+ struct global_collection * clp = rep->clp;
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ if (rep->clp->verbose > 2)
+ pr2serr_lk("%s: tid=%d: to_fd=%d, before=%d\n", __func__, rep->id,
+ to_fd, (int)before);
+ seip->sei_wr_mask |= SG_SEIM_CHG_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_CHG_SHARE_FD;
+ seip->share_fd = to_fd;
+ if (before) {
+ /* clear READ_SIDE_FINI bit: puts read-side in SG_RQ_SHR_SWAP state */
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_READ_SIDE_FINI;
+ seip->ctl_flags &= ~SG_CTL_FLAGM_READ_SIDE_FINI;/* would be 0 anyway */
+ }
+ for (k = 0; (ioctl(read_side_fd, SG_SET_GET_EXTENDED, seip) < 0) &&
+ (EBUSY == errno); ++k) {
+ err = errno;
+ if (k > 10000)
+ break;
+ if (! not_first) {
+ if (clp->verbose > 3)
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), "
+ "failed errno=%d %s\n", rep->id, read_side_fd, err,
+ strerror(err));
+ not_first = true;
+ }
+ err = 0;
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ if (err) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), failed "
+ "errno=%d %s\n", rep->id, read_side_fd, err, strerror(err));
+ return false;
+ }
+ if (clp->verbose > 15)
+ pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(change_shared_fd)) ok, "
+ "read_side_fd=%d, to_write_side_fd=%d\n", __func__, rep->id,
+ read_side_fd, to_fd);
+ return true;
+}
+
+/* Enters this function holding out_mutex */
+static void
+sg_out_wr_cmd(Rq_elem * rep, mrq_arr_t & def_arr, bool is_wr2, bool prefetch)
+{
+ int res, status, pack_id, nblks;
+ struct global_collection * clp = rep->clp;
+ uint32_t ofsplit = clp->ofsplit;
+ pthread_mutex_t * mutexp = is_wr2 ? &clp->out2_mutex : &clp->out_mutex;
+ struct sg_io_extra xtr {};
+ struct sg_io_extra * xtrp = &xtr;
+ const char * wr_or_ver = clp->verify ? "verify" : "out";
+
+ xtrp->is_wr2 = is_wr2;
+ xtrp->prefetch = prefetch;
+ nblks = rep->num_blks;
+ if (rep->has_share && is_wr2)
+ sg_wr_swap_share(rep, rep->out2fd, true);
+
+ if (prefetch) {
+again:
+ res = sg_start_io(rep, def_arr, pack_id, xtrp);
+ if (1 == res)
+ err_exit(ENOMEM, "sg starting out command");
+ else if (res < 0) {
+ pr2serr_lk("%ssg %s failed, blk=%" PRId64 "\n",
+ my_name, wr_or_ver, rep->oblk);
+ status = pthread_mutex_unlock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ stop_both(clp);
+ goto fini;
+ }
+ /* Now release in mutex to let other reads run in parallel */
+ status = pthread_mutex_unlock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+
+ res = sg_finish_io(rep->wr, rep, pack_id, xtrp);
+ switch (res) {
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ /* try again with same addr, count info */
+ /* now re-acquire out mutex for balance */
+ /* N.B. This re-write could now be out of write sequence */
+ status = pthread_mutex_lock(mutexp);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ goto again;
+ case SG_LIB_CAT_CONDITION_MET:
+ case 0:
+ status = pthread_mutex_lock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ break;
+ default:
+ pr2serr_lk("error finishing sg prefetch command (%d)\n", res);
+ if (exit_status <= 0)
+ exit_status = res;
+ stop_both(clp);
+ goto fini;
+ }
+ }
+
+ /* start write (or verify) on current segment on sg device */
+ xtrp->prefetch = false;
+ if ((ofsplit > 0) && (rep->num_blks > (int)ofsplit)) {
+ xtrp->dout_is_split = true;
+ xtrp->blk_offset = 0;
+ xtrp->blks = ofsplit;
+ nblks = ofsplit;
+ xtrp->hpv4_ind = 0;
+ }
+split_upper:
+ while (1) {
+ res = sg_start_io(rep, def_arr, pack_id, xtrp);
+ if (1 == res)
+ err_exit(ENOMEM, "sg starting out command");
+ else if (res < 0) {
+ pr2serr_lk("%ssg %s failed, blk=%" PRId64 "\n", my_name,
+ wr_or_ver, rep->oblk);
+ status = pthread_mutex_unlock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ stop_both(clp);
+ goto fini;
+ }
+ /* Now release in mutex to let other reads run in parallel */
+ status = pthread_mutex_unlock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+
+ res = sg_finish_io(rep->wr, rep, pack_id, xtrp);
+ switch (res) {
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ /* try again with same addr, count info */
+ /* now re-acquire out mutex for balance */
+ /* N.B. This re-write could now be out of write sequence */
+ status = pthread_mutex_lock(mutexp);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ break; /* loops around */
+ case SG_LIB_CAT_MEDIUM_HARD:
+ if (0 == clp->out_flags.coe) {
+ pr2serr_lk("error finishing sg %s command (medium)\n",
+ wr_or_ver);
+ if (exit_status <= 0)
+ exit_status = res;
+ stop_both(clp);
+ goto fini;
+ } else
+ pr2serr_lk(">> ignored error for %s blk=%" PRId64 " for %d "
+ "bytes\n", wr_or_ver, rep->oblk, nblks * rep->bs);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CONDITION_MET:
+ case 0:
+ if (! is_wr2) {
+ status = pthread_mutex_lock(mutexp);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ if (rep->dio_incomplete_count || rep->resid) {
+ clp->dio_incomplete_count += rep->dio_incomplete_count;
+ clp->sum_of_resids += rep->resid;
+ }
+ clp->out_rem_count -= nblks;
+ status = pthread_mutex_unlock(mutexp);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+ }
+ goto fini;
+ case SG_LIB_CAT_MISCOMPARE:
+ ++num_miscompare;
+ // fall through
+ default:
+ pr2serr_lk("error finishing sg %s command (%d)\n", wr_or_ver,
+ res);
+ if (exit_status <= 0)
+ exit_status = res;
+ stop_both(clp);
+ goto fini;
+ }
+ } /* end of while (1) loop */
+fini:
+ if (xtrp->dout_is_split) { /* set up upper half of split */
+ if ((0 == xtrp->hpv4_ind) && (rep->num_blks > (int)ofsplit)) {
+ xtrp->hpv4_ind = 1;
+ xtrp->blk_offset = ofsplit;
+ xtrp->blks = rep->num_blks - ofsplit;
+ nblks = xtrp->blks;
+ status = pthread_mutex_lock(mutexp);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ goto split_upper;
+ }
+ }
+ if (rep->has_share && is_wr2)
+ sg_wr_swap_share(rep, rep->outfd, false);
+}
+
+static int
+process_mrq_response(Rq_elem * rep, const struct sg_io_v4 * ctl_v4p,
+ const struct sg_io_v4 * a_v4p, int num_mrq,
+ uint32_t & good_inblks, uint32_t & good_outblks)
+{
+ struct global_collection * clp = rep->clp;
+ bool ok, all_good;
+ bool sb_in_co = !!(ctl_v4p->response);
+ int id = rep->id;
+ int resid = ctl_v4p->din_resid;
+ int sres = ctl_v4p->spare_out;
+ int n_subm = num_mrq - ctl_v4p->dout_resid;
+ int n_cmpl = ctl_v4p->info;
+ int n_good = 0;
+ int hole_count = 0;
+ int vb = clp->verbose;
+ int k, j, f1, slen, blen;
+ char b[160];
+
+ blen = sizeof(b);
+ good_inblks = 0;
+ good_outblks = 0;
+ if (vb > 2)
+ pr2serr_lk("[thread_id=%d] %s: num_mrq=%d, n_subm=%d, n_cmpl=%d\n",
+ id, __func__, num_mrq, n_subm, n_cmpl);
+ if (n_subm < 0) {
+ pr2serr_lk("[%d] co.dout_resid(%d) > num_mrq(%d)\n", id,
+ ctl_v4p->dout_resid, num_mrq);
+ return -1;
+ }
+ if (n_cmpl != (num_mrq - resid))
+ pr2serr_lk("[%d] co.info(%d) != (num_mrq(%d) - co.din_resid(%d))\n"
+ "will use co.info\n", id, n_cmpl, num_mrq, resid);
+ if (n_cmpl > n_subm) {
+ pr2serr_lk("[%d] n_cmpl(%d) > n_subm(%d), use n_subm for both\n",
+ id, n_cmpl, n_subm);
+ n_cmpl = n_subm;
+ }
+ if (sres) {
+ pr2serr_lk("[%d] secondary error: %s [%d], info=0x%x\n", id,
+ strerror(sres), sres, ctl_v4p->info);
+ if (E2BIG == sres) {
+ sg_take_snap(rep->infd, id, true);
+ sg_take_snap(rep->outfd, id, true);
+ }
+ }
+ /* Check if those submitted have finished or not. N.B. If there has been
+ * an error then there may be "holes" (i.e. info=0x0) in the array due
+ * to completions being out-of-order. */
+ for (k = 0, j = 0; ((k < num_mrq) && (j < n_subm));
+ ++k, j += f1, ++a_v4p) {
+ slen = a_v4p->response_len;
+ if (! (SG_INFO_MRQ_FINI & a_v4p->info))
+ ++hole_count;
+ ok = true;
+ f1 = !!(a_v4p->info); /* want to skip n_subm count if info is 0x0 */
+ if (SG_INFO_CHECK & a_v4p->info) {
+ ok = false;
+ pr2serr_lk("[%d] a_v4[%d]: SG_INFO_CHECK set [%s]\n", id, k,
+ sg_info_str(a_v4p->info, sizeof(b), b));
+ }
+ if (sg_scsi_status_is_bad(a_v4p->device_status) ||
+ a_v4p->transport_status || a_v4p->driver_status) {
+ ok = false;
+ if (SAM_STAT_CHECK_CONDITION != a_v4p->device_status) {
+ pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
+ if (vb)
+ lk_chk_n_print4(" >>", a_v4p, vb > 4);
+ }
+ }
+ if (slen > 0) {
+ struct sg_scsi_sense_hdr ssh;
+ const uint8_t *sbp = (const uint8_t *)
+ (sb_in_co ? ctl_v4p->response : a_v4p->response);
+
+ if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
+ (ssh.response_code >= 0x70)) {
+ if (ssh.response_code & 0x1)
+ ok = true;
+ if (vb) {
+ sg_get_sense_str(" ", sbp, slen, false, blen, b);
+ pr2serr_lk("[%d] a_v4[%d]:\n%s\n", id, k, b);
+ }
+ }
+ }
+ if (ok && f1) {
+ ++n_good;
+ if (a_v4p->dout_xfer_len >= (uint32_t)rep->bs)
+ good_outblks += (a_v4p->dout_xfer_len - a_v4p->dout_resid) /
+ rep->bs;
+ if (a_v4p->din_xfer_len >= (uint32_t)rep->bs)
+ good_inblks += (a_v4p->din_xfer_len - a_v4p->din_resid) /
+ rep->bs;
+ }
+ } /* end of request array scan loop */
+ if ((n_subm == num_mrq) || (vb < 3))
+ goto fini;
+ pr2serr_lk("[%d] checking response array _beyond_ number of "
+ "submissions [%d] to num_mrq:\n", id, k);
+ for (all_good = true; k < num_mrq; ++k, ++a_v4p) {
+ if (SG_INFO_MRQ_FINI & a_v4p->info) {
+ pr2serr_lk("[%d] a_v4[%d]: unexpected SG_INFO_MRQ_FINI set [%s]\n",
+ id, k, sg_info_str(a_v4p->info, sizeof(b), b));
+ all_good = false;
+ }
+ if (a_v4p->device_status || a_v4p->transport_status ||
+ a_v4p->driver_status) {
+ pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
+ lk_chk_n_print4(" ", a_v4p, vb > 4);
+ all_good = false;
+ }
+ }
+ if (all_good)
+ pr2serr_lk(" ... all good\n");
+fini:
+ return n_good;
+}
+
+#if 0
+static int
+chk_mrq_response(Rq_elem * rep, const struct sg_io_v4 * ctl_v4p,
+ const struct sg_io_v4 * a_v4p, int nrq,
+ uint32_t * good_inblksp, uint32_t * good_outblksp)
+{
+ struct global_collection * clp = rep->clp;
+ bool ok;
+ int id = rep->id;
+ int resid = ctl_v4p->din_resid;
+ int sres = ctl_v4p->spare_out;
+ int n_subm = nrq - ctl_v4p->dout_resid;
+ int n_cmpl = ctl_v4p->info;
+ int n_good = 0;
+ int vb = clp->verbose;
+ int k, slen, blen;
+ uint32_t good_inblks = 0;
+ uint32_t good_outblks = 0;
+ const struct sg_io_v4 * a_np = a_v4p;
+ char b[80];
+
+ blen = sizeof(b);
+ if (n_subm < 0) {
+ pr2serr_lk("[%d] %s: co.dout_resid(%d) > nrq(%d)\n", id, __func__,
+ ctl_v4p->dout_resid, nrq);
+ return -1;
+ }
+ if (n_cmpl != (nrq - resid))
+ pr2serr_lk("[%d] %s: co.info(%d) != (nrq(%d) - co.din_resid(%d))\n"
+ "will use co.info\n", id, __func__, n_cmpl, nrq, resid);
+ if (n_cmpl > n_subm) {
+ pr2serr_lk("[%d] %s: n_cmpl(%d) > n_subm(%d), use n_subm for both\n",
+ id, __func__, n_cmpl, n_subm);
+ n_cmpl = n_subm;
+ }
+ if (sres) {
+ pr2serr_lk("[%d] %s: secondary error: %s [%d], info=0x%x\n", id,
+ __func__, strerror(sres), sres, ctl_v4p->info);
+ if (E2BIG == sres) {
+ sg_take_snap(rep->infd, id, true);
+ sg_take_snap(rep->outfd, id, true);
+ }
+ }
+ /* Check if those submitted have finished or not */
+ for (k = 0; k < n_subm; ++k, ++a_np) {
+ slen = a_np->response_len;
+ if (! (SG_INFO_MRQ_FINI & a_np->info)) {
+ pr2serr_lk("[%d] %s, a_n[%d]: missing SG_INFO_MRQ_FINI [%s]\n",
+ id, __func__, k, sg_info_str(a_np->info, blen, b));
+ v4hdr_out_lk("a_np", a_np, id);
+ v4hdr_out_lk("cop", ctl_v4p, id);
+ }
+ ok = true;
+ if (SG_INFO_CHECK & a_np->info) {
+ ok = false;
+ pr2serr_lk("[%d] a_n[%d]: SG_INFO_CHECK set [%s]\n", id, k,
+ sg_info_str(a_np->info, sizeof(b), b));
+ }
+ if (sg_scsi_status_is_bad(a_np->device_status) ||
+ a_np->transport_status || a_np->driver_status) {
+ ok = false;
+ if (SAM_STAT_CHECK_CONDITION != a_np->device_status) {
+ pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k);
+ if (vb)
+ lk_chk_n_print4(" >>", a_np, false);
+ }
+ }
+ if (slen > 0) {
+ struct sg_scsi_sense_hdr ssh;
+ const uint8_t *sbp = (const uint8_t *)a_np->response;
+
+ if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
+ (ssh.response_code >= 0x70)) {
+ char b[256];
+
+ if (ssh.response_code & 0x1)
+ ok = true;
+ if (vb) {
+ sg_get_sense_str(" ", sbp, slen, false, sizeof(b), b);
+ pr2serr_lk("[%d] %s, a_n[%d]:\n%s\n", id, __func__, k, b);
+ }
+ }
+ }
+ if (ok) {
+ ++n_good;
+ if (a_np->dout_xfer_len >= (uint32_t)rep->bs)
+ good_outblks += (a_np->dout_xfer_len - a_np->dout_resid) /
+ rep->bs;
+ if (a_np->din_xfer_len >= (uint32_t)rep->bs)
+ good_inblks += (a_np->din_xfer_len - a_np->din_resid) /
+ rep->bs;
+ }
+ }
+ if ((n_subm == nrq) || (vb < 3))
+ goto fini;
+ pr2serr_lk("[%d] %s: checking response array beyond number of "
+ "submissions:\n", id, __func__);
+ for (k = n_subm; k < nrq; ++k, ++a_np) {
+ if (SG_INFO_MRQ_FINI & a_np->info)
+ pr2serr_lk("[%d] %s, a_n[%d]: unexpected SG_INFO_MRQ_FINI set\n",
+ id, __func__, k);
+ if (a_np->device_status || a_np->transport_status ||
+ a_np->driver_status) {
+ pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k);
+ lk_chk_n_print4(" ", a_np, vb > 4);
+ }
+ }
+fini:
+ if (good_inblksp)
+ *good_inblksp = good_inblks;
+ if (good_outblksp)
+ *good_outblksp = good_outblks;
+ return n_good;
+}
+#endif
+
+/* do mrq 'full non-blocking' invocation so both submission and completion
+ * is async (i.e. uses SGV4_FLAG_IMMED flag). This type of mrq is
+ * restricted to a single file descriptor (i.e. the 'fd' argument). */
+static int
+sgh_do_async_mrq(Rq_elem * rep, mrq_arr_t & def_arr, int fd,
+ struct sg_io_v4 * ctlop, int nrq)
+{
+ int half = nrq / 2;
+ int k, res, nwait, half_num, rest, err, num_good, b_len;
+ const int64_t wait_us = 10;
+ uint32_t in_fin_blks, out_fin_blks;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 hold_ctlo;
+ struct global_collection * clp = rep->clp;
+ char b[80];
+
+ hold_ctlo = *ctlop;
+ b_len = sizeof(b);
+ a_v4p = def_arr.first.data();
+ ctlop->flags = SGV4_FLAG_MULTIPLE_REQS;
+ if (clp->in_flags.polled || clp->out_flags.polled) {
+ /* submit of full non-blocking with POLLED */
+ ctlop->flags |= (SGV4_FLAG_IMMED | SGV4_FLAG_POLLED);
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_s_nb_s);
+ }
+ } else {
+ ctlop->flags |= SGV4_FLAG_IMMED; /* submit non-blocking */
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_s_nb_s);
+ }
+ }
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object _before_ ioctl(SG_IOSUBMIT):\n",
+ __func__);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)ctlop, sizeof(*ctlop), 1);
+ v4hdr_out_lk("Controlling object before", ctlop, rep->id);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, ctlop);
+ if (res < 0) {
+ err = errno;
+ if (E2BIG == err)
+ sg_take_snap(fd, rep->id, true);
+ pr2serr_lk("%s: ioctl(SG_IOSUBMIT, %s)-->%d, errno=%d: %s\n", __func__,
+ sg_flags_str(ctlop->flags, b_len, b), res, err,
+ strerror(err));
+ return -1;
+ }
+ /* fetch first half */
+ for (k = 0; k < 100000; ++k) {
+ ++num_waiting_calls;
+ res = ioctl(fd, SG_GET_NUM_WAITING, &nwait);
+ if (res < 0) {
+ err = errno;
+ pr2serr_lk("%s: ioctl(SG_GET_NUM_WAITING)-->%d, errno=%d: %s\n",
+ __func__, res, err, strerror(err));
+ return -1;
+ }
+ if (nwait >= half)
+ break;
+ this_thread::sleep_for(chrono::microseconds{wait_us});
+ }
+ ctlop->flags = (SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_IMMED);
+ res = ioctl(fd, SG_IORECEIVE, ctlop);
+ if (res < 0) {
+ err = errno;
+ if (ENODATA != err) {
+ pr2serr_lk("%s: ioctl(SG_IORECEIVE, %s),1-->%d, errno=%d: %s\n",
+ __func__, sg_flags_str(ctlop->flags, b_len, b), res,
+ err, strerror(err));
+ return -1;
+ }
+ half_num = 0;
+ } else
+ half_num = ctlop->info;
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object output by ioctl(SG_IORECEIVE),1: "
+ "num_received=%d\n", __func__, half_num);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)ctlop, sizeof(*ctlop), 1);
+ v4hdr_out_lk("Controlling object after", ctlop, rep->id);
+ if (clp->verbose > 5) {
+ for (k = 0; k < half_num; ++k) {
+ pr2serr_lk("AFTER: def_arr[%d]:\n", k);
+ v4hdr_out_lk("normal v4 object", (a_v4p + k), rep->id);
+ // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p),
+ // 1);
+ }
+ }
+ }
+ in_fin_blks = 0;
+ out_fin_blks = 0;
+ num_good = process_mrq_response(rep, ctlop, a_v4p, half_num, in_fin_blks,
+ out_fin_blks);
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: >>>1 num_good=%d, in_q/fin blks=%u/%u; out_q/fin "
+ "blks=%u/%u\n", __func__, num_good, rep->in_mrq_q_blks,
+ in_fin_blks, rep->out_mrq_q_blks, out_fin_blks);
+
+ if (num_good < 0)
+ res = -1;
+ else if (num_good < half_num) {
+ int resid_blks = rep->in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ gcoll.in_rem_count += resid_blks;
+ resid_blks = rep->out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0)
+ gcoll.out_rem_count += resid_blks;
+
+ return -1;
+ }
+
+ rest = nrq - half_num;
+ if (rest < 1)
+ goto fini;
+ /* fetch remaining */
+ for (k = 0; k < 100000; ++k) {
+ ++num_waiting_calls;
+ res = ioctl(fd, SG_GET_NUM_WAITING, &nwait);
+ if (res < 0) {
+ pr2serr_lk("%s: ioctl(SG_GET_NUM_WAITING)-->%d, errno=%d: %s\n",
+ __func__, res, errno, strerror(errno));
+ return -1;
+ }
+ if (nwait >= rest)
+ break;
+ this_thread::sleep_for(chrono::microseconds{wait_us});
+ }
+ ctlop = &hold_ctlo;
+ ctlop->din_xferp += (half_num * sizeof(struct sg_io_v4));
+ ctlop->din_xfer_len -= (half_num * sizeof(struct sg_io_v4));
+ ctlop->dout_xferp = ctlop->din_xferp;
+ ctlop->dout_xfer_len = ctlop->din_xfer_len;
+ ctlop->flags = (SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_IMMED);
+ res = ioctl(fd, SG_IORECEIVE, ctlop);
+ if (res < 0) {
+ err = errno;
+ if (ENODATA != err) {
+ pr2serr_lk("%s: ioctl(SG_IORECEIVE, %s),2-->%d, errno=%d: %s\n",
+ __func__, sg_flags_str(ctlop->flags, b_len, b), res,
+ err, strerror(err));
+ return -1;
+ }
+ half_num = 0;
+ } else
+ half_num = ctlop->info;
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object output by ioctl(SG_IORECEIVE),2: "
+ "num_received=%d\n", __func__, half_num);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)ctlop, sizeof(*ctlop), 1);
+ v4hdr_out_lk("Controlling object after", ctlop, rep->id);
+ if (clp->verbose > 5) {
+ for (k = 0; k < half_num; ++k) {
+ pr2serr_lk("AFTER: def_arr[%d]:\n", k);
+ v4hdr_out_lk("normal v4 object", (a_v4p + k), rep->id);
+ // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p),
+ // 1);
+ }
+ }
+ }
+ in_fin_blks = 0;
+ out_fin_blks = 0;
+ num_good = process_mrq_response(rep, ctlop, a_v4p, half_num, in_fin_blks,
+ out_fin_blks);
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: >>>2 num_good=%d, in_q/fin blks=%u/%u; out_q/fin "
+ "blks=%u/%u\n", __func__, num_good, rep->in_mrq_q_blks,
+ in_fin_blks, rep->out_mrq_q_blks, out_fin_blks);
+
+ if (num_good < 0)
+ res = -1;
+ else if (num_good < half_num) {
+ int resid_blks = rep->in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ gcoll.in_rem_count += resid_blks;
+ resid_blks = rep->out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0)
+ gcoll.out_rem_count += resid_blks;
+
+ res = -1;
+ }
+
+fini:
+ return res;
+}
+
+/* Split def_arr into fd_def_arr and o_fd_arr based on whether each element's
+ * flags field has SGV4_FLAG_DO_ON_OTHER set. If it is set place in
+ * o_fd_def_arr and mask out SGV4_DO_ON_OTHER. Returns number of elements
+ * in o_fd_def_arr. */
+static int
+split_def_arr(const mrq_arr_t & def_arr, mrq_arr_t & fd_def_arr,
+ mrq_arr_t & o_fd_def_arr)
+{
+ int nrq, k;
+ int res = 0;
+ const struct sg_io_v4 * a_v4p;
+
+ a_v4p = def_arr.first.data();
+ nrq = def_arr.first.size();
+
+ for (k = 0; k < nrq; ++k) {
+ int flags;
+ const struct sg_io_v4 * h4p = a_v4p + k;
+
+ flags = h4p->flags;
+ if (flags & SGV4_FLAG_DO_ON_OTHER) {
+ o_fd_def_arr.first.push_back(def_arr.first[k]);
+ o_fd_def_arr.second.push_back(def_arr.second[k]);
+ flags &= ~SGV4_FLAG_DO_ON_OTHER; /* mask out DO_ON_OTHER */
+ o_fd_def_arr.first[res].flags = flags;
+ ++res;
+ } else {
+ fd_def_arr.first.push_back(def_arr.first[k]);
+ fd_def_arr.second.push_back(def_arr.second[k]);
+ }
+ }
+ return res;
+}
+
+/* This function sets up a multiple request (mrq) transaction and sends it
+ * to the pass-through. Returns 0 on success, 1 if ENOMEM error else -1 for
+ * other errors. */
+static int
+sgh_do_deferred_mrq(Rq_elem * rep, mrq_arr_t & def_arr)
+{
+ bool launch_mrq_abort = false;
+ int nrq, k, res, fd, mrq_pack_id, status, id, num_good, b_len;
+ uint32_t in_fin_blks, out_fin_blks;
+ const int max_cdb_sz = 16;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 ctl_v4 {};
+ uint8_t * cmd_ap = NULL;
+ struct global_collection * clp = rep->clp;
+ const char * iosub_str = "iosub_str";
+ char b[80];
+
+ id = rep->id;
+ b_len = sizeof(b);
+ ctl_v4.guard = 'Q';
+ a_v4p = def_arr.first.data();
+ nrq = def_arr.first.size();
+ if (nrq < 1) {
+ pr2serr_lk("[%d] %s: strange nrq=0, nothing to do\n", id, __func__);
+ return 0;
+ }
+ if (clp->mrq_cmds) {
+ cmd_ap = (uint8_t *)calloc(nrq, max_cdb_sz);
+ if (NULL == cmd_ap) {
+ pr2serr_lk("[%d] %s: no memory for calloc(%d * 16)\n", id,
+ __func__, nrq);
+ return 1;
+ }
+ }
+ for (k = 0; k < nrq; ++k) {
+ struct sg_io_v4 * h4p = a_v4p + k;
+ uint8_t *cmdp = &def_arr.second[k].front();
+
+ if (clp->mrq_cmds) {
+ memcpy(cmd_ap + (k * max_cdb_sz), cmdp, h4p->request_len);
+ h4p->request = 0;
+ } else
+ h4p->request = (uint64_t)cmdp;
+ if (clp->verbose > 5) {
+ pr2serr_lk("%s%s[%d] def_arr[%d]", ((0 == k) ? __func__ : ""),
+ ((0 == k) ? ": " : ""), id, k);
+ if (h4p->din_xferp)
+ pr2serr_lk(" [din=0x%p]:\n", (void *)h4p->din_xferp);
+ else if (h4p->dout_xferp)
+ pr2serr_lk(" [dout=0x%p]:\n", (void *)h4p->dout_xferp);
+ else
+ pr2serr_lk(":\n");
+ hex2stderr_lk((const uint8_t *)h4p, sizeof(*h4p), 1);
+ }
+ }
+ if (rep->both_sg || rep->same_sg)
+ fd = rep->infd; /* assume share to rep->outfd */
+ else if (rep->only_in_sg)
+ fd = rep->infd;
+ else if (rep->only_out_sg)
+ fd = rep->outfd;
+ else {
+ pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__);
+ res = -1;
+ goto fini;
+ }
+ res = 0;
+ if (clp->mrq_cmds) {
+ ctl_v4.request_len = nrq * max_cdb_sz;
+ ctl_v4.request = (uint64_t)cmd_ap;
+ }
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS;
+ if (! clp->mrq_async) {
+ ctl_v4.flags |= SGV4_FLAG_STOP_IF;
+ if (clp->in_flags.mrq_svb || clp->out_flags.mrq_svb)
+ ctl_v4.flags |= SGV4_FLAG_SHARE;
+ }
+ ctl_v4.dout_xferp = (uint64_t)a_v4p; /* request array */
+ ctl_v4.dout_xfer_len = nrq * sizeof(*a_v4p);
+ ctl_v4.din_xferp = (uint64_t)a_v4p; /* response array */
+ ctl_v4.din_xfer_len = nrq * sizeof(*a_v4p);
+ mrq_pack_id = atomic_fetch_add(&mono_mrq_id, 1);
+ if ((clp->m_aen > 0) && (MONO_MRQ_ID_INIT != mrq_pack_id) &&
+ (0 == ((mrq_pack_id - MONO_MRQ_ID_INIT) % clp->m_aen))) {
+ launch_mrq_abort = true;
+ if (clp->verbose > 2)
+ pr2serr_lk("[%d] %s: Decide to launch MRQ abort thread, "
+ "mrq_id=%d\n", id, __func__, mrq_pack_id);
+ memset(&rep->mai, 0, sizeof(rep->mai));
+ rep->mai.from_tid = id;
+ rep->mai.mrq_id = mrq_pack_id;
+ rep->mai.fd = fd;
+ rep->mai.debug = clp->verbose;
+
+ status = pthread_create(&rep->mrq_abort_thread_id, NULL,
+ mrq_abort_thread, (void *)&rep->mai);
+ if (0 != status) err_exit(status, "pthread_create, sig...");
+ }
+ ctl_v4.request_extra = launch_mrq_abort ? mrq_pack_id : 0;
+ rep->mrq_id = mrq_pack_id;
+ if (clp->verbose && rep->both_sg && clp->mrq_async)
+ iosub_str = "SG_IOSUBMIT(variable)";
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object _before_ ioctl(%s):\n",
+ __func__, iosub_str);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object before", &ctl_v4, id);
+ }
+ if (clp->mrq_async && (! rep->both_sg)) {
+ /* do 'submit non-blocking' or 'full non-blocking' mrq */
+ mrq_arr_t fd_def_arr;
+ mrq_arr_t o_fd_def_arr;
+
+ /* need to deconstruct def_arr[] into two separate lists, one for
+ * the source, the other for the destination. */
+ int o_num_fd = split_def_arr(def_arr, fd_def_arr, o_fd_def_arr);
+ int num_fd = fd_def_arr.first.size();
+ if (num_fd > 0) {
+ struct sg_io_v4 fd_ctl = ctl_v4;
+ struct sg_io_v4 * aa_v4p = fd_def_arr.first.data();
+
+ for (k = 0; k < num_fd; ++k) {
+ struct sg_io_v4 * h4p = aa_v4p + k;
+ uint8_t *cmdp = &fd_def_arr.second[k].front();
+
+ if (clp->mrq_cmds) {
+ memcpy(cmd_ap + (k * max_cdb_sz), cmdp, h4p->request_len);
+ h4p->request = 0;
+ } else
+ h4p->request = (uint64_t)cmdp;
+ if (clp->verbose > 5) {
+ pr2serr_lk("[%d] df_def_arr[%d]:\n", id, k);
+ hex2stderr_lk((const uint8_t *)(aa_v4p + k),
+ sizeof(*aa_v4p), 1);
+ }
+ }
+ fd_ctl.dout_xferp = (uint64_t)aa_v4p; /* request array */
+ fd_ctl.dout_xfer_len = num_fd * sizeof(*aa_v4p);
+ fd_ctl.din_xferp = (uint64_t)aa_v4p; /* response array */
+ fd_ctl.din_xfer_len = num_fd * sizeof(*aa_v4p);
+ fd_ctl.request_extra = launch_mrq_abort ? mrq_pack_id : 0;
+ /* this is the source side mrq command */
+ res = sgh_do_async_mrq(rep, fd_def_arr, fd, &fd_ctl, num_fd);
+ rep->in_mrq_q_blks = 0;
+ if (res)
+ goto fini;
+ }
+ if (o_num_fd > 0) {
+ struct sg_io_v4 o_fd_ctl = ctl_v4;
+ struct sg_io_v4 * aa_v4p = o_fd_def_arr.first.data();
+
+ for (k = 0; k < o_num_fd; ++k) {
+ struct sg_io_v4 * h4p = aa_v4p + k;
+ uint8_t *cmdp = &o_fd_def_arr.second[k].front();
+
+ if (clp->mrq_cmds) {
+ memcpy(cmd_ap + (k * max_cdb_sz), cmdp, h4p->request_len);
+ h4p->request = 0;
+ } else
+ h4p->request = (uint64_t)cmdp;
+ if (clp->verbose > 5) {
+ pr2serr_lk("[%d] o_fd_def_arr[%d]:\n", id, k);
+ hex2stderr_lk((const uint8_t *)(aa_v4p + k),
+ sizeof(*aa_v4p), 1);
+ }
+ }
+ o_fd_ctl.dout_xferp = (uint64_t)aa_v4p; /* request array */
+ o_fd_ctl.dout_xfer_len = o_num_fd * sizeof(*aa_v4p);
+ o_fd_ctl.din_xferp = (uint64_t)aa_v4p; /* response array */
+ o_fd_ctl.din_xfer_len = o_num_fd * sizeof(*aa_v4p);
+ o_fd_ctl.request_extra = launch_mrq_abort ? mrq_pack_id : 0;
+ /* this is the destination side mrq command */
+ res = sgh_do_async_mrq(rep, o_fd_def_arr, rep->outfd, &o_fd_ctl,
+ o_num_fd);
+ rep->out_mrq_q_blks = 0;
+ }
+ goto fini;
+ }
+
+try_again:
+ if (clp->unbalanced_mrq) {
+ iosub_str = "SG_IOSUBMIT(variable_blocking)";
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: unbalanced %s\n", __func__, mrq_vb_s);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4);
+ } else {
+ if (clp->mrq_async) {
+ iosub_str = "SG_IOSUBMIT(variable_blocking)";
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_vb_s);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4);
+ } else if (clp->in_flags.mrq_svb || clp->out_flags.mrq_svb) {
+ iosub_str = "SG_IOSUBMIT(shared_variable_blocking)";
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_svb_s);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4);
+ } else {
+ iosub_str = "SG_IO(ordered_blocking)";
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_blk_s);
+ }
+ res = ioctl(fd, SG_IO, &ctl_v4);
+ }
+ }
+ if (res < 0) {
+ int err = errno;
+
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* allow another thread to progress */
+ goto try_again;
+ }
+ pr2serr_lk("%s: ioctl(%s, %s)-->%d, errno=%d: %s\n",
+ __func__, iosub_str, sg_flags_str(ctl_v4.flags, b_len, b),
+ res, err, strerror(err));
+ res = -1;
+ goto fini;
+ }
+ if (clp->verbose && vb_first_time.load()) {
+ pr2serr_lk("First controlling object output by ioctl(%s), flags: "
+ "%s\n", iosub_str, sg_flags_str(ctl_v4.flags, b_len, b));
+ vb_first_time.store(false);
+ } else if (clp->verbose > 4)
+ pr2serr_lk("%s: Controlling object output by ioctl(%s):\n",
+ __func__, iosub_str);
+ if (clp->verbose > 4) {
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object after", &ctl_v4, id);
+ if (clp->verbose > 5) {
+ for (k = 0; k < nrq; ++k) {
+ pr2serr_lk("AFTER: def_arr[%d]:\n", k);
+ v4hdr_out_lk("normal v4 object", (a_v4p + k), id);
+ // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p),
+ // 1);
+ }
+ }
+ }
+ in_fin_blks = 0;
+ out_fin_blks = 0;
+ num_good = process_mrq_response(rep, &ctl_v4, a_v4p, nrq, in_fin_blks,
+ out_fin_blks);
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: >>> num_good=%d, in_q/fin blks=%u/%u; out_q/fin "
+ "blks=%u/%u\n", __func__, num_good, rep->in_mrq_q_blks,
+ in_fin_blks, rep->out_mrq_q_blks, out_fin_blks);
+
+ if (num_good < 0)
+ res = -1;
+ else if (num_good < nrq) {
+ int resid_blks = rep->in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ gcoll.in_rem_count += resid_blks;
+ resid_blks = rep->out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0)
+ gcoll.out_rem_count += resid_blks;
+
+ res = -1;
+ }
+ rep->in_mrq_q_blks = 0;
+ rep->out_mrq_q_blks = 0;
+fini:
+ def_arr.first.clear();
+ def_arr.second.clear();
+ if (cmd_ap)
+ free(cmd_ap);
+ if (launch_mrq_abort) {
+ if (clp->verbose > 1)
+ pr2serr_lk("[%d] %s: About to join MRQ abort thread, "
+ "mrq_id=%d\n", id, __func__, mrq_pack_id);
+
+ void * vp; /* not used */
+ status = pthread_join(rep->mrq_abort_thread_id, &vp);
+ if (0 != status) err_exit(status, "pthread_join");
+ }
+ return res;
+}
+
+/* Returns 0 on success, 1 if ENOMEM error else -1 for other errors. */
+static int
+sg_start_io(Rq_elem * rep, mrq_arr_t & def_arr, int & pack_id,
+ struct sg_io_extra *xtrp)
+{
+ struct global_collection * clp = rep->clp;
+ bool wr = rep->wr;
+ bool fua = wr ? clp->out_flags.fua : clp->in_flags.fua;
+ bool dpo = wr ? clp->out_flags.dpo : clp->in_flags.dpo;
+ bool dio = wr ? clp->out_flags.dio : clp->in_flags.dio;
+ bool mmap = wr ? clp->out_flags.mmap : clp->in_flags.mmap;
+ bool noxfer = wr ? clp->out_flags.noxfer : clp->in_flags.noxfer;
+ bool v4 = wr ? clp->out_flags.v4 : clp->in_flags.v4;
+ bool qhead = wr ? clp->out_flags.qhead : clp->in_flags.qhead;
+ bool qtail = wr ? clp->out_flags.qtail : clp->in_flags.qtail;
+ bool polled = wr ? clp->out_flags.polled : clp->in_flags.polled;
+ bool mout_if = wr ? clp->out_flags.mout_if : clp->in_flags.mout_if;
+ bool prefetch = xtrp ? xtrp->prefetch : false;
+ bool is_wr2 = xtrp ? xtrp->is_wr2 : false;
+ int cdbsz = wr ? clp->cdbsz_out : clp->cdbsz_in;
+ int flags = 0;
+ int res, err, fd, b_len, nblks, blk_off;
+ int64_t blk = wr ? rep->oblk : rep->iblk;
+ struct sg_io_hdr * hp = &rep->io_hdr;
+ struct sg_io_v4 * h4p = &rep->io_hdr4[xtrp ? xtrp->hpv4_ind : 0];
+ const char * cp = "";
+ const char * crwp;
+ char b[80];
+
+ b_len = sizeof(b);
+ if (wr) {
+ fd = is_wr2 ? rep->out2fd : rep->outfd;
+ if (clp->verify) {
+ crwp = is_wr2 ? "verifying2" : "verifying";
+ if (prefetch)
+ crwp = is_wr2 ? "prefetch2" : "prefetch";
+ } else
+ crwp = is_wr2 ? "writing2" : "writing";
+ } else {
+ fd = rep->infd;
+ crwp = "reading";
+ }
+ if (qhead)
+ qtail = false; /* qhead takes precedence */
+
+ if (v4 && xtrp && xtrp->dout_is_split) {
+ res = sg_build_scsi_cdb(rep->cmd, cdbsz, xtrp->blks,
+ blk + (unsigned int)xtrp->blk_offset,
+ clp->verify, true, fua, dpo);
+ } else
+ res = sg_build_scsi_cdb(rep->cmd, cdbsz, rep->num_blks, blk,
+ wr ? clp->verify : false, wr, fua, dpo);
+ if (res) {
+ pr2serr_lk("%sbad cdb build, start_blk=%" PRId64 ", blocks=%d\n",
+ my_name, blk, rep->num_blks);
+ return -1;
+ }
+ if (prefetch) {
+ if (cdbsz == 10)
+ rep->cmd[0] = SGP_PRE_FETCH10;
+ else if (cdbsz == 16)
+ rep->cmd[0] = SGP_PRE_FETCH16;
+ else {
+ pr2serr_lk("%sbad PRE-FETCH build, start_blk=%" PRId64 ", "
+ "blocks=%d\n", my_name, blk, rep->num_blks);
+ return -1;
+ }
+ rep->cmd[1] = 0x2; /* set IMMED (no fua or dpo) */
+ }
+ if (mmap && (clp->noshare || (rep->outregfd >= 0)))
+ flags |= SG_FLAG_MMAP_IO;
+ if (noxfer)
+ flags |= SG_FLAG_NO_DXFER;
+ if (dio)
+ flags |= SG_FLAG_DIRECT_IO;
+ if (polled)
+ flags |= SGV4_FLAG_POLLED;
+ if (qhead)
+ flags |= SG_FLAG_Q_AT_HEAD;
+ if (qtail)
+ flags |= SG_FLAG_Q_AT_TAIL;
+ if (mout_if)
+ flags |= SGV4_FLAG_META_OUT_IF;
+ if (rep->has_share) {
+ flags |= SGV4_FLAG_SHARE;
+ if (wr)
+ flags |= SGV4_FLAG_NO_DXFER;
+ else if (rep->outregfd < 0)
+ flags |= SGV4_FLAG_NO_DXFER;
+
+ cp = (wr ? " write_side active" : " read_side active");
+ } else
+ cp = (wr ? " write-side not sharing" : " read_side not sharing");
+ if (rep->both_sg) {
+ if (wr)
+ pack_id = rep->rd_p_id + 1;
+ else {
+ pack_id = 2 * atomic_fetch_add(&mono_pack_id, 1);
+ rep->rd_p_id = pack_id;
+ }
+ } else
+ pack_id = atomic_fetch_add(&mono_pack_id, 1); /* fetch before */
+ rep->rq_id = pack_id;
+ nblks = rep->num_blks;
+ blk_off = 0;
+ if (clp->verbose && 0 == clp->nmrqs && vb_first_time.load()) {
+ vb_first_time.store(false);
+ pr2serr("First normal IO: %s, flags: %s\n", cp,
+ sg_flags_str(flags, b_len, b));
+ }
+ if (v4) {
+ memset(h4p, 0, sizeof(struct sg_io_v4));
+ if (clp->nmrqs > 0) {
+ if (rep->both_sg && (rep->outfd == fd))
+ flags |= SGV4_FLAG_DO_ON_OTHER;
+ }
+ if (xtrp && xtrp->dout_is_split && (nblks > 0)) {
+ if (1 == xtrp->hpv4_ind) {
+ flags |= SGV4_FLAG_DOUT_OFFSET;
+ blk_off = xtrp->blk_offset;
+ h4p->spare_in = clp->bs * blk_off;
+ }
+ nblks = xtrp->blks;
+ if ((0 == xtrp->hpv4_ind) && (nblks < rep->num_blks))
+ flags |= SGV4_FLAG_KEEP_SHARE;
+ }
+ if (clp->ofile2_given && wr && rep->has_share && ! is_wr2)
+ flags |= SGV4_FLAG_KEEP_SHARE; /* set on first write only */
+ else if (clp->fail_mask & 1)
+ flags |= SGV4_FLAG_KEEP_SHARE; /* troublemaking .... */
+ } else
+ memset(hp, 0, sizeof(struct sg_io_hdr));
+ if (clp->verbose > 3) {
+ bool lock = true;
+ char prefix[128];
+
+ if (4 == clp->verbose) {
+ snprintf(prefix, sizeof(prefix), "tid,rq_id=%d,%d: ", rep->id,
+ pack_id);
+ lock = false;
+ } else {
+ prefix[0] = '\0';
+ pr2serr_lk("%s tid,rq_id=%d,%d: SCSI %s%s %s, blk=%" PRId64
+ " num_blks=%d\n", __func__, rep->id, pack_id, crwp, cp,
+ sg_flags_str(flags, b_len, b), blk + blk_off, nblks);
+ }
+ lk_print_command_len(prefix, rep->cmd, cdbsz, lock);
+ }
+ if (v4)
+ goto do_v4; // <<<<<<<<<<<<<<< look further down
+
+ hp->interface_id = 'S';
+ hp->cmd_len = cdbsz;
+ hp->cmdp = rep->cmd;
+ hp->dxferp = get_buffp(rep);
+ hp->dxfer_len = clp->bs * rep->num_blks;
+ if (!wr)
+ hp->dxfer_direction = SG_DXFER_FROM_DEV;
+ else if (prefetch) {
+ hp->dxfer_direction = SG_DXFER_NONE;
+ hp->dxfer_len = 0;
+ hp->dxferp = NULL;
+ } else
+ hp->dxfer_direction = SG_DXFER_TO_DEV;
+ hp->mx_sb_len = sizeof(rep->sb);
+ hp->sbp = rep->sb;
+ hp->timeout = clp->cmd_timeout;
+ hp->usr_ptr = rep;
+ hp->pack_id = pack_id;
+ hp->flags = flags;
+
+ while (((res = write(fd, hp, sizeof(struct sg_io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno))) {
+ if (EAGAIN == errno) {
+ ++num_start_eagain;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ } else if (EBUSY == errno) {
+ ++num_ebusy;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ }
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ err = errno;
+ if (res < 0) {
+ if (ENOMEM == err)
+ return 1;
+ pr2serr_lk("%s tid=%d: %s %s write(2) failed: %s\n", __func__,
+ rep->id, cp, sg_flags_str(hp->flags, b_len, b),
+ strerror(err));
+ return -1;
+ }
+ return 0;
+
+do_v4:
+ h4p->guard = 'Q';
+ h4p->request_len = cdbsz;
+ h4p->request = (uint64_t)rep->cmd;
+ if (wr) {
+ if (prefetch) {
+ h4p->dout_xfer_len = 0; // din_xfer_len is also 0
+ h4p->dout_xferp = 0;
+ } else {
+ h4p->dout_xfer_len = clp->bs * nblks;
+ h4p->dout_xferp = (uint64_t)get_buffp(rep);
+ }
+ } else if (nblks > 0) {
+ h4p->din_xfer_len = clp->bs * nblks;
+ h4p->din_xferp = (uint64_t)get_buffp(rep);
+ }
+ h4p->max_response_len = sizeof(rep->sb);
+ h4p->response = (uint64_t)rep->sb;
+ h4p->timeout = clp->cmd_timeout;
+ h4p->usr_ptr = (uint64_t)rep;
+ h4p->request_extra = pack_id; /* this is the pack_id */
+ h4p->flags = flags;
+ if (clp->nmrqs > 0) {
+ big_cdb cdb_arr;
+ uint8_t * cmdp = &(cdb_arr[0]);
+
+ if (wr)
+ rep->out_mrq_q_blks += nblks;
+ else
+ rep->in_mrq_q_blks += nblks;
+ memcpy(cmdp, rep->cmd, cdbsz);
+ def_arr.first.push_back(*h4p);
+ def_arr.second.push_back(cdb_arr);
+ res = 0;
+ if ((int)def_arr.first.size() >= clp->nmrqs) {
+ res = sgh_do_deferred_mrq(rep, def_arr);
+ if (res)
+ pr2serr_lk("%s tid=%d: sgh_do_deferred_mrq failed\n",
+ __func__, rep->id);
+ }
+ return res;
+ }
+ while (((res = ioctl(fd, SG_IOSUBMIT, h4p)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno))) {
+ if (EAGAIN == errno) {
+ ++num_start_eagain;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ } else if (EBUSY == errno) {
+ ++num_ebusy;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ }
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ err = errno;
+ if (res < 0) {
+ if (ENOMEM == err)
+ return 1;
+ if (E2BIG == err)
+ sg_take_snap(fd, rep->id, true);
+ pr2serr_lk("%s tid=%d: %s %s ioctl(2) failed: %s\n", __func__,
+ rep->id, cp, sg_flags_str(h4p->flags, b_len, b),
+ strerror(err));
+ // v4hdr_out_lk("leadin", h4p, rep->id);
+ return -1;
+ }
+ if ((clp->aen > 0) && (rep->rep_count > 0)) {
+ if (0 == (rep->rq_id % clp->aen)) {
+ struct timespec tspec = {0, 4000 /* 4 usecs */};
+
+ nanosleep(&tspec, NULL);
+#if 0
+ struct pollfd a_poll;
+
+ a_poll.fd = fd;
+ a_poll.events = POLL_IN;
+ a_poll.revents = 0;
+ res = poll(&a_poll, 1 /* element */, 1 /* millisecond */);
+ if (res < 0)
+ pr2serr_lk("%s: poll() failed: %s [%d]\n",
+ __func__, safe_strerror(errno), errno);
+ else if (0 == res) { /* timeout, cmd still inflight, so abort */
+ }
+#endif
+ ++num_abort_req;
+ res = ioctl(fd, SG_IOABORT, h4p);
+ if (res < 0) {
+ err = errno;
+ if (ENODATA == err) {
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: ioctl(SG_IOABORT) no match on "
+ "pack_id=%d\n", __func__, pack_id);
+ } else
+ pr2serr_lk("%s: ioctl(SG_IOABORT) failed: %s [%d]\n",
+ __func__, safe_strerror(err), err);
+ } else {
+ ++num_abort_req_success;
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: sent ioctl(SG_IOABORT) on rq_id=%d, "
+ "success\n", __func__, pack_id);
+ }
+ } /* else got response, too late for timeout, so skip */
+ }
+ return 0;
+}
+
+/* 0 -> successful, SG_LIB_CAT_UNIT_ATTENTION or SG_LIB_CAT_ABORTED_COMMAND
+ -> try again, SG_LIB_CAT_NOT_READY, SG_LIB_CAT_MEDIUM_HARD,
+ -1 other errors */
+static int
+sg_finish_io(bool wr, Rq_elem * rep, int pack_id, struct sg_io_extra *xtrp)
+{
+ struct global_collection * clp = rep->clp;
+ bool v4 = wr ? clp->out_flags.v4 : clp->in_flags.v4;
+ bool mout_if = wr ? clp->out_flags.mout_if : clp->in_flags.mout_if;
+ bool is_wr2 = xtrp ? xtrp->is_wr2 : false;
+ bool prefetch = xtrp ? xtrp->prefetch : false;
+ int res, fd;
+ int64_t blk = wr ? rep->oblk : rep->iblk;
+ struct sg_io_hdr io_hdr;
+ struct sg_io_hdr * hp;
+ struct sg_io_v4 * h4p;
+ const char *cp;
+
+ if (wr) {
+ fd = is_wr2 ? rep->out2fd : rep->outfd;
+ cp = is_wr2 ? "writing2" : "writing";
+ if (clp->verify) {
+ cp = is_wr2 ? "verifying2" : "verifying";
+ if (prefetch)
+ cp = is_wr2 ? "prefetch2" : "prefetch";
+ }
+ } else {
+ fd = rep->infd;
+ cp = "reading";
+ }
+ if (v4)
+ goto do_v4;
+ memset(&io_hdr, 0 , sizeof(struct sg_io_hdr));
+ /* FORCE_PACK_ID active set only read packet with matching pack_id */
+ io_hdr.interface_id = 'S';
+ io_hdr.dxfer_direction = wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ io_hdr.pack_id = pack_id;
+
+ while (((res = read(fd, &io_hdr, sizeof(struct sg_io_hdr))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno))) {
+ if (EAGAIN == errno) {
+ ++num_fin_eagain;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ } else if (EBUSY == errno) {
+ ++num_ebusy;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ }
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ if (res < 0) {
+ perror("finishing io [read(2)] on sg device, error");
+ return -1;
+ }
+ if (rep != (Rq_elem *)io_hdr.usr_ptr)
+ err_exit(0, "sg_finish_io: bad usr_ptr, request-response mismatch\n");
+ memcpy(&rep->io_hdr, &io_hdr, sizeof(struct sg_io_hdr));
+ hp = &rep->io_hdr;
+
+ res = sg_err_category3(hp);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ case SG_LIB_CAT_CONDITION_MET:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ lk_chk_n_print3(cp, hp, false);
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ if (clp->verbose > 3)
+ lk_chk_n_print3(cp, hp, false);
+ return res;
+ case SG_LIB_CAT_MISCOMPARE:
+ ++num_miscompare;
+ // fall through
+ case SG_LIB_CAT_NOT_READY:
+ default:
+ {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s blk=%" PRId64, cp, blk);
+ lk_chk_n_print3(ebuff, hp, clp->verbose > 1);
+ return res;
+ }
+ }
+ if ((wr ? clp->out_flags.dio : clp->in_flags.dio) &&
+ (! (hp->info & SG_INFO_DIRECT_IO_MASK)))
+ rep->dio_incomplete_count = 1; /* count dios done as indirect IO */
+ else
+ rep->dio_incomplete_count = 0;
+ rep->resid = hp->resid;
+ if (clp->verbose > 3)
+ pr2serr_lk("%s: tid=%d: completed %s\n", __func__, rep->id, cp);
+ return 0;
+
+do_v4:
+ if (clp->nmrqs > 0) {
+ rep->resid = 0;
+ return 0;
+ }
+ h4p = &rep->io_hdr4[xtrp ? xtrp->hpv4_ind : 0];
+ h4p->request_extra = pack_id;
+ if (mout_if) {
+ h4p->info = 0;
+ h4p->din_resid = 0;
+ }
+ while (((res = ioctl(fd, SG_IORECEIVE, h4p)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno))) {
+ if (EAGAIN == errno) {
+ ++num_fin_eagain;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ } else if (EBUSY == errno) {
+ ++num_ebusy;
+#ifdef SGH_DD_SNAP_DEV
+ if (0 == (num_ebusy % 1000))
+ sg_take_snap(fd, rep->id, (clp->verbose > 2));
+#endif
+ }
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ if (res < 0) {
+ perror("finishing io [SG_IORECEIVE] on sg device, error");
+ return -1;
+ }
+ if (mout_if && (0 == h4p->info) && (0 == h4p->din_resid))
+ goto all_good;
+ if (rep != (Rq_elem *)h4p->usr_ptr)
+ err_exit(0, "sg_finish_io: bad usr_ptr, request-response mismatch\n");
+ res = sg_err_category_new(h4p->device_status, h4p->transport_status,
+ h4p->driver_status,
+ (const uint8_t *)h4p->response,
+ h4p->response_len);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ case SG_LIB_CAT_CONDITION_MET:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ lk_chk_n_print4(cp, h4p, false);
+ break;
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ if (clp->verbose > 3)
+ lk_chk_n_print4(cp, h4p, false);
+ return res;
+ case SG_LIB_CAT_MISCOMPARE:
+ ++num_miscompare;
+ // fall through
+ case SG_LIB_CAT_NOT_READY:
+ default:
+ {
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s rq_id=%d, blk=%" PRId64, cp,
+ pack_id, blk);
+ lk_chk_n_print4(ebuff, h4p, clp->verbose > 1);
+ if ((clp->verbose > 4) && h4p->info)
+ pr2serr_lk(" info=0x%x sg_info_check=%d direct=%d "
+ "detaching=%d aborted=%d\n", h4p->info,
+ !!(h4p->info & SG_INFO_CHECK),
+ !!(h4p->info & SG_INFO_DIRECT_IO),
+ !!(h4p->info & SG_INFO_DEVICE_DETACHING),
+ !!(h4p->info & SG_INFO_ABORTED));
+ return res;
+ }
+ }
+ if ((wr ? clp->out_flags.dio : clp->in_flags.dio) &&
+ ! (h4p->info & SG_INFO_DIRECT_IO))
+ rep->dio_incomplete_count = 1; /* count dios done as indirect IO */
+ else
+ rep->dio_incomplete_count = 0;
+ rep->resid = h4p->din_resid;
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: tid,rq_id=%d,%d: completed %s\n", __func__, rep->id,
+ pack_id, cp);
+ if ((clp->verbose > 4) && h4p->info)
+ pr2serr_lk(" info=0x%x sg_info_check=%d direct=%d "
+ "detaching=%d aborted=%d\n", h4p->info,
+ !!(h4p->info & SG_INFO_CHECK),
+ !!(h4p->info & SG_INFO_DIRECT_IO),
+ !!(h4p->info & SG_INFO_DEVICE_DETACHING),
+ !!(h4p->info & SG_INFO_ABORTED));
+ }
+all_good:
+ return 0;
+}
+
+/* Returns reserved_buffer_size/mmap_size if success, else 0 for failure */
+static int
+sg_prepare_resbuf(int fd, bool is_in, struct global_collection *clp,
+ uint8_t **mmpp)
+{
+ static bool done = false;
+ bool def_res = is_in ? clp->in_flags.defres : clp->out_flags.defres;
+ bool no_dur = is_in ? clp->in_flags.no_dur : clp->out_flags.no_dur;
+ bool masync = is_in ? clp->in_flags.masync : clp->out_flags.masync;
+ bool wq_excl = is_in ? clp->in_flags.wq_excl : clp->out_flags.wq_excl;
+ bool skip_thresh = is_in ? clp->in_flags.no_thresh :
+ clp->out_flags.no_thresh;
+ int res, t;
+ int num = 0;
+ uint8_t *mmp;
+ struct sg_extended_info sei {};
+ struct sg_extended_info * seip = &sei;
+
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 40000)) {
+ if (ioctl(fd, SG_GET_RESERVED_SIZE, &num) < 0) {
+ perror("SG_GET_RESERVED_SIZE ioctl failed");
+ return 0;
+ }
+ if (! done) {
+ done = true;
+ sg_version_lt_4 = true;
+ pr2serr_lk("%ssg driver prior to 4.0.00, reduced functionality\n",
+ my_name);
+ }
+ goto bypass;
+ }
+ if (! sg_version_ge_40045)
+ goto bypass;
+ if (clp->elem_sz >= 4096) {
+ seip->sei_rd_mask |= SG_SEIM_SGAT_ELEM_SZ;
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("%s%s: SG_SET_GET_EXTENDED(SGAT_ELEM_SZ) rd "
+ "error: %s\n", my_name, __func__, strerror(errno));
+ if (clp->elem_sz != (int)seip->sgat_elem_sz) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_SGAT_ELEM_SZ;
+ seip->sgat_elem_sz = clp->elem_sz;
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("%s%s: SG_SET_GET_EXTENDED(SGAT_ELEM_SZ) wr "
+ "error: %s\n", my_name, __func__, strerror(errno));
+ }
+ }
+ if (no_dur || masync || skip_thresh) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ if (no_dur) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags |= SG_CTL_FLAGM_NO_DURATION;
+ }
+ if (masync) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC;
+ seip->ctl_flags |= SG_CTL_FLAGM_MORE_ASYNC;
+ }
+ if (wq_excl) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_EXCL_WAITQ;
+ seip->ctl_flags |= SG_CTL_FLAGM_EXCL_WAITQ;
+ }
+ if (skip_thresh) {
+ seip->tot_fd_thresh = 0;
+ sei.sei_wr_mask |= SG_SEIM_TOT_FD_THRESH;
+ }
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("%s%s: SG_SET_GET_EXTENDED(NO_DURATION) error: %s\n",
+ my_name, __func__, strerror(errno));
+ }
+bypass:
+ if (! def_res) {
+ num = clp->bs * clp->bpt;
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &num);
+ if (res < 0) {
+ perror("sgh_dd: SG_SET_RESERVED_SIZE error");
+ return 0;
+ } else {
+ int nn;
+
+ res = ioctl(fd, SG_GET_RESERVED_SIZE, &nn);
+ if (res < 0) {
+ perror("sgh_dd: SG_GET_RESERVED_SIZE error");
+ return 0;
+ }
+ if (nn < num) {
+ pr2serr_lk("%s: SG_GET_RESERVED_SIZE shows size truncated, "
+ "wanted %d got %d\n", __func__, num, nn);
+ return 0;
+ }
+ }
+ if (mmpp) {
+ mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (MAP_FAILED == mmp) {
+ int err = errno;
+
+ pr2serr_lk("%s%s: sz=%d, fd=%d, mmap() failed: %s\n",
+ my_name, __func__, num, fd, strerror(err));
+ return 0;
+ }
+ *mmpp = mmp;
+ }
+ }
+ t = 1;
+ res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
+ if (res < 0)
+ perror("sgh_dd: SG_SET_FORCE_PACK_ID error");
+ if (clp->unit_nanosec && sg_version_ge_40045) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+ }
+ t = 1;
+ res = ioctl(fd, SG_SET_DEBUG, &t); /* more info in the kernel log */
+ if (res < 0)
+ perror("sgs_dd: SG_SET_DEBUG error");
+ return (res < 0) ? 0 : num;
+}
+
+static bool
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return false;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "00"))
+ fp->zero = true;
+ else if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "coe"))
+ fp->coe = true;
+ else if (0 == strcmp(cp, "defres"))
+ fp->defres = true;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "ff"))
+ fp->ff = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "hipri"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "masync"))
+ fp->masync = true;
+ else if (0 == strcmp(cp, "mmap"))
+ ++fp->mmap; /* mmap > 1 stops munmap() being called */
+ else if (0 == strcmp(cp, "mrq_imm"))
+ fp->mrq_immed = true;
+ else if (0 == strcmp(cp, "mrq_immed"))
+ fp->mrq_immed = true;
+ else if (0 == strcmp(cp, "mrq_svb"))
+ fp->mrq_svb = true;
+ else if (0 == strcmp(cp, "nodur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "no_dur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "nocreat"))
+ fp->nocreat = true;
+ else if (0 == strcmp(cp, "noshare"))
+ fp->noshare = true;
+ else if (0 == strcmp(cp, "no_share"))
+ fp->noshare = true;
+ else if (0 == strcmp(cp, "no_thresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "no-thresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "nothresh"))
+ fp->no_thresh = true;
+ else if (0 == strcmp(cp, "no_unshare"))
+ fp->no_unshare = true;
+ else if (0 == strcmp(cp, "no-unshare"))
+ fp->no_unshare = true;
+ else if (0 == strcmp(cp, "no_waitq"))
+ fp->no_waitq = true;
+ else if (0 == strcmp(cp, "no-waitq"))
+ fp->no_waitq = true;
+ else if (0 == strcmp(cp, "nowaitq"))
+ fp->no_waitq = true;
+ else if (0 == strcmp(cp, "noxfer"))
+ fp->noxfer = true;
+ else if (0 == strcmp(cp, "no_xfer"))
+ fp->noxfer = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "polled"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "qhead"))
+ fp->qhead = true;
+ else if (0 == strcmp(cp, "qtail"))
+ fp->qtail = true;
+ else if (0 == strcmp(cp, "random"))
+ fp->random = true;
+ else if ((0 == strcmp(cp, "mout_if")) || (0 == strcmp(cp, "mout-if")))
+ fp->mout_if = true;
+ else if (0 == strcmp(cp, "same_fds"))
+ fp->same_fds = true;
+ else if (0 == strcmp(cp, "swait"))
+ fp->swait = true;
+ else if (0 == strcmp(cp, "v3"))
+ fp->v3 = true;
+ else if (0 == strcmp(cp, "v4")) {
+ fp->v4 = true;
+ fp->v4_given = true;
+ } else if (0 == strcmp(cp, "wq_excl"))
+ fp->wq_excl = true;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return false;
+ }
+ cp = np;
+ } while (cp);
+ return true;
+}
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+static int
+sg_in_open(struct global_collection *clp, const char *inf, uint8_t **mmpp,
+ int * mmap_lenp)
+{
+ int fd, n;
+ int flags = O_RDWR;
+
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(inf, flags)) < 0) {
+ int err = errno;
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg reading",
+ __func__, inf);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, true, clp, mmpp);
+ if (n <= 0) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ if (clp->noshare)
+ sg_noshare_enlarge(fd, clp->verbose > 3);
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+static int
+sg_out_open(struct global_collection *clp, const char *outf, uint8_t **mmpp,
+ int * mmap_lenp)
+{
+ int fd, n;
+ int flags = O_RDWR;
+
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(outf, flags)) < 0) {
+ int err = errno;
+ char ebuff[EBUFF_SZ];
+
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg %s",
+ __func__, outf, (clp->verify ? "verifying" : "writing"));
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, false, clp, mmpp);
+ if (n <= 0) {
+ close(fd);
+ return -SG_LIB_FILE_ERROR;
+ }
+ if (clp->noshare)
+ sg_noshare_enlarge(fd, clp->verbose > 3);
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+/* Process arguments given to 'conv=" option. Returns 0 on success,
+ * 1 on error. */
+static int
+process_conv(const char * arg, struct flags_t * ifp, struct flags_t * ofp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no conversions found\n");
+ return 1;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "nocreat"))
+ ofp->nocreat = true;
+ else if (0 == strcmp(cp, "noerror"))
+ ifp->coe = true; /* will still fail on write error */
+ else if (0 == strcmp(cp, "notrunc"))
+ ; /* this is the default action of sg_dd so ignore */
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "sync"))
+ ; /* dd(susv4): pad errored block(s) with zeros but sg_dd does
+ * that by default. Typical dd use: 'conv=noerror,sync' */
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return 1;
+ }
+ cp = np;
+ } while (cp);
+ return 0;
+}
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+
+static int
+parse_cmdline_sanity(int argc, char * argv[], struct global_collection * clp,
+ char * inf, char * outf, char * out2f, char * outregf)
+{
+ bool verbose_given = false;
+ bool version_given = false;
+ bool verify_given = false;
+ bool bpt_given = false;
+ int ibs = 0;
+ int obs = 0;
+ int k, keylen, n, res;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ const char * cp;
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ }
+ else
+ continue;
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key, "ae")) {
+ clp->aen = sg_get_num(buf);
+ if (clp->aen < 0) {
+ pr2serr("%sbad AEN argument to 'ae=', want 0 or higher\n",
+ my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cp = strchr(buf, ',');
+ if (cp) {
+ clp->m_aen = sg_get_num(cp + 1);
+ if (clp->m_aen < 0) {
+ pr2serr("%sbad MAEN argument to 'ae=', want 0 or "
+ "higher\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->m_aen_given = true;
+ }
+ clp->aen_given = true;
+ } else if (0 == strcmp(key, "bpt")) {
+ clp->bpt = sg_get_num(buf);
+ if ((clp->bpt < 0) || (clp->bpt > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bpt='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key, "bs")) {
+ clp->bs = sg_get_num(buf);
+ if ((clp->bs < 0) || (clp->bs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'bs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "cdbsz")) {
+ clp->cdbsz_in = sg_get_num(buf);
+ if ((clp->cdbsz_in < 6) || (clp->cdbsz_in > 32)) {
+ pr2serr("%s'cdbsz' expects 6, 10, 12, 16 or 32\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->cdbsz_out = clp->cdbsz_in;
+ clp->cdbsz_given = true;
+ } else if (0 == strcmp(key, "coe")) {
+ clp->in_flags.coe = !! sg_get_num(buf);
+ clp->out_flags.coe = clp->in_flags.coe;
+ } else if (0 == strcmp(key, "conv")) {
+ if (process_conv(buf, &clp->in_flags, &clp->out_flags)) {
+ pr2serr("%s: bad argument to 'conv='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "count")) {
+ if (0 != strcmp("-1", buf)) {
+ dd_count = sg_get_llnum(buf);
+ if ((dd_count < 0) || (dd_count > MAX_COUNT_SKIP_SEEK)) {
+ pr2serr("%sbad argument to 'count='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } /* treat 'count=-1' as calculate count (same as not given) */
+ } else if (0 == strcmp(key, "dio")) {
+ clp->in_flags.dio = !! sg_get_num(buf);
+ clp->out_flags.dio = clp->in_flags.dio;
+ } else if (0 == strcmp(key, "elemsz_kb")) {
+ n = sg_get_num(buf);
+ if (n < 1) {
+ pr2serr("elemsz_kb=EKB wants an integer > 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (n & (n - 1)) {
+ pr2serr("elemsz_kb=EKB wants EKB to be power of 2\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->elem_sz = n * 1024;
+ } else if ((0 == strcmp(key, "fail_mask")) ||
+ (0 == strcmp(key, "fail-mask"))) {
+ clp->fail_mask = sg_get_num(buf);
+ if (clp->fail_mask < 0) {
+ pr2serr("fail_mask: couldn't decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "fua")) {
+ n = sg_get_num(buf);
+ if (n & 1)
+ clp->out_flags.fua = true;
+ if (n & 2)
+ clp->in_flags.fua = true;
+ } else if (0 == strcmp(key, "ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'ibs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "if")) {
+ if ('\0' != inf[0]) {
+ pr2serr("Second 'if=' argument??\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ memcpy(inf, buf, INOUTF_SZ);
+ inf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (! process_flags(buf, &clp->in_flags)) {
+ pr2serr("%sbad argument to 'iflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "mrq")) {
+ if (isdigit(buf[0]))
+ cp = buf;
+ else {
+ if ('I' == isupper(buf[0]))
+ clp->is_mrq_i = true;
+ else if ('O' == isupper(buf[0]))
+ clp->is_mrq_o = true;
+ else {
+ pr2serr("%sonly mrq=i,NRQS or mrq=o,NRQS allowed here\n",
+ my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cp = strchr(buf, ',');
+ ++cp;
+ }
+ clp->nmrqs = sg_get_num(cp);
+ if (clp->nmrqs < 0) {
+ pr2serr("%sbad argument to 'mrq='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cp = strchr(cp, ',');
+ if (cp && ('C' == toupper(cp[1])))
+ clp->mrq_cmds = true;
+ } else if (0 == strcmp(key, "noshare")) {
+ clp->noshare = !! sg_get_num(buf);
+ } else if (0 == strcmp(key, "obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr("%sbad argument to 'obs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key, "of2") == 0) {
+ if ('\0' != out2f[0]) {
+ pr2serr("Second OFILE2 argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(out2f, buf, INOUTF_SZ);
+ out2f[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (strcmp(key, "ofreg") == 0) {
+ if ('\0' != outregf[0]) {
+ pr2serr("Second OFREG argument??\n");
+ return SG_LIB_CONTRADICT;
+ } else {
+ memcpy(outregf, buf, INOUTF_SZ);
+ outregf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (0 == strcmp(key, "ofsplit")) {
+ clp->ofsplit = sg_get_num(buf);
+ if (-1 == clp->ofsplit) {
+ pr2serr("%sbad argument to 'ofsplit='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key, "of") == 0) {
+ if ('\0' != outf[0]) {
+ pr2serr("Second 'of=' argument??\n");
+ return SG_LIB_SYNTAX_ERROR;
+ } else {
+ memcpy(outf, buf, INOUTF_SZ);
+ outf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (! process_flags(buf, &clp->out_flags)) {
+ pr2serr("%sbad argument to 'oflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "sdt")) {
+ cp = strchr(buf, ',');
+ n = sg_get_num(buf);
+ if (n < 0) {
+ pr2serr("%sbad argument to 'sdt=CRT[,ICT]'\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->sdt_crt = n;
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (n < 0) {
+ pr2serr("%sbad 2nd argument to 'sdt=CRT,ICT'\n",
+ my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->sdt_ict = n;
+ }
+ } else if (0 == strcmp(key, "seek")) {
+ clp->seek = sg_get_llnum(buf);
+ if (clp->seek < 0) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "skip")) {
+ clp->skip = sg_get_llnum(buf);
+ if (clp->skip < 0) {
+ pr2serr("%sbad argument to 'skip='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key, "sync"))
+ do_sync = !! sg_get_num(buf);
+ else if (0 == strcmp(key, "thr"))
+ num_threads = sg_get_num(buf);
+ else if (0 == strcmp(key, "time")) {
+ do_time = sg_get_num(buf);
+ if (do_time < 0) {
+ pr2serr("%sbad argument to 'time=0|1|2'\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ cp = strchr(buf, ',');
+ if (cp) {
+ n = sg_get_num(cp + 1);
+ if (n < 0) {
+ pr2serr("%sbad argument to 'time=0|1|2,TO'\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->cmd_timeout = n ? (n * 1000) : DEF_TIMEOUT;
+ }
+ } else if (0 == strcmp(key, "unshare"))
+ clp->unshare = !! sg_get_num(buf); /* default: true */
+ else if (0 == strncmp(key, "verb", 4))
+ clp->verbose = sg_get_num(buf);
+ else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) {
+ res = 0;
+ n = num_chs_in_str(key + 1, keylen - 1, 'c');
+ clp->chkaddr += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'd');
+ clp->dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ clp->help += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ if (n > 0)
+ clp->prefetch = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ clp->verbose += n; /* -v ---> --verbose */
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'x');
+ if (n > 0)
+ verify_given = true;
+ res += n;
+
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strncmp(key, "--chkaddr", 9))
+ ++clp->chkaddr;
+ else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++clp->dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?")))
+ ++clp->help;
+ else if ((0 == strncmp(key, "--prefetch", 10)) ||
+ (0 == strncmp(key, "--pre-fetch", 11)))
+ clp->prefetch = true;
+ else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++clp->verbose; /* --verbose */
+ } else if (0 == strncmp(key, "--veri", 6))
+ verify_given = true;
+ else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help' or '-h'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ clp->verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ clp->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", clp->verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("%s%s\n", my_name, version_str);
+ return SG_LIB_OK_FALSE;
+ }
+ if (clp->help > 0) {
+ usage(clp->help);
+ return SG_LIB_OK_FALSE;
+ }
+ if (clp->bs <= 0) {
+ clp->bs = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ clp->bs);
+ }
+ if (verify_given) {
+ pr2serr("Doing verify/cmp rather than copy\n");
+ clp->verify = true;
+ }
+ if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage(0);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->skip < 0) || (clp->seek < 0)) {
+ pr2serr("skip and seek cannot be negative\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->out_flags.append) {
+ if (clp->seek > 0) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (verify_given) {
+ pr2serr("Can't use both append and verify switches\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (clp->bpt < 1) {
+ pr2serr("bpt must be greater than 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->in_flags.mmap && clp->out_flags.mmap) {
+ pr2serr("mmap flag on both IFILE and OFILE doesn't work\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (! clp->noshare) {
+ if (clp->in_flags.noshare || clp->out_flags.noshare)
+ clp->noshare = true;
+ }
+ if (clp->unshare) {
+ if (clp->in_flags.no_unshare || clp->out_flags.no_unshare)
+ clp->unshare = false;
+ }
+ if (clp->out_flags.mmap && ! clp->noshare) {
+ pr2serr("oflag=mmap needs either noshare=1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->in_flags.mmap || clp->out_flags.mmap) &&
+ (clp->in_flags.same_fds || clp->out_flags.same_fds)) {
+ pr2serr("can't have both 'mmap' and 'same_fds' flags\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((! clp->noshare) && (clp->in_flags.dio || clp->out_flags.dio)) {
+ pr2serr("dio flag can only be used with noshare=1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->nmrqs > 0) {
+ if (clp->in_flags.mrq_immed || clp->out_flags.mrq_immed)
+ clp->mrq_async = true;
+ }
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ for the block layer in lk 2.6 and results in an EIO on the
+ SG_IO ioctl. So reduce it in that case. */
+ if ((clp->bs >= 2048) && (! bpt_given))
+ clp->bpt = DEF_BLOCKS_PER_2048TRANSFER;
+ if (clp->ofsplit >= clp->bpt) {
+ pr2serr("ofsplit when given must be less than BPT\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) {
+ pr2serr("too few or too many threads requested\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->in_flags.swait || clp->out_flags.swait) {
+ if (clp->verbose)
+ pr2serr("the 'swait' flag is now ignored\n");
+ /* remnants ... */
+ if (clp->in_flags.swait && (! clp->out_flags.swait))
+ clp->out_flags.swait = true;
+ }
+ clp->unit_nanosec = (do_time > 1) || !!getenv("SG3_UTILS_LINUX_NANO");
+#if 0
+ if (clp->verbose) {
+ pr2serr("%sif=%s skip=%" PRId64 " of=%s seek=%" PRId64 " count=%"
+ PRId64, my_name, inf, clp->skip, outf, clp->seek, dd_count);
+ if (clp->nmrqs > 0)
+ pr2serr(" mrq=%d%s\n", clp->nmrqs, (clp->mrq_cmds ? ",C" : ""));
+ else
+ pr2serr("\n");
+ }
+#endif
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ char inf[INOUTF_SZ];
+ char outf[INOUTF_SZ];
+ char out2f[INOUTF_SZ];
+ char outregf[INOUTF_SZ];
+ int res, k, err;
+ int64_t in_num_sect = 0;
+ int64_t out_num_sect = 0;
+ int in_sect_sz, out_sect_sz, status, flags;
+ void * vp;
+ const char * ccp = NULL;
+ const char * cc2p;
+ struct global_collection * clp = &gcoll;
+ Thread_info thread_arr[MAX_NUM_THREADS] {};
+ char ebuff[EBUFF_SZ];
+#if SG_LIB_ANDROID
+ struct sigaction actions;
+
+ memset(&actions, 0, sizeof(actions));
+ sigemptyset(&actions.sa_mask);
+ actions.sa_flags = 0;
+ actions.sa_handler = thread_exit_handler;
+ sigaction(SIGUSR1, &actions, NULL);
+ sigaction(SIGUSR2, &actions, NULL);
+#endif
+ /* memset(clp, 0, sizeof(*clp)); */
+ clp->bpt = DEF_BLOCKS_PER_TRANSFER;
+ clp->cmd_timeout = DEF_TIMEOUT;
+ clp->in_type = FT_OTHER;
+ /* change dd's default: if of=OFILE not given, assume /dev/null */
+ clp->out_type = FT_DEV_NULL;
+ clp->out2_type = FT_DEV_NULL;
+ clp->cdbsz_in = DEF_SCSI_CDBSZ;
+ clp->cdbsz_out = DEF_SCSI_CDBSZ;
+ clp->sdt_ict = DEF_SDT_ICT_MS;
+ clp->sdt_crt = DEF_SDT_CRT_SEC;
+ clp->nmrqs = DEF_NUM_MRQS;
+ clp->unshare = true;
+ inf[0] = '\0';
+ outf[0] = '\0';
+ out2f[0] = '\0';
+ outregf[0] = '\0';
+ fetch_sg_version();
+ if (sg_version >= 40045)
+ sg_version_ge_40045 = true;
+
+ res = parse_cmdline_sanity(argc, argv, clp, inf, outf, out2f, outregf);
+ if (SG_LIB_OK_FALSE == res)
+ return 0;
+ if (res)
+ return res;
+ if (sg_version > 40000) {
+ if (! clp->in_flags.v3)
+ clp->in_flags.v4 = true;
+ if (! clp->out_flags.v3)
+ clp->out_flags.v4 = true;
+ }
+
+ install_handler(SIGINT, interrupt_handler);
+ install_handler(SIGQUIT, interrupt_handler);
+ install_handler(SIGPIPE, interrupt_handler);
+ install_handler(SIGUSR1, siginfo_handler);
+ install_handler(SIGUSR2, siginfo2_handler);
+
+ clp->infd = STDIN_FILENO;
+ clp->outfd = STDOUT_FILENO;
+ if (clp->in_flags.ff && clp->in_flags.zero) {
+ ccp = "<addr_as_data>";
+ cc2p = "addr_as_data";
+ } else if (clp->in_flags.ff) {
+ ccp = "<0xff bytes>";
+ cc2p = "ff";
+ } else if (clp->in_flags.random) {
+ ccp = "<random>";
+ cc2p = "random";
+ } else if (clp->in_flags.zero) {
+ ccp = "<zero bytes>";
+ cc2p = "00";
+ }
+ if (ccp) {
+ if (inf[0]) {
+ pr2serr("%siflag=%s and if=%s contradict\n", my_name, cc2p, inf);
+ return SG_LIB_CONTRADICT;
+ }
+ clp->in_type = FT_RANDOM_0_FF;
+ clp->infp = ccp;
+ clp->infd = -1;
+ } else if (inf[0] && ('-' != inf[0])) {
+ clp->in_type = dd_filetype(inf, clp->in_st_size);
+
+ if (FT_ERROR == clp->in_type) {
+ pr2serr("%sunable to access %s\n", my_name, inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == clp->in_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_CHAR == clp->in_type) {
+ pr2serr("%sunable to use unknown char device %s\n", my_name, inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->in_type) {
+ clp->infd = sg_in_open(clp, inf, NULL, NULL);
+ if (clp->verbose > 2)
+ pr2serr("using sg v%c interface on %s\n",
+ (clp->in_flags.v4 ? '4' : '3'), inf);
+ if (clp->infd < 0)
+ return -clp->infd;
+ } else {
+ flags = O_RDONLY;
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((clp->infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for reading",
+ my_name, inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ } else if (clp->skip > 0) {
+ off64_t offset = clp->skip;
+
+ offset *= clp->bs; /* could exceed 32 here! */
+ if (lseek64(clp->infd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't skip to required "
+ "position on %s", my_name, inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ clp->infp = inf;
+ if ((clp->in_flags.v3 || clp->in_flags.v4_given) &&
+ (FT_SG != clp->in_type)) {
+ clp->in_flags.v3 = false;
+ clp->in_flags.v4 = false;
+ pr2serr("%siflag= v3 and v4 both ignored when IFILE is not sg "
+ "device\n", my_name);
+ }
+ }
+ if (clp->verbose && (clp->in_flags.no_waitq || clp->out_flags.no_waitq))
+ pr2serr("no_waitq: flag no longer does anything\n");
+ if (outf[0])
+ clp->ofile_given = true;
+ if (outf[0] && ('-' != outf[0])) {
+ clp->out_type = dd_filetype(outf, clp->out_st_size);
+
+ if ((FT_SG != clp->out_type) && clp->verify) {
+ pr2serr("%s --verify only supported by sg OFILEs\n", my_name);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == clp->out_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, outf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_CHAR == clp->out_type) {
+ pr2serr("%sunable to use unknown char device %s\n", my_name, outf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->out_type) {
+ clp->outfd = sg_out_open(clp, outf, NULL, NULL);
+ if (clp->verbose > 2)
+ pr2serr("using sg v%c interface on %s\n",
+ (clp->out_flags.v4 ? '4' : '3'), outf);
+ if (clp->outfd < 0)
+ return -clp->outfd;
+ } else if (FT_DEV_NULL == clp->out_type)
+ clp->outfd = -1; /* don't bother opening */
+ else {
+ flags = O_WRONLY;
+ if (! clp->out_flags.nocreat)
+ flags |= O_CREAT;
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+
+ if ((clp->outfd = open(outf, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for "
+ "writing", my_name, outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (clp->seek > 0) {
+ off64_t offset = clp->seek;
+
+ offset *= clp->bs; /* could exceed 32 bits here! */
+ if (lseek64(clp->outfd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required "
+ "position on %s", my_name, outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ clp->outfp = outf;
+ if ((clp->out_flags.v3 || clp->out_flags.v4_given) &&
+ (FT_SG != clp->out_type)) {
+ clp->out_flags.v3 = false;
+ clp->out_flags.v4 = false;
+ pr2serr("%soflag= v3 and v4 both ignored when OFILE is not sg "
+ "device\n", my_name);
+ }
+ }
+
+ if (out2f[0])
+ clp->ofile2_given = true;
+ if (out2f[0] && ('-' != out2f[0])) {
+ off_t out2_st_size;
+
+ clp->out2_type = dd_filetype(out2f, out2_st_size);
+ if (FT_ST == clp->out2_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, out2f);
+ return SG_LIB_FILE_ERROR;
+ }
+ else if (FT_SG == clp->out2_type) {
+ clp->out2fd = sg_out_open(clp, out2f, NULL, NULL);
+ if (clp->out2fd < 0)
+ return -clp->out2fd;
+ }
+ else if (FT_DEV_NULL == clp->out2_type)
+ clp->out2fd = -1; /* don't bother opening */
+ else {
+ flags = O_WRONLY;
+ if (! clp->out_flags.nocreat)
+ flags |= O_CREAT;
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+
+ if ((clp->out2fd = open(out2f, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for "
+ "writing", my_name, out2f);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (clp->seek > 0) {
+ off64_t offset = clp->seek;
+
+ offset *= clp->bs; /* could exceed 32 bits here! */
+ if (lseek64(clp->out2fd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required "
+ "position on %s", my_name, out2f);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ clp->out2fp = out2f;
+ }
+ if ((FT_SG == clp->in_type ) && (FT_SG == clp->out_type)) {
+ if (clp->nmrqs > 0) {
+ if (clp->is_mrq_i == clp->is_mrq_o) {
+ if (clp->ofsplit > 0) {
+ if (0 != (clp->nmrqs % 3)) {
+ pr2serr("When both IFILE+OFILE sg devices and OSP>0, "
+ "mrq=NRQS must be divisible by 3\n");
+ pr2serr(" triple NRQS to avoid error\n");
+ clp->nmrqs *= 3;
+ }
+ } else if (0 != (clp->nmrqs % 2)) {
+ pr2serr("When both IFILE+OFILE sg devices (and OSP=0), "
+ "mrq=NRQS must be even\n");
+ pr2serr(" double NRQS to avoid error\n");
+ clp->nmrqs *= 2;
+ }
+ }
+ if (clp->is_mrq_i && clp->is_mrq_o)
+ ;
+ else if (clp->is_mrq_i || clp->is_mrq_o)
+ clp->unbalanced_mrq = true;
+ }
+ if (clp->in_flags.v4_given && (! clp->out_flags.v3)) {
+ if (! clp->out_flags.v4_given) {
+ clp->out_flags.v4 = true;
+ if (clp->verbose)
+ pr2serr("Changing OFILE from v3 to v4, use oflag=v3 to "
+ "force v3\n");
+ }
+ }
+ if (clp->out_flags.v4_given && (! clp->in_flags.v3)) {
+ if (! clp->in_flags.v4_given) {
+ clp->in_flags.v4 = true;
+ if (clp->verbose)
+ pr2serr("Changing IFILE from v3 to v4, use iflag=v3 to "
+ "force v3\n");
+ }
+ }
+#if 0
+ if (clp->mrq_async && !(clp->noshare)) {
+ pr2serr("With mrq_immed also need noshare on sg-->sg copy\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+#endif
+ } else if ((FT_SG == clp->in_type ) || (FT_SG == clp->out_type)) {
+ if (clp->nmrqs > 0)
+ clp->unbalanced_mrq = true;
+ }
+ if (outregf[0]) {
+ off_t outrf_st_size;
+ int ftyp = dd_filetype(outregf, outrf_st_size);
+
+ clp->outreg_type = ftyp;
+ if (! ((FT_OTHER == ftyp) || (FT_ERROR == ftyp) ||
+ (FT_DEV_NULL == ftyp))) {
+ pr2serr("File: %s can only be regular file or pipe (or "
+ "/dev/null)\n", outregf);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->outregfd = open(outregf, O_WRONLY | O_CREAT, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "could not open %s for writing",
+ outregf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (clp->verbose > 1)
+ pr2serr("ofreg=%s opened okay, fd=%d\n", outregf, clp->outregfd);
+ if (FT_ERROR == ftyp)
+ clp->outreg_type = FT_OTHER; /* regular file created */
+ } else
+ clp->outregfd = -1;
+
+ if ((STDIN_FILENO == clp->infd) && (STDOUT_FILENO == clp->outfd)) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to "
+ "/dev/null\n");
+ pr2serr("For more information use '--help' or '-h'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (dd_count < 0) {
+ in_num_sect = -1;
+ if (FT_SG == clp->in_type) {
+ res = scsi_read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(in), continuing\n");
+ res = scsi_read_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", inf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", inf);
+ else
+ pr2serr("Unable to read capacity on %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != in_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->infp, clp->bs,
+ in_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ } else if (FT_BLOCK == clp->in_type) {
+ if (0 != read_blkdev_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ if (clp->bs != in_sect_sz) {
+ pr2serr("logical block size on %s confusion; bs=%d, from "
+ "device=%d\n", inf, clp->bs, in_sect_sz);
+ in_num_sect = -1;
+ }
+ } else if (FT_OTHER == clp->in_type) {
+ in_num_sect = clp->in_st_size / clp->bs;
+ if (clp->in_st_size % clp->bs) {
+ ++in_num_sect;
+ pr2serr("Warning: the file size of %s is not a multiple of BS "
+ "[%d]\n", inf, clp->bs);
+ }
+ }
+ if (in_num_sect > clp->skip)
+ in_num_sect -= clp->skip;
+
+ out_num_sect = -1;
+ if (FT_SG == clp->out_type) {
+ res = scsi_read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(out), continuing\n");
+ res = scsi_read_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", outf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", outf);
+ else
+ pr2serr("Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != out_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->outfp, clp->bs,
+ out_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ } else if (FT_BLOCK == clp->out_type) {
+ if (0 != read_blkdev_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outf);
+ out_num_sect = -1;
+ }
+ if (clp->bs != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, from "
+ "device=%d\n", outf, clp->bs, out_sect_sz);
+ out_num_sect = -1;
+ }
+ } else if (FT_OTHER == clp->out_type) {
+ out_num_sect = clp->out_st_size / clp->bs;
+ if (clp->out_st_size % clp->bs) {
+ ++out_num_sect;
+ pr2serr("Warning: the file size of %s is not a multiple of BS "
+ "[%d]\n", outf, clp->bs);
+ }
+ }
+ if (out_num_sect > clp->seek)
+ out_num_sect -= clp->seek;
+
+ if (in_num_sect > 0) {
+ if (out_num_sect > 0)
+ dd_count = (in_num_sect > out_num_sect) ? out_num_sect :
+ in_num_sect;
+ else
+ dd_count = in_num_sect;
+ }
+ else
+ dd_count = out_num_sect;
+ }
+ if (clp->verbose > 2)
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64
+ ", out_num_sect=%" PRId64 "\n", dd_count, in_num_sect,
+ out_num_sect);
+ if (dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (! clp->cdbsz_given) {
+ if ((FT_SG == clp->in_type) && (MAX_SCSI_CDBSZ != clp->cdbsz_in) &&
+ (((dd_count + clp->skip) > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ clp->cdbsz_in = MAX_SCSI_CDBSZ;
+ }
+ if ((FT_SG == clp->out_type) && (MAX_SCSI_CDBSZ != clp->cdbsz_out) &&
+ (((dd_count + clp->seek) > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ clp->cdbsz_out = MAX_SCSI_CDBSZ;
+ }
+ }
+
+ // clp->in_count = dd_count;
+ clp->in_rem_count = dd_count;
+ clp->out_count = dd_count;
+ clp->out_rem_count = dd_count;
+ clp->out_blk = clp->seek;
+ status = pthread_mutex_init(&clp->in_mutex, NULL);
+ if (0 != status) err_exit(status, "init in_mutex");
+ status = pthread_mutex_init(&clp->out_mutex, NULL);
+ if (0 != status) err_exit(status, "init out_mutex");
+ status = pthread_mutex_init(&clp->out2_mutex, NULL);
+ if (0 != status) err_exit(status, "init out2_mutex");
+ status = pthread_cond_init(&clp->out_sync_cv, NULL);
+ if (0 != status) err_exit(status, "init out_sync_cv");
+
+ if (clp->dry_run > 0) {
+ pr2serr("Due to --dry-run option, bypass copy/read\n");
+ goto fini;
+ }
+ if (! clp->ofile_given)
+ pr2serr("of=OFILE not given so only read from IFILE, to output to "
+ "stdout use 'of=-'\n");
+
+ sigemptyset(&signal_set);
+ sigaddset(&signal_set, SIGINT);
+ sigaddset(&signal_set, SIGUSR2);
+ status = pthread_sigmask(SIG_BLOCK, &signal_set, &orig_signal_set);
+ if (0 != status) err_exit(status, "pthread_sigmask");
+ status = pthread_create(&sig_listen_thread_id, NULL,
+ sig_listen_thread, (void *)clp);
+ if (0 != status) err_exit(status, "pthread_create, sig...");
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+
+/* vvvvvvvvvvv Start worker threads vvvvvvvvvvvvvvvvvvvvvvvv */
+ if ((clp->out_rem_count.load() > 0) && (num_threads > 0)) {
+ Thread_info *tip = thread_arr + 0;
+
+ tip->gcp = clp;
+ tip->id = 0;
+ /* Run 1 work thread to shake down infant retryable stuff */
+ status = pthread_mutex_lock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "lock out_mutex");
+ status = pthread_create(&tip->a_pthr, NULL, read_write_thread,
+ (void *)tip);
+ if (0 != status) err_exit(status, "pthread_create");
+
+ /* wait for any broadcast */
+ pthread_cleanup_push(cleanup_out, (void *)clp);
+ status = pthread_cond_wait(&clp->out_sync_cv, &clp->out_mutex);
+ if (0 != status) err_exit(status, "cond out_sync_cv");
+ pthread_cleanup_pop(0);
+ status = pthread_mutex_unlock(&clp->out_mutex);
+ if (0 != status) err_exit(status, "unlock out_mutex");
+
+ /* now start the rest of the threads */
+ for (k = 1; k < num_threads; ++k) {
+ tip = thread_arr + k;
+ tip->gcp = clp;
+ tip->id = k;
+ status = pthread_create(&tip->a_pthr, NULL, read_write_thread,
+ (void *)tip);
+ if (0 != status) err_exit(status, "pthread_create");
+ }
+
+ /* now wait for worker threads to finish */
+ for (k = 0; k < num_threads; ++k) {
+ tip = thread_arr + k;
+ status = pthread_join(tip->a_pthr, &vp);
+ if (0 != status) err_exit(status, "pthread_join");
+ if (clp->verbose > 2)
+ pr2serr_lk("%d <-- Worker thread terminated, vp=%s\n", k,
+ ((vp == clp) ? "clp" : "NULL (or !clp)"));
+ }
+ } /* started worker threads and here after they have all exited */
+
+ if (do_time && (start_tm.tv_sec || start_tm.tv_usec))
+ calc_duration_throughput(0);
+
+ shutting_down = true;
+ status = pthread_join(sig_listen_thread_id, &vp);
+ if (0 != status) err_exit(status, "pthread_join");
+#if 0
+ /* pthread_cancel() has issues and is not supported in Android */
+ status = pthread_kill(sig_listen_thread_id, SIGUSR2);
+ if (0 != status) err_exit(status, "pthread_kill");
+ std::this_thread::yield(); // not enough it seems
+ { /* allow time for SIGUSR2 signal to get through */
+ struct timespec tspec = {0, 400000}; /* 400 usecs */
+
+ nanosleep(&tspec, NULL);
+ }
+#endif
+
+ if (do_sync) {
+ if (FT_SG == clp->out_type) {
+ pr2serr_lk(">> Synchronizing cache on %s\n", outf);
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr_lk("Unit attention(out), continuing\n");
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr_lk("Unable to synchronize cache\n");
+ }
+ if (FT_SG == clp->out2_type) {
+ pr2serr_lk(">> Synchronizing cache on %s\n", out2f);
+ res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr_lk("Unit attention(out2), continuing\n");
+ res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr_lk("Unable to synchronize cache (of2)\n");
+ }
+ }
+
+fini:
+ if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0))
+ close(clp->infd);
+ if ((STDOUT_FILENO != clp->outfd) && (FT_DEV_NULL != clp->out_type) &&
+ (clp->outfd >= 0))
+ close(clp->outfd);
+ if ((clp->out2fd >= 0) && (STDOUT_FILENO != clp->out2fd) &&
+ (FT_DEV_NULL != clp->out2_type))
+ close(clp->out2fd);
+ if ((clp->outregfd >= 0) && (STDOUT_FILENO != clp->outregfd) &&
+ (FT_DEV_NULL != clp->outreg_type))
+ close(clp->outregfd);
+ res = exit_status;
+ if ((0 != clp->out_count.load()) && (0 == clp->dry_run)) {
+ pr2serr(">>>> Some error occurred, remaining blocks=%" PRId64 "\n",
+ clp->out_count.load());
+ if (0 == res)
+ res = SG_LIB_CAT_OTHER;
+ }
+ print_stats("");
+ if (clp->dio_incomplete_count.load()) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ clp->dio_incomplete_count.load());
+ if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", sg_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (clp->sum_of_resids.load())
+ pr2serr(">> Non-zero sum of residual counts=%d\n",
+ clp->sum_of_resids.load());
+ if (clp->verbose && (num_start_eagain > 0))
+ pr2serr("Number of start EAGAINs: %d\n", num_start_eagain.load());
+ if (clp->verbose && (num_fin_eagain > 0))
+ pr2serr("Number of finish EAGAINs: %d\n", num_fin_eagain.load());
+ if (clp->verbose && (num_ebusy > 0))
+ pr2serr("Number of EBUSYs: %d\n", num_ebusy.load());
+ if (clp->verbose && clp->aen_given && (num_abort_req > 0)) {
+ pr2serr("Number of Aborts: %d\n", num_abort_req.load());
+ pr2serr("Number of successful Aborts: %d\n",
+ num_abort_req_success.load());
+ }
+ if (clp->verbose && clp->m_aen_given && (num_mrq_abort_req > 0)) {
+ pr2serr("Number of MRQ Aborts: %d\n", num_mrq_abort_req.load());
+ pr2serr("Number of successful MRQ Aborts: %d\n",
+ num_mrq_abort_req_success.load());
+ }
+ if (clp->verbose && (num_miscompare > 0))
+ pr2serr("Number of miscompare%s: %d\n",
+ (num_miscompare > 1) ? "s" : "", num_miscompare.load());
+ if (clp->verbose > 1) {
+ if (clp->verbose > 3)
+ pr2serr("Final pack_id=%d, mrq_id=%d\n", mono_pack_id.load(),
+ mono_mrq_id.load());
+ pr2serr("Number of SG_GET_NUM_WAITING calls=%ld\n",
+ num_waiting_calls.load());
+ }
+ if (clp->verify && (SG_LIB_CAT_MISCOMPARE == res))
+ pr2serr("Verify/compare failed due to miscompare\n");
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+}
diff --git a/testing/sgs_dd.c b/testing/sgs_dd.c
new file mode 100644
index 00000000..c139a178
--- /dev/null
+++ b/testing/sgs_dd.c
@@ -0,0 +1,1667 @@
+/*
+ * Test code for the extensions to the Linux OS SCSI generic ("sg")
+ * device driver.
+ * Copyright (C) 1999-2022 D. Gilbert and P. Allworth
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is a specialization of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device. A block size
+ * ('bs') is assumed to be 512 if not given. This program complains if
+ * 'ibs' or 'obs' are given with some other value than 'bs'.
+ * If 'if' is not given or 'if=-' then stdin is assumed. If 'of' is
+ * not given of 'of=-' then stdout assumed. The multipliers "c, b, k, m"
+ * are recognized on numeric arguments.
+ *
+ * A non-standard argument "bpt" (blocks per transfer) is added to control
+ * the maximum number of blocks in each transfer. The default bpt value is
+ * (64 * 1024 * 1024 / bs) or 1 if the first expression is 0. That is an
+ * integer division (rounds toward 0). For example if "bs=512" and "bpt=32"
+ * are given then a maximum of 32 blocks (16KB in this case) are transferred
+ * to or from the sg device in a single SCSI command.
+ *
+ * BEWARE: If the 'of' file is a 'sg' device (eg a disk) then it _will_
+ * be written to, potentially destroying its previous contents.
+ *
+ * This version should compile with Linux sg drivers with version numbers
+ * >= 30000 . Also this version also allows SIGPOLL or a RT signal to be
+ * chosen. SIGIO is a synonym for SIGPOLL; SIGIO seems to be deprecated.
+ */
+
+/* We need F_SETSIG, (signal redirect), so following define */
+#define _GNU_SOURCE 1
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h> /* for mmap() system call */
+#include <sys/eventfd.h>
+#include <sys/epoll.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+#include "sg_io_linux.h"
+#include "sg_pr2serr.h"
+#include "sg_unaligned.h"
+
+
+static const char * version_str = "4.24 20221020";
+static const char * my_name = "sgs_dd";
+
+#ifndef SGV4_FLAG_POLLED
+#define SGV4_FLAG_POLLED 0x800
+#endif
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BPT_TIMES_BS_SZ (64 * 1024) /* 64 KB */
+
+#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT 40000 /* 40,000 millisecs == 40 seconds */
+#define S_RW_LEN 10 /* Use SCSI READ(10) and WRITE(10) */
+#define SGQ_MAX_RD_AHEAD 32
+#define SGQ_MAX_WR_AHEAD 32
+#define SGQ_NUM_ELEMS (SGQ_MAX_RD_AHEAD + SGQ_MAX_WR_AHEAD + 1)
+#define MAX_BPT_VALUE (1 << 24) /* used for maximum bs as well */
+#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */
+
+#define SGQ_FREE 0
+#define SGQ_IO_STARTED 1
+#define SGQ_IO_FINISHED 2
+#define SGQ_IO_ERR 3
+#define SGQ_IO_WAIT 4
+
+#define SGQ_CAN_DO_NOTHING 0 /* only temporarily in use */
+#define SGQ_CAN_READ 1
+#define SGQ_CAN_WRITE 2
+#define SGQ_TIMEOUT 4
+
+#define DEF_SIGTIMEDWAIT_USEC 100
+
+
+#define STR_SZ 1024
+#define INOUTF_SZ 900
+#define EBUFF_SZ 1024
+
+struct flags_t {
+ bool dio;
+ bool evfd;
+ bool excl;
+ bool immed;
+ bool mmap;
+ bool noxfer;
+ bool pack;
+ bool polled;
+ bool tag;
+ bool v3;
+ bool v4;
+ bool given_v3v4;
+};
+
+typedef struct request_element
+{
+ struct request_element * nextp;
+ bool stop_after_wr;
+ bool wr;
+ int state;
+ int blk;
+ int num_blks;
+ uint8_t * buffp;
+ uint8_t * free_buffp;
+ sg_io_hdr_t io_hdr;
+ struct sg_io_v4 io_v4;
+ struct flags_t * iflagp;
+ struct flags_t * oflagp;
+ uint8_t cmd[S_RW_LEN];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int result;
+} Rq_elem;
+
+typedef struct request_collection
+{
+ bool in_is_sg;
+ bool out_is_sg;
+ bool no_sig;
+ bool use_rt_sig;
+ bool both_mmap;
+ int infd;
+ int in_evfd;
+ int in_blk; /* most recent read */
+ int in_count; /* most recent read */
+ int in_done_count; /* count of completed in blocks */
+ int in_partial;
+ int outfd;
+ int out_evfd;
+ int lowest_seek;
+ int out_blk; /* most recent write */
+ int out_count; /* most recent write */
+ int out_done_count; /* count of completed out blocks */
+ int out_partial;
+ int bs;
+ int bpt;
+ int dio_incomplete;
+ int sum_of_resids;
+ int poll_ms;
+ int pollerr_count;
+ int debug; /* also set with -v up to -vvvvv */
+ sigset_t blocked_sigs;
+ int sigs_waiting;
+ int sigs_rt_received;
+ int sigs_io_received;
+ int blk_poll_count;
+ Rq_elem * rd_posp;
+ Rq_elem * wr_posp;
+ uint8_t * in_mmapp;
+ uint8_t * out_mmapp;
+ struct flags_t iflag;
+ struct flags_t oflag;
+ Rq_elem elem[SGQ_NUM_ELEMS];
+} Rq_coll;
+
+static bool sgs_old_sg_driver = false; /* true if VERSION_NUM < 4.00.00 */
+static bool sgs_full_v4_sg_driver = false; /* set if VERSION_NUM >= 4.00.30 */
+static bool sgs_nanosec_unit = false;
+
+static int sgq_rd_ahead_lim = SGQ_MAX_RD_AHEAD;
+static int sgq_wr_ahead_lim = SGQ_MAX_WR_AHEAD;
+static int sgq_num_elems = (SGQ_MAX_RD_AHEAD + SGQ_MAX_WR_AHEAD + 1);
+
+
+static void
+usage(int pg_num)
+{
+ if (pg_num > 1)
+ goto second_page;
+ printf("Usage: "
+ "sgs_dd [bpt=BPT] [bs=BS] [count=NUM] [deb=DEB] [if=IFILE]\n"
+ " [iflag=FLAGS] [no_sig=0|1] [of=OFILE] "
+ "[oflag=FLAGS]\n"
+ " [poll_ms=MS] [rt_sig=0|1] [seek=SEEK] "
+ "[skip=SKIP]\n"
+ " [--help] [--version]\n"
+ "where:\n"
+ " bpt blocks_per_transfer (default: 65536/bs (or 128 for "
+ "bs=512))\n"
+ " bs must be the logical block size of device (def: 512)\n"
+ " deb debug: 0->no debug (def); > 0 -> more debug\n"
+ " -v (up to -vvvvv) sets deb value to number of 'v's\n"
+ " iflag comma separated list from: dio,evfd,excl,immed,mmap,"
+ "noxfer,\n"
+ " null,pack,polled,tag,v3,v4 bound to IFILE\n"
+ " no_sig 0-> use signals; 1-> no signals, hard polling "
+ "instead;\n"
+ " default 0, unless polled flag(s) given then it's 1\n"
+ " oflag same flags as iflag but bound to OFILE\n"
+ " poll_ms number of milliseconds to wait on poll (def: 0)\n"
+ " rt_sig 0->use SIGIO (def); 1->use RT sig (SIGRTMIN + 1)\n"
+ " <other operands> as per dd command\n\n");
+ printf("dd clone for testing Linux sg driver SIGPOLL and/or polling. "
+ "Either\nIFILE or OFILE must be a scsi generic device. If OFILE "
+ "not given then\n/dev/null assumed (rather than stdout like "
+ "dd). Use '-hh' for flag\ninformation.\n");
+ return;
+second_page:
+ printf("flag description:\n"
+ " dio this driver's version of O_DIRECT\n"
+ " evfd when poll() gives POLLIN, use eventfd to find "
+ "out how many\n"
+ " excl open IFILE or OFILE with O_EXCL\n"
+ " hipri same as 'polled'; name 'hipri' is deprecated\n"
+ " immed use SGV4_FLAG_IMMED flag on each request\n"
+ " mmap use mmap()-ed IO on IFILE or OFILE\n"
+ " noxfer no transfer between user space and kernel IO "
+ "buffers\n"
+ " null does nothing, placeholder\n"
+ " pack submit with rising pack_id, complete matching "
+ "each pack_id\n"
+ " polled set POLLED flag and use blk_poll() for completion\n"
+ " tag use tag (from block layer) rather than "
+ "pack_id\n"
+ " v3 use sg v3 interface (default)\n"
+ " v4 use sg vr interface (i.e. struct sg_io_v4)\n");
+}
+
+static int
+get_mmap_addr(int fd, int num, uint8_t ** mmpp)
+{
+ uint8_t * mmp;
+
+ if (! mmpp)
+ return -EINVAL;
+ mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (MAP_FAILED == mmp) {
+ int err = errno;
+
+ pr2serr("%s%s: sz=%d, fd=%d, mmap() failed: %s\n",
+ my_name, __func__, num, fd, strerror(err));
+ return -err;
+ }
+ *mmpp = mmp;
+ return 0;
+}
+
+/* Return of 0 -> success, -1 -> failure, 2 -> try again */
+static int
+read_capacity(int sg_fd, int * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t rcBuff[64];
+ uint8_t sense_b[64];
+ sg_io_hdr_t io_hdr;
+
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rcCmdBlk);
+ io_hdr.mx_sb_len = sizeof(sense_b);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = sizeof(rcBuff);
+ io_hdr.dxferp = rcBuff;
+ io_hdr.cmdp = rcCmdBlk;
+ io_hdr.sbp = sense_b;
+ io_hdr.timeout = DEF_TIMEOUT;
+
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ res = -errno;
+ perror("read_capacity (SG_IO) error");
+ return res;
+ }
+ res = sg_err_category3(&io_hdr);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res)
+ return 2; /* probably have another go ... */
+ else if (SG_LIB_CAT_CLEAN != res) {
+ sg_chk_n_print3("read capacity", &io_hdr, true);
+ return -1;
+ }
+ *num_sect = sg_get_unaligned_be32(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ return 0;
+}
+
+/* -ve -> unrecoverable error, 0 -> successful, 1 -> recoverable (ENOMEM) */
+static int
+sg_start_io(Rq_coll * clp, Rq_elem * rep)
+{
+ bool is_wr = rep->wr;
+ int res;
+ int fd = is_wr ? clp->outfd : clp->infd;
+ int num_bytes = clp->bs * rep->num_blks;
+ struct flags_t * flagp = is_wr ? rep->oflagp : rep->iflagp;
+ sg_io_hdr_t * hp = &rep->io_hdr;
+ struct sg_io_v4 * h4p = &rep->io_v4;
+
+ if (clp->both_mmap && is_wr)
+ memcpy(clp->out_mmapp, clp->in_mmapp, num_bytes);
+ memset(rep->cmd, 0, sizeof(rep->cmd));
+ rep->cmd[0] = is_wr ? 0x2a : 0x28;
+ sg_put_unaligned_be32((uint32_t)rep->blk, rep->cmd + 2);
+ sg_put_unaligned_be16((uint16_t)rep->num_blks, rep->cmd + 7);
+ if (flagp->v4)
+ goto do_v4;
+
+ memset(hp, 0, sizeof(sg_io_hdr_t));
+ hp->interface_id = 'S';
+ hp->cmd_len = sizeof(rep->cmd);
+ hp->cmdp = rep->cmd;
+ hp->dxfer_direction = is_wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
+ hp->dxfer_len = num_bytes;
+ hp->mx_sb_len = sizeof(rep->sb);
+ hp->sbp = rep->sb;
+ hp->timeout = DEF_TIMEOUT;
+ hp->usr_ptr = rep;
+ hp->pack_id = rep->blk;
+ if (flagp->dio)
+ hp->flags |= SG_FLAG_DIRECT_IO;
+ if (flagp->noxfer)
+ hp->flags |= SG_FLAG_NO_DXFER;
+ if (flagp->immed)
+ hp->flags |= SGV4_FLAG_IMMED;
+ if (flagp->polled)
+ hp->flags |= SGV4_FLAG_POLLED;
+ if (flagp->mmap) {
+ hp->flags |= SG_FLAG_MMAP_IO;
+ hp->dxferp = is_wr ? clp->out_mmapp : clp->in_mmapp;
+ } else
+ hp->dxferp = rep->buffp;
+ if (flagp->evfd)
+ hp->flags |= SGV4_FLAG_EVENTFD;
+ if (clp->debug > 5) {
+ pr2serr("%s: SCSI %s, blk=%d num_blks=%d\n", __func__,
+ is_wr ? "WRITE" : "READ", rep->blk, rep->num_blks);
+ sg_print_command(hp->cmdp);
+ pr2serr("dir=%d, len=%d, dxfrp=%p, cmd_len=%d\n", hp->dxfer_direction,
+ hp->dxfer_len, hp->dxferp, hp->cmd_len);
+ }
+
+ while (((res = write(fd, hp, sizeof(sg_io_hdr_t))) < 0) &&
+ (EINTR == errno))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return 1;
+ if ((EDOM == errno) || (EAGAIN == errno) || (EBUSY == errno)) {
+ rep->state = SGQ_IO_WAIT; /* busy so wait */
+ return 0;
+ }
+ pr2serr("%s: write(): %s [%d]\n", __func__, strerror(errno), errno);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ rep->state = SGQ_IO_STARTED;
+ if (! clp->no_sig)
+ clp->sigs_waiting++;
+ return 0;
+do_v4:
+ memset(h4p, 0, sizeof(struct sg_io_v4));
+ h4p->guard = 'Q';
+ h4p->request_len = sizeof(rep->cmd);
+ h4p->request = (uint64_t)(uintptr_t)rep->cmd;
+ if (is_wr)
+ h4p->dout_xfer_len = num_bytes;
+ else if (rep->num_blks > 0)
+ h4p->din_xfer_len = num_bytes;
+ h4p->max_response_len = sizeof(rep->sb);
+ h4p->response = (uint64_t)(uintptr_t)rep->sb;
+ h4p->timeout = DEF_TIMEOUT;
+ h4p->usr_ptr = (uint64_t)(uintptr_t)rep;
+ h4p->request_extra = rep->blk;/* N.B. blk --> pack_id --> request_extra */
+ if (flagp->dio)
+ h4p->flags |= SG_FLAG_DIRECT_IO;
+ if (flagp->noxfer)
+ h4p->flags |= SG_FLAG_NO_DXFER;
+ if (flagp->immed)
+ h4p->flags |= SGV4_FLAG_IMMED;
+ if (flagp->polled)
+ h4p->flags |= SGV4_FLAG_POLLED;
+ if (flagp->mmap) {
+ h4p->flags |= SG_FLAG_MMAP_IO;
+ hp->dxferp = is_wr ? clp->out_mmapp : clp->in_mmapp;
+ } else {
+ if (is_wr)
+ h4p->dout_xferp = (uint64_t)(uintptr_t)rep->buffp;
+ else if (rep->num_blks > 0)
+ h4p->din_xferp = (uint64_t)(uintptr_t)rep->buffp;
+ }
+ if (flagp->tag)
+ h4p->flags |= SGV4_FLAG_YIELD_TAG;
+ if (flagp->evfd)
+ h4p->flags |= SGV4_FLAG_EVENTFD;
+ if (! clp->no_sig)
+ h4p->flags |= SGV4_FLAG_SIGNAL;
+
+ while (((res = ioctl(fd, SG_IOSUBMIT, h4p)) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ if (ENOMEM == errno)
+ return 1;
+ if ((EDOM == errno) || (EAGAIN == errno) || (EBUSY == errno)) {
+ rep->state = SGQ_IO_WAIT; /* busy so wait */
+ return 0;
+ }
+ pr2serr("%s: ioctl(SG_IOSUBMIT): %s [%d]\n", __func__,
+ strerror(errno), errno);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ rep->state = SGQ_IO_STARTED;
+ if (! clp->no_sig)
+ clp->sigs_waiting++;
+ if (clp->debug > 5) {
+ if (is_wr ? clp->oflag.tag : clp->iflag.tag)
+ pr2serr("%s: generated_tag=0x%" PRIx64 "\n", __func__,
+ (uint64_t)h4p->generated_tag);
+ }
+ return 0;
+}
+
+/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */
+static int
+sg_finish_io(Rq_coll * clp, bool wr, Rq_elem ** repp)
+{
+ struct flags_t *flagsp = wr ? &clp->oflag : &clp->iflag;
+ bool dio = false;
+ bool is_v4 = flagsp->v4;
+ bool use_pack = flagsp->pack;
+ bool use_tag = flagsp->tag;
+ int fd = wr ? clp->outfd : clp->infd;
+ int res, id, n;
+ sg_io_hdr_t io_hdr;
+ sg_io_hdr_t * hp;
+ struct sg_io_v4 io_v4;
+ struct sg_io_v4 * h4p;
+ Rq_elem * rep;
+
+ if (is_v4)
+ goto do_v4;
+ if (use_pack) {
+ while (true) {
+ if ( ((res = ioctl(fd, SG_GET_NUM_WAITING, &n))) < 0) {
+ res = -errno;
+ pr2serr("%s: ioctl(SG_GET_NUM_WAITING): %s [%d]\n",
+ __func__, strerror(errno), errno);
+ return res;
+ }
+ if (n > 0) {
+ if ( (ioctl(fd, SG_GET_PACK_ID, &id)) < 0) {
+ res = errno;
+ pr2serr("%s: ioctl(SG_GET_PACK_ID): %s [%d]\n",
+ __func__, strerror(res), res);
+ return -res;
+ }
+ /* got pack_id or tag of first waiting */
+ break;
+ }
+ }
+ }
+ memset(&io_hdr, 0 , sizeof(sg_io_hdr_t));
+ if (use_pack)
+ io_hdr.pack_id = id;
+ while (((res = read(fd, &io_hdr, sizeof(sg_io_hdr_t))) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ rep = (Rq_elem *)io_hdr.usr_ptr;
+ if (rep) {
+ dio = flagsp->dio;
+ if (rep->io_hdr.flags & SGV4_FLAG_POLLED)
+ ++clp->blk_poll_count;
+ }
+ if (res < 0) {
+ res = -errno;
+ pr2serr("%s: read(): %s [%d]\n", __func__, strerror(errno), errno);
+ if (rep)
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ if (! (rep && (SGQ_IO_STARTED == rep->state))) {
+ pr2serr("%s: bad usr_ptr\n", __func__);
+ if (rep)
+ rep->state = SGQ_IO_ERR;
+ return -1;
+ }
+ memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t));
+ hp = &rep->io_hdr;
+ if (repp)
+ *repp = rep;
+
+ switch (sg_err_category3(hp)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ pr2serr("Recovered error on block=%d, num=%d\n", rep->blk,
+ rep->num_blks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ return 1;
+ default:
+ sg_chk_n_print3(wr ? "writing": "reading", hp, true);
+ rep->state = SGQ_IO_ERR;
+ return -1;
+ }
+ if (dio && ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ ++clp->dio_incomplete; /* count dios done as indirect IO */
+ clp->sum_of_resids += hp->resid;
+ rep->state = SGQ_IO_FINISHED;
+ if (clp->debug > 5) {
+ pr2serr("%s: %s ", __func__, wr ? "writing" : "reading");
+ pr2serr(" SGQ_IO_FINISHED elem idx=%zd\n", rep - clp->elem);
+ }
+ return 0;
+do_v4:
+ id = -1;
+ if (use_pack || use_tag) {
+ while (true) {
+ if ( ((res = ioctl(fd, SG_GET_NUM_WAITING, &n))) < 0) {
+ res = -errno;
+ pr2serr("%s: ioctl(SG_GET_NUM_WAITING): %s [%d]\n",
+ __func__, strerror(errno), errno);
+ return res;
+ }
+ if (n > 0) {
+ if ( (ioctl(fd, SG_GET_PACK_ID, &id)) < 0) {
+ res = errno;
+ pr2serr("%s: ioctl(SG_GET_PACK_ID): %s [%d]\n",
+ __func__, strerror(res), res);
+ return -res;
+ }
+ /* got pack_id or tag of first waiting */
+ break;
+ }
+ }
+ }
+ memset(&io_v4, 0 , sizeof(io_v4));
+ io_v4.guard = 'Q';
+ if (use_tag)
+ io_v4.request_tag = id;
+ else if (use_pack)
+ io_v4.request_extra = id;
+ io_v4.flags |= SGV4_FLAG_IMMED;
+ if (flagsp->evfd)
+ io_v4.flags |= SGV4_FLAG_EVENTFD;
+ while (((res = ioctl(fd, SG_IORECEIVE, &io_v4)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
+ ;
+ rep = (Rq_elem *)(unsigned long)io_v4.usr_ptr;
+ if (res < 0) {
+ res = -errno;
+ pr2serr("%s: ioctl(SG_IORECEIVE): %s [%d]\n", __func__,
+ strerror(errno), errno);
+ if (rep)
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ if (rep) {
+ if (rep->io_v4.flags & SGV4_FLAG_POLLED)
+ ++clp->blk_poll_count;
+ }
+ if (! (rep && (SGQ_IO_STARTED == rep->state))) {
+ pr2serr("%s: bad usr_ptr=0x%p\n", __func__, (void *)rep);
+ if (rep)
+ rep->state = SGQ_IO_ERR;
+ return -1;
+ }
+ memcpy(&rep->io_v4, &io_v4, sizeof(struct sg_io_v4));
+ h4p = &rep->io_v4;
+ if (repp)
+ *repp = rep;
+
+ res = sg_err_category_new(h4p->device_status, h4p->transport_status,
+ h4p->driver_status,
+ (const uint8_t *)(unsigned long)h4p->response,
+ h4p->response_len);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ pr2serr("Recovered error on block=%d, num=%d\n", rep->blk,
+ rep->num_blks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ return 1;
+ default:
+ sg_linux_sense_print(wr ? "writing": "reading",
+ h4p->device_status, h4p->transport_status,
+ h4p->driver_status,
+ (const uint8_t *)(unsigned long)h4p->response,
+ h4p->response_len, true);
+ rep->state = SGQ_IO_ERR;
+ return -1;
+ }
+ if (dio && ((h4p->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ ++clp->dio_incomplete; /* count dios done as indirect IO */
+ clp->sum_of_resids += h4p->din_resid;
+ rep->state = SGQ_IO_FINISHED;
+ if (clp->debug > 5) {
+ pr2serr("%s: %s ", __func__, wr ? "writing" : "reading");
+ pr2serr(" SGQ_IO_FINISHED elem idx=%zd\n", rep - clp->elem);
+ if (use_pack)
+ pr2serr("%s: pack_id=%d\n", __func__, h4p->request_extra);
+ else if (use_tag)
+ pr2serr("%s: request_tag=0x%" PRIx64 "\n", __func__,
+ (uint64_t)h4p->request_tag);
+ }
+ return 0;
+}
+
+static int
+sz_reserve(Rq_coll * clp, bool is_in)
+{
+ const struct flags_t *flagsp = is_in ? &clp->iflag : &clp->oflag;
+ bool pack = flagsp->pack;
+ bool vb = clp->debug;
+ int res, t, flags, err;
+ int fd = is_in ? clp->infd : clp->outfd;
+ int tag = flagsp->tag;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 30000)) {
+ pr2serr("%s: sg driver prior to 3.0.00\n", my_name);
+ return 1;
+ } else if (t < 40000) {
+ if (vb)
+ pr2serr("%s: warning: sg driver prior to 4.0.00\n", my_name);
+ sgs_old_sg_driver = true;
+ } else if (t < 40045) {
+ sgs_old_sg_driver = false;
+ sgs_full_v4_sg_driver = false;
+ } else
+ sgs_full_v4_sg_driver = true;
+ t = clp->bs * clp->bpt;
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
+ if (res < 0)
+ perror("sgs_dd: SG_SET_RESERVED_SIZE error");
+
+ if (sgs_full_v4_sg_driver) {
+ if (sgs_nanosec_unit) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ return 1;
+ }
+ }
+ if (tag || pack) {
+ t = 1;
+ if (ioctl(fd, SG_SET_FORCE_PACK_ID, &t) < 0) {
+ pr2serr("ioctl(SG_SET_FORCE_PACK_ID(on)) failed, errno=%d "
+ "%s\n", errno, strerror(errno));
+ return 1;
+ }
+ if (tag) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TAG_FOR_PACK_ID;
+ seip->ctl_flags |= SG_CTL_FLAGM_TAG_FOR_PACK_ID;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr("ioctl(EXTENDED(TAG_FOR_PACK_ID)) failed, "
+ "errno=%d %s\n", errno, strerror(errno));
+ return 1;
+ }
+ }
+ }
+ if (flagsp->evfd) {
+ int evfd = eventfd(0,0);
+
+ if (evfd < 0) {
+ err = errno;
+ pr2serr("eventfd() failed: %s\n", strerror(err));
+ return 1;
+ }
+ if (is_in)
+ clp->in_evfd = evfd;
+ else
+ clp->out_evfd = evfd;
+
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_EVENTFD;
+ seip->sei_rd_mask |= SG_SEIM_EVENTFD;
+ seip->share_fd = evfd;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ err = errno;
+ pr2serr("ioctl(EXTENDED(SG_SEIM_EVENTFD)) failed, "
+ "errno=%d %s\n", err, strerror(err));
+ return 1;
+ }
+ }
+ }
+ if (!clp->no_sig) {
+ if (-1 == fcntl(fd, F_SETOWN, getpid())) {
+ perror("fcntl(F_SETOWN)");
+ return 1;
+ }
+ flags = fcntl(fd, F_GETFL, 0);
+ if (-1 == fcntl(fd, F_SETFL, flags | O_ASYNC)) {
+ perror("fcntl(F_SETFL)");
+ return 1;
+ }
+ if (clp->use_rt_sig) {/* displaces SIGIO/SIGPOLL with SIGRTMIN + 1 */
+ if (-1 == fcntl(fd, F_SETSIG, SIGRTMIN + 1))
+ perror("fcntl(F_SETSIG)");
+ }
+ }
+ return 0;
+}
+
+static int
+init_elems(Rq_coll * clp)
+{
+ bool either_mmap = false;
+ int res = 0;
+ int num_bytes = clp->bpt * clp->bs;
+ int k;
+ Rq_elem * rep;
+
+ clp->wr_posp = &clp->elem[0]; /* making ring buffer */
+ clp->rd_posp = clp->wr_posp;
+ if (clp->iflag.mmap || clp->oflag.mmap) {
+ int res;
+
+ either_mmap = true;
+ sgq_num_elems = 2;
+ sgq_rd_ahead_lim = 1;
+ sgq_wr_ahead_lim = 1;
+ if (clp->iflag.mmap) {
+ res = get_mmap_addr(clp->infd, num_bytes, &clp->in_mmapp);
+ if (res < 0)
+ return res;
+ }
+ if (clp->oflag.mmap) {
+ res = get_mmap_addr(clp->outfd, num_bytes, &clp->out_mmapp);
+ if (res < 0)
+ return res;
+ }
+ }
+ for (k = 0; k < sgq_num_elems - 1; ++k)
+ clp->elem[k].nextp = &clp->elem[k + 1];
+ clp->elem[sgq_num_elems - 1].nextp = &clp->elem[0];
+ for (k = 0; k < sgq_num_elems; ++k) {
+ rep = &clp->elem[k];
+ rep->state = SGQ_FREE;
+ rep->iflagp = &clp->iflag;
+ rep->oflagp = &clp->oflag;
+ if (either_mmap) {
+ if (clp->both_mmap)
+ continue;
+ if (clp->iflag.mmap)
+ rep->buffp = clp->in_mmapp;
+ else
+ rep->buffp = clp->out_mmapp;
+ continue;
+ }
+ rep->buffp = sg_memalign(num_bytes, 0, &rep->free_buffp, false);
+ if (NULL == rep->buffp) {
+ pr2serr("out of memory creating user buffers\n");
+ res = -ENOMEM;
+ }
+ }
+ return res;
+}
+
+static void
+remove_elems(Rq_coll * clp)
+{
+ Rq_elem * rep;
+ int k;
+
+ for (k = 0; k < sgq_num_elems; ++k) {
+ rep = &clp->elem[k];
+ if (rep->free_buffp)
+ free(rep->free_buffp);
+ }
+}
+
+static int
+start_read(Rq_coll * clp)
+{
+ int blocks = (clp->in_count > clp->bpt) ? clp->bpt : clp->in_count;
+ Rq_elem * rep = clp->rd_posp;
+ int buf_sz, res;
+ char ebuff[EBUFF_SZ];
+
+ if (clp->debug > 5)
+ pr2serr("%s: elem idx=%zd\n", __func__, rep - clp->elem);
+ rep->wr = false;
+ rep->blk = clp->in_blk;
+ rep->num_blks = blocks;
+ clp->in_blk += blocks;
+ clp->in_count -= blocks;
+ if (clp->in_is_sg) {
+ res = sg_start_io(clp, rep);
+ if (1 == res) { /* ENOMEM, find what's available+try that */
+ if (ioctl(clp->infd, SG_GET_RESERVED_SIZE, &buf_sz) < 0) {
+ res = -errno;
+ perror("RESERVED_SIZE ioctls failed");
+ return res;
+ }
+ clp->bpt = (buf_sz + clp->bs - 1) / clp->bs;
+ pr2serr("Reducing blocks per transfer to %d\n", clp->bpt);
+ if (clp->bpt < 1)
+ return -ENOMEM;
+ res = sg_start_io(clp, rep);
+ if (1 == res)
+ res = -ENOMEM;
+ }
+ if (res < 0) {
+ pr2serr("%s: inputting from sg failed, blk=%d\n", my_name,
+ rep->blk);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ }
+ else {
+ rep->state = SGQ_IO_STARTED;
+ while (((res = read(clp->infd, rep->buffp, blocks * clp->bs)) < 0) &&
+ (EINTR == errno))
+ ;
+ if (res < 0) {
+ res = -errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: reading, in_blk=%d ", my_name,
+ rep->blk);
+ perror(ebuff);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ if (res < blocks * clp->bs) {
+ int o_blocks = blocks;
+ rep->stop_after_wr = true;
+ blocks = res / clp->bs;
+ if ((res % clp->bs) > 0) {
+ blocks++;
+ clp->in_partial++;
+ }
+ /* Reverse out + re-apply blocks on clp */
+ clp->in_blk -= o_blocks;
+ clp->in_count += o_blocks;
+ rep->num_blks = blocks;
+ clp->in_blk += blocks;
+ clp->in_count -= blocks;
+ }
+ clp->in_done_count -= blocks;
+ rep->state = SGQ_IO_FINISHED;
+ }
+ clp->rd_posp = rep->nextp;
+ return blocks;
+}
+
+static int
+start_write(Rq_coll * clp)
+{
+ Rq_elem * rep = clp->wr_posp;
+ int res, blocks;
+ char ebuff[EBUFF_SZ];
+
+ while ((0 != rep->wr) || (SGQ_IO_FINISHED != rep->state)) {
+ rep = rep->nextp;
+ if (rep == clp->rd_posp)
+ return -1;
+ }
+ if (clp->debug > 5)
+ pr2serr("%s: elem idx=%zd\n", __func__, rep - clp->elem);
+ rep->wr = true;
+ blocks = rep->num_blks;
+ rep->blk = clp->out_blk;
+ clp->out_blk += blocks;
+ clp->out_count -= blocks;
+ if (clp->out_is_sg) {
+ res = sg_start_io(clp, rep);
+ if (1 == res) /* ENOMEM, give up */
+ return -ENOMEM;
+ else if (res < 0) {
+ pr2serr("%s: output to sg failed, blk=%d\n", my_name, rep->blk);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ }
+ else {
+ rep->state = SGQ_IO_STARTED;
+ while (((res = write(clp->outfd, rep->buffp,
+ rep->num_blks * clp->bs)) < 0) && (EINTR == errno))
+ ;
+ if (res < 0) {
+ res = -errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: output, out_blk=%d ", my_name,
+ rep->blk);
+ perror(ebuff);
+ rep->state = SGQ_IO_ERR;
+ return res;
+ }
+ if (res < blocks * clp->bs) {
+ blocks = res / clp->bs;
+ if ((res % clp->bs) > 0) {
+ blocks++;
+ clp->out_partial++;
+ }
+ rep->num_blks = blocks;
+ }
+ rep->state = SGQ_IO_FINISHED;
+ }
+ return blocks;
+}
+
+/* Returns 0 if SIGIO/SIGPOLL or (SIGRTMIN + 1) received, else returns negated
+ * errno value; -EAGAIN for timeout. */
+static int
+do_sigwait(Rq_coll * clp, bool inc1_clear0)
+{
+ siginfo_t info;
+ struct timespec ts;
+
+ if (clp->debug > 9)
+ pr2serr("%s: inc1_clear0=%d\n", __func__, (int)inc1_clear0);
+ ts.tv_sec = 0;
+ ts.tv_nsec = DEF_SIGTIMEDWAIT_USEC * 1000;
+ while (sigtimedwait(&clp->blocked_sigs, &info, &ts) < 0) {
+ int err = errno;
+
+ if (EINTR != err) {
+
+ if (EAGAIN != err)
+ pr2serr("%s: sigtimedwait(): %s [%d]\n", __func__,
+ strerror(err), err);
+ return -err; /* EAGAIN is timeout error */
+ }
+ }
+ if ((SIGRTMIN + 1) == info.si_signo) {
+ if (inc1_clear0) {
+ clp->sigs_waiting--;
+ clp->sigs_rt_received++;
+ } else
+ clp->sigs_waiting = 0;
+ } else if (SIGPOLL == info.si_signo) {
+ if (inc1_clear0) {
+ clp->sigs_waiting--;
+ clp->sigs_io_received++;
+ } else
+ clp->sigs_waiting = 0;
+ } else {
+ pr2serr("%s: sigwaitinfo() returned si_signo=%d\n",
+ __func__, info.si_signo);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Returns 1 (or more) on success (found), 0 on not found, -1 on error. */
+static int
+do_num_poll_in(Rq_coll * clp, int fd, bool is_evfd)
+{
+ int err, res;
+ struct pollfd a_pollfd = {0, POLLIN | POLLOUT, 0};
+
+ if (! clp->no_sig) {
+ if (clp->sigs_waiting) {
+ int res = do_sigwait(clp, true);
+
+ if ((res < 0) && (-EAGAIN != res))
+ return res;
+ }
+ }
+ a_pollfd.fd = fd;
+ if (poll(&a_pollfd, 1, clp->poll_ms) < 0) {
+ err = errno;
+ pr2serr("%s: poll(): %s [%d]\n", __func__, strerror(err), err);
+ return -err;
+ }
+ /* pr2serr("%s: revents=0x%x\n", __func__, a_pollfd.revents); */
+ if (a_pollfd.revents & POLLIN) {
+ if (is_evfd) {
+ uint64_t count;
+
+ if ((res = read(fd, &count, sizeof(count))) < 0) {
+ err = errno;
+ pr2serr("%s: read(): %s [%d]\n", __func__,
+ strerror(err), err);
+ return -err;
+ }
+ return (res < (int)sizeof(uint64_t)) ? 0 : (int)count;
+ } else
+ return 1; /* could be more but don't know without evfd */
+ } else if (a_pollfd.revents & POLLERR)
+ ++clp->pollerr_count;
+
+ return 0;
+}
+
+static int
+can_read_write(Rq_coll * clp)
+{
+ Rq_elem * rep = NULL;
+ bool writeable = false;
+ bool in_is_evfd = (clp->in_evfd >= 0);
+ bool out_is_evfd = (clp->out_evfd >= 0);
+ int res = 0;
+ int reading = 0;
+ int writing = 0;
+ int rd_waiting = 0;
+ int wr_waiting = 0;
+ int sg_finished = 0;
+ int num;
+ int ofd = out_is_evfd ? clp->out_evfd : clp->outfd;
+ int ifd= in_is_evfd ? clp->in_evfd : clp->infd;
+
+ /* if write completion pending, then complete it + start read */
+ if (clp->out_is_sg) {
+ while ((res = do_num_poll_in(clp, ofd, out_is_evfd))) {
+ if (res < 0)
+ return res;
+ num = res;
+ while (--num >= 0) {
+ res = sg_finish_io(clp, true /* write */, &rep);
+ if (res < 0)
+ return res;
+ else if (1 == res) {
+ res = sg_start_io(clp, rep);
+ if (0 != res)
+ return -1; /* give up if any problems with retry */
+ } else
+ sg_finished++;
+ }
+ }
+ while ((rep = clp->wr_posp) && (SGQ_IO_FINISHED == rep->state) &&
+ rep->wr && (rep != clp->rd_posp)) {
+ rep->state = SGQ_FREE;
+ clp->out_done_count -= rep->num_blks;
+ clp->wr_posp = rep->nextp;
+ if (rep->stop_after_wr)
+ return -1;
+ }
+ }
+ else if ((rep = clp->wr_posp) && rep->wr &&
+ (SGQ_IO_FINISHED == rep->state)) {
+ rep->state = SGQ_FREE;
+ clp->out_done_count -= rep->num_blks;
+ clp->wr_posp = rep->nextp;
+ if (rep->stop_after_wr)
+ return -1;
+ }
+
+ /* if read completion pending, then complete it + start maybe write */
+ if (clp->in_is_sg) {
+ while ((res = do_num_poll_in(clp, ifd, in_is_evfd))) {
+ if (res < 0)
+ return res;
+ num = res;
+ while (--num >= 0) {
+ res = sg_finish_io(clp, false /* read */, &rep);
+ if (res < 0)
+ return res;
+ if (1 == res) {
+ res = sg_start_io(clp, rep);
+ if (0 != res)
+ return -1; /* give up if any problems with retry */
+ } else {
+ sg_finished++;
+ clp->in_done_count -= rep->num_blks;
+ }
+ }
+ }
+ }
+
+ for (rep = clp->wr_posp, res = 1;
+ rep && (rep != clp->rd_posp); rep = rep->nextp) {
+ if (SGQ_IO_STARTED == rep->state) {
+ if (rep->wr)
+ ++writing;
+ else {
+ res = 0;
+ ++reading;
+ }
+ }
+ else if ((! rep->wr) && (SGQ_IO_FINISHED == rep->state)) {
+ if (res)
+ writeable = true;
+ }
+ else if (SGQ_IO_WAIT == rep->state) {
+ res = 0;
+ if (rep->wr)
+ ++wr_waiting;
+ else
+ ++rd_waiting;
+ }
+ else
+ res = 0;
+ }
+ if (clp->debug > 6) {
+ if ((clp->debug > 7) || wr_waiting || rd_waiting) {
+ pr2serr("%d/%d (nwb/nrb): read=%d/%d (do/wt) "
+ "write=%d/%d (do/wt) writeable=%d sg_fin=%d\n",
+ clp->out_blk, clp->in_blk, reading, rd_waiting,
+ writing, wr_waiting, (int)writeable, sg_finished);
+ }
+ // fflush(stdout);
+ }
+ if (writeable && (writing < sgq_wr_ahead_lim) && (clp->out_count > 0))
+ return SGQ_CAN_WRITE;
+ if ((reading < sgq_rd_ahead_lim) && (clp->in_count > 0) &&
+ (0 == rd_waiting) && (clp->rd_posp->nextp != clp->wr_posp))
+ return SGQ_CAN_READ;
+
+ if (clp->out_done_count <= 0)
+ return SGQ_CAN_DO_NOTHING;
+
+ /* usleep(10000); */ /* hang about for 10 milliseconds */
+ if ((! clp->no_sig) && clp->sigs_waiting) {
+ res = do_sigwait(clp, false);
+ if ((res < 0) && (-EAGAIN != res))
+ return res; /* wasn't timeout */
+ }
+ /* Now check the _whole_ buffer for pending requests */
+ for (rep = clp->rd_posp->nextp; rep && (rep != clp->rd_posp);
+ rep = rep->nextp) {
+ if (SGQ_IO_WAIT == rep->state) {
+ res = sg_start_io(clp, rep);
+ if (res < 0)
+ return res;
+ if (res > 0)
+ return -1;
+ break;
+ }
+ }
+ return SGQ_CAN_DO_NOTHING;
+}
+
+static bool
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found, 'null' can be used as a placeholder\n");
+ return false;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "evfd"))
+ fp->evfd = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "hipri"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "immed"))
+ fp->immed = true;
+ else if (0 == strcmp(cp, "mmap"))
+ fp->mmap = true;
+ else if (0 == strcmp(cp, "noxfer"))
+ fp->noxfer = true;
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "pack"))
+ fp->pack = true;
+ else if (0 == strcmp(cp, "polled"))
+ fp->polled = true;
+ else if (0 == strcmp(cp, "tag"))
+ fp->tag = true;
+ else if (0 == strcmp(cp, "v3")) {
+ fp->v3 = true;
+ fp->v4 = false;
+ fp->given_v3v4 = true;
+ } else if (0 == strcmp(cp, "v4")) {
+ fp->v3 = false;
+ fp->v4 = true;
+ fp->given_v3v4 = true;
+ } else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return false;
+ }
+ cp = np;
+ } while (cp);
+ if (fp->dio && fp->mmap) {
+ pr2serr(" Can't set both mmap and dio\n");
+ return false;
+ }
+ if ((fp->dio || fp->mmap) && fp->noxfer) {
+ pr2serr(" Can't have mmap or dio with noxfer\n");
+ return false;
+ }
+ return true;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool bs_given = false;
+ bool no_sig_given = false;
+ bool polled_present;
+ int skip = 0;
+ int seek = 0;
+ int ibs = 0;
+ int obs = 0;
+ int count = -1;
+ int in_num_sect = 0;
+ int out_num_sect = 0;
+ int help_pg = 0;
+ int res, k, in_sect_sz, out_sect_sz, crw, open_fl;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ char inf[INOUTF_SZ];
+ char outf[INOUTF_SZ];
+ char ebuff[EBUFF_SZ];
+ Rq_coll rcoll;
+ Rq_coll * clp = &rcoll;
+
+ memset(clp, 0, sizeof(*clp));
+ clp->bpt = 0;
+ clp->in_evfd = -1;
+ clp->out_evfd = -1;
+ clp->iflag.v3 = true;
+ clp->oflag.v3 = true;
+ inf[0] = '\0';
+ outf[0] = '\0';
+ if (argc < 2) {
+ usage(1);
+ return 1;
+ }
+ sgs_nanosec_unit = !!getenv("SG3_UTILS_LINUX_NANO");
+
+ 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 == strcmp(key,"bpt")) {
+ clp->bpt = sg_get_num(buf);
+ if ((clp->bpt < 0) || (clp->bpt > MAX_BPT_VALUE)) {
+ pr2serr("%s: bad argument to 'bpt='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"bs")) {
+ clp->bs = sg_get_num(buf);
+ if ((clp->bs < 0) || (clp->bs > MAX_BPT_VALUE)) {
+ pr2serr("%s: bad argument to 'bs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"count")) {
+ count = sg_get_num(buf);
+ if (count < 0) {
+ pr2serr("%s: bad argument to 'count='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"deb"))
+ clp->debug += sg_get_num(buf);
+ else if (0 == strcmp(key,"ibs")) {
+ ibs = sg_get_num(buf);
+ if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
+ pr2serr("%s: bad argument to 'ibs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"if") == 0) {
+ memcpy(inf, buf, INOUTF_SZ);
+ inf[INOUTF_SZ - 1] = '\0';
+ } else if (0 == strcmp(key, "iflag")) {
+ if (! process_flags(buf, &clp->iflag)) {
+ pr2serr("%s: bad argument to 'iflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"mrq") == 0)
+ ; /* do nothing */
+ else if (0 == strcmp(key,"no_sig")) { /* default changes */
+ clp->no_sig = !!sg_get_num(buf);
+ no_sig_given = true;
+ } else if (0 == strcmp(key,"obs")) {
+ obs = sg_get_num(buf);
+ if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
+ pr2serr("%s: bad argument to 'obs='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (strcmp(key,"of") == 0) {
+ memcpy(outf, buf, INOUTF_SZ);
+ outf[INOUTF_SZ - 1] = '\0';
+ } else if (0 == strcmp(key, "oflag")) {
+ if (! process_flags(buf, &clp->oflag)) {
+ pr2serr("%s: bad argument to 'oflag='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"poll_ms"))
+ clp->poll_ms = sg_get_num(buf);
+ else if (0 == strcmp(key,"rt_sig"))
+ clp->use_rt_sig = !!sg_get_num(buf);
+ else if (0 == strcmp(key,"seek")) {
+ seek = sg_get_num(buf);
+ if (seek < 0) {
+ pr2serr("%s: bad argument to 'seek='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"skip")) {
+ skip = sg_get_num(buf);
+ if (skip < 0) {
+ pr2serr("%s: bad argument to 'skip='\n", my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == strcmp(key,"time"))
+ ; /* do nothing */
+ else if ((0 == strcmp(key,"-V")) || (0 == strcmp(key,"--version"))) {
+ pr2serr("%s: version: %s\n", my_name, version_str);
+ return 0;
+ } else if (0 == strncmp(key,"-vvvvvvv", 8))
+ clp->debug += 7;
+ else if (0 == strncmp(key,"-vvvvvv", 7))
+ clp->debug += 6;
+ else if (0 == strncmp(key,"-vvvvv", 6))
+ clp->debug += 5;
+ else if (0 == strncmp(key,"-vvvv", 5))
+ clp->debug += 4;
+ else if (0 == strncmp(key,"-vvv", 4))
+ clp->debug += 3;
+ else if (0 == strncmp(key,"-vv", 3))
+ clp->debug += 2;
+ else if ((0 == strcmp(key,"--verbose")) || (0 == strncmp(key,"-v", 2)))
+ ++clp->debug;
+ else if (0 == strcmp(key,"-hhhh"))
+ help_pg += 4;
+ else if (0 == strcmp(key,"-hhh"))
+ help_pg += 3;
+ else if (0 == strcmp(key,"-hh"))
+ help_pg += 2;
+ else if ((0 == strcmp(key,"-h")) || (0 == strcmp(key,"--help")))
+ ++help_pg;
+ else {
+ pr2serr("Unrecognized argument '%s'\n", key);
+ usage(help_pg);
+ return 1;
+ }
+ }
+ if (clp->bs <= 0) {
+ clp->bs = DEF_BLOCK_SIZE;
+ } else
+ bs_given = true;
+
+ if (help_pg > 0) {
+ usage(help_pg);
+ return 0;
+ }
+
+ polled_present = (clp->iflag.polled || clp->oflag.polled);
+ if (no_sig_given) {
+ if ((0 == clp->no_sig) && polled_present)
+ pr2serr("Warning: signalling doesn't work with polled flag\n");
+ } else /* no_sig default varies: 0 normally and 1 if polled present */
+ clp->no_sig = polled_present ? 1 : 0;
+
+ if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage(1);
+ return 1;
+ }
+ if (clp->bpt <= 0) {
+ clp->bpt = (DEF_BPT_TIMES_BS_SZ / clp->bs);
+ if (0 == clp->bpt)
+ clp->bpt = 1;
+ if (! bs_given)
+ pr2serr("Assume blocks size bs=%d [bytes] and blocks "
+ "per transfer bpt=%d\n", clp->bs, clp->bpt);
+ } else if (! bs_given)
+ pr2serr("Assume 'bs' (block size) of %d bytes\n", clp->bs);
+
+ if ((skip < 0) || (seek < 0)) {
+ pr2serr("%s: skip and seek cannot be negative\n", my_name);
+ return 1;
+ }
+ if (clp->iflag.mmap && clp->oflag.mmap)
+ clp->both_mmap = true;
+
+ if (clp->debug > 3)
+ pr2serr("%s: if=%s skip=%d of=%s seek=%d count=%d\n", my_name,
+ inf, skip, outf, seek, count);
+ if (! clp->no_sig) {
+ /* Need to block signals before SIGPOLL is enabled in sz_reserve() */
+ sigemptyset(&clp->blocked_sigs);
+ if (clp->use_rt_sig)
+ sigaddset(&clp->blocked_sigs, SIGRTMIN + 1);
+ sigaddset(&clp->blocked_sigs, SIGINT);
+ sigaddset(&clp->blocked_sigs, SIGPOLL);
+ sigprocmask(SIG_BLOCK, &clp->blocked_sigs, 0);
+ }
+
+ clp->infd = STDIN_FILENO;
+ clp->outfd = STDOUT_FILENO;
+ if (inf[0] && ('-' != inf[0])) {
+ open_fl = clp->iflag.excl ? O_EXCL : 0;
+ if ((clp->infd = open(inf, open_fl | O_RDONLY)) < 0) {
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for reading",
+ my_name, inf);
+ perror(ebuff);
+ return 1;
+ }
+ if (ioctl(clp->infd, SG_GET_TIMEOUT, 0) < 0) {
+ clp->in_is_sg = false;
+ if (skip > 0) {
+ off_t offset = skip;
+
+ offset *= clp->bs; /* could overflow here! */
+ if (lseek(clp->infd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ, "%s: couldn't skip to required "
+ "position on %s", my_name, inf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ } else { /* looks like sg device so close then re-open it RW */
+ close(clp->infd);
+ open_fl = clp->iflag.excl ? O_EXCL : 0;
+ open_fl |= (O_RDWR | O_NONBLOCK);
+ if ((clp->infd = open(inf, open_fl)) < 0) {
+ pr2serr("If %s is a sg device, need read+write "
+ "permissions, even to read it!\n", inf);
+ return 1;
+ }
+ clp->in_is_sg = true;
+ if (sz_reserve(clp, true /* is_in */))
+ return 1;
+ if (sgs_old_sg_driver && (clp->iflag.v4 || clp->oflag.v4)) {
+ pr2serr("Unable to implement v4 flag because sg driver too "
+ "old\n");
+ return 1;
+ }
+ }
+ }
+ if (outf[0] && ('-' != outf[0])) {
+ open_fl = clp->oflag.excl ? O_EXCL : 0;
+ open_fl |= (O_RDWR | O_NONBLOCK);
+ if ((clp->outfd = open(outf, open_fl)) >= 0) {
+ if (ioctl(clp->outfd, SG_GET_TIMEOUT, 0) < 0) {
+ /* not a scsi generic device so now try and open RDONLY */
+ close(clp->outfd);
+ clp->outfd = -1;
+ }
+ else {
+ clp->out_is_sg = true;
+ if (sz_reserve(clp, false /* hence ! is_in */))
+ return 1;
+ if (sgs_old_sg_driver && (clp->iflag.v4 || clp->oflag.v4)) {
+ pr2serr("Unable to implement v4 flag because sg driver "
+ "too old\n");
+ return 1;
+ }
+ }
+ }
+ if (! clp->out_is_sg) {
+ if (clp->outfd >= 0) {
+ close(clp->outfd);
+ clp->outfd = -1;
+ }
+ open_fl = clp->oflag.excl ? O_EXCL : 0;
+ open_fl |= (O_WRONLY | O_CREAT);
+ if ((clp->outfd = open(outf, open_fl, 0666)) < 0) {
+ snprintf(ebuff, EBUFF_SZ,
+ "%s: could not open %s for writing", my_name, outf);
+ perror(ebuff);
+ return 1;
+ }
+ else if (seek > 0) {
+ off_t offset = seek;
+
+ offset *= clp->bs; /* could overflow here! */
+ if (lseek(clp->outfd, offset, SEEK_SET) < 0) {
+ snprintf(ebuff, EBUFF_SZ, "%s: couldn't seek to required "
+ "position on %s", my_name, outf);
+ perror(ebuff);
+ return 1;
+ }
+ }
+ }
+ } else if ('\0' == outf[0]) {
+ if (STDIN_FILENO == clp->infd) {
+ pr2serr("Can't have both 'if' as stdin _and_ 'of' as "
+ "/dev/null\n");
+ return 1;
+ }
+ clp->outfd = open("/dev/null", O_RDWR);
+ if (clp->outfd < 0) {
+ perror("sgs_dd: could not open /dev/null");
+ return 1;
+ }
+ clp->out_is_sg = false;
+ /* ignore any seek */
+ } else { /* must be '-' for stdout */
+ if (STDIN_FILENO == clp->infd) {
+ pr2serr("Can't have both 'if' as stdin _and_ 'of' as stdout\n");
+ return 1;
+ }
+ }
+ if ((clp->in_is_sg || clp->out_is_sg) && !clp->iflag.given_v3v4 &&
+ !clp->oflag.given_v3v4 && (clp->debug > 0)) {
+ clp->iflag.v3 = true;
+ pr2serr("using sg driver version 3 interface on %s\n",
+ clp->in_is_sg ? inf : outf);
+ }
+
+ if (0 == count)
+ return 0;
+ else if (count < 0) {
+ if (clp->in_is_sg) {
+ res = read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(in), try again\n");
+ res = read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
+ }
+ if (0 != res) {
+ pr2serr("Unable to read capacity on %s\n", inf);
+ in_num_sect = -1;
+ } else {
+ if (clp->debug > 4)
+ pr2serr("ifile: number of sectors=%d, sector size=%d\n",
+ in_num_sect, in_sect_sz);
+ if (in_num_sect > skip)
+ in_num_sect -= skip;
+ }
+ }
+ if (clp->out_is_sg) {
+ res = read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(out), try again\n");
+ res = read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
+ }
+ if (0 != res) {
+ pr2serr("Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ } else {
+ if (clp->debug > 4)
+ pr2serr("ofile: number of sectors=%d, sector size=%d\n",
+ out_num_sect, out_sect_sz);
+ if (out_num_sect > seek)
+ out_num_sect -= seek;
+ }
+ }
+ if (clp->debug > 3)
+ pr2serr("Start of loop, count=%d, in_num_sect=%d, "
+ "out_num_sect=%d\n", count, in_num_sect, out_num_sect);
+ if (in_num_sect > 0) {
+ if (out_num_sect > 0)
+ count = (in_num_sect > out_num_sect) ? out_num_sect :
+ in_num_sect;
+ else
+ count = in_num_sect;
+ }
+ else
+ count = out_num_sect;
+ }
+ if (clp->debug > 4)
+ pr2serr("Start of loop, count=%d, bpt=%d\n", count, clp->bpt);
+
+ clp->in_count = count;
+ clp->in_done_count = count;
+ clp->in_blk = skip;
+ clp->out_count = count;
+ clp->out_done_count = count;
+ clp->out_blk = seek;
+ res = init_elems(clp);
+ if (res < 0)
+ pr2serr("init_elems() failed, res=%d\n", res);
+ res = 0;
+
+/* vvvvvvvvvvvvvvvvv Main Loop vvvvvvvvvvvvvvvvvvvvvvvv */
+ while (clp->out_done_count > 0) {
+ crw = can_read_write(clp);
+ if (crw < 0)
+ break;
+ if (SGQ_CAN_READ & crw) {
+ res = start_read(clp);
+ if (res <= 0) {
+ pr2serr("start_read: res=%d\n", res);
+ break;
+ }
+ res = 0;
+ }
+ if (SGQ_CAN_WRITE & crw) {
+ res = start_write(clp);
+ if (res <= 0) {
+ pr2serr("start_write: res=%d\n", res);
+ break;
+ }
+ res = 0;
+ }
+ }
+
+ if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0))
+ close(clp->infd);
+ if ((STDOUT_FILENO != clp->outfd) && (clp->outfd >= 0))
+ close(clp->outfd);
+ if (0 != clp->out_count) {
+ pr2serr("Some error occurred, remaining blocks=%d\n", clp->out_count);
+ res = 1;
+ }
+ pr2serr("%d+%d records in\n", count - clp->in_done_count,
+ clp->in_partial);
+ pr2serr("%d+%d records out\n", count - clp->out_done_count,
+ clp->out_partial);
+ if (clp->dio_incomplete)
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ clp->dio_incomplete);
+ if (clp->sum_of_resids)
+ pr2serr(">> Non-zero sum of residual counts=%d\n",
+ clp->sum_of_resids);
+ if (clp->debug > 0) {
+ if (! clp->no_sig)
+ pr2serr("SIGIO/SIGPOLL signals received: %d, RT sigs: %d\n",
+ clp->sigs_io_received, clp->sigs_rt_received);
+ if (polled_present)
+ pr2serr("POLLED (blk_poll) used to complete %d commands\n",
+ clp->blk_poll_count);
+ }
+ if (clp->pollerr_count > 0)
+ pr2serr(">> poll() system call gave POLLERR %d times\n",
+ clp->pollerr_count);
+ remove_elems(clp);
+ return res < 0 ? 99 : res;
+}
diff --git a/testing/tst_sg_lib.c b/testing/tst_sg_lib.c
new file mode 100644
index 00000000..10cf3bbb
--- /dev/null
+++ b/testing/tst_sg_lib.c
@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) 2013-2022 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#include <time.h>
+
+#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD)
+#include <byteswap.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h" /* need this to see if HAVE_BYTESWAP_H */
+#endif
+
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+/* Uncomment the next two undefs to force use of the generic (i.e. shifting)
+ * unaligned functions (i.e. sg_get_* and sg_put_*). Use "-b 16|32|64
+ * -n 100m" to see the differences in timing. */
+/* #undef HAVE_CONFIG_H */
+/* #undef HAVE_BYTESWAP_H */
+#include "sg_unaligned.h"
+
+/*
+ * A utility program to test sg_libs string handling, specifically
+ * related to snprintf().
+ */
+
+static const char * version_str = "1.17 20220717";
+
+
+#define MY_NAME "tst_sg_lib"
+
+#define MAX_LINE_LEN 1024
+
+
+static struct option long_options[] = {
+ {"byteswap", required_argument, 0, 'b'},
+ {"exit", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"hex2", no_argument, 0, 'H'},
+ {"json", optional_argument, 0, 'j'},
+ {"leadin", required_argument, 0, 'l'},
+ {"num", required_argument, 0, 'n'},
+ {"printf", no_argument, 0, 'p'},
+ {"sense", no_argument, 0, 's'},
+ {"unaligned", no_argument, 0, 'u'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0}, /* sentinel */
+};
+
+static const uint8_t desc_sense_data1[] = {
+ /* unrec_err, excessive_writes, sdat_ovfl, additional_len=? */
+ 0x72, 0x1, 0x3, 0x2, 0x80, 0x0, 0x0, 12+12+8+4+8+4+28,
+ /* Information: 0x11223344556677bb */
+ 0x0, 0xa, 0x80, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0xbb,
+ /* command specific: 0x3344556677bbccff */
+ 0x1, 0xa, 0x0, 0x0, 0x33, 0x44, 0x55, 0x66, 0x77, 0xbb, 0xcc, 0xff,
+ /* sense key specific: SKSV=1, actual_count=257 (hex: 0x101) */
+ 0x2, 0x6, 0x0, 0x0, 0x80, 0x1, 0x1, 0x0,
+ /* field replaceable code=0x45 */
+ 0x3, 0x2, 0x0, 0x45,
+ /* another progress report indicator */
+ 0xa, 0x6, 0x2, 0x1, 0x2, 0x0, 0x32, 0x01,
+ /* incorrect length indicator (ILI) */
+ 0x5, 0x2, 0x0, 0x20,
+ /* user data segment referral */
+ 0xb, 26, 0x1, 0x0,
+ 0,0,0,1, 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,
+ 0x1,0x2,0x3,0x4,0x55,0x6,0x7,0x8,
+ 2,0,0x12,0x34,
+ };
+
+static const uint8_t desc_sense_data2[] = {
+ /* ill_req, inv fld in para list, additional_len=? */
+ 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 8+4,
+ /* sense key specific: SKSV=1, C/D*=0, bitp=7 bytep=34 */
+ 0x2, 0x6, 0x0, 0x0, 0x8f, 0x0, 0x34, 0x0,
+ /* field replaceable code=0x45 */
+ 0x3, 0x2, 0x0, 0x45,
+ };
+
+static const uint8_t desc_sense_data3[] = {
+ /* medium err, vibration induced ..., additional_len=? */
+ 0x72, 0x3, 0x9, 0x5, 0x0, 0x0, 0x0, 32+16,
+ /* 0xd: block dev: sense key specific: SKSV=1, retry_count=257, fru=0x45
+ * info=0x1122334455, command_specific=0x1 */
+ 0xd, 0x1e, 0xa0, 0x0, 0x80, 0x1, 0x1, 0x45,
+ 0x0, 0x0, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ /* following sbc3 (standard) and sbc4r10 inconsistency; add padding */
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ /* 0xe: reason: send_to_given+henceforth, lu, naa-5, 0x5333333000001f40 */
+ 0xe, 0xe, 0x0, 0x1, 0x1, 0x3, 0x0, 0x8,
+ 0x53, 0x33, 0x33, 0x30, 0x0, 0x0, 0x1f, 0x40,
+ };
+
+static const uint8_t desc_sense_data4[] = {
+ /* ill_req, inv fld in para list, additional_len=? */
+ 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 24,
+ /* Forwarded sense data, FSDT=0, sd_src=7, f_status=2 */
+ 0xc, 22, 0x7, 0x2,
+ /* ill_req, inv fld in para list, additional_len=? */
+ 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 8+4,
+ /* sense key specific: SKSV=1, C/D*=0, bitp=7 bytep=34 */
+ 0x2, 0x6, 0x0, 0x0, 0x8f, 0x0, 0x34, 0x0,
+ /* field replaceable code=0x45 */
+ 0x3, 0x2, 0x0, 0x45,
+ };
+
+static const uint8_t desc_sense_data5[] = {
+ /* no_sense, ATA info available */
+ 0x72, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 14+14,
+ /* ATA descriptor extend=1 */
+ 0x9, 0xc, 0x1, 0x0, 0x34, 0x12, 0x44, 0x11,
+ 0x55, 0x22, 0x66, 0x33, 0x1, 0x0,
+ /* ATA descriptor extend=0 */
+ 0x9, 0xc, 0x0, 0x0, 0x34, 0x12, 0x44, 0x11,
+ 0x55, 0x22, 0x66, 0x33, 0x1, 0x0,
+ };
+
+static const uint8_t desc_sense_data6[] = {
+ /* UA, req, subsidiary binding */
+ 0x72, 0x6, 0x3f, 0x1a, 0x0, 0x0, 0x0, 26+12+12,
+ /* 0xe: designator, reason: preferred admin lu, uuid */
+ 0xe, 0x18, 0x0, 0x4, 0x1, 0xa, 0x0, 0x12,
+ 0x10, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
+ 0xfe, 0xdc,
+ /* 0x0: Information(valid): lun */
+ 0x0, 0xa, 0x80, 0x0,
+ 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ /* 0x1: Command specific: 0x1 */
+ 0x1, 0xa, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ };
+
+static const char * leadin = NULL;
+
+
+static void
+usage()
+{
+ fprintf(stderr,
+ "Usage: tst_sg_lib [--exit] [--help] [--hex2] [--leadin=STR] "
+ "[--printf]\n"
+ " [--sense] [--unaligned] [--verbose] "
+ "[--version]\n"
+ " where:\n"
+#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD)
+ " --byteswap=B|-b B B is 16, 32 or 64; tests NUM "
+ "byteswaps\n"
+ " compared to sg_unaligned "
+ "equivalent\n"
+ " --exit|-e test exit status strings\n"
+#else
+ " --exit|-e test exit status strings\n"
+#endif
+ " --help|-h print out usage message\n"
+ " --hex2|-H test hex2* variants\n"
+ " --leadin=STR|-l STR every line output by --sense "
+ "should\n"
+ " be prefixed by STR\n"
+ " --num=NUM|-n NUM number of iterations (def=1)\n"
+ " --printf|-p test library printf variants\n"
+ " --sense|-s test sense data handling\n"
+ " --unaligned|-u test unaligned data handling\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n\n"
+ "Test various parts of sg_lib, see options. Sense data tests "
+ "overlap\nsomewhat with examples/sg_sense_test .\n"
+ );
+
+}
+
+static char *
+get_exit_status_str(int exit_status, bool longer, int b_len, char * b)
+{
+ int n;
+
+ n = sg_scnpr(b, b_len, " ES=%d: ", exit_status);
+ if (n >= (b_len - 1))
+ return b;
+ if (sg_exit2str(exit_status, longer, b_len - n, b + n)) {
+ n = (int)strlen(b);
+ if (n < (b_len - 1))
+ sg_scnpr(b + n, b_len - n, " [ok=true]");
+ return b;
+ } else
+ snprintf(b, b_len, " No ES string for %d%s", exit_status,
+ (longer ? " [ok=false]" : ""));
+ return b;
+}
+
+static uint8_t arr[64];
+
+#define OFF 7 /* in byteswap mode, can test different alignments (def: 8) */
+
+int
+main(int argc, char * argv[])
+{
+ bool as_json = false;
+ bool do_exit_status = false;
+ bool ok;
+ int k, c, n, len;
+ int byteswap_sz = 0;
+ int do_hex2 = 0;
+ int do_num = 1;
+ int do_printf = 0;
+ int do_sense = 0;
+ int do_unaligned = 0;
+ int did_something = 0;
+ int vb = 0;
+ int ret = 0;
+ sgj_opaque_p jop = NULL;
+ sgj_opaque_p jo2p;
+ sgj_state json_st SG_C_CPP_ZERO_INIT;
+ sgj_state * jsp = &json_st;
+ char b[2048];
+ char bb[256];
+ const int b_len = sizeof(b);
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:ehHj::l:n:psuvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ byteswap_sz = sg_get_num(optarg);
+ if (! ((16 == byteswap_sz) || (32 == byteswap_sz) ||
+ (64 == byteswap_sz))) {
+ fprintf(stderr, "--byteswap= requires 16, 32 or 64\n");
+ return 1;
+ }
+ break;
+ case 'e':
+ do_exit_status = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex2;
+ break;
+ case 'j':
+ if (! sgj_init_state(&json_st, optarg)) {
+ pr2serr("bad argument to --json= option, unrecognized "
+ "character '%c'\n", json_st.first_bad_char);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ break;
+
+ case 'l':
+ leadin = optarg;
+ break;
+ case 'n':
+ do_num = sg_get_num(optarg);
+ if (do_num < 0) {
+ fprintf(stderr, "--num= unable decode argument as number\n");
+ return 1;
+ }
+ break;
+ case 'p':
+ ++do_printf;
+ break;
+ case 's':
+ ++do_sense;
+ break;
+ case 'u':
+ ++do_unaligned;
+ break;
+ case 'v':
+ ++vb;
+ break;
+ case 'V':
+ fprintf(stderr, "version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised switch code 0x%x ??\n", c);
+ usage();
+ return 1;
+ }
+ }
+ if (optind < argc) {
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return 1;
+ }
+ }
+
+ as_json = json_st.pr_as_json;
+ if (as_json)
+ jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
+
+ if (do_exit_status) {
+ ++did_something;
+
+ printf("Test Exit Status strings (add -v for long version):\n");
+ printf(" No error (es=0): %s\n",
+ sg_get_category_sense_str(0, b_len, b, vb));
+ ok = sg_exit2str(0, true, b_len, b);
+ printf(" No error (force verbose): %s\n", b);
+ if (vb)
+ printf(" for previous line sg_exit2str() returned: %s\n",
+ (ok ? "true" : "false"));
+ printf("%s\n", get_exit_status_str(1, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(2, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(3, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(4, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(5, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(6, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(7, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(8, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(25, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(33, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(36, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(48, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(50, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(51, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(96, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(97, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(97, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(255, (vb > 0), b_len, b));
+ printf("%s\n", get_exit_status_str(-1, (vb > 0), b_len, b));
+
+ printf("\n");
+ }
+
+ if (do_sense ) {
+ ++did_something;
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop, "desc_sense_data__test1");
+ sgj_js_sense(jsp, jo2p, desc_sense_data1,
+ (int)sizeof(desc_sense_data1));
+ } else {
+ printf("desc_sense_data test1:\n");
+ sg_print_sense(leadin, desc_sense_data1,
+ (int)sizeof(desc_sense_data1), vb);
+ printf("\n");
+ }
+#if 1
+ if (as_json) {
+ sgj_js_str_out(jsp, "sg_get_sense_str(ds_data1)", 999);
+ sg_get_sense_str(leadin, desc_sense_data1,
+ sizeof(desc_sense_data1), vb, b_len, b);
+ sgj_js_str_out(jsp, b, strlen(b));
+
+ } else {
+ printf("sg_get_sense_str(ds_data1):\n");
+ sg_get_sense_str(leadin, desc_sense_data1,
+ sizeof(desc_sense_data1), vb, b_len, b);
+ printf("sg_get_sense_str: strlen(b)->%u\n", (uint32_t)strlen(b));
+ printf("%s", b);
+ printf("\n");
+ }
+#endif
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop, "desc_sense_data__test2");
+ sgj_js_sense(jsp, jo2p, desc_sense_data2,
+ (int)sizeof(desc_sense_data2));
+ } else {
+ printf("desc_sense_data test2\n");
+ sg_print_sense(leadin, desc_sense_data2,
+ (int)sizeof(desc_sense_data2), vb);
+ printf("\n");
+ }
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop,
+ "desc_sense_block_combo_test3");
+ sgj_js_sense(jsp, jo2p, desc_sense_data3,
+ (int)sizeof(desc_sense_data3));
+ } else {
+ printf("desc_sense block dev combo plus designator test3\n");
+ sg_print_sense(leadin, desc_sense_data3,
+ (int)sizeof(desc_sense_data3), vb);
+ printf("\n");
+ }
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop,
+ "desc_sense_forwarded_sense_test4");
+ sgj_js_sense(jsp, jo2p, desc_sense_data4,
+ (int)sizeof(desc_sense_data4));
+ } else {
+ printf("desc_sense forwarded sense test4\n");
+ sg_print_sense(leadin, desc_sense_data4,
+ (int)sizeof(desc_sense_data4), vb);
+ printf("\n");
+ }
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop,
+ "desc_sense_ata_info_test5");
+ sgj_js_sense(jsp, jo2p, desc_sense_data5,
+ (int)sizeof(desc_sense_data5));
+ } else {
+ printf("desc_sense ATA Info test5\n");
+ sg_print_sense(leadin, desc_sense_data5,
+ (int)sizeof(desc_sense_data5), vb);
+ printf("\n");
+ }
+ if (as_json) {
+ jo2p = sgj_named_subobject_r(jsp, jop,
+ "desc_sense_ua_binding_test6");
+ sgj_js_sense(jsp, jo2p, desc_sense_data6,
+ (int)sizeof(desc_sense_data6));
+ } else {
+ printf("desc_sense UA subsidiary binding changed test6\n");
+ sg_print_sense(leadin, desc_sense_data6,
+ (int)sizeof(desc_sense_data6), vb);
+ printf("\n");
+ printf("\n");
+ }
+ }
+
+ if (do_printf) {
+ ++did_something;
+ printf("Testing sg_scnpr():\n");
+ b[0] = '\0';
+ len = b_len;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = -1;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 0;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 1;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 2;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 3;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 4;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 5;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 6;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+
+ b[0] = '\0';
+ len = 7;
+ n = sg_scnpr(b, len, "%s", "test");
+ printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n",
+ len, n, (uint32_t)strlen(b));
+ if (strlen(b) > 0)
+ printf("Resulting string: %s\n", b);
+ }
+ if (do_hex2) {
+ uint8_t b[] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58};
+
+ ++did_something;
+ for (k = 0; k < 19; ++k) {
+ printf("k=%d:\n", k);
+ hex2stdout(b, k, 0);
+ hex2str(b, k, "h2str0: ", 0, sizeof(bb), bb);
+ printf("%s", bb);
+ hex2stdout(b, k, 1);
+ hex2str(b, k, "h2str1: ", 1, sizeof(bb), bb);
+ printf("%s", bb);
+ hex2str(b, k, "h2str2: ", 2, sizeof(bb), bb);
+ printf("%s\n", bb);
+ hex2stdout(b, k, -1);
+ printf("\n");
+ }
+ }
+ if (do_unaligned) {
+ uint16_t u16 = 0x55aa;
+ uint16_t u16r;
+ uint32_t u24 = 0x224488;
+ uint32_t u24r;
+ uint32_t u32 = 0x224488aa;
+ uint32_t u32r;
+ uint64_t u48 = 0x112233445566ULL;
+ uint64_t u48r;
+ uint64_t u64 = 0x1122334455667788ULL;
+ uint64_t u64r;
+ uint8_t u8[64];
+
+ ++did_something;
+ if (vb)
+ memset(u8, 0, sizeof(u8));
+ printf("u16=0x%" PRIx16 "\n", u16);
+ sg_put_unaligned_le16(u16, u8);
+ printf(" le16:\n");
+ hex2stdout(u8, vb ? 10 : 2, -1);
+ u16r = sg_get_unaligned_le16(u8);
+ printf(" u16r=0x%" PRIx16 "\n", u16r);
+ sg_put_unaligned_be16(u16, u8);
+ printf(" be16:\n");
+ hex2stdout(u8, vb ? 10 : 2, -1);
+ u16r = sg_get_unaligned_be16(u8);
+ printf(" u16r=0x%" PRIx16 "\n\n", u16r);
+
+ printf("u24=0x%" PRIx32 "\n", u24);
+ sg_put_unaligned_le24(u24, u8);
+ printf(" le24:\n");
+ hex2stdout(u8, vb ? 10 : 3, -1);
+ u24r = sg_get_unaligned_le24(u8);
+ printf(" u24r=0x%" PRIx32 "\n", u24r);
+ sg_put_unaligned_be24(u24, u8);
+ printf(" be24:\n");
+ hex2stdout(u8, vb ? 10 : 3, -1);
+ u24r = sg_get_unaligned_be24(u8);
+ printf(" u24r=0x%" PRIx32 "\n\n", u24r);
+
+ printf("u32=0x%" PRIx32 "\n", u32);
+ sg_put_unaligned_le32(u32, u8);
+ printf(" le32:\n");
+ hex2stdout(u8, vb ? 10 : 4, -1);
+ u32r = sg_get_unaligned_le32(u8);
+ printf(" u32r=0x%" PRIx32 "\n", u32r);
+ sg_put_unaligned_be32(u32, u8);
+ printf(" be32:\n");
+ hex2stdout(u8, vb ? 10 : 4, -1);
+ u32r = sg_get_unaligned_be32(u8);
+ printf(" u32r=0x%" PRIx32 "\n\n", u32r);
+
+ printf("u48=0x%" PRIx64 "\n", u48);
+ sg_put_unaligned_le48(u48, u8);
+ printf(" le48:\n");
+ hex2stdout(u8, vb ? 10 : 6, -1);
+ u48r = sg_get_unaligned_le48(u8);
+ printf(" u48r=0x%" PRIx64 "\n", u48r);
+ sg_put_unaligned_be48(u48, u8);
+ printf(" be48:\n");
+ hex2stdout(u8, vb ? 10 : 6, -1);
+ u48r = sg_get_unaligned_be48(u8);
+ printf(" u48r=0x%" PRIx64 "\n\n", u48r);
+
+ printf("u64=0x%" PRIx64 "\n", u64);
+ sg_put_unaligned_le64(u64, u8);
+ printf(" le64:\n");
+ hex2stdout(u8, vb ? 10 : 8, -1);
+ u64r = sg_get_unaligned_le64(u8);
+ printf(" u64r=0x%" PRIx64 "\n", u64r);
+ sg_put_unaligned_be64(u64, u8);
+ printf(" be64:\n");
+ hex2stdout(u8, vb ? 10 : 8, -1);
+ u64r = sg_get_unaligned_be64(u8);
+ printf(" u64r=0x%" PRIx64 "\n\n", u64r);
+
+ printf(" be[v=8 bytes]:\n");
+ hex2stdout(u8, vb ? 10 : 8, -1);
+ u64r = sg_get_unaligned_be(8, u8);
+ printf(" u64r[v=8 bytes]=0x%" PRIx64 "\n", u64r);
+ printf(" le[v=8 bytes]:\n");
+ hex2stdout(u8, vb ? 10 : 8, -1);
+ u64r = sg_get_unaligned_le(8, u8);
+ printf(" u64r[v=8 bytes]=0x%" PRIx64 "\n\n", u64r);
+ }
+
+#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD)
+ if (byteswap_sz > 0) {
+ uint32_t elapsed_msecs;
+ uint16_t count16 = 0;
+ uint32_t count32 = 0;
+ uint64_t count64 = 0;
+ struct timespec start_tm, end_tm;
+
+ ++did_something;
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) {
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ return 1;
+ }
+ for (k = 0; k < do_num; ++k) {
+ switch (byteswap_sz) {
+ case 16:
+ sg_put_unaligned_be16(count16 + 1, arr + OFF);
+ count16 = sg_get_unaligned_be16(arr + OFF);
+ break;
+ case 32:
+ sg_put_unaligned_be32(count32 + 1, arr + OFF);
+ count32 = sg_get_unaligned_be32(arr + OFF);
+ break;
+ case 64:
+ sg_put_unaligned_be64(count64 + 1, arr + OFF);
+ count64 = sg_get_unaligned_be64(arr + OFF);
+ break;
+ default:
+ break;
+ }
+ }
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &end_tm)) {
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ return 1;
+ }
+ elapsed_msecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000;
+ elapsed_msecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000000;
+ if (16 == byteswap_sz)
+ printf(" last k=%d, last count16=%u\n", k, count16);
+ else if (32 == byteswap_sz)
+ printf(" last k=%d, last count32=%u\n", k, count32);
+ else
+ printf(" last k=%d, last count64=%" PRIu64 "\n", k, count64);
+ printf("Unaligned elapsed milliseconds: %u\n", elapsed_msecs);
+ count16 = 0;
+ count32 = 0;
+ count64 = 0;
+
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) {
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ return 1;
+ }
+ for (k = 0; k < do_num; ++k) {
+ switch (byteswap_sz) {
+ case 16:
+ count16 = bswap_16(count16 + 1);
+ memcpy(arr + OFF, &count16, 2);
+ memcpy(&count16, arr + OFF, 2);
+ count16 = bswap_16(count16);
+ break;
+ case 32:
+ count32 = bswap_32(count32 + 1);
+ memcpy(arr + OFF, &count32, 4);
+ memcpy(&count32, arr + OFF, 4);
+ count32 = bswap_32(count32);
+ break;
+ case 64:
+ count64 = bswap_64(count64 + 1);
+ memcpy(arr + OFF, &count64, 8);
+ memcpy(&count64, arr + OFF, 8);
+ count64 = bswap_64(count64);
+ break;
+ default:
+ break;
+ }
+ }
+ if (0 != clock_gettime(CLOCK_MONOTONIC, &end_tm)) {
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ return 1;
+ }
+ elapsed_msecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000;
+ elapsed_msecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000000;
+ if (16 == byteswap_sz)
+ printf(" last k=%d, last count16=%u\n", k, count16);
+ else if (32 == byteswap_sz)
+ printf(" last k=%d, last count32=%u\n", k, count32);
+ else
+ printf(" last k=%d, last count64=%" PRIu64 "\n", k, count64);
+ printf("Byteswap/memcpy elapsed milliseconds: %u\n", elapsed_msecs);
+ count16 = 0;
+ count32 = 0;
+ count64 = 0;
+ }
+#endif
+
+ if (0 == did_something)
+ printf("Looks like no tests done, check usage with '-h'\n");
+ ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+ if (as_json) {
+ if (0 == do_hex2)
+ sgj_js2file(jsp, NULL, ret, stdout);
+ sgj_finish(jsp);
+ }
+ return ret;
+}
diff --git a/testing/uapi_sg.h b/testing/uapi_sg.h
new file mode 100644
index 00000000..41377c6a
--- /dev/null
+++ b/testing/uapi_sg.h
@@ -0,0 +1,493 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_SCSI_SG_H
+#define _UAPI_SCSI_SG_H
+
+/*
+ * History:
+ * Started: Aug 9 by Lawrence Foard (entropy@world.std.com), to allow user
+ * process control of SCSI devices.
+ * Development Sponsored by Killy Corp. NY NY
+ *
+ * Original driver (sg.h):
+ * Copyright (C) 1992 Lawrence Foard
+ *
+ * Later extensions (versions 2, 3 and 4) to driver:
+ * Copyright (C) 1998 - 2021 Douglas Gilbert
+ *
+ * Version 4.0.47 (20210605)
+ * This version is for Linux 4 and 5 series kernels.
+ *
+ * Documentation
+ * =============
+ * A web site for the SG device driver can be found at:
+ * https://sg.danny.cz/sg [alternatively check the MAINTAINERS file]
+ * The documentation for the sg version 3 driver can be found at:
+ * https://sg.danny.cz/sg/p/sg_v3_ho.html
+ * Also see: <kernel_source>/Documentation/scsi/scsi-generic.txt
+ *
+ * For utility and test programs see: https://sg.danny.cz/sg/sg3_utils.html
+ */
+
+#include <stddef.h>
+#include <linux/types.h>
+#include <linux/major.h>
+
+/*
+ * bsg.h contains the sg v4 user space interface structure (sg_io_v4).
+ * That structure is also used as the controlling object when multiple
+ * requests are issued with one ioctl() call.
+ */
+#include <linux/bsg.h>
+
+/*
+ * Same structure as used by readv() call. It defines one scatter-gather
+ * element. "Scatter-gather" is abbreviated to "sgat" in this driver to
+ * avoid confusion with this driver's name.
+ */
+typedef struct sg_iovec {
+ void __user *iov_base; /* Starting address (of a byte) */
+ size_t iov_len; /* Length in bytes */
+} sg_iovec_t;
+
+
+typedef struct sg_io_hdr {
+ int interface_id; /* [i] 'S' for SCSI generic (required) */
+ int dxfer_direction; /* [i] data transfer direction */
+ unsigned char cmd_len; /* [i] SCSI command length */
+ unsigned char mx_sb_len;/* [i] max length to write to sbp */
+ unsigned short iovec_count; /* [i] 0 implies no sgat list */
+ unsigned int dxfer_len; /* [i] byte count of data transfer */
+ /* dxferp points to data transfer memory or scatter gather list */
+ void __user *dxferp; /* [i], [*io] */
+ unsigned char __user *cmdp;/* [i], [*i] points to command to perform */
+ void __user *sbp; /* [i], [*o] points to sense_buffer memory */
+ unsigned int timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */
+ unsigned int flags; /* [i] 0 -> default, see SG_FLAG... */
+ int pack_id; /* [i->o] unused internally (normally) */
+ void __user *usr_ptr; /* [i->o] unused internally */
+ unsigned char status; /* [o] scsi status */
+ unsigned char masked_status;/* [o] shifted, masked scsi status */
+ unsigned char msg_status;/* [o] messaging level data (optional) */
+ unsigned char sb_len_wr; /* [o] byte count actually written to sbp */
+ unsigned short host_status; /* [o] errors from host adapter */
+ unsigned short driver_status;/* [o] errors from software driver */
+ int resid; /* [o] dxfer_len - actual_transferred */
+ /* unit may be nanoseconds after SG_SET_GET_EXTENDED ioctl use */
+ unsigned int duration; /* [o] time taken by cmd (unit: millisec) */
+ unsigned int info; /* [o] auxiliary information */
+} sg_io_hdr_t;
+
+#define SG_INTERFACE_ID_ORIG 'S'
+
+/* Use negative values to flag difference from original sg_header structure */
+#define SG_DXFER_NONE (-1) /* e.g. a SCSI Test Unit Ready command */
+#define SG_DXFER_TO_DEV (-2) /* data-out buffer e.g. SCSI WRITE command */
+#define SG_DXFER_FROM_DEV (-3) /* data-in buffer e.g. SCSI READ command */
+/*
+ * SG_DXFER_TO_FROM_DEV is treated like SG_DXFER_FROM_DEV with the additional
+ * property than during indirect IO the user buffer is copied into the kernel
+ * buffers _before_ the transfer from the device takes place. Useful if short
+ * DMA transfers (less than requested) are not reported (e.g. resid always 0).
+ */
+#define SG_DXFER_TO_FROM_DEV (-4)
+#define SG_DXFER_UNKNOWN (-5) /* Unknown data direction, do not use */
+
+/* following flag values can be OR-ed together in v3::flags or v4::flags */
+#define SG_FLAG_DIRECT_IO 1 /* default is indirect IO */
+/* SG_FLAG_UNUSED_LUN_INHIBIT is ignored in sg v4 driver */
+#define SG_FLAG_UNUSED_LUN_INHIBIT 2 /* ignored, was LUN overwrite in cdb */
+#define SG_FLAG_MMAP_IO 4 /* request memory mapped IO */
+/* no transfers between kernel<-->user space; keep device<-->kernel xfers */
+#define SG_FLAG_NO_DXFER 0x10000 /* See comment on previous line! */
+/* defaults: for sg driver (v3_v4): Q_AT_HEAD; for block layer: Q_AT_TAIL */
+#define SG_FLAG_Q_AT_TAIL 0x10
+#define SG_FLAG_Q_AT_HEAD 0x20
+
+/*
+ * Flags used by ioctl(SG_IOSUBMIT) [abbrev: SG_IOS] and ioctl(SG_IORECEIVE)
+ * [abbrev: SG_IOR] OR-ed into sg_io_v4::flags. The sync v4 interface uses
+ * ioctl(SG_IO) and can take these new flags, as can the v3 interface.
+ * These flags apply for SG_IOS unless otherwise noted. May be OR-ed together.
+ */
+#define SGV4_FLAG_DIRECT_IO SG_FLAG_DIRECT_IO
+#define SGV4_FLAG_MMAP_IO SG_FLAG_MMAP_IO
+#define SGV4_FLAG_YIELD_TAG 0x8 /* sg_io_v4::generated_tag set after SG_IOS */
+#define SGV4_FLAG_Q_AT_TAIL SG_FLAG_Q_AT_TAIL
+#define SGV4_FLAG_Q_AT_HEAD SG_FLAG_Q_AT_HEAD
+#define SGV4_FLAG_DOUT_OFFSET 0x40 /* dout byte offset in v4::spare_in */
+#define SGV4_FLAG_EVENTFD 0x80 /* signal completion on ... */
+#define SGV4_FLAG_COMPLETE_B4 0x100 /* mrq: complete this rq before next */
+#define SGV4_FLAG_SIGNAL 0x200 /* v3: ignored; v4 signal on completion */
+#define SGV4_FLAG_IMMED 0x400 /* issue request and return immediately ... */
+#define SGV4_FLAG_HIPRI 0x800 /* use blk_poll (deprecated name, use POLLED) */
+#define SGV4_FLAG_POLLED 0x800 /* request will use blk_poll to complete */
+#define SGV4_FLAG_STOP_IF 0x1000 /* Stops sync mrq if error or warning */
+#define SGV4_FLAG_DEV_SCOPE 0x2000 /* permit SG_IOABORT to have wider scope */
+#define SGV4_FLAG_SHARE 0x4000 /* share IO buffer; needs SG_SEIM_SHARE_FD */
+#define SGV4_FLAG_DO_ON_OTHER 0x8000 /* available on either of shared pair */
+#define SGV4_FLAG_NO_DXFER SG_FLAG_NO_DXFER /* but keep dev<-->kernel xfr */
+#define SGV4_FLAG_KEEP_SHARE 0x20000 /* ... buffer for another dout command */
+#define SGV4_FLAG_MULTIPLE_REQS 0x40000 /* 1 or more sg_io_v4-s in data-in */
+#define SGV4_FLAG_ORDERED_WR 0x80000 /* svb: issue in-order writes */
+#define SGV4_FLAG_REC_ORDER 0x100000 /* receive order in v4:request_priority */
+#define SGV4_FLAG_META_OUT_IF 0x200000 /* ... there is something to report */
+
+/* Output (potentially OR-ed together) in v3::info or v4::info field */
+#define SG_INFO_OK_MASK 0x1
+#define SG_INFO_OK 0x0 /* no sense, host nor driver "noise" */
+#define SG_INFO_CHECK 0x1 /* something abnormal happened */
+
+#define SG_INFO_DIRECT_IO_MASK 0x6
+#define SG_INFO_INDIRECT_IO 0x0 /* data xfer via kernel buffers (or no xfer) */
+#define SG_INFO_DIRECT_IO 0x2 /* direct IO requested and performed */
+#define SG_INFO_MIXED_IO 0x4 /* not used, always 0 */
+#define SG_INFO_DEVICE_DETACHING 0x8 /* completed successfully but ... */
+#define SG_INFO_ABORTED 0x10 /* this command has been aborted */
+#define SG_INFO_MRQ_FINI 0x20 /* marks multi-reqs that have finished */
+
+/*
+ * Pointer to object of this structure filled by ioctl(SG_GET_SCSI_ID). Last
+ * field changed in v4 driver, was 'int unused[2]' so remains the same size.
+ */
+typedef struct sg_scsi_id {
+ int host_no; /* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
+ int channel;
+ int scsi_id; /* scsi id of target device */
+ int lun; /* lower 32 bits of internal 64 bit integer */
+ int scsi_type; /* TYPE_... defined in scsi/scsi.h */
+ short h_cmd_per_lun;/* host (adapter) maximum commands per lun */
+ short d_queue_depth;/* device (or adapter) maximum queue length */
+ union {
+ int unused[2]; /* as per version 3 driver */
+ __u8 scsi_lun[8]; /* full 8 byte SCSI LUN [in v4 driver] */
+ };
+} sg_scsi_id_t;
+
+/* For backward compatibility v4 driver yields at most SG_MAX_QUEUE of these */
+typedef struct sg_req_info { /* used by SG_GET_REQUEST_TABLE ioctl() */
+ char req_state; /* See 'enum sg_rq_state' definition in v4 driver */
+ char orphan; /* 0 -> normal request, 1 -> from interrupted SG_IO */
+ /* sg_io_owned set imples synchronous, clear implies asynchronous */
+ char sg_io_owned;/* 0 -> complete with read(), 1 -> owned by SG_IO */
+ char problem; /* 0 -> no problem detected, 1 -> error to report */
+ /* If SG_CTL_FLAGM_TAG_FOR_PACK_ID set on fd then next field is tag */
+ int pack_id; /* pack_id, in v4 driver may be tag instead */
+ void __user *usr_ptr; /* user provided pointer in v3+v4 interface */
+ /*
+ * millisecs elapsed since the command started (req_state==1) or
+ * command duration (req_state==2). Will be in nanoseconds after
+ * the SG_SET_GET_EXTENDED{TIME_IN_NS} ioctl.
+ */
+ unsigned int duration;
+ int unused;
+} sg_req_info_t;
+
+/*
+ * The following defines are for manipulating struct sg_extended_info which
+ * is abbreviated to "SEI". A following "M" (i.e. "_SEIM_") indicates a
+ * mask. Most mask values correspond to a integer (usually a uint32_t) apart
+ * from SG_SEIM_CTL_FLAGS which is for boolean values packed into an integer.
+ * The mask values for those booleans start with "SG_CTL_FLAGM_". The scope
+ * of these settings, like most other ioctls, is usually that of the file
+ * descriptor the ioctl is executed on. The "rd:" indication means read-only,
+ * attempts to write to them are ignored. "rd>" means action when reading.
+ */
+#define SG_SEIM_CTL_FLAGS 0x1 /* ctl_flags_mask bits in ctl_flags */
+#define SG_SEIM_READ_VAL 0x2 /* write SG_SEIRV_*, read back value */
+#define SG_SEIM_RESERVED_SIZE 0x4 /* reserved_sz of reserve request */
+#define SG_SEIM_TOT_FD_THRESH 0x8 /* tot_fd_thresh of data buffers */
+#define SG_SEIM_MINOR_INDEX 0x10 /* sg device minor index number */
+#define SG_SEIM_SHARE_FD 0x20 /* write-side gives fd of read-side */
+#define SG_SEIM_CHG_SHARE_FD 0x40 /* read-side given new write-side fd */
+#define SG_SEIM_SGAT_ELEM_SZ 0x80 /* sgat element size (>= PAGE_SIZE) */
+#define SG_SEIM_BLK_POLL 0x100 /* call blk_poll, uses 'num' field */
+#define SG_SEIM_EVENTFD 0x200 /* pass eventfd to driver */
+#define SG_SEIM_ALL_BITS 0x3ff /* should be OR of previous items */
+
+/* flag and mask values for boolean fields follow */
+#define SG_CTL_FLAGM_TIME_IN_NS 0x1 /* time: nanosecs (def: millisecs) */
+#define SG_CTL_FLAGM_TAG_FOR_PACK_ID 0x2 /* prefer tag over pack_id (def) */
+#define SG_CTL_FLAGM_OTHER_OPENS 0x4 /* rd: other sg fd_s on this dev */
+#define SG_CTL_FLAGM_ORPHANS 0x8 /* rd: orphaned requests on this fd */
+#define SG_CTL_FLAGM_Q_TAIL 0x10 /* used for future cmds on this fd */
+#define SG_CTL_FLAGM_IS_SHARE 0x20 /* rd: fd is read-side or write-side share */
+#define SG_CTL_FLAGM_IS_READ_SIDE 0x40 /* rd: this fd is read-side share */
+#define SG_CTL_FLAGM_UNSHARE 0x80 /* undo share after inflight cmd */
+/* rd> 1: read-side finished, 0: not; wr> 1: finish share post read-side */
+#define SG_CTL_FLAGM_READ_SIDE_FINI 0x100 /* wr> 0: setup for repeat write-side req */
+#define SG_CTL_FLAGM_READ_SIDE_ERR 0x200 /* rd: sharing, read-side got error */
+#define SG_CTL_FLAGM_NO_DURATION 0x400 /* don't calc command duration */
+#define SG_CTL_FLAGM_MORE_ASYNC 0x800 /* yield EAGAIN in more cases */
+#define SG_CTL_FLAGM_EXCL_WAITQ 0x1000 /* only 1 wake up per response */
+#define SG_CTL_FLAGM_SNAP_DEV 0x2000 /* output to debugfs::snapped */
+#define SG_CTL_FLAGM_RM_EVENTFD 0x4000 /* only if new eventfd wanted */
+#define SG_CTL_FLAGM_ALL_BITS 0x7fff /* should be OR of previous items */
+
+/* Write one of the following values to sg_extended_info::read_value, get... */
+#define SG_SEIRV_INT_MASK 0x0 /* get SG_SEIM_ALL_BITS */
+#define SG_SEIRV_BOOL_MASK 0x1 /* get SG_CTL_FLAGM_ALL_BITS */
+#define SG_SEIRV_VERS_NUM 0x2 /* get driver version number as int */
+#define SG_SEIRV_INACT_RQS 0x3 /* number of inactive requests */
+#define SG_SEIRV_DEV_INACT_RQS 0x4 /* sum(inactive rqs) on owning dev */
+#define SG_SEIRV_SUBMITTED 0x5 /* number of mrqs submitted+unread */
+#define SG_SEIRV_DEV_SUBMITTED 0x6 /* sum(submitted) on all dev's fds */
+#define SG_SEIRV_MAX_RSV_REQS 0x7 /* maximum reserve requests */
+#define SG_SEIRV_DEV_TS_LOWER 0x8 /* device timestamp's lower 32 bits */
+#define SG_SEIRV_DEV_TS_UPPER 0x9 /* device timestamp's upper 32 bits */
+
+/*
+ * A pointer to the following structure is passed as the third argument to
+ * ioctl(SG_SET_GET_EXTENDED). Each bit in the *_wr_mask fields causes the
+ * corresponding integer (e.g. reserved_sz) or bit (e.g. the
+ * SG_CTL_FLAG_TIME_IN_NS bit in ctl_flags) to be read from the user space
+ * and modify the driver. Each bit in the *_rd_mask fields causes the
+ * corresponding integer or bit to be fetched from the driver and written
+ * back to the user space. If the same bit is set in both the *_wr_mask and
+ * corresponding *_rd_mask fields, then which one comes first depends on the
+ * setting but no other operation will split the two. This structure is
+ * padded to 96 bytes to allow for new values to be added in the future.
+ */
+
+/* If both sei_wr_mask and sei_rd_mask are 0, this ioctl does nothing */
+struct sg_extended_info {
+ __u32 sei_wr_mask; /* OR-ed SG_SEIM_* user->driver values */
+ __u32 sei_rd_mask; /* OR-ed SG_SEIM_* driver->user values */
+ __u32 ctl_flags_wr_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ __u32 ctl_flags_rd_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ __u32 ctl_flags; /* bit values OR-ed, see SG_CTL_FLAGM_* */
+ __u32 read_value; /* write SG_SEIRV_*, read back related */
+
+ __u32 reserved_sz; /* data/sgl size of pre-allocated request */
+ __u32 tot_fd_thresh; /* total data/sgat for this fd, 0: no limit */
+ __u32 minor_index; /* rd: kernel's sg device minor number */
+ __u32 share_fd; /* for SHARE_FD, CHG_SHARE_FD or EVENTFD */
+ __u32 sgat_elem_sz; /* sgat element size (must be power of 2) */
+ __s32 num; /* blk_poll: loop_count (-1 -> spin)) */
+ __u8 pad_to_96[48]; /* pad so struct is 96 bytes long */
+};
+
+/*
+ * IOCTLs: Those ioctls that are relevant to the SG 3.x drivers follow.
+ * [Those that only apply to the SG 2.x drivers are at the end of the file.]
+ * (_GET_s yield result via 'int *' 3rd argument unless otherwise indicated)
+ */
+
+#define SG_EMULATED_HOST 0x2203 /* true for emulated host adapter (ATAPI) */
+
+/*
+ * Used to configure SCSI command transformation layer for ATAPI devices.
+ * Only supported by the ide-scsi driver. 20181014 No longer supported, this
+ * driver passes them to the mid-level which returns a EINVAL (22) errno.
+ *
+ * Original note: N.B. 3rd arg is not pointer but value: 3rd arg = 0 to
+ * disable transform, 1 to enable it
+ */
+#define SG_SET_TRANSFORM 0x2204
+#define SG_GET_TRANSFORM 0x2205
+
+#define SG_SET_RESERVED_SIZE 0x2275 /* request new reserved buffer size */
+#define SG_GET_RESERVED_SIZE 0x2272 /* actual size of reserved buffer */
+
+/*
+ * Historically the scsi/sg driver has used 0x22 as it ioctl base number.
+ * Add a define for that value and use it for several new ioctls added in
+ * version 4.0.01 sg driver and later.
+ */
+#define SG_IOCTL_MAGIC_NUM 0x22
+
+#define SG_SET_GET_EXTENDED _IOWR(SG_IOCTL_MAGIC_NUM, 0x51, \
+ struct sg_extended_info)
+
+/* The following ioctl has a 'sg_scsi_id_t *' object as its 3rd argument. */
+#define SG_GET_SCSI_ID 0x2276 /* Yields fd's bus, chan, dev, lun + type */
+/* SCSI id information can also be obtained from SCSI_IOCTL_GET_IDLUN */
+
+/* Override host setting and always DMA using low memory ( <16MB on i386) */
+#define SG_SET_FORCE_LOW_DMA 0x2279 /* 0-> use adapter setting, 1-> force */
+#define SG_GET_LOW_DMA 0x227a /* 0-> use all ram for dma; 1-> low dma ram */
+
+/*
+ * When SG_SET_FORCE_PACK_ID set to 1, pack_id (or tag) is input to read() or
+ * ioctl(SG_IO_RECEIVE). These functions wait until matching packet (request/
+ * command) is finished but they will return with EAGAIN quickly if the file
+ * descriptor was opened O_NONBLOCK or (in v4) if SGV4_FLAG_IMMED is given.
+ * The tag is used when SG_CTL_FLAGM_TAG_FOR_PACK_ID is set on the parent
+ * file descriptor (default: use pack_id). If pack_id or tag is -1 then read
+ * oldest waiting and this is the same action as when FORCE_PACK_ID is
+ * clear on the parent file descriptor. In the v4 interface the pack_id is
+ * placed the in sg_io_v4::request_extra field .
+ */
+#define SG_SET_FORCE_PACK_ID 0x227b /* pack_id or in v4 can be tag */
+#define SG_GET_PACK_ID 0x227c /* Yields oldest readable pack_id/tag, or -1 */
+
+#define SG_GET_NUM_WAITING 0x227d /* Number of commands awaiting read() */
+
+/* Yields max scatter gather tablesize allowed by current host adapter */
+#define SG_GET_SG_TABLESIZE 0x227F /* 0 implies can't do scatter gather */
+
+/*
+ * Integer form of version number: [x]xyyzz where [x] empty when x=0 .
+ * String form of version number: "[x]x.[y]y.zz"
+ */
+#define SG_GET_VERSION_NUM 0x2282 /* Example: version "2.1.34" yields 20134 */
+
+/* Returns -EBUSY if occupied. 3rd argument pointer to int (see next) */
+#define SG_SCSI_RESET 0x2284
+/*
+ * Associated values that can be given to SG_SCSI_RESET follow.
+ * SG_SCSI_RESET_NO_ESCALATE may be OR-ed to the _DEVICE, _TARGET, _BUS
+ * or _HOST reset value so only that action is attempted.
+ */
+#define SG_SCSI_RESET_NOTHING 0
+#define SG_SCSI_RESET_DEVICE 1
+#define SG_SCSI_RESET_BUS 2
+#define SG_SCSI_RESET_HOST 3
+#define SG_SCSI_RESET_TARGET 4
+#define SG_SCSI_RESET_NO_ESCALATE 0x100
+
+/* synchronous SCSI command ioctl, (for version 3 and 4 interface) */
+#define SG_IO 0x2285 /* similar effect as write() followed by read() */
+
+#define SG_GET_REQUEST_TABLE 0x2286 /* yields table of active requests */
+
+/* How to treat EINTR during SG_IO ioctl(), only in sg v3 and v4 driver */
+#define SG_SET_KEEP_ORPHAN 0x2287 /* 1 -> hold for read(), 0 -> drop (def) */
+#define SG_GET_KEEP_ORPHAN 0x2288
+
+/*
+ * Yields scsi midlevel's access_count for this SCSI device. 20181014 No
+ * longer available, always yields 1.
+ */
+#define SG_GET_ACCESS_COUNT 0x2289
+
+
+/*
+ * Default size (in bytes) a single scatter-gather list element can have.
+ * The value used by the driver is 'max(SG_SCATTER_SZ, PAGE_SIZE)'. This
+ * value should be a power of 2 (and may be rounded up internally). In the
+ * v4 driver this can be changed by ioctl(SG_SET_GET_EXTENDED{SGAT_ELEM_SZ}).
+ */
+#define SG_SCATTER_SZ (8 * 4096)
+
+/* sg driver users' code should handle retries (e.g. from Unit Attentions) */
+#define SG_DEFAULT_RETRIES 0
+
+/* Defaults, commented if they differ from original sg driver */
+#define SG_DEF_FORCE_PACK_ID 0
+#define SG_DEF_KEEP_ORPHAN 0
+#define SG_DEF_RESERVED_SIZE SG_SCATTER_SZ /* load time option */
+
+/*
+ * Maximum outstanding requests (i.e write()s without corresponding read()s)
+ * yields EDOM from write() if exceeded. This limit only applies prior to
+ * version 3.9 . It is still used as a maximum number of sg_req_info objects
+ * that are returned from the SG_GET_REQUEST_TABLE ioctl.
+ */
+#define SG_MAX_QUEUE 16
+
+#define SG_BIG_BUFF SG_DEF_RESERVED_SIZE /* for backward compatibility */
+
+/*
+ * Alternate style type names, "..._t" variants (as found in the
+ * 'typedef struct * {};' definitions above) are preferred to these:
+ */
+typedef struct sg_io_hdr Sg_io_hdr;
+typedef struct sg_io_vec Sg_io_vec;
+typedef struct sg_scsi_id Sg_scsi_id;
+typedef struct sg_req_info Sg_req_info;
+
+
+/* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
+/* The v1+v2 SG interface based on the 'sg_header' structure follows. */
+/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
+
+#define SG_MAX_SENSE 16 /* this only applies to the sg_header interface */
+
+struct sg_header {
+ int pack_len; /* [o] reply_len (ie useless), ignored as input */
+ int reply_len; /* [i] max length of expected reply (inc. sg_header) */
+ int pack_id; /* [io] id number of packet (use ints >= 0) */
+ int result; /* [o] 0==ok, else (+ve) Unix errno (best ignored) */
+ unsigned int twelve_byte:1;
+ /* [i] Force 12 byte command length for group 6 & 7 commands */
+ unsigned int target_status:5; /* [o] scsi status from target */
+ unsigned int host_status:8; /* [o] host status (see "DID" codes) */
+ unsigned int driver_status:8; /* [o] driver status+suggestion */
+ unsigned int other_flags:10; /* unused */
+ unsigned char sense_buffer[SG_MAX_SENSE];
+ /*
+ * [o] Output in 3 cases:
+ * when target_status is CHECK_CONDITION or
+ * when target_status is COMMAND_TERMINATED or
+ * when (driver_status & DRIVER_SENSE) is true.
+ */
+};
+
+/*
+ * IOCTLs: The following are not required (or ignored) when the v3 or v4
+ * interface is used as those structures contain a timeout field. These
+ * ioctls are kept for backward compatibility with v1+v2 interfaces.
+ */
+
+#define SG_SET_TIMEOUT 0x2201 /* unit: (user space) jiffies */
+#define SG_GET_TIMEOUT 0x2202 /* yield timeout as _return_ value */
+
+/*
+ * Get/set command queuing state per fd (default is SG_DEF_COMMAND_Q.
+ * Each time a sg_io_hdr_t object is seen on this file descriptor, this
+ * command queuing flag is set on (overriding the previous setting).
+ * This setting defaults to 0 (i.e. no queuing) but gets set the first
+ * time that fd sees a v3 or v4 interface request.
+ */
+#define SG_GET_COMMAND_Q 0x2270 /* Yields 0 (queuing off) or 1 (on) */
+#define SG_SET_COMMAND_Q 0x2271 /* Change queuing state with 0 or 1 */
+
+/*
+ * Turn on/off error sense trace in the kernel log (1 and 0 respectively, default is
+ * off).
+ */
+#define SG_SET_DEBUG 0x227e /* 0 -> turn off debug */
+
+/*
+ * override SCSI command length with given number on the next write() on
+ * this file descriptor (v1 and v2 interface only)
+ */
+#define SG_NEXT_CMD_LEN 0x2283
+
+/*
+ * New ioctls to replace async (non-blocking) write()/read() interface.
+ * Present in version 4 and later of the sg driver [>20190427]. The
+ * SG_IOSUBMIT_V3 and SG_IORECEIVE_V3 ioctls accept the sg_v3 interface
+ * based on struct sg_io_hdr shown above. The SG_IOSUBMIT and SG_IORECEIVE
+ * ioctls accept the sg_v4 interface based on struct sg_io_v4 found in
+ * <include/uapi/linux/bsg.h>. These objects are passed by a pointer in
+ * the third argument of the ioctl.
+ *
+ * Data may be transferred both from the user space to the driver by these
+ * ioctls. Hence the _IOWR macro is used here to generate the ioctl number
+ * rather than _IOW or _IOR.
+ */
+/* Submits a v4 interface object to driver, optionally receive tag back */
+#define SG_IOSUBMIT _IOWR(SG_IOCTL_MAGIC_NUM, 0x41, struct sg_io_v4)
+
+/* Gives some v4 identifying info to driver, receives associated response */
+#define SG_IORECEIVE _IOWR(SG_IOCTL_MAGIC_NUM, 0x42, struct sg_io_v4)
+
+/* Submits a v3 interface object to driver */
+#define SG_IOSUBMIT_V3 _IOWR(SG_IOCTL_MAGIC_NUM, 0x45, struct sg_io_hdr)
+
+/* Gives some v3 identifying info to driver, receives associated response */
+#define SG_IORECEIVE_V3 _IOWR(SG_IOCTL_MAGIC_NUM, 0x46, struct sg_io_hdr)
+
+/* Provides identifying info about a prior submission (e.g. a tag) */
+#define SG_IOABORT _IOW(SG_IOCTL_MAGIC_NUM, 0x43, struct sg_io_v4)
+
+/* command queuing is always on when the v3 or v4 interface is used */
+#define SG_DEF_COMMAND_Q 0
+
+#define SG_DEF_UNDERRUN_FLAG 0
+
+/* If the timeout value in the v3_v4 interfaces is 0, this value is used */
+#define SG_DEFAULT_TIMEOUT (60*HZ) /* HZ == 'jiffies in 1 second' */
+
+#endif /* end of _UAPI_SCSI_SG_H guard */
diff --git a/utils/Makefile b/utils/Makefile
new file mode 100644
index 00000000..9558f9eb
--- /dev/null
+++ b/utils/Makefile
@@ -0,0 +1,56 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/share/man
+
+CC = gcc
+LD = gcc
+
+EXECS = hxascdmp
+EXTRA_EXECS = hxascdmp
+
+MAN_PGS = hxascdmp.1
+MAN_PREF = man1
+
+CFLAGS = -g -O2 -W -Wall -iquote ../include
+# CFLAGS = -g -O2 -W -iquote ../include -pedantic -std=c99
+
+LDFLAGS =
+
+all: $(EXECS)
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXTRA_EXECS) core .depend
+
+hxascdmp: hxascdmp.o
+ $(LD) -o $@ $(LDFLAGS) $^
+
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $^; \
+ do install -s -o root -g root -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
+ifeq (.depend,$(wildcard .depend))
+include .depend
+endif
diff --git a/utils/Makefile.cygwin b/utils/Makefile.cygwin
new file mode 100644
index 00000000..86e9fa34
--- /dev/null
+++ b/utils/Makefile.cygwin
@@ -0,0 +1,33 @@
+# Assumes Makefile is used in a cygwin shell
+
+SHELL = /bin/sh
+
+CC = gcc
+LD = gcc
+
+EXECS = hxascdmp
+
+EXE_S = hxascdmp.exe
+
+# OS_FLAGS = -DSG_LIB_WIN32 -DSPTD
+OS_FLAGS = -DSG_LIB_WIN32
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+EXTRA_FLAGS = $(OS_FLAGS) $(LARGE_FILE_FLAGS)
+
+# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS)
+CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS)
+# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS)
+
+LDFLAGS =
+
+all: $(EXECS)
+
+clean:
+ rm *.o $(EXE_S)
+
+.c.o:
+ $(CC) $(INCLUDES) $(CFLAGS) $(S_CFLAGS) -c -o $@ $<
+
+hxascdmp: hxascdmp.o
+ $(LD) -o $@ $(LDFLAGS) $@.o
+
diff --git a/utils/Makefile.freebsd b/utils/Makefile.freebsd
new file mode 100644
index 00000000..a43a5695
--- /dev/null
+++ b/utils/Makefile.freebsd
@@ -0,0 +1,52 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+CC = clang
+LD = clang
+
+EXECS = hxascdmp
+
+MAN_PGS =
+MAN_PREF = man8
+
+CFLAGS = -g -O2 -W
+# CFLAGS = -g -O2 -W -pedantic -std=c99
+
+LDFLAGS =
+
+all: $(EXECS)
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) core .depend
+
+hxascdmp: hxascdmp.o
+ $(LD) -o $@ $(LDFLAGS) $@.o
+
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $(EXECS); \
+ do install -s -m 755 $$name $(INSTDIR); \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -m 644 $$mp $(MANDIR)/$(MAN_PREF); \
+ gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
diff --git a/utils/Makefile.mingw b/utils/Makefile.mingw
new file mode 100644
index 00000000..6fed3484
--- /dev/null
+++ b/utils/Makefile.mingw
@@ -0,0 +1,33 @@
+# Assumes makefile is used in a MSYS shell with a MinGW compiler available.
+
+SHELL = /bin/sh
+
+CC = gcc
+LD = gcc
+
+EXECS = hxascdmp
+
+EXE_S = hxascdmp.exe
+
+# OS_FLAGS = -DSG_LIB_WIN32 -DSG_LIB_MINGW -DSPTD
+OS_FLAGS = -DSG_LIB_WIN32 -DSG_LIB_MINGW
+LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+EXTRA_FLAGS = $(OS_FLAGS) $(LARGE_FILE_FLAGS)
+
+# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS)
+CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS)
+# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS)
+
+LDFLAGS =
+
+all: $(EXECS)
+
+clean:
+ rm *.o $(EXE_S)
+
+.c.o:
+ $(CC) $(INCLUDES) $(CFLAGS) $(S_CFLAGS) -c -o $@ $<
+
+hxascdmp: hxascdmp.o
+ $(LD) -o $@ $(LDFLAGS) $@.o
+
diff --git a/utils/Makefile.solaris b/utils/Makefile.solaris
new file mode 100644
index 00000000..075d0046
--- /dev/null
+++ b/utils/Makefile.solaris
@@ -0,0 +1,51 @@
+SHELL = /bin/sh
+
+PREFIX=/usr/local
+INSTDIR=$(DESTDIR)/$(PREFIX)/bin
+MANDIR=$(DESTDIR)/$(PREFIX)/man
+
+CC = gcc
+LD = gcc
+
+EXECS = hxascdmp
+
+MAN_PGS =
+MAN_PREF = man8
+
+CFLAGS = -g -O2 -W
+# CFLAGS = -g -O2 -W -pedantic -std=c99
+
+LDFLAGS =
+
+all: $(EXECS)
+
+depend dep:
+ for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \
+ done > .depend
+
+clean:
+ /bin/rm -f *.o $(EXECS) core .depend
+
+hxascdmp: hxascdmp.o
+ $(LD) -o $@ $(LDFLAGS) $@.o
+
+
+install: $(EXECS)
+ install -d $(INSTDIR)
+ for name in $(EXECS); \
+ do install -s -f $(INSTDIR) $$name; \
+ done
+ install -d $(MANDIR)/$(MAN_PREF)
+ for mp in $(MAN_PGS); \
+ do install -m 644 -f $(MANDIR)/$(MAN_PREF) $$mp; \
+ done
+
+uninstall:
+ dists="$(EXECS)"; \
+ for name in $$dists; do \
+ rm -f $(INSTDIR)/$$name; \
+ done
+ for mp in $(MAN_PGS); do \
+ rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \
+ done
+
diff --git a/utils/README b/utils/README
new file mode 100644
index 00000000..c00c22a7
--- /dev/null
+++ b/utils/README
@@ -0,0 +1,20 @@
+This directory contains these utilities:
+ - hxascdmp: takes a binary stream and converts it to hexadecimal ASCII
+ which is sent to stdout. The incoming binary stream can either be
+ from a file or, in the absence of a file name, from stdin. Similar to
+ the Unix "od" command. By default, it decodes 16 bytes per line with
+ an ASCII interpretation to the right of each line. See its
+ hxascdmp(1) man page.
+ - sg_chk_asc and tst_sg_lib: are no longer here, they have been moved
+ to the 'testing' directory (a sibling of this directory).
+
+
+By default, the Makefile only builds the hxascdmp utility. The 'Makefile'
+file (i.e. with no suffix) builds for Linux; the 'Makefile.freebsd' file
+builds for FreeBSD (e.g. 'make -f Makefile.freebsd'); the
+'Makefile.solaris' file builds for Solaris; the 'Makefile.mingw' builds
+in the Windows MinGW environment (e.g. msys shell); and 'Makefile.cygwin'
+builds in the Windows Cygwin environment.
+
+Douglas Gilbert
+4th November 2017
diff --git a/utils/hxascdmp.1 b/utils/hxascdmp.1
new file mode 100644
index 00000000..22814922
--- /dev/null
+++ b/utils/hxascdmp.1
@@ -0,0 +1,111 @@
+.TH HXASCDMP "1" "February 2019" "sg3_utils\-1.45" SG3_UTILS
+.SH NAME
+hxascdmp \- hexadecimal ASCII dump
+.SH SYNOPSIS
+.B hxascdmp
+[\fI\-b=BPL\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-N\fR] [\fI\-o=OFF\fR]
+[\fI\-V\fR] [\fIFILE+\fR]
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+This utility reads one or more \fIFILE\fR names and dumps them in hexadecimal
+and ASCII to stdout. If no \fIFILE\fR is given then stdin is read instead;
+reading continues (or stalls) until an EOF is received.
+.PP
+The default format is to start each line with the hexadecimal address (offset
+from the start of file) followed by 16 hexadecimal bytes separated by a
+single space (apart from the 8th and 9th bytes which are separated by two
+spaces). If the \fI\-H\fR is not given, there is then a string of 16 ASCII
+characters corresponding to the hexadecimal bytes earlier in the line; only
+bytes in the range 0x20 to 0x7e are printed in ASCII, other bytes values are
+printed as '.' . If the \fI\-H\fR is not given, each \fIFILE\fR name that
+appears on the command line is printed on a separate line prior to that
+file's hexadecimal ASCII dump.
+.PP
+If the \fI\-N\fR option is given then no address is printed out and each
+line starts with the next hexadecimal byte.
+.PP
+This utility is pretty close to the 'hexdump -C' variant of BSD's
+.B hexdump(1)
+command.
+.SH OPTIONS
+.TP
+\fB\-b\fR=\fIBPL\fR
+where \fIBPL\fR specifies the number of bytes per line. The default value is
+16. 16 bytes per line is just enough to allow the address, 16 bytes in
+hexadecimal followed by 16 bytes as ASCII to fit on a standard 80 column
+wide terminal.
+.TP
+\fB\-h\fR
+output the usage message then exit.
+.TP
+\fB\-H\fR
+output hexadecimal only (i.e. don't place an ASCII representation at the
+end of each line).
+.TP
+\fB\-N\fR
+no address; so each line starts with the next hexadecimal byte.
+.TP
+\fB\-o\fR=\fIOFF\fR
+where \fIOFF\fR specifies the byte offset from the beginning of the pipe or
+the beginning of each file that the output starts. If the address is being
+printed out then it starts at \fIOFF\fR. The default is an \fIOFF\fR of 0 .
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version string and then exit.
+.SH NOTES
+In Windows the given file (or files) are set to binary mode.
+.SH EXIT STATUS
+The exit status of hxascdmp is 0 when it is successful. If any of the
+given \fIFILE\fR names cannot be opened then the exit status is 1.
+.SH EXAMPLES
+First we manufacture a short file with a mix of data in it: mostly ASCII with
+some control characters and 0xaa (which the echo command only accepts in
+octal (0252):
+.PP
+ $ echo -e "three blind mice,\t\r\0252" > 3bm.txt
+.PP
+Now we use this utility to see exactly what is in the file. To avoid
+problems with line wrapping, the bytes per line option is set to 8:
+.PP
+ $ hxascdmp -b=8 3bm.txt
+.br
+ASCII hex dump of file: 3bm.txt
+.br
+ 00 74 68 72 65 65 20 62 6c three bl
+.br
+ 08 69 6e 64 20 6d 69 63 65 ind mice
+.br
+ 10 2c 09 0d aa 0a ,....
+.PP
+Using the same file, use this utility to output only hexadecimal formatted
+16 bytes per line.
+.PP
+ $ hxascdmp -H 3bm.txt
+.br
+hex dump of file: 3bm.txt
+.br
+ 00 74 68 72 65 65 20 62 6c 69 6e 64 20 6d 69 63 65
+.br
+ 10 2c 09 0d aa 0a
+.PP
+For comparison the hexdump utility gives similar output:
+.PP
+ $ hexdump -C 3bm.txt
+.br
+00000000 74 68 72 65 65 20 62 6c 69 6e 64 20 6d 69 63 65 |three blind mice|
+.br
+00000010 2c 09 0d aa 0a |,....|
+.br
+00000015
+.SH AUTHORS
+Written by Douglas Gilbert.
+.SH "REPORTING BUGS"
+Report bugs to <dgilbert at interlog dot com>.
+.SH COPYRIGHT
+Copyright \(co 2004\-2019 Douglas Gilbert
+.br
+This software is distributed under a FreeBSD license. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.B hexdump(1)
diff --git a/utils/hxascdmp.c b/utils/hxascdmp.c
new file mode 100644
index 00000000..f08d0313
--- /dev/null
+++ b/utils/hxascdmp.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2004-2019 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.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#define DEF_BYTES_PER_LINE 16
+
+static int bytes_per_line = DEF_BYTES_PER_LINE;
+
+static const char * version_str = "1.11 20190527";
+
+#define CHARS_PER_HEX_BYTE 3
+#define BINARY_START_COL 6
+#define MAX_LINE_LENGTH 257
+
+
+#ifdef SG_LIB_MINGW
+/* Non Unix OSes distinguish between text and binary files.
+ Set text mode on fd. Does nothing in Unix. Returns negative number on
+ failure. */
+int
+sg_set_text_mode(int fd)
+{
+ return setmode(fd, O_TEXT);
+}
+
+/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
+ failure. */
+int
+sg_set_binary_mode(int fd)
+{
+ return setmode(fd, O_BINARY);
+}
+
+#else
+/* For Unix the following functions are dummies. */
+int
+sg_set_text_mode(int fd)
+{
+ return fd; /* fd should be >= 0 */
+}
+
+int
+sg_set_binary_mode(int fd)
+{
+ return fd;
+}
+#endif
+
+/* Returns the number of times 'ch' is found in string 's' given the
+ * string's length. */
+static int
+num_chs_in_str(const char * s, int slen, int ch)
+{
+ int res = 0;
+
+ while (--slen >= 0) {
+ if (ch == s[slen])
+ ++res;
+ }
+ return res;
+}
+
+/* If the number in 'buf' can be decoded or the multiplier is unknown
+ * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal
+ * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
+ * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces
+ * and tabs; accept comma, hyphen, space, tab and hash as terminator. */
+int64_t
+sg_get_llnum(const char * buf)
+{
+ int res, len, n;
+ int64_t num, ll;
+ uint64_t unum;
+ char * cp;
+ const char * b;
+ char c = 'c';
+ char c2 = '\0'; /* keep static checker happy */
+ char c3 = '\0'; /* keep static checker happy */
+ char lb[32];
+
+ if ((NULL == buf) || ('\0' == buf[0]))
+ return -1LL;
+ len = strlen(buf);
+ n = strspn(buf, " \t");
+ if (n > 0) {
+ if (n == len)
+ return -1LL;
+ buf += n;
+ len -= n;
+ }
+ /* following hack to keep C++ happy */
+ cp = strpbrk((char *)buf, " \t,#-");
+ if (cp) {
+ len = cp - buf;
+ n = (int)sizeof(lb) - 1;
+ len = (len < n) ? len : n;
+ memcpy(lb, buf, len);
+ lb[len] = '\0';
+ b = lb;
+ } else
+ b = buf;
+ if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
+ res = sscanf(b + 2, "%" SCNx64 , &unum);
+ num = unum;
+ } else if ('H' == toupper((int)b[len - 1])) {
+ res = sscanf(b, "%" SCNx64 , &unum);
+ num = unum;
+ } else
+ res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
+ if (res < 1)
+ return -1LL;
+ else if (1 == res)
+ return num;
+ else {
+ if (res > 2)
+ c2 = toupper((int)c2);
+ if (res > 3)
+ c3 = toupper((int)c3);
+ switch (toupper((int)c)) {
+ case 'C':
+ return num;
+ case 'W':
+ return num * 2;
+ case 'B':
+ return num * 512;
+ case 'K':
+ if (2 == res)
+ return num * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1024;
+ return -1LL;
+ case 'M':
+ if (2 == res)
+ return num * 1048576;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1048576;
+ return -1LL;
+ case 'G':
+ if (2 == res)
+ return num * 1073741824;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1073741824;
+ return -1LL;
+ case 'T':
+ if (2 == res)
+ return num * 1099511627776LL;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL;
+ return -1LL;
+ case 'P':
+ if (2 == res)
+ return num * 1099511627776LL * 1024;
+ if (('B' == c2) || ('D' == c2))
+ return num * 1000000000000LL * 1000;
+ if (('I' == c2) && (4 == res) && ('B' == c3))
+ return num * 1099511627776LL * 1024;
+ return -1LL;
+ case 'X':
+ cp = (char *)strchr(b, 'x');
+ if (NULL == cp)
+ cp = (char *)strchr(b, 'X');
+ if (cp) {
+ ll = sg_get_llnum(cp + 1);
+ if (-1LL != ll)
+ return num * ll;
+ }
+ return -1LL;
+ default:
+ fprintf(stderr, "unrecognized multiplier\n");
+ return -1LL;
+ }
+ }
+}
+
+static void
+dStrHex(const char* str, int len, long start, int noAddr)
+{
+ const char* p = str;
+ unsigned char c;
+ char buff[MAX_LINE_LENGTH];
+ long a = start;
+ int bpstart, cpstart;
+ int j, k, line_length, nl, cpos, bpos, midline_space;
+
+ if (noAddr) {
+ bpstart = 0;
+ cpstart = ((CHARS_PER_HEX_BYTE * bytes_per_line) + 1) + 5;
+ } else {
+ bpstart = BINARY_START_COL;
+ cpstart = BINARY_START_COL +
+ ((CHARS_PER_HEX_BYTE * bytes_per_line) + 1) + 5;
+ }
+ cpos = cpstart;
+ bpos = bpstart;
+ midline_space = ((bytes_per_line + 1) / 2);
+
+ if (len <= 0)
+ return;
+ line_length = BINARY_START_COL +
+ (bytes_per_line * (1 + CHARS_PER_HEX_BYTE)) + 7;
+ if (line_length >= MAX_LINE_LENGTH) {
+ fprintf(stderr, "bytes_per_line causes maximum line length of %d "
+ "to be exceeded\n", MAX_LINE_LENGTH);
+ return;
+ }
+ memset(buff, ' ', line_length);
+ buff[line_length] = '\0';
+ if (0 == noAddr) {
+ k = sprintf(buff + 1, "%.2lx", a);
+ buff[k + 1] = ' ';
+ }
+
+ for(j = 0; j < len; j++) {
+ nl = (0 == (j % bytes_per_line));
+ if ((j > 0) && nl) {
+ printf("%s\n", buff);
+ bpos = bpstart;
+ cpos = cpstart;
+ a += bytes_per_line;
+ memset(buff,' ', line_length);
+ if (0 == noAddr) {
+ k = sprintf(buff + 1, "%.2lx", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ c = *p++;
+ bpos += (nl && noAddr) ? 0 : CHARS_PER_HEX_BYTE;
+ if ((bytes_per_line > 4) && ((j % bytes_per_line) == midline_space))
+ bpos++;
+ sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c);
+ buff[bpos + 2] = ' ';
+ if ((c < ' ') || (c >= 0x7f))
+ c='.';
+ buff[cpos++] = c;
+ }
+ if (cpos > cpstart)
+ printf("%s\n", buff);
+}
+
+static void
+dStrHexOnly(const char* str, int len, long start, int noAddr)
+{
+ const char* p = str;
+ unsigned char c;
+ char buff[MAX_LINE_LENGTH];
+ long a = start;
+ int bpstart, bpos, nl;
+ int midline_space = ((bytes_per_line + 1) / 2);
+ int j, k, line_length;
+
+ if (len <= 0)
+ return;
+ bpstart = (noAddr ? 0 : BINARY_START_COL);
+ bpos = bpstart;
+ line_length = (noAddr ? 0 : BINARY_START_COL) +
+ (bytes_per_line * CHARS_PER_HEX_BYTE) + 4;
+ if (line_length >= MAX_LINE_LENGTH) {
+ fprintf(stderr, "bytes_per_line causes maximum line length of %d "
+ "to be exceeded\n", MAX_LINE_LENGTH);
+ return;
+ }
+ memset(buff, ' ', line_length);
+ buff[line_length] = '\0';
+ if (0 == noAddr) {
+ k = sprintf(buff + 1, "%.2lx", a);
+ buff[k + 1] = ' ';
+ }
+
+ for(j = 0; j < len; j++) {
+ nl = (0 == (j % bytes_per_line));
+ if ((j > 0) && nl) {
+ printf("%s\n", buff);
+ bpos = bpstart;
+ a += bytes_per_line;
+ memset(buff,' ', line_length);
+ if (0 == noAddr) {
+ k = sprintf(buff + 1, "%.2lx", a);
+ buff[k + 1] = ' ';
+ }
+ }
+ c = *p++;
+ bpos += (nl && noAddr) ? 0 : CHARS_PER_HEX_BYTE;
+ if ((bytes_per_line > 4) && ((j % bytes_per_line) == midline_space))
+ bpos++;
+ sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c);
+ buff[bpos + 2] = ' ';
+ }
+ if (bpos > bpstart)
+ printf("%s\n", buff);
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: hxascdmp [-1] [-2] [-b=<n>] [-h] [-H] [-N] "
+ "[-o=<off>] [-q]\n"
+ " [-V] [-?] [<file>+]\n");
+ fprintf(stderr, " where:\n");
+ fprintf(stderr, " -1 print first byte in hex, prepend '0x' "
+ "if '-H' given\n");
+ fprintf(stderr, " -2 like '-1' but print first two bytes\n");
+ fprintf(stderr, " -b=<n> bytes per line to display "
+ "(def: 16)\n");
+ fprintf(stderr, " -h print this usage message\n");
+ fprintf(stderr, " -H print hex only (i.e. no ASCII "
+ "to right)\n");
+ fprintf(stderr, " -N no address, start in first column\n");
+ fprintf(stderr, " -o=<off> start decoding at byte <off>. Suffix "
+ "multipliers allowed\n");
+ fprintf(stderr, " -q quiet: suppress output of header "
+ "info\n");
+ fprintf(stderr, " -V print version string then exits\n");
+ fprintf(stderr, " -? print this usage message\n");
+ fprintf(stderr, " <file>+ reads file(s) and outputs each "
+ "as hex ASCII\n");
+ fprintf(stderr, " if no <file> then reads stdin\n\n");
+ fprintf(stderr, "Sends hex ASCII dump of stdin/file to stdout\n");
+}
+
+int
+main(int argc, const char ** argv)
+{
+ char buff[8192];
+ int num = 8192;
+ long start = 0;
+ int64_t offset = 0;
+ int res, k, u, len, n;
+ int inFile = STDIN_FILENO;
+ int doHelp = 0;
+ int doHex = 0;
+ int noAddr = 0;
+ int doVersion = 0;
+ int hasFilename = 0;
+ int quiet = 0;
+ int print1 = 0;
+ int print2 = 0;
+ int ret = 0;
+ const char * cp;
+
+ for (k = 1; k < argc; k++) {
+ cp = argv[k];
+ len = strlen(cp);
+ if (0 == strncmp("-b=", cp, 3)) {
+ res = sscanf(cp + 3, "%d", &u);
+ if ((1 != res) || (u < 1)) {
+ fprintf(stderr, "Bad value after '-b=' option\n");
+ usage();
+ return 1;
+ }
+ bytes_per_line = u;
+ } else if (0 == strncmp("-o=", cp, 3)) {
+ int64_t off = sg_get_llnum(cp + 3);
+
+ if (off == -1) {
+ fprintf(stderr, "Bad value after '-o=' option\n");
+ usage();
+ return 1;
+ }
+ offset = off;
+ } else if ((len > 1) && ('-' == cp[0]) && ('-' != cp[1])) {
+ res = 0;
+ n = num_chs_in_str(cp + 1, len - 1, '1');
+ print1 += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, '2');
+ print2 += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, 'h');
+ doHelp += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, 'H');
+ doHex += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, 'N');
+ noAddr += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, 'q');
+ quiet += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, 'V');
+ doVersion += n;
+ res += n;
+ n = num_chs_in_str(cp + 1, len - 1, '?');
+ doHelp += n;
+ res += n;
+ if (0 == res) {
+ fprintf(stderr, "No option recognized in str: %s\n", cp);
+ usage();
+ return 1;
+ }
+ } else if (0 == strcmp("-?", argv[k]))
+ ++doHelp;
+ else if (*argv[k] == '-') {
+ fprintf(stderr, "unknown switch: %s\n", argv[k]);
+ usage();
+ return 1;
+ } else {
+ hasFilename = 1;
+ break;
+ }
+ if (print2)
+ print1 += print2 + print2;
+ }
+ if (doVersion) {
+ printf("%s\n", version_str);
+ return 0;
+ }
+ if (doHelp) {
+ usage();
+ return 0;
+ }
+
+ /* Make sure num to fetch is integral multiple of bytes_per_line */
+ if (0 != (num % bytes_per_line))
+ num = (num / bytes_per_line) * bytes_per_line;
+
+ if (hasFilename) {
+ for ( ; k < argc; k++)
+ {
+ inFile = open(argv[k], O_RDONLY);
+ if (inFile < 0) {
+ fprintf(stderr, "Couldn't open file: %s\n", argv[k]);
+ ret = 1;
+ } else {
+ sg_set_binary_mode(inFile);
+ if (offset > 0) {
+ int err;
+ int64_t off_res;
+
+ off_res = lseek(inFile, offset, SEEK_SET);
+ if (off_res < 0) {
+ err = errno;
+ fprintf(stderr, "failed moving filepos: wanted=%"
+ PRId64 " [0x%" PRIx64 "]\nlseek error: %s\n",
+ offset, offset, strerror(err));
+ goto fini1;
+ }
+ start = offset;
+ } else
+ start = 0;
+ if (! (doHex || quiet || print1))
+ printf("ASCII hex dump of file: %s\n", argv[k]);
+ while ((res = read(inFile, buff, num)) > 0) {
+ if (print1) {
+ if (1 == print1) {
+ if (doHex)
+ printf("0x%02x\n", (uint8_t)(buff[0]));
+ else
+ printf("%02x\n", (uint8_t)(buff[0]));
+ } else {
+ uint16_t us;
+
+ memcpy(&us, buff, 2);
+ if (doHex)
+ printf("0x%04x\n", us);
+ else
+ printf("%04x\n", us);
+ }
+ break;
+ }
+ if (doHex)
+ dStrHexOnly(buff, res, start, noAddr);
+ else
+ dStrHex(buff, res, start, noAddr);
+ start += (long)res;
+ }
+ }
+fini1:
+ close(inFile);
+ }
+ } else {
+ sg_set_binary_mode(inFile);
+ if (offset > 0) {
+ start = offset;
+ do { /* eat up offset bytes */
+ if ((res = read(inFile, buff,
+ (num > offset ? offset : num))) > 0)
+ offset -= res;
+ else {
+ fprintf(stderr, "offset read() error: %s\n",
+ strerror(errno));
+ break;
+ }
+ } while (offset > 0);
+ }
+ while ((res = read(inFile, buff, num)) > 0) {
+ if (doHex)
+ dStrHexOnly(buff, res, start, noAddr);
+ else
+ dStrHex(buff, res, start, noAddr);
+ start += (long)res;
+ }
+ }
+ return ret;
+}