diff options
Diffstat (limited to 'src')
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; +} |