aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
81 files changed, 86813 insertions, 0 deletions
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;
+}